Skip to content

Commit

Permalink
Implement Multi Channel Association
Browse files Browse the repository at this point in the history
  • Loading branch information
jdomnitz committed Sep 13, 2024
1 parent eb83acd commit 592c35c
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 3 deletions.
4 changes: 2 additions & 2 deletions SupportedCommandClasses.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ METER_TBL_MONITOR | 0 | 3 | None
METER_TBL_PUSH | 0 | 1 | None
**MTP_WINDOW_COVERING** | **1** | **1** | **Full**
**MULTI_CHANNEL** | **4** | **4** | **Full**
MULTI_CHANNEL_ASSOCIATION | 0 | 4 | None
**MULTI_CHANNEL_ASSOCIATION** | **5** | **4** | **Full**
**MULTI_CMD** | **1** | **1** | **Full**
**NO_OPERATION** | **1** | **1** | **Full**
**NODE_NAMING** | **1** | **1** | **Full**
Expand Down Expand Up @@ -104,5 +104,5 @@ USER_CODE | 0 | 2 | None
**WINDOW_COVERING** | **1** | **1** | **Full**
**ZWAVEPLUS_INFO** | **2** | **2** | **Full**

- Full Support for 62/103 Command Classes.
- Full Support for 63/103 Command Classes.
- Partial Support for 3/103 Command Classes.
2 changes: 1 addition & 1 deletion TestConsole/CCParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static void Generate()
CCVersion ccv = attrs[cc];
if (cc == CommandClass.Alarm && parts[2] != "2")
ccv = attrs[CommandClass.Mark]; //Hack for notification cc
bool complete = ccv.complete && int.TryParse(parts[2], out int result) && ccv.maxVersion == result;
bool complete = ccv.complete && int.TryParse(parts[2], out int result) && ccv.maxVersion >= result;
if (complete)
fw.Write("**");
else
Expand Down
49 changes: 49 additions & 0 deletions ZWaveDotNet/CommandClassReports/MultiChannelAssociationReport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@


using System.Data;
using ZWaveDotNet.Entities;
using ZWaveDotNet.Util;

namespace ZWaveDotNet.CommandClassReports
{
public class MultiChannelAssociationReport : ICommandClassReport
{
private const byte MULTI_CHANNEL_ASSOCIATION_SET_MARKER = 0x0;

public readonly byte GroupID;

public readonly byte MaxNodesSupported;

public readonly byte ReportsToFollow;

public readonly ushort[] Nodes;

public readonly NodeEndpoint[] Endpoints;

internal MultiChannelAssociationReport(Memory<byte> payload)
{
if (payload.Length < 3)
throw new DataException($"The Multi Channel Association Report was not in the expected format. Payload: {MemoryUtil.Print(payload)}");

GroupID = payload.Span[0];
MaxNodesSupported = payload.Span[1];
ReportsToFollow = payload.Span[2];

bool nodeMode = true;
List<ushort> nodes = new List<ushort>();
List<NodeEndpoint> eps = new List<NodeEndpoint>();

for (int p = 3; p < payload.Length - 1; p++)
{
if (payload.Span[p] == MULTI_CHANNEL_ASSOCIATION_SET_MARKER)
nodeMode = false;
else if (nodeMode)
nodes.Add(payload.Span[p]); //FIXME: The spec is undefined for 16-bit NodeIDs
else
eps.Add(new NodeEndpoint(payload.Span[p++], payload.Span[p]));
}
Nodes = nodes.ToArray();
Endpoints = eps.ToArray();
}
}
}
100 changes: 100 additions & 0 deletions ZWaveDotNet/CommandClasses/MultiChannelAssociation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// ZWaveDotNet Copyright (C) 2024
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using ZWaveDotNet.CommandClassReports;
using ZWaveDotNet.CommandClassReports.Enums;
using ZWaveDotNet.Entities;
using ZWaveDotNet.Enums;
using ZWaveDotNet.SerialAPI;

