refactor: Change CustomActivate and Initialize methods to protected access in multiple classes for better inheritance control

This commit is contained in:
Neil Dorin 2026-04-08 15:47:58 -06:00
parent e818c9ca03
commit daf9b4bda0
22 changed files with 99 additions and 82 deletions

View file

@ -164,41 +164,23 @@ Global
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x86.ActiveCfg = Release|Any CPU
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x86.Build.0 = Release|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x64.ActiveCfg = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x64.Build.0 = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x86.ActiveCfg = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x86.Build.0 = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x64.ActiveCfg = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x64.Build.0 = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x86.ActiveCfg = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x86.Build.0 = Debug|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|Any CPU.Build.0 = Release|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x64.ActiveCfg = Release|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x64.Build.0 = Release|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x86.ActiveCfg = Release|Any CPU
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x86.Build.0 = Release|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x64.ActiveCfg = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x64.Build.0 = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x86.ActiveCfg = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x86.Build.0 = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x64.ActiveCfg = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x64.Build.0 = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x86.ActiveCfg = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x86.Build.0 = Debug|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|Any CPU.Build.0 = Release|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x64.ActiveCfg = Release|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x64.Build.0 = Release|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x86.ActiveCfg = Release|Any CPU
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -32,7 +32,7 @@ If the `CustomActivate()` method is long, consider breaking it up into many smal
Note: It is best-practice in Essentials to not write arbitrarily-timed startup sequences to ensure that a "system" or room is functional. Rather, we encourage the developer to use various properties and conditions on devices to aggregate together "room is ready" statuses that can trigger further action. This ensures that all devices can be up and alive, allowing them to be debugged within a program that may otherwise be misbehaving - as well as not making users and expensive developers wait for code to start up!
```cs
public override bool CustomActivate()
protected override bool CustomActivate()
{
Debug.Console(0, this, "Final activation. Setting up actions and feedbacks");
SetupFunctions();
@ -52,7 +52,7 @@ We can see in the example below that during the `CustomActivate()` phase, we def
### **Example**
```cs
public override bool CustomActivate()
protected override bool CustomActivate()
{
foreach (var i in Config.Inputs)
{
@ -115,7 +115,7 @@ The main task that should be undertaken in the `Initialize()` method for any 3rd
### Example (from `PepperDash.Essentials.Devices.Common.VideoCodec.Cisco.CiscoSparkCodec`)
```cs
public override void Initialize()
protected override void Initialize()
{
var socket = Communication as ISocketStatus;
if (socket != null)

View file

@ -57,7 +57,7 @@ public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor
AddPostActivationAction(RegisterEisc);
}
public override bool CustomActivate()
protected override bool CustomActivate()
{
CommunicationMonitor.Start();
return base.CustomActivate();

View file

@ -57,7 +57,7 @@ namespace PepperDash.Essentials.Core;
/// CustomActivate method
/// </summary>
/// <inheritdoc />
public override bool CustomActivate()
protected override bool CustomActivate()
{
Communication.Connect();
CommunicationMonitor.StatusChange += (o, a) => { Debug.LogMessage(LogEventLevel.Verbose, this, "Communication monitor state: {0}", CommunicationMonitor.Status); };

View file

@ -87,7 +87,7 @@ namespace PepperDash.Essentials.Core;
/// CustomActivate method
/// </summary>
/// <inheritdoc />
public override bool CustomActivate()
protected override bool CustomActivate()
{
Debug.LogMessage(LogEventLevel.Information, this, "Activating");
if (!PreventRegistration)

View file

@ -36,7 +36,7 @@ namespace PepperDash.Essentials.Core
/// Make sure that overriding classes call this!
/// Registers the Crestron device, connects up to the base events, starts communication monitor
/// </summary>
public override bool CustomActivate()
protected override bool CustomActivate()
{
Debug.LogMessage(LogEventLevel.Information, this, "Activating");
var response = Hardware.RegisterWithLogging(Key);

View file

@ -87,7 +87,7 @@ public abstract class EssentialsDevice : Device
/// Override this method to perform any initialization that requires all devices to be activated. This method is called automatically after the DeviceManager.AllDevicesActivated event is fired, and should not be called directly.
/// </summary>
/// <returns></returns>
public override bool CustomActivate()
protected override bool CustomActivate()
{
CreateMobileControlMessengers();

View file

@ -63,7 +63,7 @@ namespace PepperDash.Essentials.Core.Devices;
/// CustomActivate method
/// </summary>
/// <inheritdoc />
public override bool CustomActivate()
protected override bool CustomActivate()
{
CommunicationMonitor.Start();
return true;

View file

@ -309,7 +309,7 @@ namespace PepperDash.Essentials.Core.Fusion
}
/// <inheritdoc />
public override void Initialize()
protected override void Initialize()
{
GenerateGuidFile(GetGuidFilePath(_config.IpIdInt));
@ -501,12 +501,6 @@ namespace PepperDash.Essentials.Core.Fusion
// Moved to
CurrentRoomSourceNameSig = FusionRoom.CreateOffsetStringSig(JoinMap.Display1CurrentSourceName.JoinNumber, JoinMap.Display1CurrentSourceName.AttributeName,
eSigIoMask.InputSigOnly);
// Don't think we need to get current status of this as nothing should be alive yet.
if (Room is IHasCurrentSourceInfoChange hasCurrentSourceInfoChange)
{
hasCurrentSourceInfoChange.CurrentSourceChange += Room_CurrentSourceInfoChange;
}
FusionRoom.SystemPowerOn.OutputSig.SetSigFalseAction(Room.PowerOnToDefaultOrLastSource);
FusionRoom.SystemPowerOff.OutputSig.SetSigFalseAction(() =>
@ -1749,38 +1743,6 @@ namespace PepperDash.Essentials.Core.Fusion
return Convert.ToInt32(capture.Groups[1].Value);
}
/// <summary>
/// Event handler for when room source changes
/// </summary>
protected void Room_CurrentSourceInfoChange(SourceListItem info, ChangeType type)
{
// Handle null. Nothing to do when switching from or to null
if (info == null || info.SourceDevice == null)
{
return;
}
if (info.SourceDevice is Device dev)
{
if (type == ChangeType.WillChange)
{
if (_sourceToFeedbackSigs.ContainsKey(dev))
{
_sourceToFeedbackSigs[dev].BoolValue = false;
}
}
else
{
if (_sourceToFeedbackSigs.ContainsKey(dev))
{
_sourceToFeedbackSigs[dev].BoolValue = true;
}
//var name = (room == null ? "" : room.Name);
CurrentRoomSourceNameSig.InputSig.StringValue = dev.Name;
}
}
}
/// <summary>
/// Event handler for Fusion state changes
/// </summary>

View file

@ -69,7 +69,7 @@ public class MicrophonePrivacyController : EssentialsDevice
Inputs = new List<IDigitalInput>();
}
public override bool CustomActivate()
protected override bool CustomActivate()
{
foreach (var i in Config.Inputs)
{
@ -105,7 +105,7 @@ public class MicrophonePrivacyController : EssentialsDevice
#region Overrides of Device
public override void Initialize()
protected override void Initialize()
{
CheckPrivacyMode();
}

View file

@ -168,6 +168,20 @@ public class SystemMonitorController : EssentialsBridgeableDevice
_uptimePollTimer = null;
}
/// <summary>
/// Polls the uptime and last start time from the control system and updates the feedbacks
/// This is necessary because there is no event that is fired when these values change, so we have to poll for them
/// at a regular interval
/// Uptime is also polled on activation to get initial values
/// Uptime is polled every 5 minutes (300000 ms) which should be often enough to keep the values updated without causing performance issues
/// Note: polling uptime can cause a delay in the feedback update, so it is done in a separate thread to avoid blocking the main thread
/// Note: this method uses CrestronConsole.SendControlSystemCommand which can be slow, so it is not recommended to call this method more often than every 5 minutes
/// Note: this method will not work on a server as the "uptime" command is not available, but it will not cause any issues either as it will just return an empty string
/// Note: if you need more real-time uptime updates, you could consider implementing a custom solution that tracks uptime internally and updates the feedbacks accordingly, but this would require more complex implementation and testing
/// Note: if you implement a custom solution for tracking uptime, you should still consider polling the uptime from the control system at a regular interval (e.g. every hour) to ensure that your internal tracking is accurate and to account for any potential issues that may arise with your custom implementation
/// Note: if you implement a custom solution for tracking uptime, you should also consider implementing a way to reset the uptime (e.g. on program start) to ensure that it reflects the actual uptime of the control system accurately
/// </summary>
/// <param name="obj"></param>
public void PollUptime(object obj)
{
var consoleResponse = string.Empty;
@ -306,13 +320,15 @@ public class SystemMonitorController : EssentialsBridgeableDevice
}
}
public override bool CustomActivate()
/// <inheritdoc />
protected override bool CustomActivate()
{
RefreshSystemMonitorData();
return base.CustomActivate();
}
/// <inheritdoc />
public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{
var joinMap = new SystemMonitorJoinMap(joinStart);
@ -675,6 +691,9 @@ public class SystemMonitorController : EssentialsBridgeableDevice
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, adapterIndex));
}
/// <summary>
/// Updates all the ethernet status feedbacks for this interface
/// </summary>
public void UpdateEthernetStatus()
{
HostNameFeedback.FireUpdate();
@ -692,26 +711,77 @@ public class SystemMonitorController : EssentialsBridgeableDevice
}
}
/// <summary>
/// Represents a collection of feedbacks related to the status of a program running on the control system, including its operating state, registration state, and various pieces of information about the program such as its name, compile time, and environment. This class also includes methods for retrieving program information and responding to program change events to keep the feedbacks updated in real-time.
/// </summary>
public class ProgramStatusFeedbacks
{
/// <summary>
/// Event that is fired when any of the program information properties change, allowing external classes to respond to changes in program information in real-time
/// </summary>
public event EventHandler<ProgramInfoEventArgs> ProgramInfoChanged;
/// <summary>
/// Gets or sets the Program associated with this collection of feedbacks
/// </summary>
public Program Program;
/// <summary>
/// Gets or sets the ProgramInfo object that contains detailed information about the program, such as its file name, compile time, environment, and other relevant properties. This object is updated whenever there is a change in the program's operating state or registration state, ensuring that the feedbacks always reflect the most current information about the program.
/// </summary>
public ProgramInfo ProgramInfo { get; set; }
/// <summary>
/// Gets or sets the ProgramStartedFeedback, which is a BoolFeedback that indicates whether the program is currently started (true) or not (false). This feedback is updated in real-time based on the program's operating state, allowing external classes to easily monitor whether the program is running or not.
/// </summary>
public BoolFeedback ProgramStartedFeedback;
/// <summary>
/// Gets or sets the ProgramStoppedFeedback, which is a BoolFeedback that indicates whether the program is currently stopped (true) or not (false). This feedback is updated in real-time based on the program's operating state, allowing external classes to easily monitor whether the program is stopped or not.
/// </summary>
public BoolFeedback ProgramStoppedFeedback;
/// <summary>
/// Gets or sets the ProgramRegisteredFeedback, which is a BoolFeedback that indicates whether the program is currently registered (true) or not (false). This feedback is updated in real-time based on the program's registration state, allowing external classes to easily monitor whether the program is registered or not.
/// </summary>
public BoolFeedback ProgramRegisteredFeedback;
/// <summary>
/// Gets or sets the ProgramUnregisteredFeedback, which is a BoolFeedback that indicates whether the program is currently unregistered (true) or not (false). This feedback is updated in real-time based on the program's registration state, allowing external classes to easily monitor whether the program is unregistered or not.
/// </summary>
public BoolFeedback ProgramUnregisteredFeedback;
/// <summary>
/// Gets or sets the ProgramNameFeedback, which is a StringFeedback that provides the name of the program file. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current name of the program file.
/// </summary>
public StringFeedback ProgramNameFeedback;
/// <summary>
/// Gets or sets the ProgramCompileTimeFeedback, which is a StringFeedback that provides the compile time of the program. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current compile time of the program.
/// </summary>
public StringFeedback ProgramCompileTimeFeedback;
/// <summary>
/// Gets or sets the CrestronDataBaseVersionFeedback, which is a StringFeedback that provides the version of the Crestron database used by the program. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current version of the Crestron database used by the program.
/// </summary>
public StringFeedback CrestronDataBaseVersionFeedback;
// SIMPL windows version
/// <summary>
/// Gets or sets the EnvironmentVersionFeedback, which is a StringFeedback that provides the environment version of the program. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current environment version of the program.
/// </summary>
public StringFeedback EnvironmentVersionFeedback;
/// <summary>
/// Gets or sets the AggregatedProgramInfoFeedback, which is a StringFeedback that provides a JSON serialized string of the entire ProgramInfo object. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access all current information about the program in a single feedback.
/// </summary>
public StringFeedback AggregatedProgramInfoFeedback;
/// <summary>
/// Constructor for the ProgramStatusFeedbacks class, which initializes all feedbacks based on the provided Program object and retrieves the initial program information to populate the feedbacks with accurate data. This constructor also sets up the necessary event handlers to ensure that the feedbacks are updated in real-time as changes occur to the program's operating state or registration state.
/// </summary>
/// <param name="program"></param>
public ProgramStatusFeedbacks(Program program)
{
ProgramInfo = new ProgramInfo(program.Number);
@ -851,6 +921,9 @@ public class ProgramStatusFeedbacks
OnProgramInfoChanged();
}
/// <summary>
/// Fires the ProgramInfoChanged event to notify external classes that they should update any properties related to program information based on changes to the program's operating state or registration state. This method is called whenever there is a change in the program's information, ensuring that all feedbacks and external classes that rely on program information are always up-to-date with the most current information about the program.
/// </summary>
public void OnProgramInfoChanged()
{
//Debug.LogMessage(LogEventLevel.Debug, "Firing ProgramInfoChanged for slot: {0}", Program.Number);

View file

@ -75,7 +75,7 @@ public class RoomOnToDefaultSourceWhenOccupied : ReconfigurableDevice
});
}
public override bool CustomActivate()
protected override bool CustomActivate()
{
SetUpDevice();

View file

@ -250,7 +250,7 @@ public abstract class EssentialsRoomBase : ReconfigurableDevice, IEssentialsRoom
});
}
public override bool CustomActivate()
protected override bool CustomActivate()
{
SetUpMobileControl();

View file

@ -38,7 +38,7 @@ public class RetriggerableTimer : EssentialsDevice
}
/// <inheritdoc />
public override bool CustomActivate()
protected override bool CustomActivate()
{
if (_propertiesConfig.StartTimerOnActivation)
{

View file

@ -208,7 +208,7 @@ public class EssentialsWebApi : EssentialsDevice
/// <summary>
/// Initializes the CWS class
/// </summary>
public override void Initialize()
protected override void Initialize()
{
AddRoute(new HttpCwsRoute("apiPaths")
{

View file

@ -182,7 +182,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
/// CustomActivate method
/// </summary>
/// <inheritdoc />
public override bool CustomActivate()
protected override bool CustomActivate()
{
//Create ISwitchedOutput objects based on props
switch (Mode)

View file

@ -45,7 +45,7 @@ public class RelayControlledShade : ShadeBase, IShadesOpenCloseStop
}
/// <inheritdoc />
public override bool CustomActivate()
protected override bool CustomActivate()
{
//Create ISwitchedOutput objects based on props
OpenRelay = GetSwitchedOutputFromDevice(Config.Relays.Open);

View file

@ -34,7 +34,7 @@ public class ShadeController : EssentialsDevice, IShades
}
/// <inheritdoc />
public override bool CustomActivate()
protected override bool CustomActivate()
{
foreach (var shadeConfig in Config.Shades)
{

View file

@ -1283,7 +1283,7 @@ namespace PepperDash.Essentials
}
/// <inheritdoc />
public override void Initialize()
protected override void Initialize()
{
if (!Config.EnableMessengerSubscriptions)
{

View file

@ -141,7 +141,7 @@ namespace PepperDash.Essentials.Room.MobileControl
/// <summary>
/// CustomActivate method
/// </summary>
public override bool CustomActivate()
protected override bool CustomActivate()
{
this.LogDebug("Final activation. Setting up actions and feedbacks");
//SetupFunctions();

View file

@ -364,7 +364,7 @@ namespace PepperDash.Essentials.Touchpanel
/// <summary>
/// CustomActivate method
/// </summary>
public override bool CustomActivate()
protected override bool CustomActivate()
{
var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this);

View file

@ -261,7 +261,7 @@ namespace PepperDash.Essentials.WebSocketServer
/// Initialize method
/// </summary>
/// <inheritdoc />
public override void Initialize()
protected override void Initialize()
{
try
{
@ -276,7 +276,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
ClientCertificateRequired = false,
CheckCertificateRevocation = false,
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
};
}