Skip to content

Commit a81ec60

Browse files
authored
Polish ZWavePlusInfo CC (#206)
1 parent 2b12786 commit a81ec60

3 files changed

Lines changed: 193 additions & 44 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using Microsoft.Extensions.Logging.Abstractions;
2+
3+
namespace ZWave.CommandClasses.Tests;
4+
5+
[TestClass]
6+
public class ZWavePlusInfoCommandClassTests
7+
{
8+
[TestMethod]
9+
public void GetCommand_Create_HasCorrectFormat()
10+
{
11+
ZWavePlusInfoCommandClass.ZWavePlusInfoGetCommand command = ZWavePlusInfoCommandClass.ZWavePlusInfoGetCommand.Create();
12+
13+
Assert.AreEqual(CommandClassId.ZWavePlusInfo, ZWavePlusInfoCommandClass.ZWavePlusInfoGetCommand.CommandClassId);
14+
Assert.AreEqual((byte)ZWavePlusInfoCommand.Get, ZWavePlusInfoCommandClass.ZWavePlusInfoGetCommand.CommandId);
15+
Assert.AreEqual(2, command.Frame.Data.Length);
16+
}
17+
18+
[TestMethod]
19+
public void Report_Parse_ValidPayload()
20+
{
21+
// CC=0x5E, Cmd=0x02, Version=1, RoleType=CSC(0x00), NodeType=Node(0x00),
22+
// InstallerIcon=0x0100, UserIcon=0x0200
23+
byte[] data = [0x5E, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00];
24+
CommandClassFrame frame = new(data);
25+
26+
ZWavePlusInfoReport report = ZWavePlusInfoCommandClass.ZWavePlusInfoReportCommand.Parse(frame, NullLogger.Instance);
27+
28+
Assert.AreEqual((byte)1, report.ZWavePlusVersion);
29+
Assert.AreEqual(ZWavePlusRoleType.CentralStaticController, report.RoleType);
30+
Assert.AreEqual(ZWavePlusNodeType.Node, report.NodeType);
31+
Assert.AreEqual((ushort)0x0100, report.InstallerIconType);
32+
Assert.AreEqual((ushort)0x0200, report.UserIconType);
33+
}
34+
35+
[TestMethod]
36+
public void Report_Parse_AllFieldValues()
37+
{
38+
// CC=0x5E, Cmd=0x02, Version=2, RoleType=AOEN(0x05), NodeType=IpGateway(0x02),
39+
// InstallerIcon=0x0701, UserIcon=0x0700
40+
byte[] data = [0x5E, 0x02, 0x02, 0x05, 0x02, 0x07, 0x01, 0x07, 0x00];
41+
CommandClassFrame frame = new(data);
42+
43+
ZWavePlusInfoReport report = ZWavePlusInfoCommandClass.ZWavePlusInfoReportCommand.Parse(frame, NullLogger.Instance);
44+
45+
Assert.AreEqual((byte)2, report.ZWavePlusVersion);
46+
Assert.AreEqual(ZWavePlusRoleType.AlwaysOnEndNode, report.RoleType);
47+
Assert.AreEqual(ZWavePlusNodeType.IpGateway, report.NodeType);
48+
Assert.AreEqual((ushort)0x0701, report.InstallerIconType);
49+
Assert.AreEqual((ushort)0x0700, report.UserIconType);
50+
}
51+
52+
[TestMethod]
53+
public void Report_Parse_WakeOnEventEndNode()
54+
{
55+
// CC=0x5E, Cmd=0x02, Version=2, RoleType=WOEEN(0x09), NodeType=Node(0x00),
56+
// InstallerIcon=0x0000, UserIcon=0x0000
57+
byte[] data = [0x5E, 0x02, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00];
58+
CommandClassFrame frame = new(data);
59+
60+
ZWavePlusInfoReport report = ZWavePlusInfoCommandClass.ZWavePlusInfoReportCommand.Parse(frame, NullLogger.Instance);
61+
62+
Assert.AreEqual(ZWavePlusRoleType.WakeOnEventEndNode, report.RoleType);
63+
}
64+
65+
[TestMethod]
66+
public void Report_Parse_TooShort_Throws()
67+
{
68+
// CC=0x5E, Cmd=0x02, only 6 parameter bytes (need 7)
69+
byte[] data = [0x5E, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x02];
70+
CommandClassFrame frame = new(data);
71+
72+
Assert.ThrowsExactly<ZWaveException>(
73+
() => ZWavePlusInfoCommandClass.ZWavePlusInfoReportCommand.Parse(frame, NullLogger.Instance));
74+
}
75+
76+
[TestMethod]
77+
public void Report_Parse_ExtraBytes_Succeeds()
78+
{
79+
// CC=0x5E, Cmd=0x02, 7 parameter bytes + 2 extra (forward compatibility)
80+
byte[] data = [0x5E, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0xAA, 0xBB];
81+
CommandClassFrame frame = new(data);
82+
83+
ZWavePlusInfoReport report = ZWavePlusInfoCommandClass.ZWavePlusInfoReportCommand.Parse(frame, NullLogger.Instance);
84+
85+
Assert.AreEqual((byte)1, report.ZWavePlusVersion);
86+
Assert.AreEqual(ZWavePlusRoleType.CentralStaticController, report.RoleType);
87+
Assert.AreEqual(ZWavePlusNodeType.Node, report.NodeType);
88+
Assert.AreEqual((ushort)0x0100, report.InstallerIconType);
89+
Assert.AreEqual((ushort)0x0200, report.UserIconType);
90+
}
91+
92+
[TestMethod]
93+
public void Report_Parse_EmptyPayload_Throws()
94+
{
95+
// CC=0x5E, Cmd=0x02, no parameters
96+
byte[] data = [0x5E, 0x02];
97+
CommandClassFrame frame = new(data);
98+
99+
Assert.ThrowsExactly<ZWaveException>(
100+
() => ZWavePlusInfoCommandClass.ZWavePlusInfoReportCommand.Parse(frame, NullLogger.Instance));
101+
}
102+
}

src/ZWave.CommandClasses/ZWavePlusInfoCommandClass.cs

Lines changed: 89 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,62 +20,103 @@ public enum ZWavePlusInfoCommand : byte
2020
/// </summary>
2121
public enum ZWavePlusRoleType : byte
2222
{
23+
/// <summary>
24+
/// Central Static Controller (CSC).
25+
/// </summary>
2326
CentralStaticController = 0x00,
27+
28+
/// <summary>
29+
/// Sub Static Controller (SSC).
30+
/// </summary>
2431
SubStaticController = 0x01,
32+
33+
/// <summary>
34+
/// Portable Controller (PC).
35+
/// </summary>
2536
PortableController = 0x02,
37+
38+
/// <summary>
39+
/// Reporting Portable Controller (RPC).
40+
/// </summary>
2641
ReportingPortableController = 0x03,
27-
PortableSlave = 0x04,
28-
AlwaysOnSlave = 0x05,
29-
ReportingSleepingSlave = 0x06,
30-
ListeningSleepingSlave = 0x07,
31-
NetworkAwareSlave = 0x08,
32-
}
3342

34-
/// <summary>
35-
/// Represents Z-Wave Plus information for a device.
36-
/// </summary>
37-
public readonly record struct ZWavePlusInfo(
3843
/// <summary>
39-
/// Enables a future revision of the Z-Wave Plus framework where it is necessary to distinguish it from the previous frameworks
44+
/// Portable End Node (PEN).
4045
/// </summary>
41-
byte ZWavePlusVersion,
46+
PortableEndNode = 0x04,
4247

4348
/// <summary>
44-
/// Indicates the role the Z-Wave Plus device in question possess in the network and functionalities supported.
49+
/// Always On End Node (AOEN).
4550
/// </summary>
46-
ZWavePlusRoleType RoleType,
51+
AlwaysOnEndNode = 0x05,
4752

4853
/// <summary>
49-
/// Indicates the type of node the Z-Wave Plus device in question possess in the network.
54+
/// Reporting Sleeping End Node (RSEN).
5055
/// </summary>
51-
ZWavePlusNodeType NodeType,
56+
ReportingSleepingEndNode = 0x06,
5257

5358
/// <summary>
54-
/// Indicates the icon to use in Graphical User Interfaces for network management
59+
/// Listening Sleeping End Node (LSEN).
5560
/// </summary>
56-
ushort InstallerIconType,
61+
ListeningSleepingEndNode = 0x07,
5762

5863
/// <summary>
59-
/// Indicates the icon to use in Graphical User Interfaces for end users
64+
/// Network Aware End Node (NAEN).
6065
/// </summary>
61-
ushort UserIconType);
66+
NetworkAwareEndNode = 0x08,
67+
68+
/// <summary>
69+
/// Wake On Event End Node (WOEEN).
70+
/// </summary>
71+
WakeOnEventEndNode = 0x09,
72+
}
6273

