From b0a5acd146ec11c7a943e9fcc7aecfc56ce3d47d Mon Sep 17 00:00:00 2001 From: Arya Hassanli Date: Thu, 12 Feb 2026 22:25:00 +0000 Subject: [PATCH 1/5] Create device type requirements table --- dm/devicetype.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/dm/devicetype.go b/dm/devicetype.go index b91a316f..413acc9d 100644 --- a/dm/devicetype.go +++ b/dm/devicetype.go @@ -129,12 +129,25 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) } type deviceTypeRequirements struct { + deviceRequirements []*matter.DeviceTypeRequirement clusterRequirements []*matter.DeviceTypeClusterRequirement elementRequirements map[*matter.Cluster][]*matter.ElementRequirement } dtrs := make(map[*matter.DeviceType]*deviceTypeRequirements) + for _, dr := range deviceType.DeviceTypeRequirements { + if dr.DeviceType == nil { + continue + } + dtr, ok := dtrs[dr.DeviceType] + if !ok { + dtr = &deviceTypeRequirements{} + dtrs[dr.DeviceType] = dtr + } + dtr.deviceRequirements = append(dtr.deviceRequirements, dr) + } + for _, cr := range deviceType.ComposedDeviceTypeClusterRequirements { if cr.DeviceType == nil { continue @@ -173,7 +186,11 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) cx := c.CreateElement("composedDeviceTypes") deviceTypes := make([]*matter.DeviceType, 0, len(dtrs)) for _, dtr := range dtrs { - deviceTypes = append(deviceTypes, dtr.clusterRequirements[0].DeviceType) + if len(dtr.clusterRequirements) > 0 { + deviceTypes = append(deviceTypes, dtr.clusterRequirements[0].DeviceType) + } else if len(dtr.deviceRequirements) > 0 { + deviceTypes = append(deviceTypes, dtr.deviceRequirements[0].DeviceType) + } } slices.SortStableFunc(deviceTypes, func(a, b *matter.DeviceType) int { return strings.Compare(a.Name, b.Name) @@ -188,6 +205,14 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) dte.CreateAttr("deviceTypeId", dt.ID.HexString()) } dte.CreateAttr("deviceTypeName", dt.Name) + err = renderConformanceElement(dtr.deviceRequirements[0].Conformance, dte, nil) + if err != nil { + return + } + err = renderConstraintElement(dtr.deviceRequirements[0].Constraint, nil, dte, nil) + if err != nil { + return + } if len(dtr.clusterRequirements) > 0 { crx := dte.CreateElement("clusterRequirements") reqs := make([]*matter.DeviceTypeClusterRequirement, len(dtr.clusterRequirements)) From 800ed2f32f7537bc3d150eaf6bd277b030b63ff9 Mon Sep 17 00:00:00 2001 From: Arya Hassanli Date: Thu, 12 Mar 2026 23:09:15 +0000 Subject: [PATCH 2/5] Remove redundant composedDeviceType for main clsuter requirements --- dm/devicetype.go | 47 +++++++++++------------------------------------ 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/dm/devicetype.go b/dm/devicetype.go index 413acc9d..d6aa294b 100644 --- a/dm/devicetype.go +++ b/dm/devicetype.go @@ -129,7 +129,7 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) } type deviceTypeRequirements struct { - deviceRequirements []*matter.DeviceTypeRequirement + deviceRequirements []*matter.DeviceTypeRequirement clusterRequirements []*matter.DeviceTypeClusterRequirement elementRequirements map[*matter.Cluster][]*matter.ElementRequirement } @@ -140,6 +140,7 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) if dr.DeviceType == nil { continue } + dtr, ok := dtrs[dr.DeviceType] if !ok { dtr = &deviceTypeRequirements{} @@ -205,13 +206,15 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) dte.CreateAttr("deviceTypeId", dt.ID.HexString()) } dte.CreateAttr("deviceTypeName", dt.Name) - err = renderConformanceElement(dtr.deviceRequirements[0].Conformance, dte, nil) - if err != nil { - return - } - err = renderConstraintElement(dtr.deviceRequirements[0].Constraint, nil, dte, nil) - if err != nil { - return + if len(dtr.deviceRequirements) > 0 { + err = renderConformanceElement(dtr.deviceRequirements[0].Conformance, dte, nil) + if err != nil { + return + } + err = renderConstraintElement(dtr.deviceRequirements[0].Constraint, nil, dte, nil) + if err != nil { + return + } } if len(dtr.clusterRequirements) > 0 { crx := dte.CreateElement("clusterRequirements") @@ -240,34 +243,6 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) } - if len(deviceType.ComposedDeviceTypeClusterRequirements) > 0 { - cx := c.CreateElement("composedDeviceTypes") - reqs := make([]*matter.ClusterRequirement, len(deviceType.ClusterRequirements)) - copy(reqs, deviceType.ClusterRequirements) - slices.SortStableFunc(reqs, sortClusterRequirements) - for _, cr := range reqs { - clx := cx.CreateElement("cluster") - clx.CreateAttr("id", cr.ClusterID.HexString()) - clx.CreateAttr("name", cr.ClusterName) - switch cr.Interface { - case matter.InterfaceClient: - clx.CreateAttr("side", "client") - case matter.InterfaceServer: - clx.CreateAttr("side", "server") - } - renderQuality(clx, cr.Quality) - err = renderConformanceElement(cr.Conformance, clx, nil) - if err != nil { - return - } - err = renderElementRequirements(deviceType, cr, clx) - if err != nil { - return - } - - } - } - x.Indent(2) var b bytes.Buffer From 198081c436476e1c67f5a2b1a0b2a6d1395d7215 Mon Sep 17 00:00:00 2001 From: Arya Hassanli Date: Wed, 27 May 2026 13:44:02 +0000 Subject: [PATCH 3/5] Support element requirement --- dm/devicetype.go | 129 ++++++++++++++++++++++++++---------- matter/composition.go | 4 ++ matter/spec/requirements.go | 7 +- matter/spec/table_info.go | 25 +++++++ 4 files changed, 129 insertions(+), 36 deletions(-) diff --git a/dm/devicetype.go b/dm/devicetype.go index d6aa294b..6d1f5235 100644 --- a/dm/devicetype.go +++ b/dm/devicetype.go @@ -12,6 +12,7 @@ import ( "github.com/project-chip/alchemy/asciidoc" "github.com/project-chip/alchemy/internal" "github.com/project-chip/alchemy/matter" + "github.com/project-chip/alchemy/matter/conformance" "github.com/project-chip/alchemy/matter/types" ) @@ -120,7 +121,7 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) if err != nil { return } - err = renderElementRequirements(deviceType, cr, clx) + err = renderElementRequirements(deviceType, cr, deviceType.ElementRequirements, clx) if err != nil { return } @@ -128,23 +129,30 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) } } + type deviceTypeInstance struct { + DeviceTypeName string + Label string + } + type deviceTypeRequirements struct { + DeviceType *matter.DeviceType deviceRequirements []*matter.DeviceTypeRequirement clusterRequirements []*matter.DeviceTypeClusterRequirement - elementRequirements map[*matter.Cluster][]*matter.ElementRequirement + elementRequirements map[string][]*matter.ElementRequirement } - dtrs := make(map[*matter.DeviceType]*deviceTypeRequirements) + dtrs := make(map[deviceTypeInstance]*deviceTypeRequirements) for _, dr := range deviceType.DeviceTypeRequirements { if dr.DeviceType == nil { continue } - dtr, ok := dtrs[dr.DeviceType] + key := deviceTypeInstance{DeviceTypeName: dr.DeviceType.Name, Label: ""} + dtr, ok := dtrs[key] if !ok { - dtr = &deviceTypeRequirements{} - dtrs[dr.DeviceType] = dtr + dtr = &deviceTypeRequirements{DeviceType: dr.DeviceType} + dtrs[key] = dtr } dtr.deviceRequirements = append(dtr.deviceRequirements, dr) } @@ -157,10 +165,15 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) continue } - dtr, ok := dtrs[cr.DeviceType] + label := cr.InstanceLabel + if label == cr.DeviceType.Name { + label = "" + } + key := deviceTypeInstance{DeviceTypeName: cr.DeviceType.Name, Label: label} + dtr, ok := dtrs[key] if !ok { - dtr = &deviceTypeRequirements{} - dtrs[cr.DeviceType] = dtr + dtr = &deviceTypeRequirements{DeviceType: cr.DeviceType} + dtrs[key] = dtr } dtr.clusterRequirements = append(dtr.clusterRequirements, cr) } @@ -172,40 +185,77 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) if er.ElementRequirement.Cluster == nil { continue } - dtr, ok := dtrs[er.DeviceType] + label := er.InstanceLabel + if label == er.DeviceType.Name { + label = "" + } + key := deviceTypeInstance{DeviceTypeName: er.DeviceType.Name, Label: label} + dtr, ok := dtrs[key] if !ok { - continue + dtr = &deviceTypeRequirements{DeviceType: er.DeviceType} + dtrs[key] = dtr + } + + foundCluster := false + for _, cr := range dtr.clusterRequirements { + if cr.ClusterRequirement.ClusterID.Equals(er.ElementRequirement.ClusterID) { + foundCluster = true + break + } + } + if !foundCluster { + cr := matter.NewClusterRequirement(er.DeviceType, er.Source()) + cr.ClusterID = er.ElementRequirement.ClusterID + cr.ClusterName = er.ElementRequirement.ClusterName + cr.Interface = matter.InterfaceServer // Default to server + + + dtcr := matter.NewDeviceTypeClusterRequirement(er.DeviceType, cr, er.Source()) + dtcr.DeviceTypeID = er.DeviceTypeID + dtcr.DeviceTypeName = er.DeviceTypeName + dtcr.InstanceLabel = er.InstanceLabel + + dtr.clusterRequirements = append(dtr.clusterRequirements, dtcr) } + if dtr.elementRequirements == nil { - dtr.elementRequirements = make(map[*matter.Cluster][]*matter.ElementRequirement) + dtr.elementRequirements = make(map[string][]*matter.ElementRequirement) } - dtr.elementRequirements[er.ElementRequirement.Cluster] = append(dtr.elementRequirements[er.ElementRequirement.Cluster], er.ElementRequirement) + cidStr := er.ElementRequirement.ClusterID.HexString() + dtr.elementRequirements[cidStr] = append(dtr.elementRequirements[cidStr], er.ElementRequirement) } if len(dtrs) > 0 { cx := c.CreateElement("composedDeviceTypes") - deviceTypes := make([]*matter.DeviceType, 0, len(dtrs)) - for _, dtr := range dtrs { - if len(dtr.clusterRequirements) > 0 { - deviceTypes = append(deviceTypes, dtr.clusterRequirements[0].DeviceType) - } else if len(dtr.deviceRequirements) > 0 { - deviceTypes = append(deviceTypes, dtr.deviceRequirements[0].DeviceType) - } + instances := make([]deviceTypeInstance, 0, len(dtrs)) + for key := range dtrs { + instances = append(instances, key) } - slices.SortStableFunc(deviceTypes, func(a, b *matter.DeviceType) int { - return strings.Compare(a.Name, b.Name) - }) - for _, dt := range deviceTypes { - dtr, ok := dtrs[dt] - if !ok { - continue + slices.SortStableFunc(instances, func(a, b deviceTypeInstance) int { + cmp := strings.Compare(a.DeviceTypeName, b.DeviceTypeName) + if cmp != 0 { + return cmp } + return strings.Compare(a.Label, b.Label) + }) + for _, inst := range instances { + dtr := dtrs[inst] + dt := dtr.DeviceType dte := cx.CreateElement("deviceType") if dt.ID.Valid() { dte.CreateAttr("deviceTypeId", dt.ID.HexString()) } dte.CreateAttr("deviceTypeName", dt.Name) + + var baseConformance conformance.Set + for _, dr := range deviceType.DeviceTypeRequirements { + if dr.DeviceType == dt { + baseConformance = dr.Conformance + break + } + } + if len(dtr.deviceRequirements) > 0 { err = renderConformanceElement(dtr.deviceRequirements[0].Conformance, dte, nil) if err != nil { @@ -215,7 +265,16 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) if err != nil { return } + } else if inst.Label != "" { + if baseConformance != nil { + err = renderConformanceElement(baseConformance, dte, nil) + if err != nil { + return + } + } + } + if len(dtr.clusterRequirements) > 0 { crx := dte.CreateElement("clusterRequirements") reqs := make([]*matter.DeviceTypeClusterRequirement, len(dtr.clusterRequirements)) @@ -231,12 +290,14 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) clx := crx.CreateElement("cluster") clx.CreateAttr("id", cr.ClusterRequirement.ClusterID.HexString()) clx.CreateAttr("name", cr.ClusterRequirement.ClusterName) - err = renderConformanceElement(cr.ClusterRequirement.Conformance, clx, nil) - if err != nil { - return + if len(cr.ClusterRequirement.Conformance) > 0 { + err = renderConformanceElement(cr.ClusterRequirement.Conformance, clx, nil) + if err != nil { + return + } } renderQuality(clx, cr.ClusterRequirement.Quality) - renderElementRequirements(dt, cr.ClusterRequirement, clx) + renderElementRequirements(dt, cr.ClusterRequirement, dtr.elementRequirements[cr.ClusterRequirement.ClusterID.HexString()], clx) } } } @@ -265,9 +326,9 @@ type commandRequirement struct { fields []*matter.ElementRequirement } -func renderElementRequirements(deviceType *matter.DeviceType, cr *matter.ClusterRequirement, clx *etree.Element) (err error) { +func renderElementRequirements(deviceType *matter.DeviceType, cr *matter.ClusterRequirement, ers []*matter.ElementRequirement, clx *etree.Element) (err error) { erMap := make(map[types.EntityType][]*matter.ElementRequirement) - for _, er := range deviceType.ElementRequirements { + for _, er := range ers { if er.ClusterID.Equals(cr.ClusterID) { erMap[er.Element] = append(erMap[er.Element], er) } @@ -276,7 +337,7 @@ func renderElementRequirements(deviceType *matter.DeviceType, cr *matter.Cluster var attributeRequirements []*matter.ElementRequirement var commandRequirements []*commandRequirement var eventRequirements []*matter.ElementRequirement - for _, er := range deviceType.ElementRequirements { + for _, er := range ers { if er.ClusterID.Equals(cr.ClusterID) { switch er.Element { case types.EntityTypeFeature: diff --git a/matter/composition.go b/matter/composition.go index a7371c68..bb16a022 100644 --- a/matter/composition.go +++ b/matter/composition.go @@ -63,6 +63,7 @@ type DeviceTypeClusterRequirement struct { ClusterRequirement *ClusterRequirement Origin RequirementOrigin + InstanceLabel string DeviceType *DeviceType DeviceTypeRequirement *DeviceTypeRequirement @@ -78,6 +79,7 @@ func (dtcr *DeviceTypeClusterRequirement) Clone() *DeviceTypeClusterRequirement DeviceTypeName: dtcr.DeviceTypeName, ClusterRequirement: dtcr.ClusterRequirement, Origin: dtcr.Origin, + InstanceLabel: dtcr.InstanceLabel, DeviceType: dtcr.DeviceType, DeviceTypeRequirement: dtcr.DeviceTypeRequirement, } @@ -90,6 +92,7 @@ type DeviceTypeElementRequirement struct { ElementRequirement *ElementRequirement Origin RequirementOrigin + InstanceLabel string DeviceType *DeviceType DeviceTypeRequirement *DeviceTypeRequirement @@ -105,6 +108,7 @@ func (dter *DeviceTypeElementRequirement) Clone() *DeviceTypeElementRequirement DeviceTypeName: dter.DeviceTypeName, ElementRequirement: dter.ElementRequirement, Origin: dter.Origin, + InstanceLabel: dter.InstanceLabel, DeviceType: dter.DeviceType, DeviceTypeRequirement: dter.DeviceTypeRequirement, } diff --git a/matter/spec/requirements.go b/matter/spec/requirements.go index ce694701..f2d74477 100644 --- a/matter/spec/requirements.go +++ b/matter/spec/requirements.go @@ -163,7 +163,7 @@ func (library *Library) toComposedDeviceTypeClusterRequirements(reader asciidoc. } return } - for row := range ti.ContentRows() { + for row, category := range ti.CategorizedRows(reader) { var cr *matter.ClusterRequirement cr, err = library.toClusterRequirement(reader, deviceType, ti, row) if err != nil { @@ -171,6 +171,7 @@ func (library *Library) toComposedDeviceTypeClusterRequirements(reader asciidoc. } dtcr := matter.NewDeviceTypeClusterRequirement(deviceType, cr, row) + dtcr.InstanceLabel = category dtcr.DeviceTypeID, err = ti.ReadID(reader, row, matter.TableColumnDeviceID) if err != nil { return @@ -230,13 +231,14 @@ func (library *Library) toComposedDeviceTypeElementRequirements(reader asciidoc. } return } - for row := range ti.ContentRows() { + for row, category := range ti.CategorizedRows(reader) { var er matter.ElementRequirement er, err = library.toElementRequirement(reader, d, ti, row, deviceType) if err != nil { return } dter := matter.NewDeviceTypeElementRequirement(deviceType, &er, row) + dter.InstanceLabel = category dter.DeviceTypeID, err = ti.ReadID(reader, row, matter.TableColumnDeviceID) if err != nil { return @@ -259,6 +261,7 @@ func (library *Library) toComposedDeviceTypeElementRequirements(reader asciidoc. dtcr := matter.NewDeviceTypeClusterRequirement(deviceType, cr, row) dtcr.DeviceTypeID = dter.DeviceTypeID dtcr.DeviceTypeName = dter.DeviceTypeName + dtcr.InstanceLabel = category if len(dter.ElementRequirement.Conformance) > 0 { dtcr.ClusterRequirement.Conformance = dter.ElementRequirement.Conformance.CloneSet() } diff --git a/matter/spec/table_info.go b/matter/spec/table_info.go index a7c1853b..cf2a858b 100644 --- a/matter/spec/table_info.go +++ b/matter/spec/table_info.go @@ -99,6 +99,31 @@ func (ti *TableInfo) ContentRows() iter.Seq[*asciidoc.TableRow] { } } +func (ti *TableInfo) CategorizedRows(reader asciidoc.Reader) iter.Seq2[*asciidoc.TableRow, string] { + return func(yield func(*asciidoc.TableRow, string) bool) { + var currentCategory string + for i := ti.HeaderRowIndex + 1; i < len(ti.Rows); i++ { + row := ti.Rows[i] + if len(row.Elements) > 0 { + firstCell := row.Cell(0) + if firstCell.Format.Span.Column.IsSet && firstCell.Format.Span.Column.Value == len(row.Elements) { + var err error + currentCategory, err = RenderTableCell(reader, firstCell) + if err != nil { + slog.Error("failed to render category cell", log.Element("source", ti.Doc.Path, firstCell), slog.Any("error", err)) + currentCategory = "" + } + currentCategory = strings.TrimSpace(currentCategory) + continue + } + } + if !yield(row, currentCategory) { + return + } + } + } +} + func (ti *TableInfo) ReadString(reader asciidoc.Reader, row *asciidoc.TableRow, columns ...matter.TableColumn) (string, error) { for _, column := range columns { offset, ok := ti.ColumnMap[column] From a8b53c27a5fcce334d10bdcd20d772a009c2d342 Mon Sep 17 00:00:00 2001 From: Arya Hassanli Date: Wed, 27 May 2026 20:39:26 +0000 Subject: [PATCH 4/5] Render device location --- dm/devicetype.go | 14 ++++++++++++++ matter/devicetype.go | 2 ++ matter/spec/table_info.go | 2 ++ 3 files changed, 18 insertions(+) diff --git a/dm/devicetype.go b/dm/devicetype.go index 6d1f5235..9fc48736 100644 --- a/dm/devicetype.go +++ b/dm/devicetype.go @@ -247,6 +247,20 @@ func renderDeviceType(deviceType *matter.DeviceType) (output string, err error) dte.CreateAttr("deviceTypeId", dt.ID.HexString()) } dte.CreateAttr("deviceTypeName", dt.Name) + + location := matter.DeviceTypeRequirementLocationUnknown + if len(dtr.deviceRequirements) > 0 { + location = dtr.deviceRequirements[0].Location + } else { + // Fallback to base requirement for the same device type + baseKey := deviceTypeInstance{DeviceTypeName: dt.Name, Label: ""} + if baseDtr, ok := dtrs[baseKey]; ok && len(baseDtr.deviceRequirements) > 0 { + location = baseDtr.deviceRequirements[0].Location + } + } + if location != matter.DeviceTypeRequirementLocationUnknown { + dte.CreateAttr("deviceTypeLocation", location.String()) + } var baseConformance conformance.Set for _, dr := range deviceType.DeviceTypeRequirements { diff --git a/matter/devicetype.go b/matter/devicetype.go index 924126fc..faba64ea 100644 --- a/matter/devicetype.go +++ b/matter/devicetype.go @@ -228,6 +228,7 @@ const ( DeviceTypeRequirementLocationChildEndpoint DeviceTypeRequirementLocationRootEndpoint DeviceTypeRequirementLocationDescendantEndpoint + DeviceTypeRequirementLocationAnywhere ) var ( @@ -237,6 +238,7 @@ var ( DeviceTypeRequirementLocationChildEndpoint: "childEndpoint", DeviceTypeRequirementLocationRootEndpoint: "rootEndpoint", DeviceTypeRequirementLocationDescendantEndpoint: "descendantEndpoint", + DeviceTypeRequirementLocationAnywhere: "anywhere", } ) diff --git a/matter/spec/table_info.go b/matter/spec/table_info.go index cf2a858b..8ca71cb8 100644 --- a/matter/spec/table_info.go +++ b/matter/spec/table_info.go @@ -406,6 +406,8 @@ func (ti *TableInfo) ReadLocation(reader asciidoc.Reader, row *asciidoc.TableRow relation = matter.DeviceTypeRequirementLocationRootEndpoint case "Descendant", "DescendantEndpoint": relation = matter.DeviceTypeRequirementLocationDescendantEndpoint + case "Anywhere": + relation = matter.DeviceTypeRequirementLocationAnywhere default: err = newGenericParseError(row, "unknown location: %s", rs) } From 40023247d0dcc0f973478bd85209d9b533ba5d75 Mon Sep 17 00:00:00 2001 From: Arya Hassanli Date: Wed, 27 May 2026 20:44:47 +0000 Subject: [PATCH 5/5] Render anywhere as anyEndpoint --- matter/devicetype.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matter/devicetype.go b/matter/devicetype.go index faba64ea..410858b7 100644 --- a/matter/devicetype.go +++ b/matter/devicetype.go @@ -238,7 +238,7 @@ var ( DeviceTypeRequirementLocationChildEndpoint: "childEndpoint", DeviceTypeRequirementLocationRootEndpoint: "rootEndpoint", DeviceTypeRequirementLocationDescendantEndpoint: "descendantEndpoint", - DeviceTypeRequirementLocationAnywhere: "anywhere", + DeviceTypeRequirementLocationAnywhere: "anyEndpoint", } )