HTML5 Cleanup

The last post took me way too long to write (like an entire month), and I think it’s the format of trying to step through every single change, every single time that’s slowing me down. We’re going to be moving into program sizes where that just isn’t going to work. But before we make that jump, I want to do some clean-up on our last program, the Huddle Room.

Lets Write It Again

Q: What do programmers like to do after they finish a project?
A: Do it again!

What we’re going to do this time around is refactor our code and make it easier to manage. Sure it works, and thankfully there’s very little going on, but it’s going to get out of hand if we keep the same approach.

Before we touch anything, make sure to checkout a new git branch to work in:

$ git checkout -b CleanUp

Make It More General

One of the biggest advantages of writing our programs in SIMPL# Pro is the flexibility it gives us. We may not know until run-time exactly what equipment exists in our system (say by reading a config file). The more generic we can make our code, the better it can support a wider variety of gear.

However, there are times where those benefits don’t outweigh the added complexity. For example, in our ControlSystem class, we have an occupancy sensor:

public class ControlSystem : CrestronControlSystem
{
    private BasicTriListWithSmartObject _tp;
    private CenOdtCPoe _occSensor;
    private DmRmc4kz100C _rmc;
    private DmTx4kz202C _tx;

Crestron’s class hierarchy doesn’t give us a better choice for a more generic sensor (CenOdtCPoe inherits from GeneralDevice). Until there are other classes that inherit from CenOdtCPoe, we’re kind of as generic as we can get for an Ethernet-connected occupancy sensor.

Our DM endpoints can be made more generic though. DmRmc4kz100C inherits from DmRmc100C so that makes sense (it might be better if there was just an abstract DmRmcC class). And DmTx4kz202C inherits from DmTx4kzX02CBase which inherits from DmTx4kX02CBase. That one’s a bit uglier.

What can we do to make our code more generic but not resort to memorizing crazy class hierarchies?

Wrap It

One way to make our code easier to manage and hide away these ugly details is to wrap classes around them. Lets create some new classes and add them to our project.

Occupancy.cs

using System;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.GeneralIO;

namespace HuddleRoom
{
    class Occupancy
    {
        private CenOdtCPoe _sensor;

        public event EventHandler RoomOccupied;
        public event EventHandler RoomVacant;

        public Occupancy(uint ipId, CrestronControlSystem cs)
        {
            if (cs.SupportsEthernet)
            {
                _sensor = new CenOdtCPoe(ipId, cs);
                _sensor.CenOccupancySensorChange += _sensor_Change;

                if (_sensor.Register() != eDeviceRegistrationUnRegistrationResponse.Success)
                {
                    ErrorLog.Error("Occupancy: Error registering CEN-ODT-C-POE ({0})", _sensor.RegistrationFailureReason);
                }
            }
            else
            {
                ErrorLog.Error("Occupancy: CEN-ODT-C-POE requires Ethernet support");
            }
        }

        private void _sensor_Change(object sender, GenericEventArgs args)
        {
            switch (args.EventId)
            {
                case GlsOccupancySensorBase.RoomOccupiedFeedbackEventId:
                    if (_sensor.OccupancyDetectedFeedback.BoolValue)
                    {
                        if (RoomOccupied != null)
                        {
                            RoomOccupied(this, new EventArgs());
                        }
                    }
                    break;
                case GlsOccupancySensorBase.RoomVacantFeedbackEventId:
                    if (_sensor.VacancyDetectedFeedback.BoolValue)
                    {
                        if (RoomVacant != null)
                        {
                            RoomVacant(this, new EventArgs());
                        }
                    }
                    break;
            }
        }
    }
}

Just a simple little class to do the 2 things we care about: tell us when the room is occupied and when it is vacant.

Display.cs

using System;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;

namespace HuddleRoom
{
    class Display
    {
        private ComPort _port;

        public Display(ComPort port)
        {
            _port = port;
            _port.SetComPortSpec(ComPort.eComBaudRates.ComspecBaudRate9600,
                    ComPort.eComDataBits.ComspecDataBits8, ComPort.eComParityType.ComspecParityNone,
                    ComPort.eComStopBits.ComspecStopBits1, ComPort.eComProtocolType.ComspecProtocolRS232,
                    ComPort.eComHardwareHandshakeType.ComspecHardwareHandshakeNone,
                    ComPort.eComSoftwareHandshakeType.ComspecSoftwareHandshakeNone, false);
            _port.SerialDataReceived += _port_DataReceived;
        }

        public Display(ComPort port, ComPort.ComPortSpec spec)
        {
            _port = port;
            _port.SetComPortSpec(spec);
            _port.SerialDataReceived += _port_DataReceived;
        }

        public void TurnOn()
        {
            _port.Send("PWR ON\r");
        }

        public void TurnOff()
        {
            _port.Send("PWR OFF\r");
        }

        private void _port_DataReceived(ComPort port, ComPortSerialDataEventArgs args)
        {
            // TODO
        }
    }
}

The Display class will likely get subclassed in the future to target particular displays. Right now, all we care about is turning it on and off. Eventually, we’ll build this class up to poll the display as well.

UI.cs

using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;

namespace HuddleRoom
{
    class UI
    {
        private List<BasicTriListWithSmartObject> _panels;
        private ControlSystem _cs;

        private ushort _src;

        public UI(ControlSystem cs)
        {
            _panels = new List<BasicTriListWithSmartObject>();
            _cs = cs;
            _src = 0;
        }

        public void Add(BasicTriListWithSmartObject tp)
        {
            tp.OnlineStatusChange += _tp_OnlineStatusChange;
            tp.SigChange += _tp_SigChange;

            _panels.Add(tp);
        }

