comment 1

CWS: Part 1

One thing that always seems to come up during the C# labs at Masters is Crestron Web Scripting (CWS). I never think about adding this to my own projects, but it is a good way to provide some level of advanced configuration (or even remote control) of the running program. I’m watching the Intermediate C# videos this morning and see that the first lab dives right into using CWS. So in the spirit of Masters, let me try to incorporate some of their teachings into my day-to-day programming.

I’m hoping in this series of posts to build out a CWS program that can:

  • Work on 3-series and 4-series processors
  • Configure a room system
  • Provide a REST API for external control

Let’s begin!

What is CWS?

CWS is a service that can hook into the running web server on our device to provide an API. Since 3-series and 4-series processors are already running a web server, we can use CWS to map custom URLs into our API. Here is what the processors serve when you browse to their default web pages:

3-series web page is pretty broken in current firmware.
4-series has a full web UI to manage the processor.

What we’ll do with CWS is add our own application that can be accessed using a special URL to our device.

Create a New Project

Since my goal is to have this program work on 3- and 4-series processors, that means I’m going to use VS2008. If you are fine dropping support for 3-series, you can use VS2019 instead. Create a new SIMPL# Pro program:

First thing I do is strip down the templated code to the bare essentials to cut down on some of the noise. Here’s my ControlSystem.cs:

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

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

        public override void InitializeSystem()
        {
            try
            {

            }
            catch (Exception e)
            {
                ErrorLog.Error("Error in InitializeSystem: {0}", e.Message);
            }
        }
    }
}

Build it, no problems.

Add a CWS Server

Next, we’ll add a CWS server to the program. We need to add a reference to the appropriate assembly, too. Right-click your project in the Solution Explorer and select Add Reference. We’re looking for SIMPLSharpCWSHelperInterface:

With the appropriate reference added to our project, we can include the namespace at the top of our ControlSystem.cs file:

using System;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.CrestronThread;
using Crestron.SimplSharp.WebScripting;

namespace CwsDemo
{

There’s actually a decent example for HttpCwsServer Class in the shipped CHM file with the Crestron Database, but the online version is just a couple of lines. One of the reasons I started typing out code on this blog is because I found it hard to find examples of using SIMPL# classes. At least now, I can always look back on this post when I’m wondering How do I use this again?

Next, we’ll add a new member to our ControlSystem class:

public class ControlSystem : CrestronControlSystem
{
    private HttpCwsServer _api;

    public ControlSystem()
        : base()
    {

And in InitializeSystem, we can set it up:

public override void InitializeSystem()
{
    try
    {
        _api = new HttpCwsServer("/api");
        _api.ReceivedRequestEvent += DefaultRequestHandler;
        _api.Register();
    }
    catch (Exception e)
    {
        ErrorLog.Error("Error in InitializeSystem: {0}", e.Message);
    }
}

This creates a new CWS server at the /cws/api URL on our device. We also register a new event handler that we can define just below there:

private void DefaultRequestHandler(object sender, HttpCwsRequestEventArgs args)
{
    try
    {
        using (var writer = new StreamWriter(args.Context.Response.OutputStream))
        {
            writer.WriteLine("Got request: {0}", args.Context.Request.Path);
        }
    }
    catch (Exception e)
    {
        ErrorLog.Error("Error in DefaultRequestHandler: {0}", e.Message);
    }
}

If we get a new request, we simply return a page that says what it was. It’s very exciting! See:

3-series we see /cws/api/hello
…but 4-series we only see /api/hello

Cleaning Up

After reloading this program a few times, I’ve noticed this popping up in the error log:

1. Error: CustomAppManager.exe # 2021-05-03 10:52:10  # Forcefully Shutting Down SimplSharpProManager:1
2. Error: CustomAppManager.exe # 2021-05-03 10:52:10  # SimplSharpPro exited ungracefully for app 1. 
3. Notice: TLDM.exe # 2021-05-03 10:52:10  # **Program 1 Stopped**
4. Error: nk.exe # 2021-05-03 10:52:11  # WDG: LogicEngine shutting down unexpectedly. Do Not Start=0
5. Notice: SimplSharpPro.exe [App 1] # 2021-05-03 10:52:45  # **Program 1 Started**

What are we doing wrong?

If we go back to the HttpCwsServer class help, it says it implements the IDisposable interface. We should be disposing it once we’re done with it. Right now, it’s getting forcibly killed so that our program can restart.

Let’s add an event handler for program shutdown to make sure we clean up after ourselves. First, we need to register an event handler in our ControlSystem constructor:

public ControlSystem()
    : base()
{
    try
    {
        Thread.MaxNumberOfUserThreads = 20;

        CrestronEnvironment.ProgramStatusEventHandler += ProgramStatusHandler;
    }
    catch (Exception e)
    {
        ErrorLog.Error("Error in the constructor: {0}", e.Message);
    }
}

Then define the handler below:

private void ProgramStatusHandler(eProgramStatusEventType status)
{
    if (status == eProgramStatusEventType.Stopping)
    {
        if (_api != null)
        {
            _api.Unregister();
            _api.Dispose();
        }
    }
}

Now our program will clean up after itself and not leave unnecessary messages in the error log.

Next Time

In the next part, we’ll work on adding some route handlers to our CWS application. These will let us view and mutate the state of our program. Hopefully that post will be a bit more interesting. If you’d like to grab this program, it’s available on GitHub.

1 Comment so far

  1. Pingback: CWS: Part 2 | Kiel the Coder

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 )

Google photo

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

Twitter picture

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

Facebook photo

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

Connecting to %s