diff --git a/RDMSharp/RDM/AsyncRDMRequestHelper.cs b/RDMSharp/RDM/AsyncRDMRequestHelper.cs index e865955..ad86b04 100644 --- a/RDMSharp/RDM/AsyncRDMRequestHelper.cs +++ b/RDMSharp/RDM/AsyncRDMRequestHelper.cs @@ -48,7 +48,7 @@ public bool ReceiveMessage(RDMMessage rdmMessage) return true; } //None Queued Parameters - var obj = buffer.Where(b => b.Value.Item1.Parameter != ERDM_Parameter.QUEUED_MESSAGE).FirstOrDefault(b => b.Value.Item2 == null && rdmMessage.Parameter == b.Value.Item1.Parameter && rdmMessage.TransactionCounter == b.Value.Item1.TransactionCounter && b.Value.Item1.DestUID == rdmMessage.SourceUID && b.Value.Item1.SourceUID == rdmMessage.DestUID); + var obj = buffer.Where(b => b.Value.Item1.Parameter != ERDM_Parameter.QUEUED_MESSAGE).FirstOrDefault(b => b.Value.Item2 == null && checkNonQueued(b.Value.Item1, rdmMessage)); if (obj.Value != null) { var tuple = new Tuple(obj.Value.Item1, rdmMessage); @@ -64,6 +64,22 @@ public bool ReceiveMessage(RDMMessage rdmMessage) return true; } return false; + + bool checkNonQueued(RDMMessage request, RDMMessage response) + { + if (request.Parameter != response.Parameter) + return false; + if (request.TransactionCounter != response.TransactionCounter) + return false; + if (request.SubDevice != response.SubDevice) + return false; + if (request.SourceUID != response.DestUID) + return false; + if (request.DestUID != response.SourceUID) + return false; + + return true; + } } @@ -72,6 +88,11 @@ public async Task RequestMessage(RDMMessage requerst) try { int key = random.Next(); + if (requerst.SubDevice.IsBroadcast) + { + await _sendMethode.Invoke(requerst); + return new RequestResult(requerst, null); // Broadcasts are not expected to return a response. + } buffer.TryAdd(key, new Tuple(requerst, null)); RDMMessage response = null; await _sendMethode.Invoke(requerst); diff --git a/RDMSharp/RDM/Device/AbstractGeneratedRDMDevice.cs b/RDMSharp/RDM/Device/AbstractGeneratedRDMDevice.cs index 8832cf3..5447894 100644 --- a/RDMSharp/RDM/Device/AbstractGeneratedRDMDevice.cs +++ b/RDMSharp/RDM/Device/AbstractGeneratedRDMDevice.cs @@ -28,10 +28,6 @@ public abstract class AbstractGeneratedRDMDevice : AbstractRDMDevice public sealed override IReadOnlyDictionary Slots { get { return CurrentPersonality.HasValue ? Personalities[CurrentPersonality.Value].Slots : null; } } - private List subDevices = new List(); - public sealed override IReadOnlyCollection SubDevices { get { return subDevices.AsReadOnly(); } } - - public abstract bool SupportDMXAddress { get; } private RDMDeviceInfo deviceInfo; @@ -131,7 +127,14 @@ private set } } - protected AbstractGeneratedRDMDevice(UID uid, ERDM_Parameter[] parameters, string manufacturer = null, Sensor[] sensors = null) : base(uid) + + protected AbstractGeneratedRDMDevice(UID uid, ERDM_Parameter[] parameters, string manufacturer = null, Sensor[] sensors = null, IRDMDevice[] subDevices = null) : this(uid, SubDevice.Root, parameters, manufacturer, sensors, subDevices) + { + } + protected AbstractGeneratedRDMDevice(UID uid, SubDevice subDevice, ERDM_Parameter[] parameters, string manufacturer = null, Sensor[] sensors = null) : this(uid, subDevice, parameters, manufacturer, sensors, null) + { + } + private AbstractGeneratedRDMDevice(UID uid, SubDevice subDevice, ERDM_Parameter[] parameters, string manufacturer = null, Sensor[] sensors = null, IRDMDevice[] subDevices = null) : base(uid, subDevice, subDevices) { if (!((ushort)ManufacturerID).Equals(uid.ManufacturerID)) throw new Exception($"{uid.ManufacturerID} not match the {ManufacturerID}"); @@ -156,7 +159,7 @@ protected AbstractGeneratedRDMDevice(UID uid, ERDM_Parameter[] parameters, strin _params.Add(ERDM_Parameter.SLOT_DESCRIPTION); _params.Add(ERDM_Parameter.DEFAULT_SLOT_VALUE); } - if ((Sensors?.Keys.Max() ?? 0) != 0) + if ((Sensors?.Count ?? 0) != 0) { _params.Add(ERDM_Parameter.SENSOR_DEFINITION); _params.Add(ERDM_Parameter.SENSOR_VALUE); @@ -288,6 +291,7 @@ private void updateDeviceInfo() dmx512CurrentPersonality: currentPersonality, dmx512NumberOfPersonalities: (byte)(Personalities?.Length ?? 0), dmx512StartAddress: dmxAddress, + subDeviceCount: (ushort)(SubDevices?.Where(sd=>!sd.Subdevice.IsRoot).Count() ?? 0), sensorCount: (byte)(Sensors?.Count ?? 0)); updateDeviceInfo(info); } @@ -475,8 +479,8 @@ protected sealed override async Task OnReceiveRDMMessage(RDMMessage rdmMessage) { if ((rdmMessage.DestUID.IsBroadcast || rdmMessage.DestUID == UID) && !rdmMessage.Command.HasFlag(ERDM_Command.RESPONSE)) { - await SendRDMMessage(processRequestMessage(rdmMessage)); - return; + if (rdmMessage.SubDevice.IsBroadcast || rdmMessage.SubDevice == this.Subdevice) + await SendRDMMessage(processRequestMessage(rdmMessage)); } } @@ -525,8 +529,20 @@ protected RDMMessage processRequestMessage(RDMMessage rdmMessage) return null; } } + + if (rdmMessage.SubDevice != SubDevice.Broadcast && !(this.SubDevices?.Any(sd => sd.Subdevice == rdmMessage.SubDevice) ?? true)) + { + response = new RDMMessage(ERDM_NackReason.SUB_DEVICE_OUT_OF_RANGE) { Parameter = rdmMessage.Parameter, Command = rdmMessage.Command | ERDM_Command.RESPONSE }; + goto FAIL; + } if (rdmMessage.Command == ERDM_Command.GET_COMMAND) { + if (rdmMessage.SubDevice == SubDevice.Broadcast) // no Response on Broadcast Subdevice, because this cant work on a if there are more then one Device responding on a singel line. + { + response = new RDMMessage(ERDM_NackReason.SUB_DEVICE_OUT_OF_RANGE) { Parameter = rdmMessage.Parameter, Command = rdmMessage.Command | ERDM_Command.RESPONSE }; + goto FAIL; + } + parameterValues.TryGetValue(rdmMessage.Parameter, out object responseValue); try { @@ -598,7 +614,8 @@ protected RDMMessage processRequestMessage(RDMMessage rdmMessage) Logger?.LogError(e, string.Empty); } FAIL: - + if (rdmMessage.SubDevice == SubDevice.Broadcast) // no Response on Broadcast Subdevice, because this cant work on a if there are more then one Device responding on a singel line. + return null; if (rdmMessage.DestUID.IsBroadcast) // no Response on Broadcast return null; @@ -607,6 +624,7 @@ protected RDMMessage processRequestMessage(RDMMessage rdmMessage) response.TransactionCounter = rdmMessage.TransactionCounter; response.SourceUID = rdmMessage.DestUID; response.DestUID = rdmMessage.SourceUID; + response.SubDevice = rdmMessage.SubDevice; return response; } public sealed override IReadOnlyDictionary GetAllParameterValues() diff --git a/RDMSharp/RDM/Device/AbstractRDMCache.cs b/RDMSharp/RDM/Device/AbstractRDMCache.cs index e88b2d8..6fb0008 100644 --- a/RDMSharp/RDM/Device/AbstractRDMCache.cs +++ b/RDMSharp/RDM/Device/AbstractRDMCache.cs @@ -189,12 +189,19 @@ protected async Task requestSetParameterWithPayload(ParameterBag parameterBag, M protected async Task requestGetParameterWithEmptyPayload(ParameterBag parameterBag, MetadataJSONObjectDefine define, UID uid, SubDevice subDevice) { - PeerToPeerProcess ptpProcess = new PeerToPeerProcess(ERDM_Command.GET_COMMAND, uid, subDevice, parameterBag); - await runPeerToPeerProcess(ptpProcess); - if (!ptpProcess.ResponsePayloadObject.IsUnset) + try { - updateParameterValuesDependeciePropertyBag(parameterBag.PID, ptpProcess.ResponsePayloadObject); - updateParameterValuesDataTreeBranch(new ParameterDataCacheBag(parameterBag.PID), ptpProcess.ResponsePayloadObject); + PeerToPeerProcess ptpProcess = new PeerToPeerProcess(ERDM_Command.GET_COMMAND, uid, subDevice, parameterBag); + await runPeerToPeerProcess(ptpProcess); + if (!ptpProcess.ResponsePayloadObject.IsUnset) + { + updateParameterValuesDependeciePropertyBag(parameterBag.PID, ptpProcess.ResponsePayloadObject); + updateParameterValuesDataTreeBranch(new ParameterDataCacheBag(parameterBag.PID), ptpProcess.ResponsePayloadObject); + } + } + catch(Exception e) + { + } } protected async Task requestGetParameterWithPayload(ParameterBag parameterBag, MetadataJSONObjectDefine define, UID uid, SubDevice subDevice) diff --git a/RDMSharp/RDM/Device/AbstractRDMDevice.cs b/RDMSharp/RDM/Device/AbstractRDMDevice.cs index ad52cdf..ff8f359 100644 --- a/RDMSharp/RDM/Device/AbstractRDMDevice.cs +++ b/RDMSharp/RDM/Device/AbstractRDMDevice.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace RDMSharp @@ -21,33 +23,64 @@ public abstract class AbstractRDMDevice : AbstractRDMCache, IRDMDevice public abstract RDMDeviceInfo DeviceInfo { get; } public abstract IReadOnlyDictionary Sensors { get; } public abstract IReadOnlyDictionary Slots { get; } - public abstract IReadOnlyCollection SubDevices { get; } + + private List subDevices; + protected IList SubDevices_Internal { get => subDevices; } + public IReadOnlyCollection SubDevices => SubDevices_Internal?.AsReadOnly(); public new bool IsDisposing { get; private set; } public new bool IsDisposed { get; private set; } - public bool IsInitializing { get; private set; } public bool IsInitialized { get; private set; } public abstract bool IsGenerated { get; } - protected AbstractRDMDevice(UID uid) : this(uid, SubDevice.Root) + + protected AbstractRDMDevice(UID uid, SubDevice? subDevice = null, IRDMDevice[] subDevices = null) { - } + this.uid = uid; + this.subdevice = subDevice ?? SubDevice.Root; + if (subDevices != null && !this.Subdevice.IsRoot) + throw new NotSupportedException($"A SubDevice {this.Subdevice} cannot have SubDevices."); + + if (this.Subdevice.IsBroadcast) + throw new NotSupportedException($"A SubDevice cannot be Broadcast."); + + + if (this.Subdevice == SubDevice.Root) + { + this.subDevices = new List(); + this.subDevices.Add(this); + + if (subDevices != null) + this.subDevices.AddRange(subDevices); - protected AbstractRDMDevice(UID uid, SubDevice subDevice) + if (this.subDevices.Distinct().Count() != this.subDevices.Count) + throw new InvalidOperationException($"The SubDevices of {this.UID} are not unique."); + + performInitialize(); + } + } + protected void performInitialize(RDMDeviceInfo deviceInfo=null) { - this.IsInitializing = true; + if (this.IsInitialized) + return; - this.uid = uid; - this.subdevice = subDevice; + if (this.Subdevice.IsRoot) + asyncRDMRequestHelper = new AsyncRDMRequestHelper(sendRDMRequestMessage); - asyncRDMRequestHelper = new AsyncRDMRequestHelper(sendRDMRequestMessage); - initialize(); + initialize(deviceInfo); this.IsInitialized = true; - this.IsInitializing = false; } - protected virtual void initialize() + protected virtual void initialize(RDMDeviceInfo deviceInfo = null) { + if (this.Subdevice.IsRoot) + foreach (AbstractRDMDevice sd in this.subDevices) + { + if (sd.Subdevice.IsRoot) + continue; + sd.asyncRDMRequestHelper = this.asyncRDMRequestHelper; + sd.performInitialize(); + } } @@ -61,15 +94,36 @@ private async Task sendRDMRequestMessage(RDMMessage rdmMessage) protected async Task ReceiveRDMMessage(RDMMessage rdmMessage) { + if (!this.Subdevice.IsRoot && !rdmMessage.SubDevice.IsBroadcast) + return; + if (this.IsDisposed || IsDisposing) return; try { - await OnReceiveRDMMessage(rdmMessage); + if (rdmMessage.SubDevice.IsBroadcast) + { + List tasks = new List(); + foreach (var sd in this.subDevices) + tasks.Add(OnReceiveRDMMessage(rdmMessage)); + await Task.WhenAll(tasks); + return; + } + AbstractRDMDevice sds = null; + if (rdmMessage.SubDevice.IsRoot) + sds = this; + else + sds = this.subDevices?.OfType().FirstOrDefault(sd => sd.Subdevice == rdmMessage.SubDevice); + + if (sds != null) + await sds.OnReceiveRDMMessage(rdmMessage); + else + this.asyncRDMRequestHelper.ReceiveMessage(rdmMessage); + } catch (Exception e) { - Logger.LogError(e, string.Empty); + Logger?.LogError(e, string.Empty); } } protected abstract Task OnReceiveRDMMessage(RDMMessage rdmMessage); @@ -109,6 +163,8 @@ public virtual IReadOnlyDictionary GetAllParameterValues public override string ToString() { + if (!this.Subdevice.IsRoot) + return $"[{UID}] ({this.Subdevice})"; return $"[{UID}]"; } } diff --git a/RDMSharp/RDM/Device/AbstractRemoteRDMDevice.cs b/RDMSharp/RDM/Device/AbstractRemoteRDMDevice.cs index b0c4803..9d150a9 100644 --- a/RDMSharp/RDM/Device/AbstractRemoteRDMDevice.cs +++ b/RDMSharp/RDM/Device/AbstractRemoteRDMDevice.cs @@ -8,7 +8,17 @@ namespace RDMSharp { - public abstract class AbstractRemoteRDMDevice : AbstractRDMDevice + public interface IRDMRemoteDevice: IRDMDevice + { + bool AllDataPulled { get; } + bool Present { get; } + DateTime LastSeen { get; } + Task SetParameter(ERDM_Parameter parameter, object value = null); + } + public interface IRDMRemoteSubDevice : IRDMRemoteDevice + { + } + public abstract class AbstractRemoteRDMDevice : AbstractRDMDevice , IRDMRemoteDevice { public sealed override bool IsGenerated => false; @@ -19,10 +29,6 @@ public abstract class AbstractRemoteRDMDevice : AbstractRDMDevice private RDMDeviceInfo deviceInfo; public override RDMDeviceInfo DeviceInfo { get { return deviceInfo; } } - private List subDevices = new List(); - public sealed override IReadOnlyCollection SubDevices { get { return subDevices.AsReadOnly(); } } - - private RDMDeviceModel deviceModel; public RDMDeviceModel DeviceModel { @@ -109,30 +115,85 @@ internal set public AbstractRemoteRDMDevice(UID uid) : base(uid) { } - protected override async void initialize() + public AbstractRemoteRDMDevice(UID uid, SubDevice? subDevice = null) : base(uid, subDevice) + { + if (subDevice.HasValue && subDevice.Value.IsBroadcast) + throw new NotSupportedException("A SubDevice cannot be Broadcast."); + } + protected override async void initialize(RDMDeviceInfo deviceInfo = null) { + base.initialize(deviceInfo); ParameterValueAdded += AbstractRDMDevice_ParameterValueAdded; ParameterValueChanged += AbstractRDMDevice_ParameterValueChanged; - await requestDeviceInfo(); + await requestDeviceInfo(deviceInfo); } private async void DeviceModel_Initialized(object sender, EventArgs e) { deviceModel.Initialized -= DeviceModel_Initialized; - await collectAllParameters(); + await collectAllParametersOnRoot(); + await scanSubDevices(); } + private async Task scanSubDevices() + { + if (DeviceInfo.SubDeviceCount == 0) + return; + + ushort foundSubDevices = 0; + ushort currentID = 0; + var dict = new Dictionary(); + while (foundSubDevices < DeviceInfo.SubDeviceCount) + { + currentID++; + if (currentID > 512) + break; + + PeerToPeerProcess ptpProcess = new PeerToPeerProcess(ERDM_Command.GET_COMMAND, UID, new SubDevice(currentID), new ParameterBag(ERDM_Parameter.DEVICE_INFO)); + await runPeerToPeerProcess(ptpProcess); + if (ptpProcess.ResponsePayloadObject.ParsedObject is RDMDeviceInfo _deviceInfo) + { + foundSubDevices++; + var subDevice = createSubDevice(UID, ptpProcess.SubDevice); + this.SubDevices_Internal.Add(subDevice); + dict.Add(ptpProcess.SubDevice, _deviceInfo); + } + } + foreach (AbstractRemoteRDMDevice sd in this.SubDevices_Internal) + { + if (sd.Subdevice.IsRoot) + continue; + sd.asyncRDMRequestHelper = this.asyncRDMRequestHelper; + sd.performInitialize(dict[sd.Subdevice]); + } + } + + protected abstract IRDMRemoteSubDevice createSubDevice(UID uid, SubDevice subDevice); + #region Requests - private async Task requestDeviceInfo() + private async Task requestDeviceInfo(RDMDeviceInfo deviceInfo = null) { - PeerToPeerProcess ptpProcess = new PeerToPeerProcess(ERDM_Command.GET_COMMAND, UID, Subdevice, new ParameterBag(ERDM_Parameter.DEVICE_INFO)); - await runPeerToPeerProcess(ptpProcess); - if (ptpProcess.ResponsePayloadObject.ParsedObject is RDMDeviceInfo _deviceInfo) + DataTreeBranch? rpl = null; + if (deviceInfo == null) + { + PeerToPeerProcess ptpProcess = new PeerToPeerProcess(ERDM_Command.GET_COMMAND, UID, Subdevice, new ParameterBag(ERDM_Parameter.DEVICE_INFO)); + await runPeerToPeerProcess(ptpProcess); + rpl= ptpProcess.ResponsePayloadObject; + if (rpl.Value.ParsedObject is RDMDeviceInfo _deviceInfo) + { + this.deviceInfo = _deviceInfo; + updateParameterValuesDependeciePropertyBag(ERDM_Parameter.DEVICE_INFO, ptpProcess.ResponsePayloadObject); + updateParameterValuesDataTreeBranch(new ParameterDataCacheBag(ERDM_Parameter.DEVICE_INFO), ptpProcess.ResponsePayloadObject); + await getDeviceModelAndCollectAllParameters(); + } + } + else { - deviceInfo = _deviceInfo; - updateParameterValuesDependeciePropertyBag(ERDM_Parameter.DEVICE_INFO, ptpProcess.ResponsePayloadObject); - updateParameterValuesDataTreeBranch(new ParameterDataCacheBag(ERDM_Parameter.DEVICE_INFO), ptpProcess.ResponsePayloadObject); + rpl = DataTreeBranch.FromObject(deviceInfo, null, ERDM_Command.GET_COMMAND_RESPONSE, ERDM_Parameter.DEVICE_INFO); + this.deviceInfo = deviceInfo; + updateParameterValuesDependeciePropertyBag(ERDM_Parameter.DEVICE_INFO, rpl.Value); + updateParameterValuesDataTreeBranch(new ParameterDataCacheBag(ERDM_Parameter.DEVICE_INFO), rpl.Value); await getDeviceModelAndCollectAllParameters(); } } @@ -174,9 +235,15 @@ private async Task getDeviceModelAndCollectAllParameters() await deviceModel.Initialize(); } else - await collectAllParameters(); + await collectAllParametersOnRoot(); + } + private async Task collectAllParametersOnRoot() + { + await requestParameters(); + AllDataPulled = true; } - private async Task collectAllParameters() + + private async Task collectAllParametersOnSubDevices() { await requestParameters(); AllDataPulled = true; @@ -255,13 +322,13 @@ protected sealed override async Task OnReceiveRDMMessage(RDMMessage rdmMessage) { Logger?.LogError(e, string.Empty); } - + if (rdmMessage.SourceUID != UID || !rdmMessage.Command.HasFlag(ERDM_Command.RESPONSE)) return; LastSeen = DateTime.UtcNow; - if (asyncRDMRequestHelper.ReceiveMessage(rdmMessage)) + if (asyncRDMRequestHelper?.ReceiveMessage(rdmMessage) ?? false) return; if ((rdmMessage.NackReason?.Length ?? 0) != 0) @@ -284,7 +351,7 @@ public sealed override IReadOnlyDictionary GetAllParamet public override string ToString() { - return $"[{UID}] {this.DeviceModel}"; + return $"{base.ToString()} {this.DeviceModel}"; } protected sealed override void OnDispose() { diff --git a/RDMSharp/RDM/Device/RDMDeviceModel.cs b/RDMSharp/RDM/Device/RDMDeviceModel.cs index ce95166..d27af58 100644 --- a/RDMSharp/RDM/Device/RDMDeviceModel.cs +++ b/RDMSharp/RDM/Device/RDMDeviceModel.cs @@ -15,7 +15,7 @@ public sealed class RDMDeviceModel : AbstractRDMCache, IRDMDeviceModel internal static RDMDeviceModel getDeviceModel(UID uid, SubDevice subDevice, RDMDeviceInfo deviceInfo, Func sendRdmFunktion) { knownDeviceModels ??= new ConcurrentDictionary(); - var kdm = knownDeviceModels.Values.FirstOrDefault(dm => dm.IsModelOf(uid, deviceInfo)); + var kdm = knownDeviceModels.Values.FirstOrDefault(dm => dm.IsModelOf(uid, subDevice, deviceInfo)); if (kdm == null) { kdm = new RDMDeviceModel(uid, subDevice, deviceInfo, sendRdmFunktion); @@ -29,7 +29,7 @@ internal static RDMDeviceModel getDeviceModel(UID uid, SubDevice subDevice, RDMD private ConcurrentDictionary knownPersonalityModels = new ConcurrentDictionary(); public IReadOnlyCollection KnownPersonalityModels => knownPersonalityModels.Values.ToList(); - internal async Task getPersonalityModel(AbstractRemoteRDMDevice remoteRDMDevice) + internal async Task getPersonalityModel(IRDMRemoteDevice remoteRDMDevice) { try { @@ -52,10 +52,11 @@ internal async Task getPersonalityModel(AbstractRemoteRDMDe ); if (knownPersonalityModels.TryAdd(kpm.PersonalityID, kpm)) { + AbstractRDMCache abstractRDMCache = remoteRDMDevice as AbstractRDMCache; currentUsedUID = remoteRDMDevice.UID; currentUsedSubDevice = remoteRDMDevice.Subdevice; DeviceInfo = remoteRDMDevice.DeviceInfo; - var di = remoteRDMDevice.parameterValuesDataTreeBranch.FirstOrDefault(d => d.Key.Parameter == ERDM_Parameter.DEVICE_INFO); + var di = abstractRDMCache.parameterValuesDataTreeBranch.FirstOrDefault(d => d.Key.Parameter == ERDM_Parameter.DEVICE_INFO); updateParameterValuesDependeciePropertyBag(ERDM_Parameter.DEVICE_INFO, di.Value); await requestPersonalityBlueprintParameters(kpm); } @@ -275,11 +276,13 @@ internal async Task ReceiveRDMMessage(RDMMessage rdmMessage) asyncRDMRequestHelper?.ReceiveMessage(rdmMessage); } - public bool IsModelOf(UID uid, RDMDeviceInfo other) + public bool IsModelOf(UID uid, SubDevice subDevice, RDMDeviceInfo other) { var deviceInfo = this.DeviceInfo; if (this.ManufacturerID != uid.ManufacturerID) return false; + if (this.CurrentUsedSubDevice != subDevice) + return false; if (deviceInfo.DeviceModelId != other.DeviceModelId) return false; if (deviceInfo.RdmProtocolVersionMajor != other.RdmProtocolVersionMajor) diff --git a/RDMSharp/RDM/IRDMDevice.cs b/RDMSharp/RDM/IRDMDevice.cs index 8df4261..e701273 100644 --- a/RDMSharp/RDM/IRDMDevice.cs +++ b/RDMSharp/RDM/IRDMDevice.cs @@ -18,7 +18,6 @@ public interface IRDMDevice : IDisposable, INotifyPropertyChanged bool IsDisposing { get; } bool IsDisposed { get; } - bool IsInitializing { get; } bool IsInitialized { get; } bool IsGenerated { get; } } diff --git a/RDMSharp/RDM/IRDMDeviceModel.cs b/RDMSharp/RDM/IRDMDeviceModel.cs index c7169b8..24ae897 100644 --- a/RDMSharp/RDM/IRDMDeviceModel.cs +++ b/RDMSharp/RDM/IRDMDeviceModel.cs @@ -14,6 +14,6 @@ public interface IRDMDeviceModel : IDisposable bool IsDisposing { get; } bool IsDisposed { get; } - bool IsModelOf(UID uid, RDMDeviceInfo other); + bool IsModelOf(UID uid, SubDevice subDevice, RDMDeviceInfo other); } } diff --git a/RDMSharpTests/Devices/Mock/AbstractMockDevice.cs b/RDMSharpTests/Devices/Mock/AbstractMockDevice.cs index 3c7d696..b7448ae 100644 --- a/RDMSharpTests/Devices/Mock/AbstractMockDevice.cs +++ b/RDMSharpTests/Devices/Mock/AbstractMockDevice.cs @@ -18,7 +18,7 @@ internal bool ImitateRealConditions registerEvent(); } } - public AbstractMockDevice(UID uid, bool _imitateRealConditions = false) : base(uid) + public AbstractMockDevice(UID uid, SubDevice? subDevice = null, bool _imitateRealConditions = false) : base(uid, subDevice) { ImitateRealConditions = _imitateRealConditions; registerEvent(); @@ -90,4 +90,15 @@ protected sealed override void onDispose() } protected abstract void OnDispose(); } -} + + internal abstract class AbstractMockSubDevice : AbstractMockDevice, IRDMRemoteSubDevice + { + protected AbstractMockSubDevice(UID uid, SubDevice subDevice, bool _imitateRealConditions = false) : base(uid, subDevice, _imitateRealConditions) + { + } + protected sealed override AbstractMockSubDevice createSubDevice(UID uid, SubDevice subDevice) + { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/RDMSharpTests/Devices/Mock/AbstractMockGeneratedDevice.cs b/RDMSharpTests/Devices/Mock/AbstractMockGeneratedDevice.cs index 19f8245..365764a 100644 --- a/RDMSharpTests/Devices/Mock/AbstractMockGeneratedDevice.cs +++ b/RDMSharpTests/Devices/Mock/AbstractMockGeneratedDevice.cs @@ -18,7 +18,11 @@ internal bool ImitateRealConditions registerEvent(); } } - public AbstractMockGeneratedDevice(UID uid, ERDM_Parameter[] parameters, string manufacturer, Sensor[] sensors = null) : base(uid, parameters, manufacturer, sensors: sensors) + public AbstractMockGeneratedDevice(UID uid, ERDM_Parameter[] parameters, string manufacturer, Sensor[] sensors = null, IRDMDevice[] subDevices = null) : base(uid, parameters, manufacturer, sensors: sensors, subDevices: subDevices) + { + registerEvent(); + } + public AbstractMockGeneratedDevice(UID uid, SubDevice subDevice, ERDM_Parameter[] parameters, string manufacturer, Sensor[] sensors = null) : base(uid, subDevice, parameters, manufacturer, sensors: sensors) { registerEvent(); } @@ -56,7 +60,7 @@ protected override async Task SendRDMMessage(RDMMessage rdmMessage) if (rdmMessage == null) return; registerEvent(); - rdmMessage.TransactionCounter = getTransactionCounter(); + var i = SendReceivePipeline.GetNewIdentifyer(); identifyer.TryAdd(i, rdmMessage); if (ImitateRealConditions) @@ -64,11 +68,6 @@ protected override async Task SendRDMMessage(RDMMessage rdmMessage) else SendReceivePipeline.RDMMessageSend(i, rdmMessage); } - private byte getTransactionCounter() - { - transactionCounter++; - return transactionCounter; - } protected sealed override void onDispose() { try diff --git a/RDMSharpTests/Devices/Mock/MockDevice.cs b/RDMSharpTests/Devices/Mock/MockDevice.cs index 2fd9fe7..36e7518 100644 --- a/RDMSharpTests/Devices/Mock/MockDevice.cs +++ b/RDMSharpTests/Devices/Mock/MockDevice.cs @@ -1,8 +1,24 @@ -namespace RDMSharpTests.Devices.Mock + +namespace RDMSharpTests.Devices.Mock { internal sealed class MockDevice : AbstractMockDevice { - public MockDevice(UID uid, bool imitateRealConditions) : base(uid, imitateRealConditions) + public MockDevice(UID uid, bool imitateRealConditions) : base(uid, _imitateRealConditions:imitateRealConditions) + { + } + + protected sealed override AbstractMockSubDevice createSubDevice(UID uid, SubDevice subDevice) + { + return new MockSubDevice(uid, subDevice, this.ImitateRealConditions); + } + + protected override void OnDispose() + { + } + } + internal sealed class MockSubDevice : AbstractMockSubDevice + { + public MockSubDevice(UID uid, SubDevice subDevice, bool imitateRealConditions) : base(uid, subDevice, imitateRealConditions) { } diff --git a/RDMSharpTests/Devices/Mock/MockGeneratedDevice1.cs b/RDMSharpTests/Devices/Mock/MockGeneratedDevice1.cs index 1437493..59d3cb8 100644 --- a/RDMSharpTests/Devices/Mock/MockGeneratedDevice1.cs +++ b/RDMSharpTests/Devices/Mock/MockGeneratedDevice1.cs @@ -44,7 +44,7 @@ internal sealed class MockGeneratedDevice1 : AbstractMockGeneratedDevice new MockSensorVolt3_3(3, 331), new MockSensorVolt5(4, 498) }; public override GeneratedPersonality[] Personalities => PERSONALITYS; - public MockGeneratedDevice1(UID uid) : base(uid, new ERDM_Parameter[] { ERDM_Parameter.IDENTIFY_DEVICE, ERDM_Parameter.BOOT_SOFTWARE_VERSION_LABEL }, "Dummy Manufacturer 9FFF", SENSORS) + public MockGeneratedDevice1(UID uid) : base(uid, SubDevice.Root, new ERDM_Parameter[] { ERDM_Parameter.IDENTIFY_DEVICE, ERDM_Parameter.BOOT_SOFTWARE_VERSION_LABEL }, "Dummy Manufacturer 9FFF", SENSORS) { this.DeviceLabel = "Dummy Device 1"; this.trySetParameter(ERDM_Parameter.IDENTIFY_DEVICE, false); diff --git a/RDMSharpTests/Devices/Mock/MockGeneratedDeviceWithSubDevice1.cs b/RDMSharpTests/Devices/Mock/MockGeneratedDeviceWithSubDevice1.cs new file mode 100644 index 0000000..551da5a --- /dev/null +++ b/RDMSharpTests/Devices/Mock/MockGeneratedDeviceWithSubDevice1.cs @@ -0,0 +1,94 @@ +namespace RDMSharpTests.Devices.Mock +{ + internal abstract class MockGeneratedDeviceWithSubDevice1 : AbstractMockGeneratedDevice + { + public override EManufacturer ManufacturerID => (EManufacturer)0x9fef; + public override ushort DeviceModelID => 50; + public override ERDM_ProductCategoryCoarse ProductCategoryCoarse => ERDM_ProductCategoryCoarse.DIMMER; + public override ERDM_ProductCategoryFine ProductCategoryFine => ERDM_ProductCategoryFine.DIMMER_CS_LED; + public override uint SoftwareVersionID => 0x3234; + public override string DeviceModelDescription => "Test Model Description SubDevice"; + public override bool SupportDMXAddress => true; + + protected MockGeneratedDeviceWithSubDevice1(UID uid, MockGeneratedDeviceWithSubDeviceSub1[] subDevices = null, Sensor[] sensors = null) : base(uid, new ERDM_Parameter[] { ERDM_Parameter.IDENTIFY_DEVICE, ERDM_Parameter.BOOT_SOFTWARE_VERSION_LABEL }, "Dummy Manufacturer 9FEF", sensors, subDevices) + { + this.DeviceLabel = "Dummy Device Master"; + this.setInitParameters(); + } + protected MockGeneratedDeviceWithSubDevice1(UID uid, SubDevice subDevice, Sensor[] sensors = null) : base(uid, subDevice, new ERDM_Parameter[] { ERDM_Parameter.IDENTIFY_DEVICE, ERDM_Parameter.BOOT_SOFTWARE_VERSION_LABEL }, "Dummy Manufacturer 9FEF", sensors) + { + this.DeviceLabel = "Dummy Device SubDevice"; + this.setInitParameters(); + } + private void setInitParameters() + { + this.trySetParameter(ERDM_Parameter.IDENTIFY_DEVICE, false); + this.trySetParameter(ERDM_Parameter.BOOT_SOFTWARE_VERSION_LABEL, $"Dummy Software"); + } + + + + protected sealed override void OnDispose() + { + } + } + internal sealed class MockGeneratedDeviceWithSubDeviceMaster1 : MockGeneratedDeviceWithSubDevice1 + { + private static readonly GeneratedPersonality[] PERSONALITYS = new GeneratedPersonality[] { + new GeneratedPersonality(1, "1CH", + new Slot(0, ERDM_SlotCategory.INTENSITY_MASTER, "Master" ))}; + + private static readonly Sensor[] SENSORS = new Sensor[] { + new MockSensorTemp(0, 1, 3000)}; + public override GeneratedPersonality[] Personalities => PERSONALITYS; + public MockGeneratedDeviceWithSubDeviceMaster1(UID uid, ushort subDevicesCount) : base(uid, getSubDevices(uid, subDevicesCount), SENSORS) + { + } + + private static MockGeneratedDeviceWithSubDeviceSub1[] getSubDevices(UID uid, ushort count) + { + var subDevice = new MockGeneratedDeviceWithSubDeviceSub1[count]; + for (ushort i = 0; i < count; i++) + subDevice[i] = new MockGeneratedDeviceWithSubDeviceSub1(uid, (ushort)(i + 1)); + return subDevice; + } + private class MockSensorTemp : Sensor + { + public MockSensorTemp(in byte sensorId, byte number, short initValue) : base(sensorId, ERDM_SensorType.TEMPERATURE, ERDM_SensorUnit.CENTIGRADE, ERDM_UnitPrefix.CENTI, $"Mock Ambient Temp. {number}", -2000, 10000, 2000, 5000, true, true) + { + UpdateValue(initValue); + } + } + } + internal sealed class MockGeneratedDeviceWithSubDeviceSub1 : MockGeneratedDeviceWithSubDevice1 + { + private static readonly GeneratedPersonality[] PERSONALITYS = new GeneratedPersonality[] { + new GeneratedPersonality(1, "1CH", + new Slot(0, ERDM_SlotCategory.INTENSITY, "Dimmer" ))}; + + private static readonly Sensor[] SENSORS = new Sensor[] { + new MockSensorTemp(0, 1, 3000)}; + + public override GeneratedPersonality[] Personalities => PERSONALITYS; + public MockGeneratedDeviceWithSubDeviceSub1(UID uid, ushort subDeviceID) : base(uid, getSubDevice(subDeviceID), SENSORS) + { + } + private static SubDevice getSubDevice(ushort subDeviceID) + { + var subDevice = new SubDevice(subDeviceID); + if (subDevice == SubDevice.Root) + throw new ArgumentException("SubDeviceID must not be Root", nameof(subDeviceID)); + if (subDevice == SubDevice.Broadcast) + throw new ArgumentException("SubDeviceID must not be Broadcast", nameof(subDeviceID)); + + return subDevice; + } + private class MockSensorTemp : Sensor + { + public MockSensorTemp(in byte sensorId, byte number, short initValue) : base(sensorId, ERDM_SensorType.TEMPERATURE, ERDM_SensorUnit.CENTIGRADE, ERDM_UnitPrefix.CENTI, $"Mock Channel Temp. {number}", -2000, 10000, 2000, 5000, true, true) + { + UpdateValue(initValue); + } + } + } +} \ No newline at end of file diff --git a/RDMSharpTests/Devices/TestRDMSendReceiveSubDevices.cs b/RDMSharpTests/Devices/TestRDMSendReceiveSubDevices.cs new file mode 100644 index 0000000..261ce21 --- /dev/null +++ b/RDMSharpTests/Devices/TestRDMSendReceiveSubDevices.cs @@ -0,0 +1,132 @@ +using RDMSharpTests.Devices.Mock; + +namespace RDMSharpTests.RDM.Devices +{ + public class TestRDMSendReceiveSubDevices + { + private MockGeneratedDeviceWithSubDeviceMaster1 generated; + private MockDevice remote; + + private const ushort SUBDEVICE_COUNT = 12; + + [SetUp] + public void Setup() + { + var uid = new UID(0x9fef, 1); + generated = new MockGeneratedDeviceWithSubDeviceMaster1(uid, SUBDEVICE_COUNT); + remote = new MockDevice(uid, false); + } + [TearDown] + public void TearDown() + { + generated.Dispose(); + remote.Dispose(); + } + + [Test] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Assertion", "NUnit2010:Use EqualConstraint for better assertion messages in case of failure", Justification = "")] + public async Task TestDevice1() + { + while (!remote.IsInitialized) + await Task.Delay(10); + + SubDevice[] subDeviceIDs_generated = generated.SubDevices.Select(x => x.Subdevice).ToArray(); + SubDevice[] subDeviceIDs_remote = remote.SubDevices.Select(x => x.Subdevice).ToArray(); + + Assert.That(generated.DeviceInfo.SubDeviceCount, Is.EqualTo(SUBDEVICE_COUNT)); + Assert.That(generated.SubDevices.Count, Is.EqualTo(SUBDEVICE_COUNT + 1)); + + Assert.That(remote.DeviceInfo.SubDeviceCount, Is.EqualTo(SUBDEVICE_COUNT)); + Assert.That(remote.SubDevices.Count, Is.EqualTo(SUBDEVICE_COUNT + 1)); + + Assert.That(subDeviceIDs_remote, Is.EquivalentTo(subDeviceIDs_generated)); + + Assert.That(subDeviceIDs_generated, Has.ItemAt(0).EqualTo(SubDevice.Root)); + Assert.That(subDeviceIDs_remote, Has.ItemAt(0).EqualTo(SubDevice.Root)); + + foreach(AbstractGeneratedRDMDevice gen in generated.SubDevices) + { + await PerformTests((AbstractRemoteRDMDevice)remote.SubDevices.First(sd => sd.Subdevice == gen.Subdevice), gen); + } + + } + private async Task PerformTests(AbstractRemoteRDMDevice remote, AbstractGeneratedRDMDevice generated) + { + var parameterValuesRemote = remote.GetAllParameterValues(); + var parameterValuesGenerated = generated.GetAllParameterValues(); + //Assert.Multiple(() => + //{ + Assert.That(parameterValuesGenerated.Keys, Is.EquivalentTo(parameterValuesRemote.Keys)); + foreach (var parameter in parameterValuesGenerated.Keys) + { + Assert.That(parameterValuesRemote.Keys, Contains.Item(parameter), $"Tested Parameter {parameter}"); + if (parameterValuesGenerated[parameter] is Array) + Assert.That(parameterValuesGenerated[parameter], Is.EquivalentTo((Array)parameterValuesRemote[parameter]), $"Tested Parameter {parameter}"); + else + Assert.That(parameterValuesGenerated[parameter], Is.EqualTo(parameterValuesRemote[parameter]), $"Tested Parameter {parameter}"); + } + foreach (var parameter in parameterValuesRemote.Keys) + { + Assert.That(parameterValuesGenerated.Keys, Contains.Item(parameter), $"Tested Parameter {parameter}"); + if (parameterValuesRemote[parameter] is Array) + Assert.That(parameterValuesRemote[parameter], Is.EquivalentTo((Array)parameterValuesGenerated[parameter]), $"Tested Parameter {parameter}"); + else + Assert.That(parameterValuesRemote[parameter], Is.EqualTo(parameterValuesGenerated[parameter]), $"Tested Parameter {parameter}"); + } + Assert.That(parameterValuesRemote, Has.Count.EqualTo(parameterValuesGenerated.Count)); + //}); + + //Assert.Multiple(() => + //{ + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.DEVICE_INFO], Is.EqualTo(generated.DeviceInfo)); + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.DEVICE_LABEL], Is.EqualTo(generated.DeviceLabel)); + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.DEVICE_MODEL_DESCRIPTION], Is.EqualTo(generated.DeviceModelDescription)); + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.MANUFACTURER_LABEL], Is.EqualTo(generated.ManufacturerLabel)); + Assert.That(((RDMDMXPersonality)remote.GetAllParameterValues()[ERDM_Parameter.DMX_PERSONALITY]).Index, Is.EqualTo(generated.CurrentPersonality)); + //}); + + await remote.SetParameter(ERDM_Parameter.DMX_START_ADDRESS, (ushort)512); + //Assert.Multiple(() => + //{ + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.DMX_START_ADDRESS], Is.EqualTo(512)); + Assert.That(generated.DMXAddress, Is.EqualTo(512)); + //}); + + await remote.SetParameter(ERDM_Parameter.DMX_PERSONALITY, (byte)3); + //Assert.Multiple(() => + //{ + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.DMX_PERSONALITY], Is.EqualTo(3)); + Assert.That(generated.CurrentPersonality, Is.EqualTo(3)); + //}); + + string label = "Changed Device Label"; + await remote.SetParameter(ERDM_Parameter.DEVICE_LABEL, label); + //Assert.Multiple(() => + //{ + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.DEVICE_LABEL], Is.EqualTo(label)); + Assert.That(generated.DeviceLabel, Is.EqualTo(label)); + //}); + //Assert.Multiple(async () => + //{ + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.IDENTIFY_DEVICE], Is.False); + Assert.That(generated.GetAllParameterValues()[ERDM_Parameter.IDENTIFY_DEVICE], Is.False); + await remote.SetParameter(ERDM_Parameter.IDENTIFY_DEVICE, true); + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.IDENTIFY_DEVICE], Is.True); + Assert.That(generated.GetAllParameterValues()[ERDM_Parameter.IDENTIFY_DEVICE], Is.True); + await remote.SetParameter(ERDM_Parameter.IDENTIFY_DEVICE, false); + Assert.That(remote.GetAllParameterValues()[ERDM_Parameter.IDENTIFY_DEVICE], Is.False); + Assert.That(generated.GetAllParameterValues()[ERDM_Parameter.IDENTIFY_DEVICE], Is.False); + //}); + //Assert.Multiple(() => + //{ + Assert.Throws(typeof(NotSupportedException), () => { generated.TrySetParameter(ERDM_Parameter.DEVICE_INFO, new RDMDeviceInfo()); }); + Assert.Throws(typeof(NotSupportedException), () => { generated.TrySetParameter(ERDM_Parameter.DMX_PERSONALITY_DESCRIPTION, new RDMDMXPersonalityDescription(1, 2, "dasdad")); }); + Assert.Throws(typeof(NotSupportedException), () => { generated.TrySetParameter(ERDM_Parameter.LANGUAGE, "de"); }); + Assert.Throws(typeof(NotSupportedException), () => { generated.TrySetParameter(ERDM_Parameter.LANGUAGE, 333); }); + Assert.Throws(typeof(NotSupportedException), () => { generated.TrySetParameter(ERDM_Parameter.DEVICE_LABEL, "Test"); }); + Assert.Throws(typeof(NotSupportedException), () => { generated.TrySetParameter(ERDM_Parameter.DISC_MUTE, null); }); + Assert.Throws(typeof(NotSupportedException), () => { generated.TrySetParameter(ERDM_Parameter.DEVICE_LABEL, new RDMDeviceInfo()); }); + //}); + } + } +} \ No newline at end of file