SIMPL Module Best Practices

When I started programming Crestron systems, there were two other programmers on my team. One was a senior programmer who had been doing it for years already; the other was a junior programmer who had been doing it for a year maybe? I feel like I fit into the middle slot between them quite nicely. So, I tried to follow the senior guy’s example and help the junior guy out when I felt that I could.

The workflow I picked up from the senior programmer was:

  • Copy-and-paste the previous program you worked on and only change the bits needed for the new system. 90% of the code is probably going to be the same anyway.
  • Don’t jam ANY signals together, always buffer them or use an OR.
  • Never hide program logic inside of a module.

I can already tell this is going to be a divisive post because of how different people treat user modules. I want to present 3 modules in this post, talking about why they were written, how they evolved, and why they were the best approach.

Device Control

I recently had a project that was slated to use NVX encoders and decoders. But due to shipping delays, we weren’t able to get the equipment from Crestron. We decided that the Visionary Solutions Duet 2 encoders would also work for what we needed. The problem though: I had already written a program based on NVX control.

Fortunately, I was able to get my hands on a couple of these devices before they shipped to the job site. I started working on a driver program to see if I could control them from Crestron. The API is fairly simple and well-documented, so getting a test system working only chewed up a few hours of my morning. Lesson learned: read the manual, and when it says to enable Jumbo Frames on your network switch, do it!

So I had working code, I just needed to dump it into my real program and wire things up to that instead. This would have been fine, but I thought, “Is it possible I’ll have another system in the future where I’ll need to control these same devices?” Of course it is! So, I wrote a module where I can easily add Duet 2 devices in place of NVX:

I chose to use a UDP/IP Client in SIMPL so I could easily watch the network traffic in SIMPL debugger.

This module hasn’t undergone any evolution yet since I’ve only used it on one project so far. But I’m sure I’ll continue to improve it as I need more features this device is capable of. If you’d like to grab a copy of this module as it is now, it’s in my GitHub repo.

I think everyone agrees that device control modules are a good thing. They hide the specifics of talking to this particular encoder/decoder. I can swap back and forth to different devices (in theory) and not have to wildly update how my program interfaces to them. The module definition is a contract that I’ve devised for talking to these classes of devices.

Logic Modules

Where things get a little contentious is modules written to hide program logic. These go against one of the tenets shared with me when I shadowed our senior programmer. I’m still wary about adding them to new programs because it can make tracking down bugs more difficult.

There’s a tendency to think of modules as black boxes. Inputs go in, some magic happens, outputs come out. But in reality, SIMPL modules are “flattened” into your program. This means that input and output signals connect directly to the symbols defined inside your module. You have to take that much more care not to jam signals together if you route things inside your module.

But sometimes, the benefits of having a module that behaves a certain way makes writing the rest of your program that much easier. For example, I used to use Crestron display control modules, but their behavior varied based on the programmer who developed them. Sometimes, I’d have to work around one module’s behavior because it broke my touchpanel flow. So, I started by using my own display control logic that either sat in front of these other modules or (more likely) drove a Serial I/O symbol with the appropriate control strings. This way I could forget how a particular module behaved and make sure my programming worked consistently from system to system.

Until display state matches what we want, keep sending power on/off commands.

But having all these signals in my program would clog up debugger. I never cared about the state of any of these intermediate signals once I knew my logic was sound. I wished they could just disappear into the background. That’s where the benefits of wrapping this logic into a user module comes in.

Using a module, I can provide a contract with my program such that the core behavior is always the same. If I need to adjust it for displays that don’t provide true feedback, that’s as easy as setting a join high on the input side. If I want to change timing parameters, that’s easily done on the module itself rather than digging into the correct symbol somewhere in the chain. And if I’ve used the module in several places in my program, I can fix the logic once and have it applied everywhere.

My Display Management module has evolved over time as I’ve fixed things here and there. Thankfully, I didn’t find any bugs moving to 4-series, but I cleaned it up and bumped the version number anyway. It’s available in my GitHub repo if you’d like to grab it.

Abstraction

Probably what I consider the best benefit of writing your own modules is abstracting away unnecessary details. For example, there are a handful of EISC symbols available in the database:

These are handy for passing control between processors, but you do have to make sure both ends have the same IP ID so they connect up properly. If you have a head-end program that talks to many identical rooms, this becomes a problem because everyone wants to connect on the same IP ID. You have a couple choices here:

  • Remap IDs on the room programs so they actually connect on different IP IDs in the head-end program. Every system is always connected though.
  • Use a client/server connection, pass something similar to Intersystem Communication over it. Still restricted to IP Table though.
  • Implement a pub/sub messaging queue with a broker to handle routing control to the proper system. Probably overkill for what you’re trying to do.

I have a project where a number of rooms all share nearly identical code. They’re all tied into a daily schedule, but because different groups own the different spaces, the schedules are independent of each other. For convenience, I want to have a central controller be able to talk to any other system and change its schedule. Since this may only happen a few times during the life of these systems, I don’t want the central controller (which will also be running a room system of its own) to keep network connections open when it doesn’t need them. So that means the built-in EISCs are not an option. But, we can build our own!

What I’ll likely end up doing is adding a TCP/IP Server to each of the room systems, and have that drive an Intersystem Communications (XSIG) symbol:

When a client connects, we send a clear/sync sequence

Now my room systems can take a connection on port 50001 and decode XSIG packets. Note that there is no authentication being performed, so I would never expose this to the Internet or other untrusted networks. If I were to turn this into a more general purpose module, I’d likely add a way to use SSL with and without authentication.

On the central controller side I need a TCP/IP Client, however, I don’t want to use the one from the Symbol Library because it locks us into dealing with IP Tables again. I really need a dynamic way of pointing where the TCP/IP Client connects, and that means we have to drop into SIMPL+.

I just want a mechanism for dynamically changing the address or port that we connect to, which is easy enough to write:

// COMPILER DIRECTIVES /////////////////////////////////////

#DEFAULT_VOLATILE
#ENABLE_STACK_CHECKING

#DEFINE_CONSTANT ADDR_LEN 100
#DEFINE_CONSTANT BUF_LEN  1000

// INPUTS //////////////////////////////////////////////////

DIGITAL_INPUT Connect;
ANALOG_INPUT  Port;
STRING_INPUT  Address[ADDR_LEN];
STRING_INPUT  TX[BUF_LEN];

// OUTPUTS /////////////////////////////////////////////////

DIGITAL_OUTPUT Connect_Fb;
ANALOG_OUTPUT  Status;
STRING_OUTPUT  RX;

// SOCKETS /////////////////////////////////////////////////

TCP_CLIENT Socket[BUF_LEN];

// GLOBAL VARIABLES ////////////////////////////////////////

INTEGER giConnected;

// EVENT HANDLERS //////////////////////////////////////////

PUSH Connect
{
	SocketConnectClient(Socket, Address, Port, 1);
}

RELEASE Connect
{
	SocketDisconnectClient(Socket);
}

CHANGE TX
{
	If (giConnected)
	{
		SocketSend(Socket, TX);
	}
}

SOCKETSTATUS Socket
{
	Status = SocketGetStatus();
}

SOCKETCONNECT Socket
{
	giConnected = 1;
	Connect_Fb = giConnected;
}

SOCKETDISCONNECT Socket
{
	giConnected = 0;
	Connect_Fb = giConnected;
}

SOCKETRECEIVE Socket
{
	RX = Socket.SocketRxBuf;
	ClearBuffer(Socket.SocketRxBuf);
}

// MAIN ////////////////////////////////////////////////////

FUNCTION Main()
{
	WaitForInitializationComplete();
}

This is a very thin layer over the Direct Socket functions available in SIMPL+. Here’s how the module gets added to the central controller SIMPL program:

To complete the abstraction, I can wrap this inside a SIMPL user macro so the interface is even more EISC-like in our programs:

I probably won’t need this particular module very often, since a normal EISC would handle my typical use case. But now it’s another tool in the toolbox that behaves closely to how an EISC does (above the surface). It’s on GitHub if you want to play with it.

Conclusion

I hope these modules were good examples of when you might consider writing your own. It could be to control equipment you’ve never run into before, hide some routine logic you want to ignore, or abstract away the details when you’re trying to build up your own interfaces.

Next time I’ll cover SIMPL+ best practices. Thanks for reading!

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 )

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