mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-04-20 07:56:50 +00:00
feat: Enhance device testing and environment handling with new interfaces and fakes
This commit is contained in:
parent
24df4e7a03
commit
37961b9eac
11 changed files with 421 additions and 33 deletions
|
|
@ -29,6 +29,13 @@ public interface ICrestronEnvironment
|
|||
|
||||
/// <summary>Gets the application root directory path.</summary>
|
||||
string GetApplicationRootDirectory();
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> when running on real Crestron hardware.
|
||||
/// Returns <c>false</c> in test / dev environments so that SDK-specific
|
||||
/// sinks and enrichers can be safely skipped.
|
||||
/// </summary>
|
||||
bool IsHardwareRuntime { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
323
src/PepperDash.Core.Tests/Devices/DeviceTests.cs
Normal file
323
src/PepperDash.Core.Tests/Devices/DeviceTests.cs
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
using FluentAssertions;
|
||||
using PepperDash.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace PepperDash.Core.Tests.Devices;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="Device"/> — the base class for all PepperDash devices.
|
||||
/// These run without Crestron hardware; Debug is initialized with fakes via TestInitializer.
|
||||
/// </summary>
|
||||
public class DeviceTests
|
||||
{
|
||||
// -----------------------------------------------------------------------
|
||||
// Construction
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SingleArg_SetsKey()
|
||||
{
|
||||
var device = new Device("my-device");
|
||||
|
||||
device.Key.Should().Be("my-device");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SingleArg_SetsNameToEmpty()
|
||||
{
|
||||
var device = new Device("my-device");
|
||||
|
||||
device.Name.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_TwoArg_SetsKeyAndName()
|
||||
{
|
||||
var device = new Device("my-device", "My Device");
|
||||
|
||||
device.Key.Should().Be("my-device");
|
||||
device.Name.Should().Be("My Device");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_KeyWithDot_StillSetsKey()
|
||||
{
|
||||
// The dot triggers a debug log warning but must not prevent construction.
|
||||
var device = new Device("parent.child");
|
||||
|
||||
device.Key.Should().Be("parent.child");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ToString
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ToString_WithName_FormatsKeyDashName()
|
||||
{
|
||||
var device = new Device("cam-01", "Front Camera");
|
||||
|
||||
device.ToString().Should().Be("cam-01 - Front Camera");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_WithoutName_UsesDashPlaceholder()
|
||||
{
|
||||
var device = new Device("cam-01");
|
||||
|
||||
device.ToString().Should().Be("cam-01 - ---");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// DefaultDevice
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void DefaultDevice_IsNotNull()
|
||||
{
|
||||
Device.DefaultDevice.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultDevice_HasKeyDefault()
|
||||
{
|
||||
Device.DefaultDevice.Key.Should().Be("Default");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// CustomActivate / Activate / Deactivate / Initialize
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void CustomActivate_DefaultReturnTrue()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
|
||||
device.CallCustomActivate().Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deactivate_DefaultReturnsTrue()
|
||||
{
|
||||
var device = new Device("d1");
|
||||
|
||||
device.Deactivate().Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Activate_CallsCustomActivate_AndReturnsItsResult()
|
||||
{
|
||||
var stub = new ActivateTrackingDevice("d1", result: false);
|
||||
|
||||
stub.Activate().Should().BeFalse();
|
||||
stub.CustomActivateCalled.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Activate_TrueWhenCustomActivateReturnsTrue()
|
||||
{
|
||||
var stub = new ActivateTrackingDevice("d1", result: true);
|
||||
|
||||
stub.Activate().Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_DoesNotThrow()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var act = () => device.CallInitialize();
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// PreActivate
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void PreActivate_NoActions_DoesNotThrow()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var act = () => device.PreActivate();
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreActivate_RunsRegisteredActionsInOrder()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var order = new List<int>();
|
||||
|
||||
device.AddPreActivationAction(() => order.Add(1));
|
||||
device.AddPreActivationAction(() => order.Add(2));
|
||||
device.AddPreActivationAction(() => order.Add(3));
|
||||
|
||||
device.PreActivate();
|
||||
|
||||
order.Should().Equal(1, 2, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreActivate_ContinuesAfterFaultingAction()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var reached = false;
|
||||
|
||||
device.AddPreActivationAction(() => throw new InvalidOperationException("boom"));
|
||||
device.AddPreActivationAction(() => reached = true);
|
||||
|
||||
var act = () => device.PreActivate();
|
||||
|
||||
act.Should().NotThrow("exceptions in individual actions must be caught internally");
|
||||
reached.Should().BeTrue("actions after a faulting action must still run");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// PostActivate
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void PostActivate_NoActions_DoesNotThrow()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var act = () => device.PostActivate();
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PostActivate_RunsRegisteredActionsInOrder()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var order = new List<int>();
|
||||
|
||||
device.AddPostActivationAction(() => order.Add(1));
|
||||
device.AddPostActivationAction(() => order.Add(2));
|
||||
|
||||
device.PostActivate();
|
||||
|
||||
order.Should().Equal(1, 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PostActivate_ContinuesAfterFaultingAction()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var reached = false;
|
||||
|
||||
device.AddPostActivationAction(() => throw new Exception("boom"));
|
||||
device.AddPostActivationAction(() => reached = true);
|
||||
|
||||
var act = () => device.PostActivate();
|
||||
|
||||
act.Should().NotThrow();
|
||||
reached.Should().BeTrue();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Pre and Post actions are independent lists
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void PreActivationActions_DoNotRunOnPostActivate()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var preRan = false;
|
||||
|
||||
device.AddPreActivationAction(() => preRan = true);
|
||||
device.PostActivate();
|
||||
|
||||
preRan.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PostActivationActions_DoNotRunOnPreActivate()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var postRan = false;
|
||||
|
||||
device.AddPostActivationAction(() => postRan = true);
|
||||
device.PreActivate();
|
||||
|
||||
postRan.Should().BeFalse();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// OnFalse
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void OnFalse_FiresAction_WhenBoolIsFalse()
|
||||
{
|
||||
var device = new Device("d1");
|
||||
var fired = false;
|
||||
|
||||
device.OnFalse(false, () => fired = true);
|
||||
|
||||
fired.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnFalse_DoesNotFireAction_WhenBoolIsTrue()
|
||||
{
|
||||
var device = new Device("d1");
|
||||
var fired = false;
|
||||
|
||||
device.OnFalse(true, () => fired = true);
|
||||
|
||||
fired.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnFalse_DoesNotFireAction_ForNonBoolType()
|
||||
{
|
||||
var device = new Device("d1");
|
||||
var fired = false;
|
||||
|
||||
device.OnFalse("not a bool", () => fired = true);
|
||||
device.OnFalse(0, () => fired = true);
|
||||
device.OnFalse(null!, () => fired = true);
|
||||
|
||||
fired.Should().BeFalse();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Exposes protected Device members so test methods can call them directly.
|
||||
/// </summary>
|
||||
private class TestDevice : Device
|
||||
{
|
||||
public TestDevice(string key) : base(key) { }
|
||||
public TestDevice(string key, string name) : base(key, name) { }
|
||||
|
||||
public void AddPreActivationAction(Action act) => base.AddPreActivationAction(act);
|
||||
public void AddPostActivationAction(Action act) => base.AddPostActivationAction(act);
|
||||
public bool CallCustomActivate() => base.CustomActivate();
|
||||
public void CallInitialize() => base.Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records whether CustomActivate was invoked and returns a configured result.
|
||||
/// Used to verify Activate() correctly delegates to CustomActivate().
|
||||
/// </summary>
|
||||
private sealed class ActivateTrackingDevice : Device
|
||||
{
|
||||
private readonly bool _result;
|
||||
public bool CustomActivateCalled { get; private set; }
|
||||
|
||||
public ActivateTrackingDevice(string key, bool result = true) : base(key)
|
||||
{
|
||||
_result = result;
|
||||
}
|
||||
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
CustomActivateCalled = true;
|
||||
return _result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,9 @@ public class FakeCrestronEnvironment : ICrestronEnvironment
|
|||
|
||||
public string GetApplicationRootDirectory() => System.IO.Path.GetTempPath();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsHardwareRuntime => false;
|
||||
|
||||
/// <summary>Simulates a program status event for tests.</summary>
|
||||
public void RaiseProgramStatus(ProgramStatusEventType type) =>
|
||||
ProgramStatusChanged?.Invoke(this, new ProgramStatusEventArgs(type));
|
||||
|
|
|
|||
|
|
@ -21,5 +21,9 @@
|
|||
<!-- Reference the Abstractions project only — no Crestron SDK dependency -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
|
||||
<!-- PepperDash.Core is referenced so we can test Device and other concrete types.
|
||||
DebugServiceRegistration.Register() is called via a [ModuleInitializer] before any
|
||||
test runs, so Debug's static constructor uses fakes and never touches the Crestron SDK. -->
|
||||
<ProjectReference Include="..\PepperDash.Core\PepperDash.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
29
src/PepperDash.Core.Tests/TestInitializer.cs
Normal file
29
src/PepperDash.Core.Tests/TestInitializer.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using PepperDash.Core.Abstractions;
|
||||
using PepperDash.Core.Tests.Fakes;
|
||||
|
||||
namespace PepperDash.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Runs once before any type in this assembly is accessed.
|
||||
/// Registers fake Crestron service implementations with <see cref="DebugServiceRegistration"/>
|
||||
/// so that the <c>Debug</c> static constructor uses them instead of the real Crestron SDK.
|
||||
/// This must remain a module initializer (not a test fixture) because the static constructor
|
||||
/// fires the first time <em>any</em> type in PepperDash.Core is referenced — before xUnit
|
||||
/// has a chance to run fixture setup code.
|
||||
/// </summary>
|
||||
internal static class TestInitializer
|
||||
{
|
||||
[ModuleInitializer]
|
||||
internal static void Initialize()
|
||||
{
|
||||
DebugServiceRegistration.Register(
|
||||
new FakeCrestronEnvironment
|
||||
{
|
||||
DevicePlatform = DevicePlatform.Server, // avoids any appliance-only code paths
|
||||
RuntimeEnvironment = RuntimeEnvironment.Other, // skips console command registration
|
||||
},
|
||||
new NoOpCrestronConsole(),
|
||||
new InMemoryCrestronDataStore());
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,9 @@ public sealed class CrestronEnvironmentAdapter : PdCore.ICrestronEnvironment
|
|||
public string GetApplicationRootDirectory() =>
|
||||
Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsHardwareRuntime => true;
|
||||
|
||||
private static PdCore.ProgramStatusEventType MapProgramStatus(eProgramStatusEventType type) =>
|
||||
type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public class Device : IKeyName
|
|||
/// Adds a pre activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
public void AddPreActivationAction(Action act)
|
||||
protected void AddPreActivationAction(Action act)
|
||||
{
|
||||
if (_PreActivationActions == null)
|
||||
_PreActivationActions = new List<Action>();
|
||||
|
|
@ -89,7 +89,7 @@ public class Device : IKeyName
|
|||
/// <summary>
|
||||
/// AddPostActivationAction method
|
||||
/// </summary>
|
||||
public void AddPostActivationAction(Action act)
|
||||
protected void AddPostActivationAction(Action act)
|
||||
{
|
||||
if (_PostActivationActions == null)
|
||||
_PostActivationActions = new List<Action>();
|
||||
|
|
@ -156,7 +156,7 @@ public class Device : IKeyName
|
|||
/// <summary>
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
public virtual bool CustomActivate() { return true; }
|
||||
protected virtual bool CustomActivate() { return true; }
|
||||
|
||||
/// <summary>
|
||||
/// Call to deactivate device - unlink events, etc. Overriding classes do not
|
||||
|
|
@ -168,7 +168,7 @@ public class Device : IKeyName
|
|||
/// <summary>
|
||||
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public static class Debug
|
|||
/// <summary>
|
||||
/// The name of the file containing the current debug settings.
|
||||
/// </summary>
|
||||
public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber);
|
||||
public static string FileName = "app0Debug.json"; // default; updated in static ctor using _environment.ApplicationNumber
|
||||
|
||||
/// <summary>
|
||||
/// Debug level to set for a given program.
|
||||
|
|
@ -161,6 +161,9 @@ public static class Debug
|
|||
|
||||
IsRunningOnAppliance = _environment?.DevicePlatform == PdCore.DevicePlatform.Appliance;
|
||||
|
||||
// Update FileName now that _environment is available (avoids Crestron SDK ref in field initializer).
|
||||
FileName = $"app{_environment?.ApplicationNumber ?? 0}Debug.json";
|
||||
|
||||
_dataStore?.InitStore();
|
||||
|
||||
consoleDebugTimer = new Timer(defaultConsoleDebugTimeoutMin * 60000) { AutoReset = false };
|
||||
|
|
@ -180,7 +183,7 @@ public static class Debug
|
|||
errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
|
||||
fileLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
|
||||
|
||||
websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
|
||||
websocketSink = TryCreateWebsocketSink();
|
||||
|
||||
var appRoot = _environment?.GetApplicationRootDirectory()
|
||||
?? System.IO.Path.GetTempPath();
|
||||
|
|
@ -200,7 +203,6 @@ public static class Debug
|
|||
.MinimumLevel.Verbose()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: consoleLoggingLevelSwitch)
|
||||
.WriteTo.Sink(websocketSink, levelSwitch: websocketLoggingLevelSwitch)
|
||||
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
restrictedToMinimumLevel: LogEventLevel.Debug,
|
||||
|
|
@ -208,8 +210,12 @@ public static class Debug
|
|||
levelSwitch: fileLoggingLevelSwitch
|
||||
);
|
||||
|
||||
// Add Crestron-specific enricher and error-log sink only when running on hardware.
|
||||
if (_environment != null)
|
||||
// Websocket sink is null when DebugWebsocketSink failed to construct (e.g. test env).
|
||||
if (websocketSink != null)
|
||||
_defaultLoggerConfiguration.WriteTo.Sink(websocketSink, levelSwitch: websocketLoggingLevelSwitch);
|
||||
|
||||
// Add Crestron-specific enricher and error-log sink only on real hardware.
|
||||
if (_environment?.IsHardwareRuntime == true)
|
||||
{
|
||||
var errorLogTemplate = IsRunningOnAppliance
|
||||
? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
|
||||
|
|
@ -271,8 +277,34 @@ public static class Debug
|
|||
{
|
||||
// _logger may not have been initialized yet — do not call LogError here.
|
||||
// _console may also be null; fall back to CrestronConsole as last resort.
|
||||
// IMPORTANT: this catch block must not throw — any exception escaping a static
|
||||
// constructor permanently faults the type, making the entire class unusable.
|
||||
try { _console?.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); }
|
||||
catch { CrestronConsole.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); }
|
||||
catch
|
||||
{
|
||||
try { CrestronConsole.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); }
|
||||
catch { /* CrestronConsole unavailable (test/dev env) — swallow to keep type initializer healthy */ }
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Guarantee _logger is never null — all Debug.Log* calls are safe even if the
|
||||
// ctor failed partway through (e.g. on a dev machine without Crestron hardware).
|
||||
_logger ??= new LoggerConfiguration().MinimumLevel.Fatal().CreateLogger();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates the WebSocket sink, returning null if construction fails in a test/dev environment.</summary>
|
||||
private static DebugWebsocketSink? TryCreateWebsocketSink()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
|
||||
}
|
||||
catch
|
||||
{
|
||||
_console?.PrintLine("DebugWebsocketSink could not be created in this environment; websocket logging disabled.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,14 +91,19 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
|||
if (!File.Exists(CertPath))
|
||||
CreateCert();
|
||||
|
||||
try
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
||||
{
|
||||
if (type == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
StopServer();
|
||||
}
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
// CrestronEnvironment is not available in test / dev environments — safe to skip.
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateCert()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,20 +8,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||
/// </summary>
|
||||
public class DeviceStateMessageBase : DeviceMessageBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The interfaces implmented by the device sending the messsage
|
||||
/// </summary>
|
||||
[JsonProperty("interfaces")]
|
||||
public List<string> Interfaces { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the interfaces implemented by the device sending the message
|
||||
/// </summary>
|
||||
/// <param name="interfaces"></param>
|
||||
public void SetInterfaces(List<string> interfaces)
|
||||
{
|
||||
Interfaces = interfaces;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -258,8 +258,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||
throw new ArgumentNullException("device");
|
||||
}
|
||||
|
||||
message.SetInterfaces(_deviceInterfaces);
|
||||
|
||||
message.Key = _device.Key;
|
||||
|
||||
message.Name = _device.Name;
|
||||
|
|
@ -285,8 +283,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||
{
|
||||
try
|
||||
{
|
||||
deviceState.SetInterfaces(_deviceInterfaces);
|
||||
|
||||
deviceState.Key = _device.Key;
|
||||
|
||||
deviceState.Name = _device.Name;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue