Skip to content

Commit bb9b1ef

Browse files
committed
Add CurveModule and improve NACK/command handling
Implemented new CurveModule for CURVE/CURVE_DESCRIPTION (incl. tests). Improved NACK handling for UNSUPPORTED_COMMAND_CLASS. Refactored Logging, updated RDMMessage response logic, modernized Mock/Test classes, and fixed minor bugs.
1 parent 58d1b9d commit bb9b1ef

13 files changed

Lines changed: 649 additions & 219 deletions

RDMSharp/Extensions/ModulesExtension/ModulesExtension.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ public sealed override bool TryGetModules(ERDM_Parameter[] parameters, out IRead
5454
if (parameters.Contains(ERDM_Parameter.REAL_TIME_CLOCK))
5555
_modules.Add(typeof(RealTimeClockModule));
5656

57+
if (parameters.Contains(ERDM_Parameter.CURVE))
58+
_modules.Add(typeof(CurveModule));
59+
5760
if (parameters.Contains(ERDM_Parameter.PERFORM_SELFTEST))
5861
_modules.Add(typeof(SelfTestsModule));
5962

RDMSharp/Metadata/MetadataFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ private static MetadataJSONObjectDefine getDefine(ParameterBag parameter)
338338
return possibleDefines.MaxBy(d => d.Version);
339339
}
340340
if (parameter.ManufacturerID == 0)
341-
throw new InvalidOperationException($"{parameter.ManufacturerID} of 0 should lead to exact 1 Define");
341+
throw new InvalidOperationException($"Parameter: {parameter}, {parameter.ManufacturerID} of 0 should lead to exact 1 Define");
342342

343343

344344

RDMSharp/RDM/Device/AbstractGeneratedRDMDevice.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,8 @@ protected RDMMessage processRequestMessage(RDMMessage rdmMessage)
551551
{
552552
Parameter = ERDM_Parameter.STATUS_MESSAGES,
553553
Command = ERDM_Command.GET_COMMAND_RESPONSE,
554-
ControllerFlags_or_MessageCounter = 0
554+
ControllerFlags_or_MessageCounter = 0,
555+
PortID_or_Responsetype = (byte)ERDM_ResponseType.ACK
555556
};
556557

557558
fillRDMMessageWithStatusMessageData(controllerCache, statusCode, ref response);

