diff --git a/ZWaveDotNet/CommandClassReports/Enums/RateType.cs b/ZWaveDotNet/CommandClassReports/Enums/RateType.cs index c4116fb..a0578a1 100644 --- a/ZWaveDotNet/CommandClassReports/Enums/RateType.cs +++ b/ZWaveDotNet/CommandClassReports/Enums/RateType.cs @@ -14,7 +14,7 @@ namespace ZWaveDotNet.CommandClassReports.Enums { public enum RateType : byte { - Unspecified = 0x0, + Default = 0x0, Import = 0x1, Export = 0x2 } diff --git a/ZWaveDotNet/CommandClassReports/MeterReport.cs b/ZWaveDotNet/CommandClassReports/MeterReport.cs index 78e180a..c31891c 100644 --- a/ZWaveDotNet/CommandClassReports/MeterReport.cs +++ b/ZWaveDotNet/CommandClassReports/MeterReport.cs @@ -24,7 +24,7 @@ public class MeterReport : ICommandClassReport public readonly float Value; public readonly Units Unit; public readonly RateType RateType; - public readonly TimeSpan ElapsedTime; + public readonly TimeSpan ElapsedTime = TimeSpan.Zero; public readonly float LastValue; internal MeterReport(Memory payload) @@ -42,8 +42,10 @@ internal MeterReport(Memory payload) if (payload.Length >= size + 4) { ushort secs = BinaryPrimitives.ReadUInt16BigEndian(payload.Slice(2 + size, 2).Span); - if (secs != 0xFFFF && secs != 0) + if (secs != 0) { + if (secs != 0xFFFF) + ElapsedTime = TimeSpan.FromSeconds(secs); LastValue = PayloadConverter.ToFloat(payload.Slice(4 + size), size, precision); if (payload.Length > (2 * size) + 4) scale2 = payload.Span[4 + (2* size)]; @@ -51,10 +53,6 @@ internal MeterReport(Memory payload) else if (payload.Length > size + 6) scale2 = payload.Span[4 + size]; } - else - { - ElapsedTime = TimeSpan.Zero; - } Unit = GetUnit(Type, scale, scale2); } @@ -109,7 +107,7 @@ public static Units GetUnit(MeterType type, byte scale, byte scale2) public override string ToString() { - return $"Type:{Type}, Value:\"{Value} {Unit}\", Last:\"{LastValue} {Unit}\""; + return $"Type:{Type}, Value:\"{Value} {Unit}\", Last:\"{LastValue} {Unit}\", Elapsed: {ElapsedTime}"; } } } diff --git a/ZWaveDotNet/CommandClassReports/SensorAlarmReport.cs b/ZWaveDotNet/CommandClassReports/SensorAlarmReport.cs index 1fbbc6d..8a13bc7 100644 --- a/ZWaveDotNet/CommandClassReports/SensorAlarmReport.cs +++ b/ZWaveDotNet/CommandClassReports/SensorAlarmReport.cs @@ -19,8 +19,11 @@ namespace ZWaveDotNet.CommandClassReports { public class SensorAlarmReport : ICommandClassReport { - public readonly byte Source; + public readonly ushort SourceNodeID; public readonly AlarmType Type; + /// + /// 0 = No Alarm, 1 - 99 indicate % severity, 255 = Alarm + /// public readonly byte Level; public readonly ushort Duration; @@ -29,7 +32,7 @@ internal SensorAlarmReport(Memory payload) if (payload.Length < 3) throw new DataException($"The Sensor Alarm Report was not in the expected format. Payload: {MemoryUtil.Print(payload)}"); - Source = payload.Span[0]; + SourceNodeID = payload.Span[0]; Type = (AlarmType)payload.Span[1]; Level = payload.Span[2]; Duration = BinaryPrimitives.ReadUInt16BigEndian(payload.Slice(3, 2).Span); @@ -37,7 +40,7 @@ internal SensorAlarmReport(Memory payload) public override string ToString() { - return $"Source:{Source}, Type:{Type}, Level:{Level}, Duration:{Duration}"; + return $"Source:{SourceNodeID}, Type:{Type}, Level:{Level}, Duration:{Duration}"; } } } diff --git a/ZWaveDotNet/CommandClassReports/TimeOffsetReport.cs b/ZWaveDotNet/CommandClassReports/TimeOffsetReport.cs index 9461658..64e6c3b 100644 --- a/ZWaveDotNet/CommandClassReports/TimeOffsetReport.cs +++ b/ZWaveDotNet/CommandClassReports/TimeOffsetReport.cs @@ -10,9 +10,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using System.Buffers.Binary; using System.Data; -using ZWaveDotNet.CommandClasses.Enums; using ZWaveDotNet.Util; namespace ZWaveDotNet.CommandClassReports diff --git a/ZWaveDotNet/CommandClasses/CommandClassBase.cs b/ZWaveDotNet/CommandClasses/CommandClassBase.cs index bd80e7f..007d007 100644 --- a/ZWaveDotNet/CommandClasses/CommandClassBase.cs +++ b/ZWaveDotNet/CommandClasses/CommandClassBase.cs @@ -245,11 +245,10 @@ protected async Task SendCommand(CommandMessage data, CancellationToken token) DataMessage message = data.ToMessage(); for (int i = 0; i < 3; i++) { - if (await AttemptTransmission(message, token, i == 2).ConfigureAwait(false) == false) - { - Log.Error($"Controller Failed to Send Message: Retrying [Attempt {i+1}]..."); - await Task.Delay(100 + (1000 * i), token).ConfigureAwait(false); - } + if ((await AttemptTransmission(message, token, i == 2).ConfigureAwait(false)) == true) + return; + Log.Error($"Controller Failed to Send Message: Retrying [Attempt {i + 1}]..."); + await Task.Delay(100 + Random.Shared.Next(1, 25) + (1000 * i), token).ConfigureAwait(false); } } diff --git a/ZWaveDotNet/CommandClasses/Meter.cs b/ZWaveDotNet/CommandClasses/Meter.cs index f1d50c3..2b5ff86 100644 --- a/ZWaveDotNet/CommandClasses/Meter.cs +++ b/ZWaveDotNet/CommandClasses/Meter.cs @@ -117,7 +117,7 @@ private byte GetScale(MeterType type, Units unit) case Units.PowerFactor: return 6; default: - return 0; + return 7; } case MeterType.Gas: case MeterType.Water: diff --git a/ZWaveDotNet/CommandClasses/Security2.cs b/ZWaveDotNet/CommandClasses/Security2.cs index c2abc1b..6d67099 100644 --- a/ZWaveDotNet/CommandClasses/Security2.cs +++ b/ZWaveDotNet/CommandClasses/Security2.cs @@ -110,13 +110,13 @@ internal async Task SendNonceReport(bool SOS, bool MOS, bool forceNew, Cancellat else entropy = controller.SecurityManager.GetEntropy(node.ID, false) ?? controller.SecurityManager.CreateEntropy(node.ID); NonceReport nonceGetReport = new NonceReport(NextSequence(), SOS, MOS, entropy); - Log.Verbose("Declaring SPAN out of sync"); + Log.Warning("Declaring SPAN out of sync"); await SendCommand(Security2Command.NonceReport, cancellationToken, nonceGetReport.GetBytes()).ConfigureAwait(false); } internal async Task KexFail(KexFailType type, CancellationToken cancellationToken = default) { - Log.Verbose($"Sending KEX Failure {type}"); + Log.Error($"Sending KEX Failure {type}"); controller.SecurityManager?.GetRequestedKeys(node.ID, true); if (type == KexFailType.KEX_FAIL_AUTH || type == KexFailType.KEX_FAIL_DECRYPT || type == KexFailType.KEX_FAIL_KEY_VERIFY || type == KexFailType.KEX_FAIL_KEY_GET) { @@ -266,7 +266,7 @@ public async Task Encapsulate(List payload, SecurityManager.RecordType? ty { try { - Log.Verbose("Declaring SPAN failed and sending SOS"); + Log.Warning("Declaring SPAN failed and sending SOS"); controller.SecurityManager.PurgeRecords(msg.SourceNodeID, networkKey.Key); using (CancellationTokenSource cts = new CancellationTokenSource(3000)) await controller.Nodes[msg.SourceNodeID].GetCommandClass()!.SendNonceReport(true, false, false, cts.Token).ConfigureAwait(false); @@ -284,13 +284,14 @@ public async Task Encapsulate(List payload, SecurityManager.RecordType? ty groupId = decoded!.Value.Span[2]; Memory mpan = decoded.Value.Slice(3, 16); //TODO - Process the MPAN + Log.Warning("TODO: Process MPAN"); decoded = decoded.Value.Slice(19); } msg.Update(decoded!.Value); msg.Flags |= ReportFlags.Security; msg.SecurityLevel = SecurityManager.TypeToKey(networkKey.Key); - Log.Warning("Decoded Message: " + msg.ToString()); + Log.Verbose("Decoded Message: " + msg.ToString()); return msg; } diff --git a/ZWaveDotNet/CommandClasses/SensorAlarm.cs b/ZWaveDotNet/CommandClasses/SensorAlarm.cs index 4de28bd..6ce9efe 100644 --- a/ZWaveDotNet/CommandClasses/SensorAlarm.cs +++ b/ZWaveDotNet/CommandClasses/SensorAlarm.cs @@ -10,6 +10,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.Collections; using ZWaveDotNet.CommandClasses.Enums; using ZWaveDotNet.CommandClassReports; using ZWaveDotNet.CommandClassReports.Enums; @@ -40,6 +41,19 @@ public async Task Get(AlarmType type, CancellationToken cance return new SensorAlarmReport(response.Payload); } + public async Task SupportedGet(CancellationToken cancellationToken) + { + List types = new List(); + ReportMessage response = await SendReceive(SensorAlarmCommand.SupportedGet, SensorAlarmCommand.SupportedReport, cancellationToken); + BitArray supported = new BitArray(response.Payload.ToArray()); + for (int i = 0; i < supported.Length; i++) + { + if (supported[i]) + types.Add((AlarmType)i); + } + return types.ToArray(); + } + protected override async Task Handle(ReportMessage message) { if (message.Command == (byte)SensorAlarmCommand.Report) diff --git a/ZWaveDotNet/Entities/Controller.cs b/ZWaveDotNet/Entities/Controller.cs index 55e2144..af817c5 100644 --- a/ZWaveDotNet/Entities/Controller.cs +++ b/ZWaveDotNet/Entities/Controller.cs @@ -107,7 +107,7 @@ public async Task Reset() await Task.Delay(1500); } - public async ValueTask Start(CancellationToken cancellationToken = default) + public async ValueTask Start(string? nodeDbPath = null, CancellationToken cancellationToken = default) { SecurityManager = new SecurityManager(await GetRandom(32, cancellationToken)); _ = await Task.Factory.StartNew(EventLoop, TaskCreationOptions.LongRunning); @@ -133,6 +133,14 @@ public async ValueTask Start(CancellationToken cancellationToken = default) ControllerID = BinaryPrimitives.ReadUInt16BigEndian(networkIds.Data.Slice(4, 2).Span); } + //Load NodeDB + if (nodeDbPath != null) + { + await ImportNodeDBAsync(nodeDbPath, cancellationToken); + foreach (var kvp in Nodes) + kvp.Value.NodeFailed = await IsNodeFailed(kvp.Key, cancellationToken); + } + //Begin the controller interview if (await flow.SendAcknowledgedResponse(Function.GetSerialAPIInitData, cancellationToken) is InitData init) { @@ -392,7 +400,7 @@ private void Deserialize(ControllerJSON json) if (Nodes.ContainsKey(node.ID)) Nodes[node.ID].Deserialize(node); else - Log.Warning($"Node {node.ID} was skipped as it no longer exists"); + Nodes[node.ID] = new Node(node, this); } } diff --git a/ZWaveDotNet/Entities/Node.cs b/ZWaveDotNet/Entities/Node.cs index b90af8b..60d6fb7 100644 --- a/ZWaveDotNet/Entities/Node.cs +++ b/ZWaveDotNet/Entities/Node.cs @@ -52,7 +52,7 @@ public class Node public bool Routing { get { return nodeInfo?.Routing ?? false; } } public SpecificType SpecificType { get { return nodeInfo?.SpecificType ?? SpecificType.Unknown; } } public GenericType GenericType { get { return nodeInfo?.GenericType ?? GenericType.Unknown; } } - public bool NodeFailed { get { return failed; } } + public bool NodeFailed { get { return failed; } internal set { failed = value; } } public bool Interviewed { get { return interviewed; } } public sbyte RSSI { get; private set; } @@ -70,6 +70,14 @@ public Node(ushort id, Controller controller, NodeProtocolInfo? nodeInfo, Comman AddCommandClass(CommandClass.NoOperation); } + public Node(NodeJSON nodeJSON, Controller controller) + { + ID = nodeJSON.ID; + this.controller = controller; + Deserialize(nodeJSON); + nodeInfo = nodeJSON.NodeProtocolInfo; + } + private bool AddCommandClass(CommandClass cls, bool secure = false, byte version = 1) { return commandClasses.TryAdd(cls, CommandClassBase.Create(cls, this, 0, secure, version)); diff --git a/ZWaveDotNet/Enums/CommandClass.cs b/ZWaveDotNet/Enums/CommandClass.cs index 21f370f..5ff96c4 100644 --- a/ZWaveDotNet/Enums/CommandClass.cs +++ b/ZWaveDotNet/Enums/CommandClass.cs @@ -19,7 +19,7 @@ public enum CommandClass : ushort AntiTheftUnlock = 0x7E, ApplicationCapability = 0x57, ApplicationStatus = 0x22, - AssocCommandConfiguration = 0x9B, + AssociationCommandConfiguration = 0x9B, Association = 0x85, AssociationGroupInformation = 0x59, Authentication = 0xA1, diff --git a/ZWaveDotNet/SerialAPI/Flow.cs b/ZWaveDotNet/SerialAPI/Flow.cs index 666e583..9d71b43 100644 --- a/ZWaveDotNet/SerialAPI/Flow.cs +++ b/ZWaveDotNet/SerialAPI/Flow.cs @@ -145,8 +145,8 @@ private async Task SendAcknowledgedIntl(Channel reader, Frame frame, Canc if (await SuccessfulAck(reader, cts.Token)) break; } - Log.Verbose($"Retransmit Attempt {attempt + 1}"); - await Task.Delay(100 + (1000 * attempt), cancellationToken); + Log.Warning($"Retransmit Attempt {attempt + 1}"); + await Task.Delay(100 + Random.Shared.Next(1, 50) + (1000 * attempt), cancellationToken); } } finally diff --git a/ZWaveDotNet/SerialAPI/Frame.cs b/ZWaveDotNet/SerialAPI/Frame.cs index 4d1cbad..d258ff4 100644 --- a/ZWaveDotNet/SerialAPI/Frame.cs +++ b/ZWaveDotNet/SerialAPI/Frame.cs @@ -10,7 +10,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.IO.Ports; using ZWaveDotNet.SerialAPI.Enums; +using ZWaveDotNet.Util; namespace ZWaveDotNet.SerialAPI { @@ -19,6 +21,7 @@ public class Frame public static readonly Frame ACK = new Frame(FrameType.ACK); public static readonly Frame NAK = new Frame(FrameType.NAK); public static readonly Frame CAN = new Frame(FrameType.CAN); + public static volatile bool reading; public readonly FrameType Type; public readonly DataFrameType DataType; @@ -27,17 +30,20 @@ public class Frame public Frame() { } - public Frame (FrameType type, DataFrameType dataType = DataFrameType.Other, Function CommandID = Function.None, Memory payload = default) + public Frame (FrameType type, DataFrameType dataType = DataFrameType.Other, Function CommandID = Function.None, PayloadWriter? payload = null) { - this.Payload = payload; + if (payload == null) + this.Payload = Memory.Empty; + else + this.Payload = payload.GetPayload(); this.Type = type; this.DataType = dataType; this.CommandID = CommandID; } - public Frame(FrameType type, DataFrameType dataType, Function CommandID, List payload) + public Frame(FrameType type, DataFrameType dataType, Function CommandID, Memory payload) { - this.Payload = payload.ToArray(); + this.Payload = payload; this.Type = type; this.DataType = dataType; this.CommandID = CommandID; @@ -49,8 +55,10 @@ public Frame(FrameType type, DataFrameType dataType, Function CommandID, List buff) { - byte chk = 0xFF; byte len = buff.Span[1]; - for (int i = 1; i <= len; i++) - chk ^= buff.Span[i]; - return (buff.Span[len + 1] == chk); + return (buff.Span[len + 1] == MemoryUtil.XOR(buff.Slice(1, len), 0xFF)); } - private List GetPayload() + private Memory GetPayload() { - var buffer = new List - { - (byte)FrameType.SOF, - 0x00, - (byte)DataType, - (byte)CommandID - }; - buffer.AddRange(Payload.ToArray()); - return buffer; + Memory ret = new byte[Payload.Length + 5]; + ret.Span[0] = (byte)FrameType.SOF; + ret.Span[1] = (byte)(Payload.Length + 3); + ret.Span[2] = (byte)DataType; + ret.Span[3] = (byte)CommandID; + Payload.CopyTo(ret.Slice(4)); + //Add checksum + ret.Span[ret.Length - 1] = MemoryUtil.XOR(ret.Slice(1, ret.Length - 2), 0xFF); + return ret; } - public async Task WriteBytes(Stream stream, CancellationToken cancellationToken = default) + public async Task WriteBytes(SerialPort port, CancellationToken cancellationToken = default) { if (Type == FrameType.SOF) { - var payload = GetPayload(); + Memory payload = GetPayload(); - //Calculate length - payload[1] = (byte)(payload.Count - 1); - //Add checksum - payload.Add(payload.Skip(1).Aggregate((byte)0xFF, (total, next) => total ^= next)); - await stream.WriteAsync(payload.ToArray(), 0, payload.Count, cancellationToken); + while (reading || port.BytesToRead != 0) + await Task.Delay(1, cancellationToken); + await port.BaseStream.WriteAsync(payload.ToArray(), 0, payload.Length, cancellationToken); } else - stream.WriteByte((byte)Type); + port.BaseStream.WriteByte((byte)Type); } public override string ToString() diff --git a/ZWaveDotNet/SerialAPI/Messages/ApplicationUpdate.cs b/ZWaveDotNet/SerialAPI/Messages/ApplicationUpdate.cs index abd50cc..4949940 100644 --- a/ZWaveDotNet/SerialAPI/Messages/ApplicationUpdate.cs +++ b/ZWaveDotNet/SerialAPI/Messages/ApplicationUpdate.cs @@ -11,6 +11,7 @@ // along with this program. If not, see . using ZWaveDotNet.SerialAPI.Enums; +using ZWaveDotNet.Util; namespace ZWaveDotNet.SerialAPI.Messages { @@ -68,11 +69,11 @@ public static ApplicationUpdate From(Memory payload) } } - public override List GetPayload() + public override PayloadWriter GetPayload() { - List bytes = base.GetPayload(); - bytes.Add((byte)UpdateType); - return bytes; + PayloadWriter stream = base.GetPayload(); + stream.Write((byte)UpdateType); + return stream; } public override string ToString() diff --git a/ZWaveDotNet/SerialAPI/Messages/DataMessage.cs b/ZWaveDotNet/SerialAPI/Messages/DataMessage.cs index e74f054..827da12 100644 --- a/ZWaveDotNet/SerialAPI/Messages/DataMessage.cs +++ b/ZWaveDotNet/SerialAPI/Messages/DataMessage.cs @@ -10,11 +10,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using System.Buffers.Binary; using ZWaveDotNet.CommandClassReports.Enums; using ZWaveDotNet.Entities; using ZWaveDotNet.SerialAPI.Enums; using ZWaveDotNet.SerialAPI.Messages.Enums; +using ZWaveDotNet.Util; namespace ZWaveDotNet.SerialAPI.Messages @@ -55,35 +55,27 @@ public DataMessage(Controller controller, ushort nodeId, List data, bool c this.controller = controller; } - public override List GetPayload() + public override PayloadWriter GetPayload() { - List bytes = base.GetPayload(); + PayloadWriter writer = base.GetPayload(); if (Function == Function.SendDataBridge) { if (controller.WideID) - { - byte[] tmp = new byte[2]; - BinaryPrimitives.WriteUInt16BigEndian(tmp, SourceNodeID); - bytes.AddRange(tmp); - } + writer.Write(SourceNodeID); else - bytes.Add((byte)SourceNodeID); + writer.Write((byte)SourceNodeID); } if (controller.WideID) - { - byte[] tmp = new byte[2]; - BinaryPrimitives.WriteUInt16BigEndian(tmp, DestinationNodeID); - bytes.AddRange(tmp); - } + writer.Write(DestinationNodeID); else - bytes.Add((byte)DestinationNodeID); - bytes.Add((byte)Data.Count); - bytes.AddRange(Data); - bytes.Add((byte)Options); + writer.Write((byte)DestinationNodeID); + writer.Write((byte)Data.Count); + writer.Write(Data); + writer.Write((byte)Options); if (Function == Function.SendDataBridge) - bytes.AddRange(new byte[] { 0, 0, 0, 0 }); //Use default route - bytes.Add(SessionID); - return bytes; + writer.Seek(4); //Use default route + writer.Write(SessionID); + return writer; } public override string ToString() diff --git a/ZWaveDotNet/SerialAPI/Messages/Message.cs b/ZWaveDotNet/SerialAPI/Messages/Message.cs index 22add99..902f298 100644 --- a/ZWaveDotNet/SerialAPI/Messages/Message.cs +++ b/ZWaveDotNet/SerialAPI/Messages/Message.cs @@ -11,6 +11,7 @@ // along with this program. If not, see . using ZWaveDotNet.SerialAPI.Enums; +using ZWaveDotNet.Util; namespace ZWaveDotNet.SerialAPI.Messages { @@ -22,9 +23,9 @@ protected Message(Function function) this.Function = function; } - public virtual List GetPayload() + public virtual PayloadWriter GetPayload() { - return new List(); + return new PayloadWriter(256); } public override string ToString() diff --git a/ZWaveDotNet/SerialAPI/Messages/MulticastDataMessage.cs b/ZWaveDotNet/SerialAPI/Messages/MulticastDataMessage.cs index 2c2e623..72bfa4b 100644 --- a/ZWaveDotNet/SerialAPI/Messages/MulticastDataMessage.cs +++ b/ZWaveDotNet/SerialAPI/Messages/MulticastDataMessage.cs @@ -15,6 +15,7 @@ using ZWaveDotNet.Entities; using ZWaveDotNet.SerialAPI.Enums; using ZWaveDotNet.SerialAPI.Messages.Enums; +using ZWaveDotNet.Util; namespace ZWaveDotNet.SerialAPI.Messages { @@ -93,36 +94,29 @@ public MulticastDataMessage(Controller controller, ushort[] nodeIds, Memory GetPayload() + public override PayloadWriter GetPayload() { - byte[] tmp = new byte[2]; - List bytes = base.GetPayload(); + PayloadWriter writer = base.GetPayload(); if (Function == Function.SendDataBridgeMulticast) { if (controller.WideID) - { - BinaryPrimitives.WriteUInt16BigEndian(tmp, controller.ControllerID); - bytes.AddRange(tmp); - } + writer.Write(controller.ControllerID); else - bytes.Add((byte)controller.ControllerID); + writer.Write((byte)controller.ControllerID); } - bytes.Add((byte)DestinationNodeIDs.Length); + writer.Write((byte)DestinationNodeIDs.Length); foreach (ushort id in DestinationNodeIDs) { if (controller.WideID) - { - BinaryPrimitives.WriteUInt16BigEndian(tmp, id); - bytes.AddRange(tmp); - } + writer.Write(id); else - bytes.Add((byte)id); + writer.Write((byte)id); } - bytes.Add((byte)Data.Length); - bytes.AddRange(Data.ToArray()); - bytes.Add((byte)Options); - bytes.Add(SessionID); - return bytes; + writer.Write((byte)Data.Length); + writer.Write(Data); + writer.Write((byte)Options); + writer.Write(SessionID); + return writer; } public override string ToString() diff --git a/ZWaveDotNet/SerialAPI/Messages/NodeInformationUpdate.cs b/ZWaveDotNet/SerialAPI/Messages/NodeInformationUpdate.cs index e69a73c..8c9a34d 100644 --- a/ZWaveDotNet/SerialAPI/Messages/NodeInformationUpdate.cs +++ b/ZWaveDotNet/SerialAPI/Messages/NodeInformationUpdate.cs @@ -34,16 +34,16 @@ public NodeInformationUpdate(Memory payload) : base(payload) CommandClasses = PayloadConverter.GetCommandClasses(payload.Slice(6)).ToArray(); } - public override List GetPayload() + public override PayloadWriter GetPayload() { - List bytes = base.GetPayload(); - bytes.Add((byte)CommandClasses.Length); - bytes.Add((byte)BasicType); - bytes.Add((byte)GenericType); - bytes.Add(SpecificTypeMapping.Get(GenericType, SpecificType)); + PayloadWriter writer = base.GetPayload(); + writer.Write((byte)CommandClasses.Length); + writer.Write((byte)BasicType); + writer.Write((byte)GenericType); + writer.Write(SpecificTypeMapping.Get(GenericType, SpecificType)); for (byte i = 0; i < CommandClasses.Length; i++) - bytes.Add((byte)CommandClasses[i]); - return bytes; + writer.Write((byte)CommandClasses[i]); + return writer; } public override string ToString() diff --git a/ZWaveDotNet/SerialAPI/Messages/NodeProtocolInfo.cs b/ZWaveDotNet/SerialAPI/Messages/NodeProtocolInfo.cs index ac4a631..fc5e4b1 100644 --- a/ZWaveDotNet/SerialAPI/Messages/NodeProtocolInfo.cs +++ b/ZWaveDotNet/SerialAPI/Messages/NodeProtocolInfo.cs @@ -19,12 +19,12 @@ namespace ZWaveDotNet.SerialAPI.Messages { public class NodeProtocolInfo : Message { - public byte Capability { get; internal set; } - public byte Reserved { get; internal set; } - public BasicType BasicType { get; internal set; } - public GenericType GenericType { get; internal set; } - public SpecificType SpecificType { get; internal set; } - public NIFSecurity Security { get; internal set; } + public byte Capability { get; set; } + public byte Reserved { get; set; } + public BasicType BasicType { get; set; } + public GenericType GenericType { get; set; } + public SpecificType SpecificType { get; set; } + public NIFSecurity Security { get; set; } public NodeProtocolInfo() : base(Function.GetNodeProtocolInfo) { } diff --git a/ZWaveDotNet/SerialAPI/Messages/SmartStartNodeInformationUpdate.cs b/ZWaveDotNet/SerialAPI/Messages/SmartStartNodeInformationUpdate.cs index 11cdb81..429a28c 100644 --- a/ZWaveDotNet/SerialAPI/Messages/SmartStartNodeInformationUpdate.cs +++ b/ZWaveDotNet/SerialAPI/Messages/SmartStartNodeInformationUpdate.cs @@ -11,6 +11,7 @@ // along with this program. If not, see . using ZWaveDotNet.SerialAPI.Messages.Enums; +using ZWaveDotNet.Util; namespace ZWaveDotNet.SerialAPI.Messages { @@ -27,13 +28,13 @@ public SmartStartNodeInformationUpdate(Memory payload) : base(payload) HomeID = payload.Slice(4, 4).ToArray(); } - public override List GetPayload() + public override PayloadWriter GetPayload() { - List bytes = base.GetPayload(); - bytes.Add(0x0); - bytes.Add((byte)RxStatus); - bytes.AddRange(HomeID); - return bytes; + PayloadWriter writer = base.GetPayload(); + writer.Seek(1); + writer.Write((byte)RxStatus); + writer.Write(HomeID); + return writer; } public override string ToString() diff --git a/ZWaveDotNet/SerialAPI/Messages/SmartStartPrime.cs b/ZWaveDotNet/SerialAPI/Messages/SmartStartPrime.cs index 4539776..5f04ef8 100644 --- a/ZWaveDotNet/SerialAPI/Messages/SmartStartPrime.cs +++ b/ZWaveDotNet/SerialAPI/Messages/SmartStartPrime.cs @@ -39,17 +39,17 @@ public SmartStartPrime(Memory payload) : base(payload) CommandClasses = PayloadConverter.GetCommandClasses(payload.Slice(11)).ToArray(); } - public override List GetPayload() + public override PayloadWriter GetPayload() { - List bytes = base.GetPayload(); - bytes.Add((byte)RxStatus); - bytes.AddRange(HomeID); - bytes.Add((byte)CommandClasses.Length); - bytes.Add((byte)BasicType); - bytes.Add((byte)GenericType); - bytes.Add(SpecificTypeMapping.Get(GenericType, SpecificType)); + PayloadWriter bytes = base.GetPayload(); + bytes.Write((byte)RxStatus); + bytes.Write(HomeID); + bytes.Write((byte)CommandClasses.Length); + bytes.Write((byte)BasicType); + bytes.Write((byte)GenericType); + bytes.Write(SpecificTypeMapping.Get(GenericType, SpecificType)); for (byte i = 0; i < CommandClasses.Length; i++) - bytes.Add((byte)CommandClasses[i]); + bytes.Write((byte)CommandClasses[i]); return bytes; } diff --git a/ZWaveDotNet/SerialAPI/Port.cs b/ZWaveDotNet/SerialAPI/Port.cs index b00ff6c..fb30ac4 100644 --- a/ZWaveDotNet/SerialAPI/Port.cs +++ b/ZWaveDotNet/SerialAPI/Port.cs @@ -68,9 +68,9 @@ private async Task WriteTask() { while (await tx.Reader.WaitToReadAsync()) { - var frame = await tx.Reader.ReadAsync(); - Log.Verbose("Wrote " + frame.ToString()); - await frame.WriteBytes(port.BaseStream); + var frame = await tx.Reader.ReadAsync(); + await frame.WriteBytes(port); + Log.Verbose($"Wrote " + frame.ToString()); } } catch (IOException io) @@ -95,7 +95,8 @@ private async Task ReadTask() } else { - Log.Verbose("Read " + frame.ToString()); + if (frame.Type == FrameType.CAN) + Log.Debug("Read " + frame.ToString()); if (frame.Type == FrameType.SOF) await QueueTX(Frame.ACK); foreach (Channel channel in rxChannels.Keys) diff --git a/ZWaveDotNet/Util/MemoryUtil.cs b/ZWaveDotNet/Util/MemoryUtil.cs index 22b4d1b..14e5947 100644 --- a/ZWaveDotNet/Util/MemoryUtil.cs +++ b/ZWaveDotNet/Util/MemoryUtil.cs @@ -54,6 +54,13 @@ public static Memory XOR(Memory a, Memory b) return ret; } + public static byte XOR(Memory a, byte start) + { + foreach (byte b in a.Span) + start ^= b; + return start; + } + public static void Increment(Span mem) { for (int i = mem.Length - 1; i >= 0; i--) diff --git a/ZWaveDotNet/Util/PayloadWriter.cs b/ZWaveDotNet/Util/PayloadWriter.cs new file mode 100644 index 0000000..fd7c42b --- /dev/null +++ b/ZWaveDotNet/Util/PayloadWriter.cs @@ -0,0 +1,96 @@ +// 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 . + + +using System.Buffers.Binary; + +namespace ZWaveDotNet.Util +{ + public class PayloadWriter + { + private readonly Memory data; + private int pos; + + public PayloadWriter(Memory data, int pos = 0) + { + this.data = data; + this.pos = pos; + } + + public PayloadWriter(int capacity) + { + this.data = new byte[capacity]; + pos = 0; + } + + public void Write(byte value) + { + data.Span[pos++] = value; + } + + public void Write(byte[] bytes) + { + bytes.CopyTo(data.Slice(pos)); + pos += bytes.Length; + } + + public void Write(List bytes) + { + #if NET7_0_OR_GREATER + bytes.CopyTo(data.Slice(pos).Span); + #else + bytes.ToArray().CopyTo(data.Slice(pos)); + #endif + pos += bytes.Count; + } + + public void Write(Memory bytes) + { + bytes.CopyTo(data.Slice(pos)); + pos += bytes.Length; + } + + public void Write(int value) + { + BinaryPrimitives.WriteInt32BigEndian(data.Span.Slice(pos, 4), value); + pos += 4; + } + + public void Write(uint value) + { + BinaryPrimitives.WriteUInt32BigEndian(data.Span.Slice(pos, 4), value); + pos += 4; + } + + public void Write(short value) + { + BinaryPrimitives.WriteInt16BigEndian(data.Span.Slice(pos, 2), value); + pos += 2; + } + + public void Write(ushort value) + { + BinaryPrimitives.WriteUInt16BigEndian(data.Span.Slice(pos, 2), value); + pos += 2; + } + + public void Seek(int offset) + { + pos += offset; + } + + public Memory GetPayload() + { + return data.Slice(0, pos); + } + } +} diff --git a/ZWaveDotNet/ZWaveDotNet.csproj b/ZWaveDotNet/ZWaveDotNet.csproj index ae7c5fc..250c7f7 100644 --- a/ZWaveDotNet/ZWaveDotNet.csproj +++ b/ZWaveDotNet/ZWaveDotNet.csproj @@ -4,7 +4,7 @@ net60; net80 enable enable - 0.6.1 + 0.7.0 jdomnitz SmartHomeOS and Contributors AGPL-3.0-or-later