Skip to content

Commit

Permalink
Rewrite flow control to minimize collisions, implement NodeList loadi…
Browse files Browse the repository at this point in the history
…ng during start
  • Loading branch information
jdomnitz committed Sep 13, 2024
1 parent 5fd5be2 commit eb83acd
Show file tree
Hide file tree
Showing 25 changed files with 278 additions and 147 deletions.
2 changes: 1 addition & 1 deletion ZWaveDotNet/CommandClassReports/Enums/RateType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace ZWaveDotNet.CommandClassReports.Enums
{
public enum RateType : byte
{
Unspecified = 0x0,
Default = 0x0,
Import = 0x1,
Export = 0x2
}
Expand Down
12 changes: 5 additions & 7 deletions ZWaveDotNet/CommandClassReports/MeterReport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte> payload)
Expand All @@ -42,19 +42,17 @@ internal MeterReport(Memory<byte> 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)];
}
else if (payload.Length > size + 6)
scale2 = payload.Span[4 + size];
}
else
{
ElapsedTime = TimeSpan.Zero;
}
Unit = GetUnit(Type, scale, scale2);
}

Expand Down Expand Up @@ -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}";
}
}
}
9 changes: 6 additions & 3 deletions ZWaveDotNet/CommandClassReports/SensorAlarmReport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ namespace ZWaveDotNet.CommandClassReports
{
public class SensorAlarmReport : ICommandClassReport
{
public readonly byte Source;
public readonly ushort SourceNodeID;
public readonly AlarmType Type;
/// <summary>
/// 0 = No Alarm, 1 - 99 indicate % severity, 255 = Alarm
/// </summary>
public readonly byte Level;
public readonly ushort Duration;

Expand All @@ -29,15 +32,15 @@ internal SensorAlarmReport(Memory<byte> 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);
}

public override string ToString()
{
return $"Source:{Source}, Type:{Type}, Level:{Level}, Duration:{Duration}";
return $"Source:{SourceNodeID}, Type:{Type}, Level:{Level}, Duration:{Duration}";
}
}
}
2 changes: 0 additions & 2 deletions ZWaveDotNet/CommandClassReports/TimeOffsetReport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
// 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 System.Buffers.Binary;
using System.Data;
using ZWaveDotNet.CommandClasses.Enums;
using ZWaveDotNet.Util;

namespace ZWaveDotNet.CommandClassReports
Expand Down
9 changes: 4 additions & 5 deletions ZWaveDotNet/CommandClasses/CommandClassBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion ZWaveDotNet/CommandClasses/Meter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions ZWaveDotNet/CommandClasses/Security2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -266,7 +266,7 @@ public async Task Encapsulate(List<byte> 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<Security2>()!.SendNonceReport(true, false, false, cts.Token).ConfigureAwait(false);
Expand All @@ -284,13 +284,14 @@ public async Task Encapsulate(List<byte> payload, SecurityManager.RecordType? ty
groupId = decoded!.Value.Span[2];
Memory<byte> 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;
}

Expand Down
14 changes: 14 additions & 0 deletions ZWaveDotNet/CommandClasses/SensorAlarm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// 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 System.Collections;
using ZWaveDotNet.CommandClasses.Enums;
using ZWaveDotNet.CommandClassReports;
using ZWaveDotNet.CommandClassReports.Enums;
Expand Down Expand Up @@ -40,6 +41,19 @@ public async Task<SensorAlarmReport> Get(AlarmType type, CancellationToken cance
return new SensorAlarmReport(response.Payload);
}

public async Task<AlarmType[]> SupportedGet(CancellationToken cancellationToken)
{
List<AlarmType> types = new List<AlarmType>();
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<SupervisionStatus> Handle(ReportMessage message)
{
if (message.Command == (byte)SensorAlarmCommand.Report)
Expand Down
12 changes: 10 additions & 2 deletions ZWaveDotNet/Entities/Controller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)
{
Expand Down Expand Up @@ -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);
}
}

Expand Down
10 changes: 9 additions & 1 deletion ZWaveDotNet/Entities/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand All @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion ZWaveDotNet/Enums/CommandClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public enum CommandClass : ushort
AntiTheftUnlock = 0x7E,
ApplicationCapability = 0x57,
ApplicationStatus = 0x22,
AssocCommandConfiguration = 0x9B,
AssociationCommandConfiguration = 0x9B,
Association = 0x85,
AssociationGroupInformation = 0x59,
Authentication = 0xA1,
Expand Down
4 changes: 2 additions & 2 deletions ZWaveDotNet/SerialAPI/Flow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ private async Task SendAcknowledgedIntl(Channel<Frame> 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
Expand Down
Loading

0 comments on commit eb83acd

Please sign in to comment.