        public void RegisterAll()
        {
            foreach (var tp in _panels)
            {
                if (tp.Register() != eDeviceRegistrationUnRegistrationResponse.Success)
                {
                    ErrorLog.Error("UI: Unable to register device ({0})", tp.RegistrationFailureReason);
                }
            }
        }

        public void SetUShort(ushort sig, ushort val)
        {
            foreach (var tp in _panels)
            {
                tp.UShortInput[sig].UShortValue = val;
            }
        }

        public void SetSource(ushort newSource)
        {
            _src = newSource;

            if (_src == (ushort)SourceIds.None)
            {
                _cs.SystemOff();
            }
            else
            {
                _cs.SystemOn();
            }

            SetUShort(1, _src);
        }

        private void _tp_OnlineStatusChange(GenericBase dev, OnlineOfflineEventArgs args)
        {
            if (args.DeviceOnLine)
            {
                var tp = dev as BasicTriListWithSmartObject;

                tp.StringInput[1].StringValue = "Huddle Room";  // Room Name
                tp.StringInput[2].StringValue = "x1234";        // Help Number

                tp.UShortInput[1].UShortValue = _src;           // Currently selected source
            }
        }

        private void _tp_SigChange(BasicTriList dev, SigEventArgs args)
        {
            switch (args.Sig.Type)
            {
                case eSigType.UShort:
                    switch (args.Sig.Number)
                    {
                        case 1:
                            SetSource(args.Sig.UShortValue);
                            break;
                    }
                    break;
            }
        }
    }
}

Lastly, we’re going to wrap our touchpanels in a UI class to make sure they all react the same way and they’re all updated by the program the same way.

Refactor

With our new classes available, we can refactor ControlSystem.cs to clean it up and see the business logic of what we’re doing a bit better:

using System;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.CrestronThread;
using Crestron.SimplSharpPro.DM.Endpoints;
using Crestron.SimplSharpPro.DM.Endpoints.Receivers;
using Crestron.SimplSharpPro.DM.Endpoints.Transmitters;
using Crestron.SimplSharpPro.UI;

namespace HuddleRoom
{
    public enum SourceIds
    {
        None = 0,
        Laptop = 1,
        AppleTV = 2,
        RoomPC = 3
    }

    public class ControlSystem : CrestronControlSystem
    {
        private DmRmc4kz100C _rmc;
        private DmTx4kz202C _tx;

        private Occupancy _occ;
        private Display _display;
        private UI _ui;

        public ControlSystem()
            : base()
        {
            try
            {
                Thread.MaxNumberOfUserThreads = 20;
            }
            catch (Exception e)
            {
                ErrorLog.Error("Error in ControlSystem: {0}", e.Message);
            }
        }

        public override void InitializeSystem()
        {
            try
            {
                _ui = new UI(this);
                _ui.Add(new Tsw1060(0x03, this));
                _ui.RegisterAll();

                _occ = new Occupancy(0x04, this);
                _occ.RoomOccupied += (sender, args) => _ui.SetSource((ushort)SourceIds.RoomPC);
                _occ.RoomVacant += (sender, args) => _ui.SetSource((ushort)SourceIds.None);

                _rmc = new DmRmc4kz100C(0x14, this);

                if (_rmc.Register() != eDeviceRegistrationUnRegistrationResponse.Success)
                {
                    ErrorLog.Error("Unable to register DM-RMC-4KZ-100-C: {0}", _rmc.RegistrationFailureReason);
                }

                _display = new Display(_rmc.ComPorts[1]);

                _tx = new DmTx4kz202C(0x15, this);
                _tx.HdmiInputs[1].InputStreamChange += laptop_StreamChange;
                _tx.HdmiInputs[2].InputStreamChange += laptop_StreamChange;
                
                if (_tx.Register() != eDeviceRegistrationUnRegistrationResponse.Success)
                {
                    ErrorLog.Error("Unable to register DM-TX-4KZ-202-C: {0}", _tx.RegistrationFailureReason);
                }
            }
            catch (Exception e)
            {
                ErrorLog.Error("Error in InitializeSystem: {0}", e.Message);
            }
        }

        public void SystemOn()
        {
            _display.TurnOn();
        }

        public void SystemOff()
        {
            _display.TurnOff();
        }

        private void laptop_StreamChange(EndpointInputStream stream, EndpointInputStreamEventArgs args)
        {
            if (args.EventId == EndpointInputStreamEventIds.SyncDetectedFeedbackEventId)
            {
                var inputStream = stream as EndpointHdmiInput;

                if (inputStream.SyncDetectedFeedback.BoolValue)
                {
                    _ui.SetSource((ushort)SourceIds.RoomPC);   // make sure Room PC is on display
                }
            }
        }
    }
}

We could also create classes for our DM endpoints, but that won’t gain us much yet (until we add AV switching to our program).

Summary

We were able to clean up our program and create some utility classes that will come in handy later.

4 thoughts on “HTML5 Cleanup

    1. Hi Jack, it might be a while before I get back to 4-series-specific posts. I’ve been pretty busy at work. I know there was a document or video Toine made for debugging 4-series. I’ll see if I can find it.

      Like

    2. If you look up answer ID 1000637 on Crestron’s online help, there’s a PDF with steps. But it says “privileged access” so not sure who can see it.

      Like

  1. Thanks’s so much. but when i follow this OLH guide, my vs2019 mono debugger will throw some exceptions It’s so wired. I will find answers in twitter.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s