namespace ZWaveDotNet.CommandClasses
{
[CCVersion(CommandClass.MultiChannelAssociation, 2, 5)]
public class MultiChannelAssociation : CommandClassBase
{
public event CommandClassEvent<MultiChannelAssociationReport>? AssociationUpdate;

public enum MultiChannelAssociationCommand
{
Set = 0x1,
Get = 0x2,
Report = 0x3,
Remove = 0x4,
GroupingsGet = 0x5,
GroupingsReport = 0x6
}
public MultiChannelAssociation(Node node, byte endpoint) : base(node, endpoint, CommandClass.MultiChannelAssociation) { }

public async Task<MultiChannelAssociationReport> Get(byte groupId, CancellationToken cancellationToken = default)
{
if (node.ID == Node.BROADCAST_ID)
throw new MethodAccessException("GET methods may not be called on broadcast nodes");
ReportMessage response = await SendReceive(MultiChannelAssociationCommand.Get, MultiChannelAssociationCommand.Report, cancellationToken, groupId);
return new MultiChannelAssociationReport(response.Payload);
}

public async Task Set(byte groupId, ushort[] nodeIds, NodeEndpoint[] endPoints, CancellationToken cancellationToken = default)
{
if (node.ID == Node.BROADCAST_ID)
throw new MethodAccessException("Multi channel commands may not be called on broadcast nodes");
await AdjustMembership(MultiChannelAssociationCommand.Set, groupId, nodeIds, endPoints, cancellationToken);
}

public async Task Remove(byte groupId, ushort[] nodeIds, NodeEndpoint[] endPoints, CancellationToken cancellationToken = default)
{
if (node.ID == Node.BROADCAST_ID)
throw new MethodAccessException("Multi channel commands may not be called on broadcast nodes");
await AdjustMembership(MultiChannelAssociationCommand.Remove, groupId, nodeIds, endPoints, cancellationToken);
}

public async Task<byte> GetSupportedGroupCount(CancellationToken cancellationToken = default)
{
if (node.ID == Node.BROADCAST_ID)
throw new MethodAccessException("GET methods may not be called on broadcast nodes");
ReportMessage response = await SendReceive(MultiChannelAssociationCommand.Get, MultiChannelAssociationCommand.GroupingsReport, cancellationToken);
return response.Payload.Span[0];
}

private async Task AdjustMembership(MultiChannelAssociationCommand command, byte groupId, ushort[] nodeIds, NodeEndpoint[] endPoints, CancellationToken cancellationToken)
{
byte[] payload = new byte[nodeIds.Length + ((endPoints.Length > 0) ? 2 : 1) + endPoints.Length];
int i = 0;
payload[i++] = groupId;
for (; i < nodeIds.Length; i++)
payload[i] = (byte)nodeIds[i];
if (endPoints.Length > 0)
i++;
i++;
foreach (var ep in endPoints)
{
payload[i++] = (byte)ep.NodeID;
payload[i] = ep.EndpointID;
if (ep.BitmaskEndpoint)
payload[i++] |= 0x80;
else
i++;
}
await SendCommand(command, cancellationToken, payload);
}

protected override async Task<SupervisionStatus> Handle(ReportMessage message)
{
if (message.Command == (byte)MultiChannelAssociationCommand.Report)
{
MultiChannelAssociationReport report = new MultiChannelAssociationReport(message.Payload);
await FireEvent(AssociationUpdate, report);
return SupervisionStatus.Success;
}
return SupervisionStatus.NoSupport;
}
}
}
28 changes: 28 additions & 0 deletions ZWaveDotNet/Entities/NodeEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// ZWaveDotNet Copyright (C) 2024
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

namespace ZWaveDotNet.Entities
{
public struct NodeEndpoint
{
public NodeEndpoint(ushort nodeId, byte endPoint)
{
NodeID = nodeId;
EndpointID = (byte)(endPoint & 0x7F);
BitmaskEndpoint = ((endPoint & 0x80) == 0x80);
}

public ushort NodeID { get; set; }
public byte EndpointID { get; set; }
public bool BitmaskEndpoint { get; set; }
}
}

0 comments on commit 592c35c

Please sign in to comment.