Skip to content

Commit 9517983

Browse files
committed
Add cluster revision attribute
Fix deserialization of list attributes
1 parent 55d4e68 commit 9517983

16 files changed

+387
-180
lines changed

Generator/ClusterGenerator.cs

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static void Generate()
2626
Directory.CreateDirectory($"outputs\\Clusters\\");
2727
XmlSerializer deserializer = new XmlSerializer(typeof(Cluster));
2828
IEnumerable<string> clusterxmls = Directory.EnumerateFiles("..\\..\\..\\Clusters");
29+
StreamWriter clusterBase = new StreamWriter("outputs\\ClusterBase.cs");
2930
foreach (string clusterxml in clusterxmls)
3031
{
3132
includes.Clear();
@@ -35,9 +36,11 @@ public static void Generate()
3536
Cluster? cluster = deserializer.Deserialize(File.OpenRead(clusterxml)) as Cluster;
3637
if (cluster == null)
3738
throw new IOException("Failed to parse cluster " + clusterxml);
39+
clusterBase.WriteLine($" case {cluster.name.Replace(" ", "").Replace('/', '_')}.CLUSTER_ID:\n return new {cluster.name.Replace(" ", "").Replace('/', '_')}(endPoint);");
3840
WriteClass(cluster);
3941
}
4042
}
43+
clusterBase.Close();
4144
}
4245

4346
private static void WriteClass(Cluster cluster)
@@ -75,9 +78,10 @@ private static void WriteClass(Cluster cluster, TextWriter writer)
7578
writer.WriteLine(" /// <summary>");
7679
writer.WriteLine($" /// {cluster.name}");
7780
writer.WriteLine(" /// </summary>");
81+
writer.WriteLine($" [ClusterRevision(CLUSTER_ID, {cluster.revision})]");
7882
writer.WriteLine(" public class " + cluster.name.Replace(" ", "").Replace('/', '_') + " : ClusterBase");
7983
writer.WriteLine(" {");
80-
writer.WriteLine(" private const uint CLUSTER_ID = " + cluster.clusterIds.clusterId.id + ";");
84+
writer.WriteLine(" internal const uint CLUSTER_ID = " + cluster.clusterIds.clusterId.id + ";");
8185
writer.WriteLine();
8286
writer.WriteLine(" /// <summary>");
8387
writer.WriteLine($" /// {cluster.name}");
@@ -143,7 +147,7 @@ private static void WriteClass(Cluster cluster, TextWriter writer)
143147
if (cluster.commands != null && cluster.commands.Length > 0)
144148
{
145149
includes.Add("MatterDotNet.Messages.InteractionModel");
146-
includes.Add("MatterDotNet.Protocol");
150+
includes.Add("MatterDotNet.Protocol.Subprotocols");
147151
writer.WriteLine(" #region Payloads");
148152
bool firstPayload = true;
149153
foreach (var command in cluster.commands)
@@ -506,10 +510,10 @@ private static void WriteStructType(bool optional, bool nullable, string type, s
506510
}
507511
}
508512

