diff --git a/ArtNetSharp/ApplicationLogging.cs b/ArtNetSharp/ApplicationLogging.cs index c8f52c8..35b04c8 100644 --- a/ArtNetSharp/ApplicationLogging.cs +++ b/ArtNetSharp/ApplicationLogging.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using System; +using System.Linq; using System.Runtime.CompilerServices; using static ArtNetSharp.ApplicationLogging; @@ -11,7 +12,34 @@ namespace ArtNetSharp /// internal static class ApplicationLogging { - internal static readonly ILoggerFactory LoggerFactory = new LoggerFactory(); + private static ILoggerFactory loggerFactory; + internal static ILoggerFactory LoggerFactory + { + get + { + if (loggerFactory == null) + { + bool isTest = AppDomain.CurrentDomain.GetAssemblies() + .Any(a => a.FullName.StartsWith("NUnit", StringComparison.OrdinalIgnoreCase)); + loggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create((builder) => + { + FileProvider fp = isTest ? new FileProvider() : null; +#if Debug + fp ?= new FileProvider(); +#endif + if (isTest) + { + builder.AddConsole(); + builder.SetMinimumLevel(LogLevel.Trace); + } + if (fp != null) + builder.AddProvider(fp); + }); + } + return loggerFactory; + } + } + internal static ILogger CreateLogger() => LoggerFactory.CreateLogger(); internal static ILogger CreateLogger(Type type) => LoggerFactory.CreateLogger(type); internal static ILogger CreateLogger(string categoryName) => LoggerFactory.CreateLogger(categoryName); diff --git a/ArtNetSharp/ArtNet.cs b/ArtNetSharp/ArtNet.cs index de93b3a..904b7b4 100644 --- a/ArtNetSharp/ArtNet.cs +++ b/ArtNetSharp/ArtNet.cs @@ -19,7 +19,7 @@ namespace ArtNetSharp public class ArtNet : IDisposable { private static readonly Random _random = new Random(); - private static ILogger Logger = null; + private static ILogger Logger = ApplicationLogging.CreateLogger(); private static ArtNet instance; public static ArtNet Instance { @@ -107,6 +107,9 @@ public bool Enabled internal NetworkClientBag(IPAddress broadcastIpAddress, UnicastIPAddressInformation unicastIPAddressInformation) { UnicastIPAddressInfo = unicastIPAddressInformation; + if (unicastIPAddressInformation.Address.Equals(IPAddress.Loopback)) + broadcastIpAddress = IPAddress.Loopback; + BroadcastIpAddress = broadcastIpAddress; broadcastEndpoint = new IPEndPoint(broadcastIpAddress, Constants.ARTNET_PORT); Logger?.LogTrace($"Create Client ({LocalIpAddress})"); @@ -152,8 +155,9 @@ private async Task openClient() _client.ExclusiveAddressUse = false; _client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); _client.EnableBroadcast = true; - var endpointIp = Tools.IsLinux() ? IPAddress.Any : LocalIpAddress; - IPEndPoint localEp = new IPEndPoint(IPAddress.Any, Constants.ARTNET_PORT); + + IPAddress endpointIp = getEndointIP(); + IPEndPoint localEp = new IPEndPoint(endpointIp, Constants.ARTNET_PORT); _client.Client.Bind(localEp); _clientAlive = true; _ = StartListening(); @@ -162,6 +166,18 @@ private async Task openClient() catch (Exception e) { Logger?.LogError(e, $"Client ({LocalIpAddress}): Error on initialize"); } finally { semaphoreSlim?.Release(); } } + private IPAddress getEndointIP() + { + IPAddress endpointIp = IPAddress.Any; + if (Tools.IsWindows()) + endpointIp = LocalIpAddress; + if (Tools.IsMac()) + endpointIp = LocalIpAddress; + if (Tools.IsLinux()) + endpointIp = LocalIpAddress; + + return endpointIp; + } private async Task StartListening() { @@ -194,6 +210,8 @@ private async Task StartListening() private readonly List matchingIpAdddresses = new List(); public async Task MatchIP(IPAddress ip) { + if (ip == IPAddress.Loopback) + return true; if (ip == null) return false; if (ip.ToString().Equals("0.0.0.0")) @@ -315,12 +333,6 @@ public void Dispose() internal ArtNet([CallerFilePath] string caller = "", [CallerLineNumber] int line = -1) { - if (Logger == null) - { - ApplicationLogging.LoggerFactory.AddProvider(new FileProvider()); - Logger = ApplicationLogging.CreateLogger(); - } - Logger?.LogTrace($"Initialize {caller} (Line: {line})"); _updateNetworkClientsTimer = new System.Timers.Timer(); _updateNetworkClientsTimer.Interval = 1000; @@ -385,48 +397,49 @@ private void UpdateNetworkClientsTimer_Elapsed(object sender, System.Timers.Elap } private void ReceivedData(object sender, Tuple e) - { - ProcessReceivedData(e.Item2); - } - private void ProcessReceivedData(UdpReceiveResult result) { if (IsDisposed || IsDisposing) return; - + IPAddress localIpAddress = e.Item1; + UdpReceiveResult result = e.Item2; IPEndPoint RemoteIpEndPoint = result.RemoteEndPoint; + IPv4Address sourceIp = RemoteIpEndPoint.Address; byte[] received = result.Buffer; try { - IPv4Address sourceIp = RemoteIpEndPoint.Address; if (Tools.TryDeserializePacket(received, out var packet)) { - var nic = networkClients.Values.FirstOrDefault(n => Tools.IsInSubnet(n.LocalIpAddress, n.IPv4Mask, sourceIp)); - if (nic != null) - { - //Logger?.LogTrace($"Process Network Packet:{packet} {Environment.NewLine} Local:{nic.LocalIpAddress}, Mask: {nic.IPv4Mask}, Remote: {sourceIp}"); - processPacket(packet, nic.LocalIpAddress, sourceIp); - } + //var nic = networkClients.Values.FirstOrDefault(n => Tools.IsInSubnet(n.LocalIpAddress, n.IPv4Mask, sourceIp)); + //if (nic != null) + //{ + // //Logger?.LogTrace($"Process Network Packet:{packet} {Environment.NewLine} Local:{nic.LocalIpAddress}, Mask: {nic.IPv4Mask}, Remote: {sourceIp}"); + // processPacket(packet, nic.LocalIpAddress, sourceIp); + //} + processPacket(packet, localIpAddress, sourceIp); return; } Logger.LogWarning($"Can't deserialize Data to ArtNet-Packet from {sourceIp}"); } catch (ObjectDisposedException ed) { Logger.LogTrace(ed); } catch (SocketException se) { Logger.LogTrace(se); } - catch (Exception e) { Logger.LogError(e); } + catch (Exception ex) { Logger.LogError(ex); } } private void processPacket(AbstractArtPacketCore packet, IPv4Address localIp, IPv4Address sourceIp) { -#if DEBUG Logger.LogTrace($"Received Packet from {sourceIp} -> {packet}"); -#endif - foreach (var inst in instances) try - { - ((IInstance)inst.Value).PacketReceived(packet, localIp, sourceIp); - } - catch (Exception e) + + foreach (var inst in instances) + Task.Run(() => { - Logger.LogError(e); - } + try + { + ((IInstance)inst.Value).PacketReceived(packet, localIp, sourceIp); + } + catch (Exception e) + { + Logger.LogError(e); + } + }); } public static bool IsNetworkAvailable(long? minimumSpeed = null) @@ -482,7 +495,7 @@ public MACAddress GetMacAdress(IPv4Address ip) IPAddress _ipToTest = ip; var nicWithThisIP = nics.FirstOrDefault(nic => nic.GetIPProperties().UnicastAddresses.Any(_ip => IPAddress.Equals(_ip.Address, _ipToTest))); - if (nicWithThisIP != null) + if (nicWithThisIP != null && nicWithThisIP.NetworkInterfaceType != NetworkInterfaceType.Loopback) mac = new MACAddress(nicWithThisIP.GetPhysicalAddress().GetAddressBytes()); else mac = new MACAddress(); @@ -514,7 +527,7 @@ private async void updateNetworkClients() NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface @interface in interfaces) { - if (@interface.NetworkInterfaceType == NetworkInterfaceType.Loopback) continue; + //if (@interface.NetworkInterfaceType == NetworkInterfaceType.Loopback) continue; if (@interface.OperationalStatus != OperationalStatus.Up) continue; UnicastIPAddressInformationCollection unicastIpInfoCol = @interface.GetIPProperties().UnicastAddresses; foreach (UnicastIPAddressInformation ipInfo in unicastIpInfoCol) diff --git a/ArtNetSharp/ArtNetSharp.csproj b/ArtNetSharp/ArtNetSharp.csproj index 310840a..c683e4f 100644 --- a/ArtNetSharp/ArtNetSharp.csproj +++ b/ArtNetSharp/ArtNetSharp.csproj @@ -33,7 +33,8 @@ - + + diff --git a/ArtNetSharp/Communication/AbstractInstance.cs b/ArtNetSharp/Communication/AbstractInstance.cs index ac3846e..4f9f612 100644 --- a/ArtNetSharp/Communication/AbstractInstance.cs +++ b/ArtNetSharp/Communication/AbstractInstance.cs @@ -12,7 +12,6 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using System.Timers; [assembly: InternalsVisibleTo("ArtNetTests")] namespace ArtNetSharp.Communication @@ -25,7 +24,7 @@ public abstract class AbstractInstance : IInstance bool IDisposableExtended.IsDisposed { get => IsDisposed; } bool IDisposableExtended.IsDisposing { get => IsDisposing; } - public bool IsDeactivated { get { return !ArtNetInstance.Instances.Contains(this); } } + public bool IsDeactivated { get { return !ArtNetInstance?.Instances.Contains(this) ?? true; } } private readonly Random _random; internal ArtNet ArtNetInstance; @@ -258,6 +257,8 @@ public void Dispose() internal class SendPollThreadBag { + private static readonly TimeSpan PollPeriod = TimeSpan.FromSeconds(2.7); // Spec 1.4dd page 13 + private readonly Thread sendPollThread; public EventHandler SendArtPollEvent; public SendPollThreadBag() @@ -269,8 +270,9 @@ public SendPollThreadBag() { try { - if ((DateTime.UtcNow - lastSendPollTime).TotalSeconds < 2.7)// Spec 1.4dd page 13 - continue; + TimeSpan elapsed = DateTime.UtcNow - lastSendPollTime; + if (elapsed < PollPeriod) + await Task.Delay(PollPeriod - elapsed); SendArtPollEvent?.InvokeFailSafe(null,EventArgs.Empty); } @@ -328,7 +330,7 @@ void IInstance.PacketReceived(AbstractArtPacketCore packet, IPv4Address localIp, { if (this.IsDisposing || this.IsDisposed || this.IsDeactivated) return; - + try { switch (packet) @@ -600,32 +602,38 @@ private async Task sendAllArtDMX() { const double dmxRefreshTime = 1000 / 44.0; // Spec 1.4dh page 56 const double dmxKeepAliveTime = 800; // Spec 1.4dh page 53 + const int interval = (int)(dmxRefreshTime / 3); + List sendTasks = new List(); while (!(this.IsDisposing || this.IsDisposed)) { + // Prevent CPU loop + await Task.Delay(interval); + if (!this.EnableDmxOutput) - continue; + await Task.Delay(300); if (this.IsDeactivated) - continue; + await Task.Delay(300); try { var ports = RemoteClientsPorts?.Where(port => port.OutputPortAddress.HasValue && !port.Timouted())?.ToList(); int sended = 0; + var utcNow = DateTime.UtcNow; foreach (var port in ports) try { if (sendDMXBuffer.TryGetValue(port.OutputPortAddress.Value, out DMXSendBag bag)) - if ((bag.Updated && (DateTime.UtcNow - bag.LastSended).TotalMilliseconds >= dmxRefreshTime) || (DateTime.UtcNow - bag.LastSended).TotalMilliseconds >= dmxKeepAliveTime) + if ((bag.Updated && (utcNow - bag.LastSended).TotalMilliseconds >= dmxRefreshTime) || (utcNow - bag.LastSended).TotalMilliseconds >= dmxKeepAliveTime) { PortConfig config = null; byte sourcePort = 0; try { - bag.LastSended = DateTime.UtcNow; + bag.LastSended = utcNow; config = portConfigs?.FirstOrDefault(pc => PortAddress.Equals(pc.PortAddress, port.OutputPortAddress)); sourcePort = config?.PortNumber ?? 0; - await sendArtDMX(port, sourcePort, bag.Data, bag.GetSequence(), config?.ForceBroadcast ?? false); + sendTasks.Add(sendArtDMX(port, sourcePort, bag.Data, bag.GetSequence(), config?.ForceBroadcast ?? false)); sended++; if (config == null) return; @@ -637,13 +645,15 @@ private async Task sendAllArtDMX() } foreach (IPv4Address ip in config?.AdditionalIPEndpoints) { - await sendArtDMX(ip, config.PortAddress, sourcePort, bag.Data, bag.GetSequence()); + sendTasks.Add(sendArtDMX(ip, config.PortAddress, sourcePort, bag.Data, bag.GetSequence())); sended++; } bag.LastSended = DateTime.UtcNow; } } catch (Exception e) { Logger.LogError(e, "Outer Block"); } + await Task.WhenAll(sendTasks); + sendTasks.Clear(); if (EnableSync && sended != 0) await sendArtSync(); @@ -787,14 +797,16 @@ private async Task processArtPollReply(ArtPollReply artPollReply, IPv4Address lo if (this.IsDisposing || this.IsDisposed || this.IsDeactivated) return; - if (MajorVersion == artPollReply.MajorVersion - && MinorVersion == artPollReply.MinorVersion - && OEMProductCode == artPollReply.OemCode - && ESTAManufacturerCode == artPollReply.ManufacturerCode - && IPv4Address.Equals(artPollReply.OwnIp, localIp) - && string.Equals(artPollReply.ShortName, ShortName) - && string.Equals(artPollReply.LongName, Name)) - return; //break loopback + if (localIp == sourceIp) + { + if (MajorVersion == artPollReply.MajorVersion + && MinorVersion == artPollReply.MinorVersion + && OEMProductCode == artPollReply.OemCode + && ESTAManufacturerCode == artPollReply.ManufacturerCode + && IPv4Address.Equals(artPollReply.OwnIp, localIp) + && EstCodes == artPollReply.Style) + return; //break loopback + } string id = RemoteClient.getIDOf(artPollReply); RemoteClient remoteClient = null; @@ -1227,6 +1239,8 @@ private async Task triggerSendArtPoll() { if (this.IsDisposed || this.IsDisposing || this.IsDeactivated) return; + //if (EstCodes != EStCodes.StController)// As Spec. only Controler are allowed to send ArtPoll + // return; if (SendArtPollBroadcast) await sendArtPoll(); else if (SendArtPollTargeted) @@ -1267,7 +1281,7 @@ private async void TimerSendPoll_Elapsed(object sender, EventArgs e) return; await triggerSendArtPoll(); - await Task.Delay(3000); + await Task.Delay(4000); await pollReplyProcessSemaphoreSlim.WaitAsync(); try { diff --git a/ArtNetSharp/Misc/ObjectTypes/GoodOutput.cs b/ArtNetSharp/Misc/ObjectTypes/GoodOutput.cs index 71b58aa..bf76de1 100644 --- a/ArtNetSharp/Misc/ObjectTypes/GoodOutput.cs +++ b/ArtNetSharp/Misc/ObjectTypes/GoodOutput.cs @@ -25,9 +25,9 @@ namespace ArtNetSharp /// public readonly bool MergingArtNetData; /// - /// Channel includes DMX512 test packets. + /// Channel includes DMX512 text packets. /// - public readonly bool DMX_TestPacketsSupported; + public readonly bool DMX_TextPacketsSupported; /// /// Channel includes DMX512 SIP’s /// @@ -35,7 +35,7 @@ namespace ArtNetSharp /// /// Channel includes DMX512 test packets. /// - public readonly bool DMX_TestPacketsSupported2; + public readonly bool DMX_TestPacketsSupported; /// /// ArtDmx or sACN data is being output as /// DMX512 on this port. @@ -55,15 +55,16 @@ namespace ArtNetSharp public GoodOutput(in byte byte1, in byte byte2) { Byte1 = byte1; - Byte2 = byte2; + // Bit 0-3 Not used, set to zero (Mask: 0b11110000 -> ~0b00001111) + Byte2 = (byte)(byte2 & 0b11110000); ConvertFrom = (EConvertFrom)(Byte1 & 0b00000001); MergeMode = (EMergeMode)(Byte1 & 0b00000010); DMX_OutputShortCircuit = Tools.BitsMatch(Byte1, 0b00000100); MergingArtNetData = Tools.BitsMatch(Byte1, 0b00001000); - DMX_TestPacketsSupported = Tools.BitsMatch(Byte1, 0b00010000); + DMX_TextPacketsSupported = Tools.BitsMatch(Byte1, 0b00010000); DMX_SIPsSupported = Tools.BitsMatch(Byte1, 0b00100000); - DMX_TestPacketsSupported2 = Tools.BitsMatch(Byte1, 0b01000000); + DMX_TestPacketsSupported = Tools.BitsMatch(Byte1, 0b01000000); IsBeingOutputAsDMX = Tools.BitsMatch(Byte1, 0b10000000); BackgroundDiscoveryIsEnabled = Tools.BitsMatch(Byte2, 0b00010000); @@ -76,23 +77,27 @@ public GoodOutput(in EConvertFrom convertFrom = EConvertFrom.ArtNet, in EMergeMode mergeMode = EMergeMode.HTP, in bool dmx_OutputShortCircuit = false, in bool mergingArtNetData = false, - in bool dMX_TestPacketsSupported = false, - in bool dMX_SIPsSupported = false, - in bool dMX_TestPacketsSupported2 = false, + in bool dmx_TextPacketsSupported = false, + in bool dmx_SIPsSupported = false, + in bool dmx_TestPacketsSupported = false, in bool isBeingOutputAsDMX = false, - in EOutputStyle outputStyle = EOutputStyle.Continuous, - in bool rdmIsDisabled = false) : this() + in EOutputStyle outputStyle = EOutputStyle.Delta, + in bool rdmIsDisabled = false, + in bool discoveryIsCurrentlyRunning = false, + in bool backgroundDiscoveryIsEnabled = false) : this() { ConvertFrom = convertFrom; MergeMode = mergeMode; DMX_OutputShortCircuit = dmx_OutputShortCircuit; MergingArtNetData = mergingArtNetData; - DMX_TestPacketsSupported = dMX_TestPacketsSupported; - DMX_SIPsSupported = dMX_SIPsSupported; - DMX_TestPacketsSupported2 = dMX_TestPacketsSupported2; + DMX_TextPacketsSupported = dmx_TextPacketsSupported; + DMX_SIPsSupported = dmx_SIPsSupported; + DMX_TestPacketsSupported = dmx_TestPacketsSupported; IsBeingOutputAsDMX = isBeingOutputAsDMX; OutputStyle = outputStyle; RDMisDisabled = rdmIsDisabled; + DiscoveryIsCurrentlyRunning = discoveryIsCurrentlyRunning; + BackgroundDiscoveryIsEnabled = backgroundDiscoveryIsEnabled; Byte1 |= (byte)ConvertFrom; Byte1 |= (byte)MergeMode; @@ -102,11 +107,11 @@ public GoodOutput(in EConvertFrom convertFrom = EConvertFrom.ArtNet, Byte1 |= 0b00000100; if (MergingArtNetData) Byte1 |= 0b00001000; - if (DMX_TestPacketsSupported) + if (DMX_TextPacketsSupported) Byte1 |= 0b00010000; if (DMX_SIPsSupported) Byte1 |= 0b00100000; - if (DMX_TestPacketsSupported2) + if (DMX_TestPacketsSupported) Byte1 |= 0b01000000; if (IsBeingOutputAsDMX) Byte1 |= 0b10000000; diff --git a/ArtNetTests/LoopTests/ControllerToControllerTests.cs b/ArtNetTests/LoopTests/ControllerToControllerTests.cs deleted file mode 100644 index f445423..0000000 --- a/ArtNetTests/LoopTests/ControllerToControllerTests.cs +++ /dev/null @@ -1,281 +0,0 @@ -using ArtNetSharp; -using ArtNetSharp.Communication; -using ArtNetTests.Mocks; -using Microsoft.Extensions.Logging; -using RDMSharp; -using System.Diagnostics; - -namespace ArtNetTests.LoopTests -{ - [Order(10)] - public class ControllerToControllerTests - { - private static readonly ILogger Logger = ApplicationLogging.CreateLogger(); - private ArtNet artNet; - private ControllerInstanceMock instanceTX; - private OutputPortConfig outputPort; - private ControllerInstanceMock instanceRX; - private InputPortConfig inputPort; - - private Task? initialTask; - - private RemoteClient? rcRX = null; - private RemoteClient? rcTX = null; - - private static readonly PortAddress portAddress = new PortAddress(2, 3, 4); - - [OneTimeSetUp] - public void OneTimeSetUp() - { - if (ArtNetSharp.Tools.IsRunningOnGithubWorker()) - Assert.Ignore("Not running on Github-Action"); - - Logger.LogDebug($"Test Setup: {nameof(ControllerToControllerTests)}"); - - artNet = new ArtNet(); - //artNet.LoopNetwork = new ArtNet.NetworkLoopAdapter(new IPv4Address("255.255.255.0")); - - instanceTX = new ControllerInstanceMock(artNet, 0x1111); - instanceTX.Name = $"{nameof(ControllerToControllerTests)}-TX"; - instanceRX = new ControllerInstanceMock(artNet, 0x2222); - instanceRX.Name = $"{nameof(ControllerToControllerTests)}-RX"; - - outputPort = new OutputPortConfig(1, portAddress); - inputPort = new InputPortConfig(1, portAddress); - - instanceTX.AddPortConfig(inputPort); - instanceRX.AddPortConfig(outputPort); - - if (ArtNetSharp.Tools.IsRunningOnGithubWorker()) - { - foreach (var client in artNet.NetworkClients.Where(nc => ((IPv4Address)nc.LocalIpAddress).B1 != 10)) - { - client.Enabled = false; - } - } - - artNet.AddInstance([instanceTX, instanceRX]); - } - - private async Task init() - { - DateTime startTime = DateTime.UtcNow; - while ((DateTime.UtcNow - startTime).TotalSeconds < 12 && (rcRX == null || rcTX == null) && !(artNet.IsDisposed || artNet.IsDisposing)) - { - await Task.Delay(2500); - rcRX ??= instanceTX.RemoteClients.FirstOrDefault(rc => rc.LongName.Equals(instanceRX.Name)); - rcTX ??= instanceRX.RemoteClients.FirstOrDefault(rc => rc.LongName.Equals(instanceTX.Name)); - foreach (var rc in instanceTX.RemoteClients) - Logger.LogTrace($"{nameof(instanceTX)} has {rc}"); - foreach (var rc in instanceRX.RemoteClients) - Logger.LogTrace($"{nameof(instanceRX)} has {rc}"); - Logger.LogTrace($"{nameof(rcRX)} is {rcRX}"); - Logger.LogTrace($"{nameof(rcTX)} is {rcTX}"); - if (rcRX != null && rcTX != null && rcRX.IpAddress != rcTX.IpAddress) - rcTX ??= instanceRX.RemoteClients.FirstOrDefault(rc => rc.IpAddress.Equals(rcRX.IpAddress)); - } - } - - [OneTimeTearDown] - public void OneTimeTearDown() - { - Logger.LogDebug($"Test Setup: {nameof(ControllerToControllerTests)} {nameof(OneTimeTearDown)}"); - - if (artNet != null) - ((IDisposable)artNet).Dispose(); - - Trace.Flush(); - } - -#pragma warning disable CS0618 // Typ oder Element ist veraltet - [Timeout(8000)] -#pragma warning restore CS0618 // Typ oder Element ist veraltet - [Test, Order(1), Retry(5)] - public async Task TestLoopDetection() - { - initialTask ??= init(); - await initialTask; - Logger.LogDebug(nameof(TestLoopDetection)); - Assert.Multiple(() => - { - Assert.That(rcRX, Is.Not.Null); - Assert.That(rcTX, Is.Not.Null); - }); - Assert.Multiple(() => - { - Assert.That(rcTX.Ports, Has.Count.EqualTo(1)); - Assert.That(rcRX.Ports, Has.Count.EqualTo(1)); - }); - - var txPort = rcTX.Ports.First(); - var rxPort = rcRX.Ports.First(); - Assert.Multiple(() => - { - Assert.That(txPort.PortType, Is.EqualTo(EPortType.InputToArtNet)); - Assert.That(rxPort.PortType, Is.EqualTo(EPortType.OutputFromArtNet)); - Assert.That(rxPort.OutputPortAddress, Is.EqualTo(portAddress)); - Assert.That(txPort.InputPortAddress, Is.EqualTo(portAddress)); - Assert.That(rxPort.GoodOutput.IsBeingOutputAsDMX, Is.False); - }); - } - -#pragma warning disable CS0618 // Typ oder Element ist veraltet - [Timeout(1000)] -#pragma warning restore CS0618 // Typ oder Element ist veraltet - [Test, Order(2), Retry(5)] - public async Task TestSendDMX() - { - initialTask ??= init(); - await initialTask; - Logger.LogDebug(nameof(TestSendDMX)); - Assert.Multiple(() => - { - Assert.That(rcRX, Is.Not.Null); - Assert.That(rcTX, Is.Not.Null); - }); - - byte[] data = new byte[512]; - bool receiveFlag = false; - - var txPort = rcTX.Ports.First(p => p.InputPortAddress.Equals(portAddress)); - var rxPort = rcRX.Ports.First(p => p.OutputPortAddress.Equals(portAddress)); - Assert.Multiple(() => - { - Assert.That(rxPort.GoodOutput.ConvertFrom, Is.EqualTo(GoodOutput.EConvertFrom.ArtNet)); - Assert.That(rxPort.GoodOutput.DMX_OutputShortCircuit, Is.False); - Assert.That(rxPort.GoodOutput.IsBeingOutputAsDMX, Is.False); - }); - - instanceRX.DMXReceived += InstanceRX_DMXReceived; - for (byte b = 0; b <= 250; b++) - await doDmxStuff(b); - - instanceRX.DMXReceived -= InstanceRX_DMXReceived; - - Assert.Multiple(() => - { - Assert.That(rxPort.GoodOutput.ConvertFrom, Is.EqualTo(GoodOutput.EConvertFrom.ArtNet)); - Assert.That(rxPort.GoodOutput.DMX_OutputShortCircuit, Is.False); - }); - bool dataReceived = false; - for (int i = 0; i < 60; i++) - { - dataReceived = rxPort.GoodOutput.IsBeingOutputAsDMX; - if (dataReceived) - continue; - } - Assert.That(dataReceived, Is.True); - - - async Task doDmxStuff(byte value) - { - receiveFlag = false; - if (data[0] != value) - for (ushort i = 0; i < 512; i++) - data[i] = value; - - instanceTX.WriteDMXValues(portAddress, data); - while (!receiveFlag) - await Task.Delay(15); - - string str = $"Error at {data[0]}"; - Assert.Multiple(() => - { - Assert.That(outputPort.GoodOutput.IsBeingOutputAsDMX, Is.True, str); - Assert.That(instanceRX.GetReceivedDMX(portAddress), Is.EqualTo(data), str); - }); - } - void InstanceRX_DMXReceived(object? sender, PortAddress e) - { - if (e != portAddress) - return; - - Assert.That(e, Is.EqualTo(portAddress)); - - receiveFlag = true; - } - } - -#pragma warning disable CS0618 // Typ oder Element ist veraltet - [Timeout(9000)] -#pragma warning restore CS0618 // Typ oder Element ist veraltet - [Test, Order(3), Retry(5)] - public async Task TestSendDMXTiming() - { - initialTask ??= init(); - await initialTask; - Logger.LogDebug(nameof(TestSendDMXTiming)); - //if(ArtNetSharp.Tools.IsRunningOnGithubWorker()) - // Assert.Ignore("Skiped, only run on Linux"); - - DateTime startTime = DateTime.UtcNow; - Assert.Multiple(() => - { - Assert.That(rcRX, Is.Not.Null); - Assert.That(rcTX, Is.Not.Null); - }); - Stopwatch swDMX = new Stopwatch(); - Stopwatch swSync = new Stopwatch(); - List refreshRate = new List(); - List syncRate = new List(); - byte[] data = new byte[100]; - bool receivedFlag = false; - bool syncFlag = false; - bool done = false; - var thread = new Thread(() => - { - try - { - instanceRX.DMXReceived += (o, e) => - { - receivedFlag = true; - swDMX.Stop(); - if (swDMX.Elapsed.TotalMilliseconds != 0) - { - refreshRate.Add(1000.0 / swDMX.Elapsed.TotalMilliseconds); - } - swDMX.Restart(); - }; - instanceRX.SyncReceived += (o, e) => - { - syncFlag = true; - swSync.Stop(); - syncRate.Add(1000.0 / swSync.Elapsed.TotalMilliseconds); - swSync.Restart(); - }; - Random rnd = new Random(); - swSync.Start(); - swDMX.Start(); - while ((DateTime.UtcNow - startTime).TotalSeconds <= 5) - { - rnd.NextBytes(data); - instanceTX.WriteDMXValues(portAddress, data); ; - Thread.Sleep(15); - } - swSync.Stop(); - swDMX.Stop(); - } - catch (Exception) - { - } - finally - { - done = true; - } - }); - thread.Priority = ThreadPriority.Normal; - thread.Name = nameof(TestSendDMXTiming); - thread.IsBackground = true; - thread.Start(); - while (!done) - await Task.Delay(100); - Assert.Multiple(() => - { - Assert.That(syncFlag, Is.True); - Assert.That(receivedFlag, Is.True); - Assert.That(syncRate.Average(), Is.AtLeast(40)); - Assert.That(refreshRate.Average(), Is.AtLeast(40)); - }); - } - } -} \ No newline at end of file diff --git a/ArtNetTests/LoopTests/LoopTest.cs b/ArtNetTests/LoopTests/LoopTest.cs new file mode 100644 index 0000000..b605088 --- /dev/null +++ b/ArtNetTests/LoopTests/LoopTest.cs @@ -0,0 +1,429 @@ +using ArtNetSharp; +using ArtNetSharp.Communication; +using ArtNetTests.Mocks; +using Microsoft.Extensions.Logging; +using RDMSharp; +using System.Diagnostics; +using System.Reflection; + +namespace ArtNetTests.LoopTests +{ + + public abstract class AbstractLoopTestTestSubject + { + public static readonly object[] TestSubjects = getTestSubjects(); + private static object[] getTestSubjects() + { + Type abstractType = typeof(AbstractLoopTestTestSubject); + + // Get all types in the current assembly that inherit from the abstract class + IEnumerable concreteTypes = Assembly.GetExecutingAssembly().GetTypes() + .Where(t => t.IsClass && !t.IsAbstract && t.GetConstructors().Any(c => c.IsPublic && c.GetParameters().Length == 0) && abstractType.IsAssignableFrom(t)); + + // Create instances of each concrete class + List instances = new List(); + foreach (Type concreteType in concreteTypes) + { + if (Activator.CreateInstance(concreteType) is AbstractLoopTestTestSubject instance) + instances.Add(instance); + } + + return instances.ToArray(); + } + + public override string ToString() => TestLabel; + + public readonly string TestLabel; + + public readonly InstanceTestSubject InstanceTestSubjectTX; + public readonly InstanceTestSubject InstanceTestSubjectRX; + public readonly PortAddress PortAddress; + public readonly byte BindIndex; + + protected AbstractLoopTestTestSubject(string testLabel, InstanceTestSubject instanceTestSubjectTX, InstanceTestSubject instanceTestSubjectRX, PortAddress portAddress, byte bindIndex) + { + TestLabel = testLabel; + InstanceTestSubjectTX = instanceTestSubjectTX; + InstanceTestSubjectRX = instanceTestSubjectRX; + PortAddress = portAddress; + BindIndex = bindIndex; + } + + public readonly struct InstanceTestSubject + { + public readonly Type Type; + public readonly string Name; + public readonly ushort ProductCode; + + public InstanceTestSubject(Type type, string name, ushort productCode) + { + Type = type; + Name = name; + ProductCode = productCode; + } + } + } + public sealed class ControllerToControllerTestSubject : AbstractLoopTestTestSubject + { + public ControllerToControllerTestSubject() : base( + "ControllerToController", + new InstanceTestSubject(typeof(ControllerInstanceMock), "Controller-TX", 0x1111), + new InstanceTestSubject(typeof(ControllerInstanceMock), "Controller-RX", 0x2222), + new PortAddress(2, 13, 4), + 1) + { + } + } + public sealed class ControllerToNodeTestSubject : AbstractLoopTestTestSubject + { + public ControllerToNodeTestSubject() : base( + "ControllerToNode", + new InstanceTestSubject(typeof(ControllerInstanceMock), "Controller-TX", 0x3333), + new InstanceTestSubject(typeof(NodeInstanceMock), "Node-RX", 0x4444), + new PortAddress(2, 15, 4), + 1) + { + } + } + public sealed class NodeToControllerTestSubject : AbstractLoopTestTestSubject + { + public NodeToControllerTestSubject() : base( + "NodeToController", + new InstanceTestSubject(typeof(NodeInstanceMock), "Node-TX", 0x5555), + new InstanceTestSubject(typeof(ControllerInstanceMock), "Controller-RX", 0x6666), + new PortAddress(2, 1, 4), + 1) + { + } + } + public sealed class NodeToNodeTestSubject : AbstractLoopTestTestSubject + { + public NodeToNodeTestSubject() : base( + "NodeToNode", + new InstanceTestSubject(typeof(NodeInstanceMock), "Node-TX", 0x7777), + new InstanceTestSubject(typeof(NodeInstanceMock), "Node-RX", 0x8888), + new PortAddress(2, 5, 4), + 1) + { + } + } + + [Order(10)] + [TestFixtureSource(typeof(AbstractLoopTestTestSubject), nameof(AbstractLoopTestTestSubject.TestSubjects))] + public class LoopTest + { + private readonly AbstractLoopTestTestSubject testSubject; + +#pragma warning disable CS8618 + public LoopTest(AbstractLoopTestTestSubject _TestSubject) +#pragma warning restore CS8618 + { + testSubject = _TestSubject; + Logger.LogDebug($"Initialize Test for {nameof(LoopTest)} ({testSubject.ToString()})"); + } + private static readonly ILogger Logger = ApplicationLogging.CreateLogger(); + private ArtNet artNet; + private AbstractInstance instanceTX; + private OutputPortConfig outputPort; + private AbstractInstance instanceRX; + private InputPortConfig inputPort; + private PortAddress portAddress; + + private Task? initialTask; + + private RemoteClient? rcRX = null; + private RemoteClient? rcTX = null; + + + //[OneTimeSetUp] + public async Task OneTimeSetUp() + { + + Logger.LogDebug($"Test Setup: {nameof(LoopTest)}"); + + artNet = new ArtNet(); + + instanceTX = (AbstractInstance)Activator.CreateInstance(testSubject.InstanceTestSubjectTX.Type, artNet, testSubject.InstanceTestSubjectTX.ProductCode)!; + instanceTX.Name = testSubject.InstanceTestSubjectTX.Name; + instanceRX = (AbstractInstance)Activator.CreateInstance(testSubject.InstanceTestSubjectRX.Type, artNet, testSubject.InstanceTestSubjectRX.ProductCode)!; + instanceRX.Name = testSubject.InstanceTestSubjectRX.Name; + + portAddress = testSubject.PortAddress; + outputPort = new OutputPortConfig(testSubject.BindIndex, portAddress); + inputPort = new InputPortConfig(testSubject.BindIndex, portAddress); + + instanceTX.AddPortConfig(inputPort); + instanceRX.AddPortConfig(outputPort); + + byte identifyer = 192; + if (ArtNetSharp.Tools.IsRunningOnGithubWorker()) + identifyer = 10; + identifyer = 127; + + foreach (var client in artNet.NetworkClients.Where(nc => ((IPv4Address)nc.LocalIpAddress).B1 != identifyer)) + client.Enabled = false; + + instanceTX.RemoteClientTimedOut += InstanceTX_RemoteClientTimedOut; + await Task.Delay(300); + artNet.AddInstance([instanceTX, instanceRX]); + } + + private void InstanceTX_RemoteClientTimedOut(object? sender, RemoteClient e) + { + Assert.Fail($"RemoteClient {e} timed out. Sender: {sender}"); + } + + private async Task init() + { + await OneTimeSetUp(); + DateTime startTime = DateTime.UtcNow; + int count = 0; + while ((rcRX == null || rcTX == null) && !(artNet.IsDisposed || artNet.IsDisposing)) + { + await Task.Delay(2500); + count++; + if (count > 7) + break; + rcRX ??= instanceTX.RemoteClients.FirstOrDefault(rc => rc.Root.OemCode.Equals(instanceRX.OEMProductCode) && rc.Root.ManufacturerCode == instanceRX.ESTAManufacturerCode); + rcTX ??= instanceRX.RemoteClients.FirstOrDefault(rc => rc.Root.OemCode.Equals(instanceTX.OEMProductCode) && rc.Root.ManufacturerCode == instanceRX.ESTAManufacturerCode); + foreach (var rc in instanceTX.RemoteClients) + Logger.LogTrace($"{nameof(instanceTX)} has {rc}"); + foreach (var rc in instanceRX.RemoteClients) + Logger.LogTrace($"{nameof(instanceRX)} has {rc}"); + Logger.LogTrace($"{nameof(rcRX)} is {rcRX}"); + Logger.LogTrace($"{nameof(rcTX)} is {rcTX}"); + if (rcRX != null && rcTX != null && rcRX.IpAddress != rcTX.IpAddress) + rcTX ??= instanceRX.RemoteClients.FirstOrDefault(rc => rc.IpAddress.Equals(rcRX.IpAddress)); + } + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + Logger.LogDebug($"Test Setup: {nameof(LoopTest)} {nameof(OneTimeTearDown)}"); + + instanceTX.RemoteClientTimedOut += InstanceTX_RemoteClientTimedOut; + artNet.RemoveInstance(instanceTX); + artNet.RemoveInstance(instanceRX); + + ((IDisposable)instanceTX).Dispose(); + ((IDisposable)instanceRX).Dispose(); + + if (artNet != null) + ((IDisposable)artNet).Dispose(); + + Trace.Flush(); + } + +#pragma warning disable CS0618 // Typ oder Element ist veraltet + [Timeout(20000)] +#pragma warning restore CS0618 // Typ oder Element ist veraltet + [Test, Order(1)] + public async Task TestLoopDetection() + { + TestContext.Out.WriteLine($"{nameof(TestLoopDetection)} [{testSubject.TestLabel}]"); + initialTask ??= init(); + await initialTask; + await Task.Delay(300); + Logger.LogDebug(nameof(TestLoopDetection)); + Assert.Multiple(() => + { + Assert.That(rcRX, Is.Not.Null); + Assert.That(rcTX, Is.Not.Null); + }); + Assert.Multiple(() => + { + Assert.That(rcTX.Ports, Has.Count.EqualTo(1)); + Assert.That(rcRX.Ports, Has.Count.EqualTo(1)); + }); + + var txPort = rcTX.Ports.First(); + var rxPort = rcRX.Ports.First(); + Assert.Multiple(() => + { + Assert.That(txPort.PortType, Is.EqualTo(EPortType.InputToArtNet)); + Assert.That(rxPort.PortType, Is.EqualTo(EPortType.OutputFromArtNet)); + Assert.That(rxPort.OutputPortAddress, Is.EqualTo(portAddress)); + Assert.That(txPort.InputPortAddress, Is.EqualTo(portAddress)); + Assert.That(rxPort.GoodOutput.IsBeingOutputAsDMX, Is.False); + }); + } + +#pragma warning disable CS0618 // Typ oder Element ist veraltet + [Timeout(20000)] +#pragma warning restore CS0618 // Typ oder Element ist veraltet + [Test, Order(2)] + public async Task TestSendDMX() + { + TestContext.Out.WriteLine($"{nameof(TestSendDMX)} [{testSubject.TestLabel}]"); + initialTask ??= init(); + await initialTask; + await Task.Delay(300); + Logger.LogDebug(nameof(TestSendDMX)); + Assert.Multiple(() => + { + Assert.That(rcRX, Is.Not.Null); + Assert.That(rcTX, Is.Not.Null); + }); + + byte[] data = new byte[512]; + bool receiveFlag = false; + + Assert.That(rcRX.Ports, Has.Count.EqualTo(1), () => + { + var portList = string.Join(", ", rcRX.Ports.Select(p => $"{p.ToString()} [{p.PortType} on PortAddress {p.OutputPortAddress}]")); + return $"rcRX.Ports.Count != 2. Aktuelle Ports: [{portList}]"; + }); + var rxPort = rcRX.Ports.First(p => p.OutputPortAddress.Equals(portAddress)); + + Assert.Multiple(() => + { + Assert.That(rxPort.GoodOutput.ConvertFrom, Is.EqualTo(GoodOutput.EConvertFrom.ArtNet)); + Assert.That(rxPort.GoodOutput.DMX_OutputShortCircuit, Is.False); + Assert.That(rxPort.GoodOutput.IsBeingOutputAsDMX, Is.False); + }); + + instanceRX.DMXReceived += InstanceRX_DMXReceived; + for (byte b = 0; b <= 10; b++) + await doDmxStuff(b); + + instanceRX.DMXReceived -= InstanceRX_DMXReceived; + + Assert.Multiple(() => + { + Assert.That(rxPort.GoodOutput.ConvertFrom, Is.EqualTo(GoodOutput.EConvertFrom.ArtNet)); + Assert.That(rxPort.GoodOutput.DMX_OutputShortCircuit, Is.False); + }); + + + async Task doDmxStuff(byte value) + { + if (data[0] != value) + for (ushort i = 0; i < 512; i++) + data[i] = value; + + receiveFlag = false; + instanceTX.WriteDMXValues(portAddress, data); + while (!receiveFlag) + await Task.Delay(30); + + string str = $"Error at {data[0]}"; + Assert.Multiple(() => + { + Assert.That(rxPort, Is.SameAs(rcRX.Ports.First(p => p.OutputPortAddress.Equals(portAddress)))); + Assert.That(rxPort.GoodOutput.IsBeingOutputAsDMX, Is.True, str); + Assert.That(instanceRX.GetReceivedDMX(portAddress), Is.EqualTo(data), str); + }); + await Task.Delay(100); + } + void InstanceRX_DMXReceived(object? sender, PortAddress e) + { + if (e != portAddress) + return; + + Assert.That(e, Is.EqualTo(portAddress)); + Task.Run(async () => + { + while (rxPort.GoodOutput.IsBeingOutputAsDMX == false) + { + Assert.That(rxPort, Is.SameAs(rcRX.Ports.First(p => p.OutputPortAddress.Equals(portAddress)))); + await Task.Delay(10); // wait for ArtPoll and ArtPollReply to update the data of ArtPollReply-Cache + } + receiveFlag = true; + }, new CancellationTokenSource(3500).Token); + } + } + +#pragma warning disable CS0618 // Typ oder Element ist veraltet + [Timeout(20000)] +#pragma warning restore CS0618 // Typ oder Element ist veraltet + [Test, Order(3)] + public async Task TestSendDMXTiming() + { + TestContext.Out.WriteLine($"{nameof(TestSendDMXTiming)} [{testSubject.TestLabel}]"); + initialTask ??= init(); + await initialTask; + await Task.Delay(300); + Logger.LogDebug(nameof(TestSendDMXTiming)); + //if(ArtNetSharp.Tools.IsRunningOnGithubWorker()) + // Assert.Ignore("Skiped, only run on Linux"); + + DateTime startTime = DateTime.UtcNow; + Assert.Multiple(() => + { + Assert.That(rcRX, Is.Not.Null); + Assert.That(rcTX, Is.Not.Null); + }); + Stopwatch swDMX = new Stopwatch(); + Stopwatch swSync = new Stopwatch(); + List refreshRate = new List(); + List syncRate = new List(); + byte[] data = new byte[100]; + bool receivedFlag = false; + bool syncFlag = false; + bool done = false; + + swSync.Start(); + swDMX.Start(); + swSync.Stop(); + swDMX.Stop(); + + Random rnd = new Random(); + instanceRX.DMXReceived += (o, e) => + { + swDMX.Stop(); + receivedFlag = true; + if (done) + return; + if (swDMX.Elapsed.TotalMilliseconds != 0) + refreshRate.Add(swDMX.Elapsed.TotalMilliseconds); + }; + instanceRX.SyncReceived += async (o, e) => + { + swSync.Stop(); + syncFlag = true; + if (done) + return; + syncRate.Add(swSync.Elapsed.TotalMilliseconds); + await nextData(); + }; + _ = nextData(); + async Task nextData() + { + await Task.Delay(5); + if ((DateTime.UtcNow - startTime).TotalSeconds >= 5) + { + done = true; + check(); + return; + } + + rnd.NextBytes(data); + instanceTX.WriteDMXValues(portAddress, data); + swDMX.Restart(); + swSync.Restart(); + + } + void check() + { + var sync = 1000.0 / syncRate.Average(); + var dmx = 1000.0 / refreshRate.Average(); + Logger.LogDebug($"Sync: {syncRate.Average()}ms, DMX: {refreshRate.Average()}ms, SyncRate: {sync}, DMXRate: {dmx}"); + var targetRate = 40; + if (ArtNetSharp.Tools.IsRunningOnGithubWorker()) + targetRate = 30;// bacuse the worker have not enougth hoursepower to run 40Hz + Assert.Multiple(() => + { + Assert.That(syncFlag, Is.True); + Assert.That(receivedFlag, Is.True); + Assert.That(sync, Is.AtLeast(targetRate)); + Assert.That(dmx, Is.AtLeast(targetRate)); + }); + } + + await Task.Delay(4500); + while (!done) + await Task.Delay(100); + } + } +} \ No newline at end of file diff --git a/ArtNetTests/Mocks/ControllerInstanceMock.cs b/ArtNetTests/Mocks/ControllerInstanceMock.cs index 5c57d4f..ccf2a14 100644 --- a/ArtNetTests/Mocks/ControllerInstanceMock.cs +++ b/ArtNetTests/Mocks/ControllerInstanceMock.cs @@ -12,6 +12,7 @@ public override ushort OEMProductCode { get { return this._oemProductCode; } } + public override ushort ESTAManufacturerCode => (ushort)Tools.ParseDotNetMajorVersion(); public override UID UID => new UID((ushort)EManufacturer.DMXControlProjects_eV, 12314); public ControllerInstanceMock(ArtNet artnet, ushort oemProductCode = Constants.DEFAULT_OEM_CODE) : base(artnet) diff --git a/ArtNetTests/Mocks/Instances/NodeMock.cs b/ArtNetTests/Mocks/Instances/NodeMock.cs index 5872bef..2481727 100644 --- a/ArtNetTests/Mocks/Instances/NodeMock.cs +++ b/ArtNetTests/Mocks/Instances/NodeMock.cs @@ -8,6 +8,7 @@ internal class NodeMock : NodeInstance public NodeMock(ArtNet _artnet) : base(_artnet) { } + public override ushort ESTAManufacturerCode => (ushort)Tools.ParseDotNetMajorVersion(); protected override bool SendArtData => true; protected override string UrlProduct => "https://github.com/DMXControl/ArtNetSharp"; diff --git a/ArtNetTests/Mocks/NodeInstanceMock.cs b/ArtNetTests/Mocks/NodeInstanceMock.cs new file mode 100644 index 0000000..56b177b --- /dev/null +++ b/ArtNetTests/Mocks/NodeInstanceMock.cs @@ -0,0 +1,20 @@ +using ArtNetSharp; +using ArtNetSharp.Communication; + +namespace ArtNetTests.Mocks +{ + internal class NodeInstanceMock : NodeInstance + { + private readonly ushort _oemProductCode; + public override ushort OEMProductCode + { + get { return this._oemProductCode; } + } + public override ushort ESTAManufacturerCode => (ushort)Tools.ParseDotNetMajorVersion(); + + public NodeInstanceMock(ArtNet artnet, ushort oemProductCode = Constants.DEFAULT_OEM_CODE) : base(artnet) + { + _oemProductCode = oemProductCode; + } + } +} diff --git a/ArtNetTests/ObjectTypesTests.cs b/ArtNetTests/ObjectTypesTests.cs index a1c43f3..1d1708b 100644 --- a/ArtNetTests/ObjectTypesTests.cs +++ b/ArtNetTests/ObjectTypesTests.cs @@ -243,27 +243,45 @@ public void TestGoodOutput() foreach (GoodOutput goodOutput in subjects) { GoodOutput result = new GoodOutput(goodOutput.Byte1, goodOutput.Byte2); - Assert.Multiple(() => + test(); + result = new GoodOutput( + goodOutput.ConvertFrom, + goodOutput.MergeMode, + goodOutput.DMX_OutputShortCircuit, + goodOutput.MergingArtNetData, + goodOutput.DMX_TextPacketsSupported, + goodOutput.DMX_SIPsSupported, + goodOutput.DMX_TestPacketsSupported, + goodOutput.IsBeingOutputAsDMX, + goodOutput.OutputStyle, + goodOutput.RDMisDisabled, + goodOutput.DiscoveryIsCurrentlyRunning, + goodOutput.BackgroundDiscoveryIsEnabled); + test(); + void test() { - Assert.That(goodOutput.Byte1, Is.EqualTo(result!.Byte1)); - Assert.That(goodOutput.Byte2, Is.EqualTo(result!.Byte2)); - Assert.That(goodOutput.ConvertFrom, Is.EqualTo(result!.ConvertFrom)); - Assert.That(goodOutput.MergeMode, Is.EqualTo(result!.MergeMode)); - Assert.That(goodOutput.DMX_OutputShortCircuit, Is.EqualTo(result!.DMX_OutputShortCircuit)); - Assert.That(goodOutput.MergingArtNetData, Is.EqualTo(result!.MergingArtNetData)); - Assert.That(goodOutput.DMX_TestPacketsSupported, Is.EqualTo(result!.DMX_TestPacketsSupported)); - Assert.That(goodOutput.DMX_SIPsSupported, Is.EqualTo(result!.DMX_SIPsSupported)); - Assert.That(goodOutput.DMX_TestPacketsSupported2, Is.EqualTo(result!.DMX_TestPacketsSupported2)); - Assert.That(goodOutput.IsBeingOutputAsDMX, Is.EqualTo(result!.IsBeingOutputAsDMX)); - Assert.That(goodOutput.OutputStyle, Is.EqualTo(result!.OutputStyle)); - Assert.That(goodOutput.RDMisDisabled, Is.EqualTo(result!.RDMisDisabled)); - Assert.That(goodOutput.GetHashCode(), Is.EqualTo(result!.GetHashCode())); - Assert.That(goodOutput, Is.EqualTo(result)); - Assert.That(goodOutput == result, Is.True); - Assert.That(goodOutput != result, Is.False); - Assert.That(goodOutput.Equals((object)result), Is.True); - Assert.That(goodOutput.Equals(null), Is.False); - }); + Assert.Multiple(() => + { + Assert.That(goodOutput.Byte1, Is.EqualTo(result!.Byte1)); + Assert.That(goodOutput.Byte2, Is.EqualTo(result!.Byte2)); + Assert.That(goodOutput.ConvertFrom, Is.EqualTo(result!.ConvertFrom)); + Assert.That(goodOutput.MergeMode, Is.EqualTo(result!.MergeMode)); + Assert.That(goodOutput.DMX_OutputShortCircuit, Is.EqualTo(result!.DMX_OutputShortCircuit)); + Assert.That(goodOutput.MergingArtNetData, Is.EqualTo(result!.MergingArtNetData)); + Assert.That(goodOutput.DMX_TextPacketsSupported, Is.EqualTo(result!.DMX_TextPacketsSupported)); + Assert.That(goodOutput.DMX_SIPsSupported, Is.EqualTo(result!.DMX_SIPsSupported)); + Assert.That(goodOutput.DMX_TestPacketsSupported, Is.EqualTo(result!.DMX_TestPacketsSupported)); + Assert.That(goodOutput.IsBeingOutputAsDMX, Is.EqualTo(result!.IsBeingOutputAsDMX)); + Assert.That(goodOutput.OutputStyle, Is.EqualTo(result!.OutputStyle)); + Assert.That(goodOutput.RDMisDisabled, Is.EqualTo(result!.RDMisDisabled)); + Assert.That(goodOutput.GetHashCode(), Is.EqualTo(result!.GetHashCode())); + Assert.That(goodOutput, Is.EqualTo(result)); + Assert.That(goodOutput == result, Is.True); + Assert.That(goodOutput != result, Is.False); + Assert.That(goodOutput.Equals((object)result), Is.True); + Assert.That(goodOutput.Equals(null), Is.False); + }); + } } GoodOutput a = new GoodOutput(0b01010101, 0b10101010); @@ -296,6 +314,106 @@ public void TestGoodOutput() Assert.That(c, Is.EqualTo(a)); c = ~a & b; Assert.That(c, Is.EqualTo(b)); + + a = new GoodOutput(0b00000000, 0b00001111); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(convertFrom: GoodOutput.EConvertFrom.ArtNet); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(convertFrom: GoodOutput.EConvertFrom.sACN); + Assert.That(a.Byte1, Is.EqualTo(0b00000001)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(mergeMode: EMergeMode.HTP); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(mergeMode: EMergeMode.LTP); + Assert.That(a.Byte1, Is.EqualTo(0b00000010)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(dmx_OutputShortCircuit: false); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(dmx_OutputShortCircuit: true); + Assert.That(a.Byte1, Is.EqualTo(0b00000100)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(mergingArtNetData: false); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(mergingArtNetData: true); + Assert.That(a.Byte1, Is.EqualTo(0b00001000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(dmx_TextPacketsSupported: false); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(dmx_TextPacketsSupported: true); + Assert.That(a.Byte1, Is.EqualTo(0b00010000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(dmx_SIPsSupported: false); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(dmx_SIPsSupported: true); + Assert.That(a.Byte1, Is.EqualTo(0b00100000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(dmx_TestPacketsSupported: false); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(dmx_TestPacketsSupported: true); + Assert.That(a.Byte1, Is.EqualTo(0b01000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(isBeingOutputAsDMX: false); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(isBeingOutputAsDMX: true); + Assert.That(a.Byte1, Is.EqualTo(0b10000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + + a = new GoodOutput(outputStyle: GoodOutput.EOutputStyle.Delta); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(outputStyle: GoodOutput.EOutputStyle.Continuous); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b01000000)); + + a = new GoodOutput(rdmIsDisabled: false); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(rdmIsDisabled: true); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b10000000)); + + a = new GoodOutput(discoveryIsCurrentlyRunning: false); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(discoveryIsCurrentlyRunning: true); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00100000)); + + a = new GoodOutput(backgroundDiscoveryIsEnabled: false); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00000000)); + + a = new GoodOutput(backgroundDiscoveryIsEnabled: true); + Assert.That(a.Byte1, Is.EqualTo(0b00000000)); + Assert.That(a.Byte2, Is.EqualTo(0b00010000)); } [Test] public void TestGoodInput() diff --git a/ArtNetTests/TestOS.cs b/ArtNetTests/TestOS.cs index ef487f7..5aae5f3 100644 --- a/ArtNetTests/TestOS.cs +++ b/ArtNetTests/TestOS.cs @@ -34,11 +34,12 @@ public void SetUp() } } [OneTimeTearDown] - public void OneTimeTearDown() + public async Task OneTimeTearDown() { if (artNet != null) ((IDisposable)artNet).Dispose(); Trace.Flush(); + await Task.Delay(6500); } [Test, Order(101)] @@ -82,6 +83,14 @@ public async Task TestOnMacOS() private async Task doTests() { + foreach (var nic in artNet.NetworkClients) + { + var str = $"NIC: {nic.LocalIpAddress}"; + Console.WriteLine(str); + Debug.WriteLine(str); + if (nic.BroadcastIpAddress != System.Net.IPAddress.Loopback) + nic.Enabled = false; + } artNet.AddInstance(nodeInstance); artNet.AddInstance(controllerInstance); diff --git a/ArtNetTests/Tools.cs b/ArtNetTests/Tools.cs index 96ed3da..361e02c 100644 --- a/ArtNetTests/Tools.cs +++ b/ArtNetTests/Tools.cs @@ -1,4 +1,5 @@ using System.Net.NetworkInformation; +using System.Runtime.InteropServices; namespace ArtNetTests { @@ -30,5 +31,17 @@ internal static Tuple[] GetIpAddresses() return list.ToArray(); } + + internal static int ParseDotNetMajorVersion() + { + // Beispiel: ".NET 8.0.0" + var parts = RuntimeInformation.FrameworkDescription.Split(' '); + if (parts.Length >= 2 && Version.TryParse(parts[1], out var version)) + { + return version.Major; + } + + return -1; // oder Fehler werfen + } } }