RDMSharp/RDM/Device/AbstractRDMCache.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ protected async Task<RequestResult> requestGetParameterWithPayload(ParameterBag
362362
DataTreeBranch dataTreeBranch = new DataTreeBranch(new DataTree(name, 0, i));
363363
PeerToPeerProcess ptpProcess = new PeerToPeerProcess(ERDM_Command.GET_COMMAND, uid, subDevice, parameterBag, dataTreeBranch);
364364
await runPeerToPeerProcess(ptpProcess);
365-
if (ptpProcess.State == EPeerToPeerProcessState.Failed && (ptpProcess.NackReason == ERDM_NackReason.DATA_OUT_OF_RANGE || ptpProcess.NackReason is null))
365+
if (ptpProcess.State == EPeerToPeerProcessState.Failed && (ptpProcess.NackReason == ERDM_NackReason.DATA_OUT_OF_RANGE || ptpProcess.NackReason == ERDM_NackReason.UNKNOWN_PID || ptpProcess.NackReason is null))
366366
{
367367
if (((IComparable)dataOutOfRangeMin).CompareTo(i) < 0)
368368
return new RequestResult(ptpProcess);

RDMSharp/RDM/Device/AbstractRemoteRDMDevice.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ private async Task updateParameters()
460460
private async Task requestQueuedMessages()
461461
{
462462
var supportedParameters = this.deviceModel?.GetSupportedNonBlueprintParameters();
463-
if (supportedParameters.FirstOrDefault(spm => spm.Parameter == ERDM_Parameter.QUEUED_MESSAGE)?.IsSupported ?? false)
463+
if (supportedParameters?.FirstOrDefault(spm => spm.Parameter == ERDM_Parameter.QUEUED_MESSAGE)?.IsSupported ?? false)
464464
{
465465
if (DateTime.UtcNow - lastSendQueuedMessage < TimeSpan.FromMilliseconds(GlobalTimers.Instance.QueuedUpdateTime))
466466
return;
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
using RDMSharp.PayloadObject;
2+
using System;
3+
using System.Collections.Concurrent;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
8+
namespace RDMSharp.RDM.Device.Module;
9+
10+
public sealed class CurveModule : AbstractModule
11+
{
12+
private const string _moduleName = "Curve";
13+
private const string _moduleDisplayName = "Curve";
14+
private static readonly ERDM_Parameter[] _moduleParameters = new ERDM_Parameter[]
15+
{
16+
ERDM_Parameter.CURVE,
17+
ERDM_Parameter.CURVE_DESCRIPTION
18+
};
19+
20+
public override string DisplayName => _moduleDisplayName;
21+
22+
private byte? currentId;
23+
public byte? CurrentId
24+
{
25+
get
26+
{
27+
return currentId;
28+
}
29+
set
30+
{
31+
if (value is null)
32+
return;
33+
34+
if (currentId == value)
35+
return;
36+
37+
bool initial = currentId is null;
38+
var backup = currentId;
39+
currentId = value;
40+
41+
if (ParentGeneratedDevice is not null)
42+
ParentGeneratedDevice.setParameterValue(ERDM_Parameter.CURVE, new RDMCurve(currentId.Value, this.count.Value));
43+
44+
if (initial)
45+
return;
46+
47+
if (ParentRemoteDevice is not null)
48+
{
49+
Task.Run(async () =>
50+
{
51+
if (await SetCurve(value.Value))
52+
OnPropertyChanged(nameof(CurrentId));
53+
else
54+
{
55+
currentId = backup;
56+
OnPropertyChanged(nameof(CurrentId));
57+
}
58+
});
59+
}
60+
}
61+
}
62+
private byte? count;
63+
public byte? Count
64+
{
65+
get
66+
{
67+
return count;
68+
}
69+
private set
70+
{
71+
if (value == count)
72+
return;
73+
count = value;
74+
OnPropertyChanged();
75+
}
76+
}
77+
78+
private Dictionary<byte, string> idDescriptionPair = new Dictionary<byte, string>();
79+
public IReadOnlyDictionary<byte, string> IdDescriptionPair => idDescriptionPair;
80+
81+
public readonly IReadOnlyCollection<RDMCurveDescription> _generatedCurves = null;
82+
83+
private byte _initialCurveId;
84+
85+
public CurveModule(byte initialCurveId, params RDMCurveDescription[] curves) : base(
86+
_moduleName,
87+
_moduleParameters)
88+
{
89+
if (!curves.Any(c => c.CurveId == initialCurveId))
90+
throw new ArgumentOutOfRangeException($"No Curve found with ID: {initialCurveId}");
91+
92+
_initialCurveId = initialCurveId;
93+
_generatedCurves = (curves ?? Array.Empty<RDMCurveDescription>()).ToList().AsReadOnly();
94+
Count = (byte)_generatedCurves.Count;
95+
}
96+
public CurveModule(AbstractRemoteRDMDevice remoteDevice) : base(
97+
remoteDevice,
98+
_moduleName,
99+
_moduleParameters)
100+
{
101+
if (ParentRemoteDevice.ParameterValues.TryGetValue(ERDM_Parameter.CURVE, out object val) && val is RDMCurve curve)
102+
fillFromRemote(curve);
103+
}
104+
105+
protected override async void OnParentGeneratedDeviceChanged(AbstractGeneratedRDMDevice device)
106+
{
107+
if (_generatedCurves is not null)
108+
{
109+
if (_generatedCurves.Count >= byte.MaxValue)
110+
throw new ArgumentOutOfRangeException($"There to many {_generatedCurves}! Maximum is {byte.MaxValue - 1}");
111+
112+
if (_generatedCurves.Count != 0)
113+
{
114+
var curveDesc = new ConcurrentDictionary<object, object>();
115+
foreach (var gCurve in _generatedCurves)
116+
if (!curveDesc.TryAdd(gCurve.CurveId, gCurve))
117+
throw new Exception($"{gCurve.CurveId} already used as {nameof(gCurve.CurveId)}");
118+
119+
device.setParameterValue(ERDM_Parameter.CURVE_DESCRIPTION, curveDesc);
120+
}
121+
}
122+
await SetCurve(_initialCurveId);
123+
}
124+
125+
protected override void ParameterChanged(ERDM_Parameter parameter, object newValue, object index)
126+
{
127+
if (ParentRemoteDevice is null)
128+
return;
129+
130+
if (!_moduleParameters.Contains(parameter))
131+
return;
132+
133+
switch (parameter)
134+
{
135+
case ERDM_Parameter.CURVE_DESCRIPTION:
136+
break;
137+
case ERDM_Parameter.CURVE when newValue is RDMCurve curve:
138+
fillFromRemote(curve);
139+
break;
140+
}
141+
}
142+
143+
private void fillFromRemote(RDMCurve curve)
144+
{
145+
Count = curve.Curves;
146+
if (idDescriptionPair.Count == 0 && ParentRemoteDevice is not null)
147+
if (ParentRemoteDevice.ParameterValues.TryGetValue(ERDM_Parameter.CURVE_DESCRIPTION, out object val) && val is ConcurrentDictionary<object, object> dict)
148+
{
149+
foreach (var pair in dict)
150+
if (pair.Value is RDMCurveDescription curveDescription)
151+
idDescriptionPair.Add((byte)curveDescription.Index, curveDescription.Description);
152+
OnPropertyChanged(nameof(IdDescriptionPair));
153+
}
154+
else
155+
{
156+
for (byte i = 1; i <= Count; i++)
157+
idDescriptionPair.Add(i, $"Curve {i}");
158+
OnPropertyChanged(nameof(IdDescriptionPair));
159+
}
160+
161+
CurrentId = curve.CurrentCurveId;
162+
}
163+
164+
public override bool IsHandlingParameter(ERDM_Parameter parameter, ERDM_Command command)
165+
{
166+
if (parameter == ERDM_Parameter.CURVE)
167+
return command == ERDM_Command.SET_COMMAND;
168+
return base.IsHandlingParameter(parameter, command);
169+
}
170+
protected override RDMMessage handleRequest(RDMMessage message)
171+
{
172+
if (message.Parameter == ERDM_Parameter.CURVE)
173+
if (message.Command == ERDM_Command.SET_COMMAND)
174+
{
175+
if (message.Value is byte b)
176+
{
177+
try
178+
{
179+
if (this._generatedCurves.FirstOrDefault(c => c.CurveId == b) is RDMCurveDescription curve)
180+
CurrentId = curve.CurveId;
181+
else
182+
{
183+
return new RDMMessage(ERDM_NackReason.DATA_OUT_OF_RANGE)
184+
{
185+
DestUID = message.SourceUID,
186+
SourceUID = message.DestUID,
187+
Parameter = message.Parameter,
188+
Command = ERDM_Command.SET_COMMAND_RESPONSE
189+
};
190+
}
191+
}
192+
catch (Exception ex)
193+
{
194+
Logger?.LogError(ex);
195+
return new RDMMessage(ERDM_NackReason.HARDWARE_FAULT)
196+
{
197+
DestUID = message.SourceUID,
198+
SourceUID = message.DestUID,
199+
Parameter = message.Parameter,
200+
Command = ERDM_Command.SET_COMMAND_RESPONSE
201+
};
202+
}
203+
return new RDMMessage()
204+
{
205+
DestUID = message.SourceUID,
206+
SourceUID = message.DestUID,
207+
Parameter = message.Parameter,
208+
Command = ERDM_Command.SET_COMMAND_RESPONSE,
209+
};
210+
}
211+
return new RDMMessage(ERDM_NackReason.FORMAT_ERROR)
212+
{
213+
DestUID = message.SourceUID,
214+
SourceUID = message.DestUID,
215+
Parameter = message.Parameter,
216+
Command = ERDM_Command.SET_COMMAND_RESPONSE
217+
};
218+
}
219+
return base.handleRequest(message);
220+
}
221+
222+
public async Task<bool> SetCurve(byte curveId)
223+
{
224+
if (ParentGeneratedDevice is not null)
225+
{
226+
if (this._generatedCurves.FirstOrDefault(p => p.CurveId == curveId) is not RDMCurveDescription curve)
227+
throw new ArgumentOutOfRangeException($"No Curve found with ID: {curveId}");
228+
CurrentId = curve.CurveId;
229+
}
230+
if (ParentRemoteDevice is not null)
231+
{
232+
if (!this.IdDescriptionPair.Any(p => p.Key == curveId))
233+
throw new ArgumentOutOfRangeException($"No Curve found with ID: {curveId}");
234+
return await ParentRemoteDevice.SetParameter(ERDM_Parameter.CURVE, curveId);
235+
}
236+
return true;
237+
}
238+
}

RDMSharp/RDM/Device/RDMDeviceModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ protected sealed override Task OnResponseMessage(RDMMessage rdmMessage)
319319

320320
internal bool handleNACKReason(RDMMessage rdmMessage)
321321
{
322-
if (rdmMessage.NackReason == ERDM_NackReason.FORMAT_ERROR || rdmMessage.NackReason == ERDM_NackReason.UNKNOWN_PID)
322+
if (rdmMessage.NackReason == ERDM_NackReason.FORMAT_ERROR || rdmMessage.NackReason == ERDM_NackReason.UNKNOWN_PID || rdmMessage.NackReason == ERDM_NackReason.UNSUPPORTED_COMMAND_CLASS)
323323
{
324324
if (this.supportedParameters.TryGetValue(rdmMessage.Parameter, out SupportedParameterMetadata supportedParameterMetadata))
325325
supportedParameterMetadata.HandleNack(rdmMessage);

RDMSharp/RDM/Device/SupportedParameterMetadata.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ private set
2929
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSupported)));
3030
}
3131
}
32+
private ERDM_CommandClass _unsupportedCommandClasses = ERDM_CommandClass.NONE;
33+
public ERDM_CommandClass UnsupportedCommandClasses
34+
{
35+
get
36+
{
37+
return _unsupportedCommandClasses;
38+
}
39+
private set
40+
{
41+
if (_unsupportedCommandClasses == value)
42+
return;
43+
_unsupportedCommandClasses = value;
44+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UnsupportedCommandClasses)));
45+
}
46+
}
3247