509-
private static void WriteFieldReader(bool optional, bool nullable, string type, string? entryType, int id, int? from, int? to, string name, string structName, Cluster cluster, TextWriter writer)
513+
private static void WriteFieldReader(bool optional, bool nullable, string type, string? entryType, string id, int? from, int? to, string name, string structName, Cluster cluster, TextWriter writer)
510514
{
511515
string totalIndent = " ";
512-
if (id != -1 && type != "list")
516+
if (id != "-1" && type != "list" && id != "i")
513517
{
514518
if (name == GeneratorUtil.SanitizeName(structName))
515519
writer.Write($"{totalIndent}{name}Field = ");
@@ -527,7 +531,7 @@ private static void WriteFieldReader(bool optional, bool nullable, string type,
527531
writer.WriteLine(">();");
528532
writer.WriteLine($"{totalIndent} foreach (var item in (List<object>)fields[{id}]) {{");
529533
writer.Write($"{totalIndent} {name}.Add(");
530-
WriteFieldReader(false, false, entryType!, null, -1, null, null, "item", structName, cluster, writer);
534+
WriteFieldReader(false, false, entryType!, null, "-1", null, null, "item", structName, cluster, writer);
531535
writer.WriteLine($"{totalIndent} }}");
532536
writer.WriteLine($"{totalIndent}}}");
533537
return;
@@ -608,16 +612,16 @@ private static void WriteFieldReader(bool optional, bool nullable, string type,
608612
writer.Write($"reader.GetDouble({id}");
609613
break;
610614
case "ref_IpAdr":
611-
writer.WriteLine($"new IPAddress(reader.GetBytes({id}, {(optional ? "true" : "false")}, 16, 4{(id == -1 ? ")!))" : ")!)")};");
615+
writer.WriteLine($"new IPAddress(reader.GetBytes({id}, {(optional ? "true" : "false")}, 16, 4{(id == "-1" || id == "i" ? ")!))" : ")!)")};");
612616
return;
613617
case "ref_Ipv4Adr":
614-
writer.WriteLine($"new IPAddress(reader.GetBytes({id}, {(optional ? "true" : "false")}, 4, 4{(id == -1 ? ")!))" : ")!)")};");
618+
writer.WriteLine($"new IPAddress(reader.GetBytes({id}, {(optional ? "true" : "false")}, 4, 4{(id == "-1" || id == "i" ? ")!))" : ")!)")};");
615619
return;
616620
case "ref_Ipv6Adr":
617-
writer.WriteLine($"new IPAddress(reader.GetBytes({id}, {(optional ? "true" : "false")}, 16, 16{(id == -1 ? ")!))" : ")!)")};");
621+
writer.WriteLine($"new IPAddress(reader.GetBytes({id}, {(optional ? "true" : "false")}, 16, 16{(id == "-1" || id == "i" ? ")!))" : ")!)")};");
618622
return;
619623
case "Hardware Address":
620-
writer.WriteLine($"new PhysicalAddress(reader.GetBytes({id}, {(optional ? "true" : "false")}, 8, 6{(id == -1 ? ")!))" : ")!)")};");
624+
writer.WriteLine($"new PhysicalAddress(reader.GetBytes({id}, {(optional ? "true" : "false")}, 8, 6{(id == "-1" || id == "i" ? ")!))" : ")!)")};");
621625
return;
622626
case "octstr":
623627
case "ipv6pre":
@@ -631,7 +635,7 @@ private static void WriteFieldReader(bool optional, bool nullable, string type,
631635
writer.Write(')');
632636
if (!optional)
633637
writer.Write('!');
634-
if (id == -1)
638+
if (id == "-1" || id == "i")
635639
writer.WriteLine(");");
636640
else
637641
writer.WriteLine(';');
@@ -647,7 +651,7 @@ private static void WriteFieldReader(bool optional, bool nullable, string type,
647651
writer.Write(')');
648652
if (!optional)
649653
writer.Write('!');
650-
if (id == -1)
654+
if (id == "-1" || id == "i")
651655
writer.WriteLine(");");
652656
else
653657
writer.WriteLine(';');
@@ -658,20 +662,28 @@ private static void WriteFieldReader(bool optional, bool nullable, string type,
658662
writer.Write($"({type})reader.GetUShort({id}");
659663
if (optional)
660664
writer.Write(", true");
661-
if (id == -1)
665+
if (id == "-1" || id == "i")
662666
writer.WriteLine(")!.Value);");
663667
else
664668
writer.WriteLine(")!.Value;");
665669
}
666-
else
667-
writer.WriteLine($"new {GeneratorUtil.SanitizeName(type)}({(id == -1 ? "(object[])item))" : "fields[{id}])")};");
670+
else {
671+
writer.Write($"new ");
672+
WriteType(type, entryType, writer);
673+
if (id == "-1")
674+
writer.WriteLine("((object[])item));");
675+
else if (id == "i")
676+
writer.WriteLine("(reader.GetStruct(i)!));");
677+
else
678+
writer.WriteLine($"fields[{id}]);");
679+
}
668680
return;
669681
}
670682
if (optional)
671683
writer.Write(", true)");
672684
else
673685
writer.Write(")!.Value");
674-
if (id == -1)
686+
if (id == "-1" || id == "i")
675687
writer.WriteLine(");");
676688
else
677689
writer.WriteLine(';');
@@ -875,7 +887,7 @@ private static void WriteStruct(clusterDataTypesStruct structType, Cluster clust
875887
writer.WriteLine($" internal {GeneratorUtil.SanitizeName(structType.name)}(object[] fields) {{");
876888
writer.WriteLine(" FieldReader reader = new FieldReader(fields);");
877889
foreach (clusterDataTypesStructField field in structType.field)
878-
WriteFieldReader(field.mandatoryConform == null, field.quality?.nullable == true, field.type, field.entry?.type, field.id, field.constraint?.fromSpecified == true ? field.constraint.from : null, field.constraint?.toSpecified == true ? field.constraint.to : null, field.name, structType.name, cluster, writer);
890+
WriteFieldReader(field.mandatoryConform == null, field.quality?.nullable == true, field.type, field.entry?.type, field.id.ToString(), field.constraint?.fromSpecified == true ? field.constraint.from : null, field.constraint?.toSpecified == true ? field.constraint.to : null, field.name, structType.name, cluster, writer);
879891

880892
writer.WriteLine(" }");
881893
foreach (clusterDataTypesStructField field in structType.field)
@@ -1010,10 +1022,25 @@ private static void WriteAttribute(Cluster cluster, clusterAttribute attribute,
10101022
WriteType(attribute.type, attribute.entry?.type, writer);
10111023
if (attribute.quality?.nullable == true)
10121024
writer.Write('?');
1013-
writer.WriteLine("> Get" + attribute.name + " (SecureSession session) {");
1014-
writer.Write(" return ");
1025+
writer.WriteLine("> Get" + attribute.name + "(SecureSession session) {");
1026+
if (attribute.type != "list")
1027+
writer.Write(" return ");
10151028
if (HasStruct(cluster, attribute.type))
10161029
writer.Write($"new {GeneratorUtil.SanitizeName(attribute.type)}((object[])(await GetAttribute(session, " + Convert.ToUInt16(attribute.id, 16) + "))!)");
1030+
else if (attribute.type == "list")
1031+
{
1032+
writer.Write(" List<");
1033+
WriteType(attribute.entry!.type, null, writer);
1034+
writer.Write("> list = new List<");
1035+
WriteType(attribute.entry!.type, null, writer);
1036+
writer.WriteLine(">();");
1037+
writer.WriteLine($" FieldReader reader = new FieldReader((IList<object>)(await GetAttribute(session, {Convert.ToUInt16(attribute.id, 16)}))!);");
1038+
writer.WriteLine(" for (int i = 0; i < reader.Count; i++)");
1039+
writer.Write(" list.Add(");
1040+
WriteFieldReader(false, false, attribute.entry!.type, null, "i", attribute.constraint?.from, attribute.constraint?.to, "", "", cluster, writer);
1041+
writer.Write(" return list");
1042+
hasDefault = false;
1043+
}
10171044
else
10181045
{
10191046
writer.Write('(');

MatterDotNet/Clusters/Application/On-OffCluster.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ namespace MatterDotNet.Clusters.Application
2323
/// <summary>
2424
/// On/Off Cluster
2525
/// </summary>
26+
[ClusterRevision(CLUSTER_ID, 6)]
2627
public class On_OffCluster : ClusterBase
2728
{
28-
private const uint CLUSTER_ID = 0x0006;
29+
internal const uint CLUSTER_ID = 0x0006;
2930

3031
/// <summary>
3132
/// On/Off Cluster
@@ -234,21 +235,21 @@ public async Task<bool> Supports(SecureSession session, Feature feature)
234235
/// <summary>
235236
/// Get the On Off attribute
236237
/// </summary>
237-
public async Task<bool> GetOnOff (SecureSession session) {
238+
public async Task<bool> GetOnOff(SecureSession session) {
238239
return (bool?)(dynamic?)await GetAttribute(session, 0) ?? false;
239240
}
240241

241242
/// <summary>
242243
/// Get the Global Scene Control attribute
243244
/// </summary>
244-
public async Task<bool> GetGlobalSceneControl (SecureSession session) {
245+
public async Task<bool> GetGlobalSceneControl(SecureSession session) {
245246
return (bool?)(dynamic?)await GetAttribute(session, 16384) ?? true;
246247
}
247248

248249
/// <summary>
249250
/// Get the On Time attribute
250251
/// </summary>
251-
public async Task<ushort> GetOnTime (SecureSession session) {
252+
public async Task<ushort> GetOnTime(SecureSession session) {
252253
return (ushort?)(dynamic?)await GetAttribute(session, 16385) ?? 0;
253254
}
254255

@@ -262,7 +263,7 @@ public async Task SetOnTime (SecureSession session, ushort? value = 0) {
262263
/// <summary>
263264
/// Get the Off Wait Time attribute
264265
/// </summary>
265-
public async Task<ushort> GetOffWaitTime (SecureSession session) {
266+
public async Task<ushort> GetOffWaitTime(SecureSession session) {
266267
return (ushort?)(dynamic?)await GetAttribute(session, 16386) ?? 0;
267268
}
268269

@@ -276,7 +277,7 @@ public async Task SetOffWaitTime (SecureSession session, ushort? value = 0) {
276277
/// <summary>
277278
/// Get the Start Up On Off attribute
278279
/// </summary>
279-
public async Task<StartUpOnOffEnum?> GetStartUpOnOff (SecureSession session) {
280+
public async Task<StartUpOnOffEnum?> GetStartUpOnOff(SecureSession session) {
280281
return (StartUpOnOffEnum?)await GetEnumAttribute(session, 16387, true);
281282
}
282283

MatterDotNet/Clusters/ClusterBase.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
// WARNING: This file was auto-generated. Do not edit.
1414

1515

16+
using MatterDotNet.Clusters.Application;
17+
using MatterDotNet.Clusters.Utility;
1618
using MatterDotNet.Messages.InteractionModel;
1719
using MatterDotNet.Protocol.Payloads.Status;
1820
using MatterDotNet.Protocol.Sessions;
@@ -217,5 +219,34 @@ private bool ValidateStatus(IMStatusCode status)
217219
return false;
218220
}
219221
}
222+
223+
public static ClusterBase Create(uint clusterId, ushort endPoint)
224+
{
225+
switch (clusterId)
226+
{
227+
case AccessControlCluster.CLUSTER_ID:
228+
return new AccessControlCluster(endPoint);
229+
case AdministratorCommissioningCluster.CLUSTER_ID:
230+
return new AdministratorCommissioningCluster(endPoint);
231+
case BasicInformationCluster.CLUSTER_ID:
232+
return new BasicInformationCluster(endPoint);
233+
case DescriptorCluster.CLUSTER_ID:
234+
return new DescriptorCluster(endPoint);
235+
case GeneralDiagnosticsCluster.CLUSTER_ID:
236+
return new GeneralDiagnosticsCluster(endPoint);
237+
case GeneralCommissioningCluster.CLUSTER_ID:
238+
return new GeneralCommissioningCluster(endPoint);
239+
case GroupKeyManagementCluster.CLUSTER_ID:
240+
return new GroupKeyManagementCluster(endPoint);
241+
case IdentifyCluster.CLUSTER_ID:
242+
return new IdentifyCluster(endPoint);
243+
case On_OffCluster.CLUSTER_ID:
244+
return new On_OffCluster(endPoint);
245+
case NodeOperationalCredentialsCluster.CLUSTER_ID:
246+
return new NodeOperationalCredentialsCluster(endPoint);
247+
default:
248+
return new UnknownCluster(clusterId, endPoint);
249+
}
250+
}
220251
}
221252
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+

2+
3+
namespace MatterDotNet.Clusters
4+
{
5+
public sealed class ClusterRevision : Attribute
6+
{
7+
public ClusterRevision(uint clusterID, int revision)
8+
{
9+
Revision = revision;
10+
ClusterID = clusterID;
11+
}
12+
13+
public int Revision { get; set; }
14+
public uint ClusterID { get; set; }
15+
}
16+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// MatterDotNet Copyright (C) 2025
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU Affero General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or any later version.
6+
// This program is distributed in the hope that it will be useful,
7+
// but WITHOUT ANY WARRANTY, without even the implied warranty of
8+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9+
// See the GNU Affero General Public License for more details.
10+
// You should have received a copy of the GNU Affero General Public License
11+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
12+
13+
namespace MatterDotNet.Clusters
14+
{
15+
public class UnknownCluster : ClusterBase
16+
{
17+
public UnknownCluster(uint cluster, ushort endPoint) : base(cluster, endPoint)
18+
{
19+
}
20+
21+
public uint ClusterID { get { return cluster; } }
22+
23+
public override string ToString()
24+
{
25+
return "Unknown Cluster: " + cluster;
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)