From f7b846b1f7b4c030b539dd4dbaf8af20c8415771 Mon Sep 17 00:00:00 2001 From: hvolmer <5054691+hvolmer@users.noreply.github.com> Date: Tue, 11 Feb 2020 12:27:23 -0700 Subject: [PATCH] Created Construction and Activation phases concepts (markdown) --- ...truction-and-Activation-phases-concepts.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 Construction-and-Activation-phases-concepts.md diff --git a/Construction-and-Activation-phases-concepts.md b/Construction-and-Activation-phases-concepts.md new file mode 100644 index 0000000..fac71a3 --- /dev/null +++ b/Construction-and-Activation-phases-concepts.md @@ -0,0 +1,101 @@ +### What is all this? + +The Essentials system architecture is a loose collection of "things", generally real or logical Devices, that all need to relate to each other. In the interest of keeping Essentials extensible and flexible, we use an no-order collection of objects that should only have references to each other in the least-binding way possible. Meaning: Devices should be designed to be able to function without related objects present, and when they are present they should only retain loose reference to those other objects for memory management and later deconstruction as Essentials grows into a real-time configurable environment. (More to come on this subject.) + +In order to facilitate this loose coupling, Essentials devices go through five phases during the startup process: Construction and DeviceManager addition; pre-activation; activation; post-activation. We will describe what is optimal behavior for each of the steps below: + +### 1. Construction & DeviceManager addition + +In general, construction is only to get the framework of the device in place. All devices are constructed in this stage. Rooms and fusion bridges included. Simple devices like IR driver devices, and devices with no controls can be completely spun up in this phase. All devices are added to the DeviceManager after they are constructed, but may not be fully functional. + +### 2. Pre-activation + +This stage is rarely used. It exists to allow an opportunity for any necessary logic to take place before the main activation phase. + +### 3. Activation + +This stage is the main phase of startup, and where most devices will get up and running, if they need additional startup behavior defined. The developer will code an optional overridden `CustomActivate()` method on the device class. This is where hardware ports may be set up; signals and feedbacks linked; UI drivers fired up; rooms linked to their displays and codec... With the exception of early-designed devices, most new Essentials classes do all of their startup here, rather than in constructor. + +Remember that in your `CustomActivate()` method, you cannot assume that a device you depend on is alive and running yet. It may be activating later. You can depend on that device existing, and link yourself to it, but it may not be functional yet. In general, there should be no conditions in any Essentials code that depend on device startup sequence and ordering. All rooms, devices, classes should be able to function without linked devices being alive, and respond appropriately when they do come to life. Any post-activation steps can be done in step four below - and should be avoided in general. + +If the `CustomActivate` method is long, consider breaking it up into many smaller methods. This will enhance exception handling and debugging when things go wrong, with more-detailed stack traces, and makes for easier-to-read code. + +``` + +public override bool CustomActivate() +{ + Debug.Console(0, this, "Final activation. Setting up actions and feedbacks"); + SetupFunctions(); + SetupFeedbacks(); + + EISC.SigChange += EISC_SigChange; + ... +} +``` + +### 4. Post-activation + +This phase is used primarily to handle any logic in a device that might be dependent on another device where we need to ensure we've waited for the dependent device to be "activated" first. For example, if we look at the MicrophonePrivacyController class, this is a "virtual" device whose purpose is to control the mute state of microphones from one or more contact closure inputs as well as provide feedback via different colored LEDs as to the current mute state. This device doesn't actually represent any sort of physical hardware device, but rather relies on associating itself with other devices that represent digital inputs and relays as well as whatever device is responsible for preforming the actual muting of the microphones. + +We can see in the example below that during the CustomActivate() phase, we define a post-activation action via a lambda in AddPostActivationAction() that will execute during the post-activation phase. The purpose here is to check the state of the microphone mute and set the state of the relays that control the LEDs accordingly. We need to do this as a post-activation action because we need to make sure that the devices PrivacyDevice, RedLedRelay and GreenLedRelay are fully activated before we can attempt to interact with them. + +**Example** +``` +public override bool CustomActivate() +{ + foreach (var i in Config.Inputs) + { + var input = DeviceManager.GetDeviceForKey(i.DeviceKey) as IDigitalInput; + + if(input != null) + AddInput(input); + } + + var greenLed = DeviceManager.GetDeviceForKey(Config.GreenLedRelay.DeviceKey) as GenericRelayDevice; + + if (greenLed != null) + GreenLedRelay = greenLed; + else + Debug.Console(0, this, "Unable to add Green LED device"); + + var redLed = DeviceManager.GetDeviceForKey(Config.RedLedRelay.DeviceKey) as GenericRelayDevice; + + if (redLed != null) + RedLedRelay = redLed; + else + Debug.Console(0, this, "Unable to add Red LED device"); + + AddPostActivationAction(() => { + CheckPrivacyMode(); + PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange -= PrivacyModeIsOnFeedback_OutputChange; + PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange += PrivacyModeIsOnFeedback_OutputChange; + }); + + initialized = true; + + return base.CustomActivate(); +} + +void CheckPrivacyMode() +{ + if (PrivacyDevice != null) + { + var privacyState = PrivacyDevice.PrivacyModeIsOnFeedback.BoolValue; + + if (privacyState) + TurnOnRedLeds(); + else + TurnOnGreenLeds(); + } +} +``` + +### Activation exceptions + +Each of the three activation phases operates in a try/catch block for each device. This way if one device has a fatal failure during activation, the failure will be logged and the system can continue to activate. This allows the developer to chase down multiple issues per load while testing, or to fix configuration omissions/errors as a group rather than one-at-a-time. A system can theoretically load fully and have many or all devices fail. We generally do not want to depend on exception handling to log device failures. Construction and activation code should have plenty of null checks, parameter validity checks, and debugging output to prevent exceptions from occurring. `String.IsEmptyOrNull(myString)` and `if(myObject == null)` are your friends. Invite them often. + +### The goal + +Robust C#-based system code should not depend on "order" or "time" to get running. We do not need to manage the order of our startup in this environment. Our Room class may come alive before our DSP and or Codec, and the Room is responsible for handling things when those devices become available. The UI layer is responsible for blocking the UI or providing status when the Room's requirements are coming alive, or if something has gone away. We use events or Feedbacks to notify dependents that other devices/classes are ready or not, but we do not prevent continued construction/activation of the system when many of these events don't happen, or don't happen in a timely fashion. This removes the need for startup management, which is often prolonged and consumes tons of developer/installer time. A fully-loaded Essentials system may go through activation in several seconds, with all devices concurrently getting themselves going, where legacy code may take 10 minutes. + +When designing new Device-based classes, be it rooms, devices, port controllers, bridges, make them as independent as possible. They could exist alone in a program with no required partner objects, and just quietly exist without failing. We want the system to be fast and flexible, and keeping the interdependence between objects at a minimum improves this flexibility into the future. \ No newline at end of file