6374
/// <summary>
6475
/// Identifies the Z-Wave Plus node type.
6576
/// </summary>
6677
public enum ZWavePlusNodeType : byte
6778
{
6879
/// <summary>
69-
/// Z-Wave Plus node
80+
/// Z-Wave Plus node.
7081
/// </summary>
7182
Node = 0x00,
7283

7384
/// <summary>
74-
/// Z-Wave Plus for IP gateway
85+
/// Z-Wave Plus for IP gateway.
7586
/// </summary>
7687
IpGateway = 0x02,
7788
}
7889

90+
/// <summary>
91+
/// Represents a Z-Wave Plus Info Report received from a device.
92+
/// </summary>
93+
public readonly record struct ZWavePlusInfoReport(
94+
/// <summary>
95+
/// Enables a future revision of the Z-Wave Plus framework where it is necessary to distinguish
96+
/// it from the previous frameworks.
97+
/// </summary>
98+
byte ZWavePlusVersion,
99+
100+
/// <summary>
101+
/// Indicates the role the Z-Wave Plus device in question possess in the network and functionalities supported.
102+
/// </summary>
103+
ZWavePlusRoleType RoleType,
104+
105+
/// <summary>
106+
/// Indicates the type of node the Z-Wave Plus device in question possess in the network.
107+
/// </summary>
108+
ZWavePlusNodeType NodeType,
109+
110+
/// <summary>
111+
/// Indicates the icon to use in Graphical User Interfaces for network management.
112+
/// </summary>
113+
ushort InstallerIconType,
114+
115+
/// <summary>
116+
/// Indicates the icon to use in Graphical User Interfaces for end users.
117+
/// </summary>
118+
ushort UserIconType);
119+
79120
[CommandClass(CommandClassId.ZWavePlusInfo)]
80121
public sealed class ZWavePlusInfoCommandClass : CommandClass<ZWavePlusInfoCommand>
81122
{
@@ -85,9 +126,14 @@ internal ZWavePlusInfoCommandClass(CommandClassInfo info, IDriver driver, IEndpo
85126
}
86127

87128
/// <summary>
88-
/// Gets the Z-Wave Plus information.
129+
/// Gets the last Z-Wave Plus Info Report received from the device.
89130
/// </summary>
90-
public ZWavePlusInfo? ZWavePlusInfo { get; private set; }
131+
public ZWavePlusInfoReport? LastReport { get; private set; }
132+
133+
/// <summary>
134+
/// Occurs when a Z-Wave Plus Info Report is received.
135+
/// </summary>
136+
public event Action<ZWavePlusInfoReport>? OnZWavePlusInfoReportReceived;
91137

92138
/// <inheritdoc />
93139
public override bool? IsCommandSupported(ZWavePlusInfoCommand command)
@@ -100,14 +146,15 @@ internal ZWavePlusInfoCommandClass(CommandClassInfo info, IDriver driver, IEndpo
100146
/// <summary>
101147
/// Get additional information of the Z-Wave Plus device in question.
102148
/// </summary>
103-
public async Task<ZWavePlusInfo> GetAsync(CancellationToken cancellationToken)
149+
public async Task<ZWavePlusInfoReport> GetAsync(CancellationToken cancellationToken)
104150
{
105151
ZWavePlusInfoGetCommand command = ZWavePlusInfoGetCommand.Create();
106152
await SendCommandAsync(command, cancellationToken).ConfigureAwait(false);
107153
CommandClassFrame reportFrame = await AwaitNextReportAsync<ZWavePlusInfoReportCommand>(cancellationToken).ConfigureAwait(false);
108-
ZWavePlusInfo info = ZWavePlusInfoReportCommand.Parse(reportFrame, Logger);
109-
ZWavePlusInfo = info;
110-
return info;
154+
ZWavePlusInfoReport report = ZWavePlusInfoReportCommand.Parse(reportFrame, Logger);
155+
LastReport = report;
156+
OnZWavePlusInfoReportReceived?.Invoke(report);
157+
return report;
111158
}
112159

113160
internal override CommandClassCategory Category => CommandClassCategory.Management;
@@ -121,19 +168,17 @@ protected override void ProcessUnsolicitedCommand(CommandClassFrame frame)
121168
{
122169
switch ((ZWavePlusInfoCommand)frame.CommandId)
123170
{
124-
case ZWavePlusInfoCommand.Get:
125-
{
126-
break;
127-
}
128171
case ZWavePlusInfoCommand.Report:
129172
{
130-
ZWavePlusInfo = ZWavePlusInfoReportCommand.Parse(frame, Logger);
173+
ZWavePlusInfoReport report = ZWavePlusInfoReportCommand.Parse(frame, Logger);
174+
LastReport = report;
175+
OnZWavePlusInfoReportReceived?.Invoke(report);
131176
break;
132177
}
133178
}
134179
}
135180

136-
private readonly struct ZWavePlusInfoGetCommand : ICommand
181+
internal readonly struct ZWavePlusInfoGetCommand : ICommand
137182
{
138183
public ZWavePlusInfoGetCommand(CommandClassFrame frame)
139184
{
@@ -153,7 +198,7 @@ public static ZWavePlusInfoGetCommand Create()
153198
}
154199
}
155200

156-
private readonly struct ZWavePlusInfoReportCommand : ICommand
201+
internal readonly struct ZWavePlusInfoReportCommand : ICommand
157202
{
158203
public ZWavePlusInfoReportCommand(CommandClassFrame frame)
159204
{
@@ -166,20 +211,22 @@ public ZWavePlusInfoReportCommand(CommandClassFrame frame)
166211

167212
public CommandClassFrame Frame { get; }
168213

169-
public static ZWavePlusInfo Parse(CommandClassFrame frame, ILogger logger)
214+
public static ZWavePlusInfoReport Parse(CommandClassFrame frame, ILogger logger)
170215
{
171216
if (frame.CommandParameters.Length < 7)
172217
{
173218
logger.LogWarning("Z-Wave Plus Info Report frame is too short ({Length} bytes)", frame.CommandParameters.Length);
174219
throw new ZWaveException(ZWaveErrorCode.InvalidPayload, "Z-Wave Plus Info Report frame is too short");
175220
}
176221

177-
byte zwavePlusVersion = frame.CommandParameters.Span[0];
178-
ZWavePlusRoleType roleType = (ZWavePlusRoleType)frame.CommandParameters.Span[1];
179-
ZWavePlusNodeType nodeType = (ZWavePlusNodeType)frame.CommandParameters.Span[2];
180-
ushort installerIconType = frame.CommandParameters.Span[3..5].ToUInt16BE();
181-
ushort userIconType = frame.CommandParameters.Span[5..7].ToUInt16BE();
182-
return new ZWavePlusInfo(zwavePlusVersion, roleType, nodeType, installerIconType, userIconType);
222+
ReadOnlySpan<byte> span = frame.CommandParameters.Span;
223+
224+
byte zwavePlusVersion = span[0];
225+
ZWavePlusRoleType roleType = (ZWavePlusRoleType)span[1];
226+
ZWavePlusNodeType nodeType = (ZWavePlusNodeType)span[2];
227+
ushort installerIconType = span[3..5].ToUInt16BE();
228+
ushort userIconType = span[5..7].ToUInt16BE();
229+
return new ZWavePlusInfoReport(zwavePlusVersion, roleType, nodeType, installerIconType, userIconType);
183230
}
184231
}
185232
}

src/ZWave.Server/CommandClasses/ZWavePlusInfo.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
@if (CommandClass != null)
88
{
99
<table class="table table-striped">
10-
@if (CommandClass.ZWavePlusInfo.HasValue)
10+
@if (CommandClass.LastReport.HasValue)
1111
{
12-
var info = CommandClass.ZWavePlusInfo.Value;
12+
var info = CommandClass.LastReport.Value;
1313

1414
<tbody>
1515
<tr>

0 commit comments

Comments
 (0)