Skip to content

Commit b4e8678

Browse files
committed
Properly handle EndPoints
1 parent 206d7cf commit b4e8678

File tree

5 files changed

+129
-20
lines changed

5 files changed

+129
-20
lines changed

ZWaveDotNet/CommandClasses/MultiChannel.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,18 @@
2020

2121
namespace ZWaveDotNet.CommandClasses
2222
{
23+
/// <summary>
24+
/// The Multi Channel Command Class is used to address one or more End Points in a Multi Channel device.
25+
/// </summary>
2326
[CCVersion(CommandClass.MultiChannel, 1, 4)]
2427
public class MultiChannel : CommandClassBase
2528
{
29+
/// <summary>
30+
/// Unsolicited End Point Capabilities Report
31+
/// </summary>
2632
public event CommandClassEvent<EndPointCapabilities>? EndpointCapabilitiesUpdated;
2733

28-
public enum MultiChannelCommand
34+
enum MultiChannelCommand
2935
{
3036
EndPointGet = 0x07,
3137
EndPointReport = 0x08,
@@ -39,6 +45,12 @@ public enum MultiChannelCommand
3945
}
4046
internal MultiChannel(Node node, byte endpoint) : base(node, endpoint, CommandClass.MultiChannel) { }
4147

48+
/// <summary>
49+
/// <b>Version 3</b>: Query the number of End Points implemented by the receiving node
50+
/// </summary>
51+
/// <param name="cancellationToken"></param>
52+
/// <returns></returns>
53+
/// <exception cref="MethodAccessException"></exception>
4254
public async Task<EndPointReport> GetEndPoints(CancellationToken cancellationToken = default)
4355
{
4456
if (node.ID == Node.BROADCAST_ID)
@@ -47,6 +59,13 @@ public async Task<EndPointReport> GetEndPoints(CancellationToken cancellationTok
4759
return new EndPointReport(response.Payload.Span);
4860
}
4961

62+
/// <summary>
63+
/// <b>Version 3</b>: Query the non-secure Command Class capabilities of an End Point
64+
/// </summary>
65+
/// <param name="endpointId"></param>
66+
/// <param name="cancellationToken"></param>
67+
/// <returns></returns>
68+
/// <exception cref="MethodAccessException"></exception>
5069
public async Task<EndPointCapabilities> GetCapabilities(byte endpointId, CancellationToken cancellationToken = default)
5170
{
5271
if (node.ID == Node.BROADCAST_ID)
@@ -55,6 +74,14 @@ public async Task<EndPointCapabilities> GetCapabilities(byte endpointId, Cancell
5574
return new EndPointCapabilities(response.Payload.Span);
5675
}
5776

77+
/// <summary>
78+
/// <b>Version 3</b>: Request End Points having a specific GenericType or SpecificType.
79+
/// </summary>
80+
/// <param name="generic"></param>
81+
/// <param name="specific"></param>
82+
/// <param name="cancellationToken"></param>
83+
/// <returns></returns>
84+
/// <exception cref="MethodAccessException"></exception>
5885
public async Task<EndPointFindReport> FindEndPoints(GenericType generic, SpecificType specific, CancellationToken cancellationToken = default)
5986
{
6087
if (node.ID == Node.BROADCAST_ID)
@@ -63,6 +90,13 @@ public async Task<EndPointFindReport> FindEndPoints(GenericType generic, Specifi
6390
return new EndPointFindReport(response.Payload.Span);
6491
}
6592

93+
/// <summary>
94+
/// <b>Version 4</b>: This command is used to query the members of an Aggregated End Point.
95+
/// </summary>
96+
/// <param name="endpointId"></param>
97+
/// <param name="cancellationToken"></param>
98+
/// <returns></returns>
99+
/// <exception cref="MethodAccessException"></exception>
66100
public async Task<List<byte>> GetAggregatedMembers(byte endpointId, CancellationToken cancellationToken = default)
67101
{
68102
if (node.ID == Node.BROADCAST_ID)

ZWaveDotNet/Entities/EndPoint.cs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
using System.Collections.ObjectModel;
1414
using System.Reflection;
1515
using ZWaveDotNet.CommandClasses;
16+
using ZWaveDotNet.CommandClasses.Enums;
1617
using ZWaveDotNet.CommandClassReports.Enums;
18+
using ZWaveDotNet.Entities.JSON;
1719
using ZWaveDotNet.Enums;
1820
using ZWaveDotNet.SerialAPI;
1921

@@ -42,7 +44,7 @@ public class EndPoint
4244
/// <param name="id"></param>
4345
/// <param name="node"></param>
4446
/// <param name="commandClasses"></param>
45-
public EndPoint(byte id, Node node, CommandClass[]? commandClasses = null)
47+
public EndPoint(byte id, Node node, CommandClass[] commandClasses)
4648
{
4749
ID = id;
4850
this.node = node;
@@ -54,10 +56,43 @@ public EndPoint(byte id, Node node, CommandClass[]? commandClasses = null)
5456
AddCommandClass(CommandClass.NoOperation);
5557
}
5658

59+
/// <summary>
60+
/// Deserialize End Point
61+
/// </summary>
62+
/// <param name="ep"></param>
63+
/// <param name="node"></param>
64+
public EndPoint(EndPointJson ep, Node node)
65+
{
66+
ID = ep.ID;
67+
this.node = node;
68+
foreach (CommandClassJson cc in ep.CommandClasses)
69+
AddCommandClass(cc.CommandClass, cc.Secure, cc.Version);
70+
}
71+
72+
internal EndPointJson Serialize()
73+
{
74+
EndPointJson json = new EndPointJson()
75+
{
76+
ID = ID,
77+
CommandClasses = new CommandClassJson[commandClasses.Count]
78+
};
79+
for (int i = 0; i < commandClasses.Count; i++)
80+
{
81+
CommandClass cls = commandClasses.ElementAt(i).Key;
82+
json.CommandClasses[i] = new CommandClassJson
83+
{
84+
CommandClass = cls,
85+
Version = commandClasses[cls].Version,
86+
Secure = commandClasses[cls].Secure
87+
};
88+
}
89+
return json;
90+
}
91+
5792
internal async Task<SupervisionStatus> HandleReport(ReportMessage msg)
5893
{
59-
if (!CommandClasses.ContainsKey(msg.CommandClass))
60-
AddCommandClass(msg.CommandClass);
94+
if (!HasCommandClass(msg.CommandClass))
95+
AddCommandClass(msg.CommandClass, (msg.SecurityLevel != SecurityKey.None));
6196
return await CommandClasses[msg.CommandClass].ProcessMessage(msg);
6297
}
6398

@@ -98,10 +133,10 @@ public bool HasCommandClass(CommandClass commandClass)
98133
return null;
99134
}
100135

101-
private void AddCommandClass(CommandClass cls, bool secure = false)
136+
private void AddCommandClass(CommandClass cls, bool secure = false, byte version = 1)
102137
{
103-
if (!this.commandClasses.ContainsKey(cls))
104-
this.commandClasses.Add(cls, CommandClassBase.Create(cls, node, ID, secure, 1)); //TODO
138+
if (!HasCommandClass(cls))
139+
this.commandClasses.Add(cls, CommandClassBase.Create(cls, node, ID, secure, version));
105140
}
106141

107142
///
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// ZWaveDotNet Copyright (C) 2024
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 ZWaveDotNet.Entities.JSON
14+
{
15+
#pragma warning disable CS1591
16+
public class EndPointJson
17+
{
18+
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
19+
public byte ID { get; set; }
20+
public CommandClassJson[] CommandClasses { get; set; }
21+
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
22+
}
23+
#pragma warning restore CS1591
24+
}

ZWaveDotNet/Entities/JSON/NodeJSON.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class NodeJSON
2424
public ushort ID { get; set; }
2525
public CommandClassJson[] CommandClasses { get; set; }
2626
public SecurityKey[] GrantedKeys { get; set; }
27+
public EndPointJson[] EndPoints { get; set; }
2728
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
2829
}
2930
#pragma warning restore CS1591

ZWaveDotNet/Entities/Node.cs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ public byte EndPointCount
178178
/// <returns></returns>
179179
public EndPoint? GetEndPoint(byte ID)
180180
{
181-
if (ID >= endPoints.Count)
181+
ID--;
182+
if (ID >= endPoints.Count || ID < 0)
182183
return null;
183184
return endPoints[ID];
184185
}
@@ -258,15 +259,15 @@ private async Task<SupervisionStatus> HandleReport(ReportMessage msg)
258259
{
259260
if (msg.SourceEndpoint == 0)
260261
{
261-
if (!commandClasses.ContainsKey(msg.CommandClass))
262+
if (!HasCommandClass(msg.CommandClass))
262263
AddCommandClass(msg.CommandClass, (msg.SecurityLevel != SecurityKey.None));
263264
return await commandClasses[msg.CommandClass].ProcessMessage(msg);
264265
}
265266
else
266267
{
267268
EndPoint? ep = GetEndPoint(msg.SourceEndpoint);
268269
if (ep != null)
269-
return await ep.HandleReport(msg).ConfigureAwait(false);
270+
return await ep.HandleReport(msg);
270271
}
271272
return SupervisionStatus.NoSupport;
272273
}
@@ -317,7 +318,8 @@ internal NodeJSON Serialize()
317318
NodeProtocolInfo = nodeInfo,
318319
ID = ID,
319320
CommandClasses = new CommandClassJson[commandClasses.Count],
320-
Interviewed = Interviewed
321+
Interviewed = Interviewed,
322+
EndPoints = new EndPointJson[endPoints.Count]
321323
};
322324
if (controller.SecurityManager != null)
323325
{
@@ -334,11 +336,13 @@ internal NodeJSON Serialize()
334336
CommandClass cls = commandClasses.ElementAt(i).Key;
335337
json.CommandClasses[i] = new CommandClassJson
336338
{
337-
CommandClass = commandClasses[cls].CommandClass,
339+
CommandClass = cls,
338340
Version = commandClasses[cls].Version,
339341
Secure = commandClasses[cls].Secure
340342
};
341343
}
344+
for (int i = 0; i < endPoints.Count; i++)
345+
json.EndPoints[i] = endPoints[i].Serialize();
342346
return json;
343347
}
344348

@@ -348,6 +352,9 @@ internal void Deserialize(NodeJSON json)
348352
foreach (CommandClassJson cc in json.CommandClasses)
349353
AddCommandClass(cc.CommandClass, cc.Secure, cc.Version);
350354

355+
foreach (EndPointJson ep in json.EndPoints)
356+
endPoints.Add(new EndPoint(ep, this));
357+
351358
if (controller.SecurityManager != null)
352359
{
353360
foreach (SecurityKey grantedKey in json.GrantedKeys)
@@ -394,7 +401,7 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key
394401
if (!newlyIncluded && key == null)
395402
{
396403
//We need to try keys one at a time
397-
if (commandClasses.ContainsKey(CommandClass.Security0))
404+
if (HasCommandClass(CommandClass.Security0))
398405
{
399406
controller.SecurityManager.GrantKey(ID, SecurityManager.RecordType.S0);
400407
Log.Information("Checking S0 Security");
@@ -426,7 +433,7 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key
426433
controller.SecurityManager.RevokeKey(ID, SecurityManager.RecordType.S0);
427434
}
428435
}
429-
if (commandClasses.ContainsKey(CommandClass.Security2))
436+
if (HasCommandClass(CommandClass.Security2))
430437
{
431438
controller.SecurityManager.GrantKey(ID, SecurityManager.RecordType.S2UnAuth, controller.NetworkKeyS2UnAuth);
432439
Log.Information("Checking S2 Unauth Security");
@@ -525,22 +532,25 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key
525532
else
526533
{
527534
//Whatever keys we have is what the device has
528-
if (key != null && key.Key == SecurityManager.RecordType.S0 && commandClasses.ContainsKey(CommandClass.Security0))
535+
if (key != null && key.Key == SecurityManager.RecordType.S0 && HasCommandClass(CommandClass.Security0))
529536
await RequestS0(cancellationToken).ConfigureAwait(false);
530-
else if (key != null && commandClasses.ContainsKey(CommandClass.Security2))
537+
else if (key != null && HasCommandClass(CommandClass.Security2))
531538
await RequestS2(cancellationToken).ConfigureAwait(false);
532539
}
533540
}
534-
if (this.commandClasses.ContainsKey(CommandClass.MultiChannel))
541+
if (HasCommandClass(CommandClass.MultiChannel))
535542
{
536543
Log.Information("Requesting MultiChannel EndPoints");
537-
EndPointReport epReport = await ((MultiChannel)commandClasses[CommandClass.MultiChannel]).GetEndPoints(cancellationToken);
544+
EndPointReport epReport = await GetCommandClass<MultiChannel>()!.GetEndPoints(cancellationToken);
538545
for (int i = 0; i < epReport.IndividualEndPoints; i++)
539-
endPoints.Add(new EndPoint((byte)(i + 1), this));
546+
{
547+
EndPointCapabilities caps = await GetCommandClass<MultiChannel>()!.GetCapabilities((byte)(i + 1), cancellationToken);
548+
endPoints.Add(new EndPoint((byte)(i + 1), this, caps.CommandClasses));
549+
}
540550
}
541551

542552
Log.Information("Checking Command Class Versions");
543-
if (this.commandClasses.ContainsKey(CommandClass.Version))
553+
if (HasCommandClass(CommandClass.Version))
544554
{
545555
CommandClasses.Version version = (CommandClasses.Version)commandClasses[CommandClass.Version];
546556
foreach (CommandClassBase cc in commandClasses.Values)
@@ -557,6 +567,11 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key
557567
try
558568
{
559569
cc.Version = await version.GetCommandClassVersion(cc.CommandClass, cts.Token);
570+
foreach (EndPoint ep in endPoints)
571+
{
572+
if (HasCommandClass(cc.CommandClass))
573+
ep.CommandClasses[cc.CommandClass].Version = cc.Version;
574+
}
560575
}
561576
catch (OperationCanceledException oc)
562577
{

0 commit comments

Comments
 (0)