3348
public bool IsManufacturerSpecific
3449
{
@@ -130,6 +145,12 @@ internal void HandleNack(RDMMessage message)
130145
case ERDM_NackReason.UNKNOWN_PID:
131146
this.IsSupported = false;
132147
break;
148+
case ERDM_NackReason.UNSUPPORTED_COMMAND_CLASS:
149+
if (message.Command == ERDM_Command.GET_COMMAND_RESPONSE)
150+
this.UnsupportedCommandClasses |= ERDM_CommandClass.GET;
151+
if (message.Command == ERDM_Command.SET_COMMAND_RESPONSE)
152+
this.UnsupportedCommandClasses |= ERDM_CommandClass.SET;
153+
break;
133154
}
134155
}
135156
}

RDMSharp/RDM/RDMMessage.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,12 @@ public RDMMessage(in byte[] data)
9191
SourceUID = new UID(manIdSource, devIdSource);
9292
DestUID = new UID(manIdDest, devIdDest);
9393
TransactionCounter = data[15];
94-
PortID_or_Responsetype = data[16];
94+
var portIDorResponseType = data[16];
95+
PortID_or_Responsetype = portIDorResponseType;
9596
ControllerFlags_or_MessageCounter = data[17];
9697
SubDevice = new SubDevice((ushort)((data[18] << 8) | data[19]));
9798
Command = (ERDM_Command)data[20];
99+
PortID_or_Responsetype = portIDorResponseType;// Set again in case it was changed in Command-Setter
98100
if (Command.HasFlag(ERDM_Command.RESPONSE) && PortID_or_Responsetype != 0 && paramLength != 2)
99101
{
100102
switch (PortID_or_Responsetype)
@@ -227,6 +229,9 @@ public ERDM_Command Command
227229
{
228230
if (value == command)
229231
return;
232+
233+
if (value.HasFlag(ERDM_Command.RESPONSE) && command == ERDM_Command.NONE && PortID_or_Responsetype == 1 && _acknowledgeTimer is null)
234+
PortID_or_Responsetype = 0; // Set To ACK, bacause default as Request is 1 and 1 as Response is Timer... Bad!
230235
valueCache = null;
231236
command = value;
232237
}

0 commit comments

Comments
 (0)