A Modern Crestron Programming Strategy

A large majority of end-users and businesses understand the pace at which technology advances in the modern world. As a Crestron Service Provider, we often have to be more pragmatic regarding the efficacy of programming strategies underlying our business processes. I would argue, that the primary goal should be to maximize value. This ensures that the decisions being made are optimized to reduce senseless changes that add little or no value to the business or your customers. Beneficial decisions will naturally follow with such a mindset.

SIMPL# Pro is one such technology that can add unparalleled value to your control system solutions. To many Crestron programmers with their adolescent knowledge of the Microsoft C# language, it seems unfamiliar and peculiar. This stems from the fact that many programmers were never required to learn C# programming in the past. Consequently, some programmers dismiss the idea entirely; a mistake that can lead to a myriad of lost opportunities. As a zealous C# programmer having an award from Microsoft on the topic, I believe I have enough credibility to suggest a case for both sides. Let’s discuss the disadvantages first, following a list of advantages.

Note: “SIMPL#” is a Crestron term for their programming solution that utilizes the Microsoft C# programming language. SIMPL# Pro allows Crestron programmers to write control system programs entirely in C#.

Disadvantages:

  1. SIMPL# Pro is more difficult to learn, in which the learning curve of both SIMPL Windows and SIMPL+ pales by comparison.
  2. SIMPL# Pro library documentation is still a work in process. (This makes many things a journey of trial and error, which can be added complexity for someone who is trying to learn the C# language at the same time. The libraries themselves also do not come without their quirks.)

Advantages:

  1. SIMPL# Libraries can be used to extend the functionality of a program written in SIMPL Windows, but not without limitations. (i.e. Types have to be “translated” to SIMPL+ compatible ones which can reduce the versatility of the underlying functionality, the SIMPL+ cross compiler contains bugs, etc.) This leads to an implied benefit in having everything encapsulated within the SIMPL# Pro environment, without having to adapt the information to comply with the limited boundaries of SIMPL+.
  2. You can create hardware at runtime dynamically. (In comparison to SIMPL Windows programs, where you have to hardcode the combined hardware definitions before compilation, regardless of whether you’re remapping devices or not.)
  3. The controller definition is entirely dynamic. What I mean, is that you can write a single program that is designed to support a DMPS of any type, or even have a program that can support a CP4 and a PRO4 simultaneously.
  4. C# source code allows you to utilize source control the way it was intended to be used. (SIMPL Windows files that define the symbols and signals within a program aren’t managed by the programmer directly, making diffs completely useless.)
  5. The C# language allows you to represent data without restricting you to a maximum of 16-bit integers. You can use primitive types that hold 64-bit values, and even use classes that can support much larger such as BigInteger. It also gives you the ability to use IEEE 754 floating point datatypes such as float and double, as well as the decimal type when you need greater precision.
  6. Being able to use IDE’s like Visual Studio or JetBrains Rider gives you the ability to be vastly more efficient with your workflow. These tools are used by millions of developers around the world, and are fine tuned to accommodate many different use cases and features.
  7. The ability to use a fully fledged CI/CD pipeline to automate build and deployment processes for agile software development is invaluable. We now have far more capabilities in the context of improving testability as well, with unit tests that can also be automated. (Note: This is made even easier with the 4-series platform, which relies on the Mono runtime.)
  8. Reusability is often an overlooked benefit, in the shadow of change that many old school Crestron programmers have a distaste for. The gates are now nearly wide open for programmers to use virtually any open-source .NET library they want, as long as they comply with any applicable license agreements. Abstract C# code when written properly, is also inherently more reusable than code written in SIMPL Windows due to the nature of true polymorphism.
  9. You can also create extremely useful console commands for a multitude of purposes. Unlike with user program commands in SIMPL Windows, inside of a SIMPL# Pro application, you have access to any part of the program memory in terms of “state.” In SIMPL Windows, that state is isolated between each symbol, unless you’ve created signal ties between symbols, or you’ve created a SIMPL# library that allows you to share memory between instances of an object that only those symbols use.

From a business perspective, it may be difficult to transition to SIMPL# Pro initially. Although, I suspect many will be forced to make the leap within the next few years, with the slow but obvious transition towards more open technology. With the advent of HTML5 and JavaScript inside the Crestron ecosystem, there may even come a point when SIMPL Windows and SIMPL+ are no longer viable solutions. Even if they are still remain available options in the future, I sure wouldn’t want to be the one bringing a knife to a gunfight!

To learn more about SIMPL#, we have developed a popular SIMPL# Fundamentals course that allows people to get their foot in the door. The course has proven to be a valuable resource for Crestron Dealers and CSP’s, regardless of whether their programming staff has beginner or intermediate knowledge on the subject. This course can be found here: https://www.tbdenterprises.ca/product/simplsharp-fundamentals-course. In the next couple months, we also hope to be unveiling a brand new course that focuses exclusively on SIMPL# Pro. Our goal is to leverage our extensive knowledge of the C# language to fill in the gaps for those who still find this technology confusing in the context of Crestron control systems.

Are you maximizing efficiency in your workplace?

 

A ubiquitous goal among every business is to reduce unnecessary costs to maximize profit margins, like a balance scale of sorts. Successfully making progress regarding cost reduction, can lead to greater opportunities and greater potential. Having the ability to reinvest more money back into the business to provide a better workplace for your employees, and better goods and services for your customers, is quintessential to a company’s’ growth. With 2020 being a challenging year for many to say the least, it is almost fully underway and 2021 is finally around the corner. The New Year is a time for many individuals and companies to reflect on the past year to plan for success in the following year.

Reflecting on how your company conducts meetings, I want you to ask yourself a few questions:

  1. Have your meetings added enough value to your business in the past? (If the answer is no, what could be done better?)
  2. Were there any technical challenges? How much productivity was lost as a result?
  3. How much time are you spending on organizing, or preparing meetings?
  4. With the new COVID-19 era, what safety precautions have you determined to be a best fit for your business operations to proceed with less concerns around health and safety?

There are many issues with the way most meetings are run. Opening up the discussion to determine what problems your business is facing, becomes the first step towards a solution.

Studies at Harvard University have shown that the most successful meetings consist of smaller groups; typically between 5 to 8 people. Meeting organizers are sometimes unsure on who should be on the invite list. However, as with emails, this typically ends up with everyone being invited as a precautionary measure, leading to some inadvertent consequences. The problem, is that larger meetings tend to suffer from less collaboration and engagement, since larger groups are consequentially less “personal” than meetings consisting of smaller groups. A downward spiral can ensue, causing your attendees to take meetings less seriously, influencing them to be less prepared, and meetings can habitually drag on. Aside from re-educating the organizers to figure out who the key stakeholders are that should be in a given meeting, another strategy would be to make it easier for the organizer to create and finalize a scheduled meeting; allowing them to focus on forming a productive meeting. There are many technological solutions that can solve this problem, with the added benefits of simplicity, uniformity, standardization, as well as increased efficiency and health safety. Control system applications can be configured for room scheduling, availability, and even make ad-hoc meetings easier to organize more immediately.

Technical challenges can also be solved by integrating a well-planned control system solution; making difficult and/or menial/repetitive tasks a breeze! Psychologically, the brain fundamentally operates significantly better with a positive state of mind and less distractions. Don’t let technical difficulties alter the capacity of which your employees are conducting business operations.

Meetings are usually a critical part of any key business decision making process. If a meeting takes 10 or 15 minutes to set up, a substantial amount of time and productivity is being wasted. If you could buy that time back, it becomes a no-brainer when considering the aggregated measurement of productivity lost over the span of even just a single calendar year. Control system automation can be fine tuned for standard meeting rooms to ensure that the meeting is ready to go by the time the first participant shows up. Resources to train staff on how to use meeting rooms can also become less necessary, especially when the user experience can be standardized across an entire building. Simplicity adds great value in terms of efficiency, and your employees won’t lose their train of thought for the meeting agenda, due to meeting preparation.

Working from home

As COVID-19 took the world by storm, and opened many eyes, there became many new requirements to consider. One such consideration, is how to ensure that the digital workplace imposes less of a health risk to key stakeholders within a business? The workplace quickly transformed into variations, ranging from entirely remote operations, to hybrid meeting spaces, and even “touchless” solutions to reduce human to human germ transmission. Once infrastructure is in place, and security implications have been addressed, the cost savings of having the ability to collaborate remotely simply cannot be ignored. Technology, being the cornerstone of nearly every possible solution to these problems, has since been adopted by many organizations across the globe. Revisiting efficiency in the context of number of meeting participants, and in addition to controlling capacity, solutions which leverage machine learning can be deployed. A number of camera manufacturers now provide capabilities for people counting, which can be used to collect data for reasons regarding health and safety, compliance, and a myriad of other reasons. In a world where data is king, you’ll want to be sure that the data is there when you want to review it in the future. Without data, making smart decisions that can impact business operations becomes nearly impossible.

If your business is struggling to find the right solution that fits your operating requirements and key needs, don’t hesitate to reach out for assistance! We believe that it isn’t worth the headache and cost implications of not making improvements in these areas, or attempting to solve it by trial and error. Don’t hesitate to reach out to one of our experienced team members if you’d like to learn about how we can help you improve your business’ collaboration workflow. Our goal is to help you attain peace of mind while having the ability to focus on other areas of your business that require more urgent attention.

 

Try our Advanced Matrix Module For Free!

We are happy to announce that our advanced matrix module is now free to try! Experience the power and flexibility of an extremely dynamic matrix routing module suite for 30 minutes without limitations.

Over the years, our module has been tested against some of the largest systems, making easy work of complicated, yet common tasks involving A/V switchers. This module can be used for small huddle space rooms with a 4×1 switcher, up to systems that have one or more DM-MD128x128 switchers that may or may not be interconnected. This module impressively makes easy work of any sized system.

Reduce development time & costs

This module does most of the heavy lifting for you behind the scenes. Work on building a configuration file, connecting signals, and you’re done!

fast & reliable

Experience the performance and power of a module written in SIMPL#. The SIMPL+ only acts as a translation layer for SIMPL Windows. This means there’s less of a bottleneck in terms of speed and reliability.

adaptability

Customer wants to add a source or a destination? Hardware wiring needs to change? No worries! Change the config file, add new virtual source routing logic and you’re done!

Automatic Routing

We’ve recently introduced a new addition to the module as of v3.0.0. By harnessing the functionality of connected graphing algorithms, we are now able to provide “automatic routing”. Define the rules by applying “weights” to output nodes within the configuration file, and let the algorithms do the rest of the work!

No more fiddling around with analogs and switching things manually. Simply specify a source and a destination routing key, and we’ll find a destination for any of your sources (as long as there is a path defined from source to definition of course).

Advanced Matrix Module v3.0.0 - Trial

FREE DOWNLOAD

Send download link to:

 

Video Conferencing &

The “New” Normal

 

We live in a world full of technology, challenged by the need to keep up with change and innovation. Many business operations have been forced to
re-evaluate their internal processes to align within the margins of government regulations. Some companies are even creating their own additional protocols to help keep their employees healthy. Now, more than ever, integrators have an important role to play in reshaping how end users interact with their technology. Amongst these new set of responsibilities, a shared goal has been born; to deliver an optimal solution that allows business operations to proceed as “normal” with minimal limitations.

How are you bringing forth valuable solutions that your clients can benefit from? What considerations and discussions are you having with your clients? What are the discovered commonalities? All of these are good questions to contemplate when considering how you can provide the best possible solution for any particular client.

The goal of Simplicity

Once you’ve identified a few of the main issues, the next step is to simplify and make things intuitive for common system functionality. A solid foundation of understanding regarding how your client might use the system, must be formulated in order to achieve this. (A good UI/UX should be easy to understand and use correctly with minimal training; and hard to use incorrectly.) Automated functions have become an obvious approach to creating a more “touchless” experience. Automated functionality should be logically intentional within this context, in order to maintain ease of use.

“We can’t hear you, You’re On mute”

Most high-end DSP equipment allows for monitoring audio signals as they transition from the inputs to the output channels. One potential solution here to help mitigate problems like these, is to monitor the input signal before a controlled mute point. This would allow you to analyze and compare frequencies within the normal voice range against a threshold level. You can then trigger notifications that remind the user to unmute themselves when the system “thinks” someone is trying to speak while they are on mute. Although it may be a small subset of the overall system functionality, we find many of our clients appreciate our meticulous approach to improving the user experience.

Room Occupancy

Most meetings are scheduled through enterprise solutions such as Microsoft Exchange, Office 365, Google Calendar, etc. Monitoring room occupancy with sensors, people counting cameras, and automating tasks based on the data these devices provide, can have many benefits. The first is an implied cost reduction, due to increased meeting efficiency (i.e. less time wasted in getting a meeting up and running). It also helps to relieve on-site support from having to deal with issues. leading to a less populated spaces in an office environment. Room occupancy also allows people to find available rooms for ad-hoc meetings more immediately; resulting in some of the same presumed benefits in terms of public health and safety.

Touchscreens are a great place for germs and bacteria to collect. During the day when the cleaning staff isn’t able to readily access meeting rooms, simplification and automation strategies can help reduce quantitative direct contact with the devices. 

Touchless Solutions

Wireless presentation systems are equally becoming more common; as are mobile solutions for system control. In corporate environments where employee WiFi access points have been provisioned, users may optionally have the ability to interact with systems through a web front-end. This would allow for less cross-pollination of surface germs to transfer from person to person within any particular meeting space in a BYOD (Bring Your Own Device) configuration. Wireless presentation solutions that don’t involve sharing a common USB or network “dongle” has the same inherent benefits.

Larger meeting spaces may have different requirements to create a more personable experience. On a video call where a single camera captures a full table, participants may be hard to distinguish on the far end of the call. With more people being allowed to work from home, viewing participants in a large boardroom on a small computer monitor during a video call might be difficult. Solutions that allow a camera to automatically track and zoom in on active speakers can not only help improve user engagement; they can reduce manual interaction with a control surface to point the camera where it’s needed.

 

MasterInstaller 3.03.00 – Software Bug

Issues with software can be a time killer, and sometimes very distracting from the task at hand. We’ve known about a small bug in the MasterInstaller software for a few months now. This information has been disclosed to Crestron in hopes that they would fix it at some point in the near future in much more detail.

Case scenario: You just get off a call with tech support after being instructed to update your software to the latest database version(s). To save some time, you quickly open up MasterInstaller, select the items you want to install, hit “Next” a few times, accept the terms, then hit the “Download and Install” button only to have the program exit on you without warning.

The installer has a bug with a buffer overrun on the stack which causes stack corruption, prompting the application to exit from a __fastfail() call. If you’ve uninstalled or installed some software recently (and perhaps you haven’t rebooted your PC in some time,) you may run into this issue! In short, if the PendingFileRenameOperations registry key contains more than 5000 bytes of data, you won’t be able to use MasterInstaller to download and install or update Crestron software. This registry value is typically cleared after a Windows system restart however, and it is a good thing to keep this in mind next time you encounter a crash while trying to update your Crestron software.

Below is a video demonstrating the bug, along with some further details in the video description.

**DISCLAIMER**: This video is for informational and educational purposes only! DO NOT edit the registry or you run the risk of potentially messing something up on your Windows machine, otherwise do so after you understand and acknowledge all of the risks involved.
Documentation for PendingFileRenameOperations registry value can be found in the Remarks section below:
Version: 3.03.00
[ No further updates to the software as of August 19, 2020 ]

SIMPL+ Expressions & Type conversions

Dealing with type conversions without the ability to explicitly cast to another type isn’t always straightforward. Especially in the context of primitive integer types built into the SIMPL+ language, narrowing conversions can cause problems if you aren’t aware of them.

SIMPL+ is a strange, minimalist, C-like language that has more than a handful of problems; including silencing implicit narrowing type conversions.

 

 

 

 

 

#ENABLE_TRACE
#DEFAULT_VOLATILE
#ENABLE_STACK_CHECKING

// Note: This function is simply here to see if any of the calls to this function result in a compiler warning
// about loss of data from the narrowing conversion.
FUNCTION TestForDataLoss(INTEGER value) { }

FUNCTION Main()
{
    SIGNED_LONG_INTEGER n;
    n = 500096; // Binary: 00000000_00000111_10100001_10000000
    // LSB = 128 (10000000)

    TestForDataLoss(n & 0xFF);   // Expression by itself is recognized as a type of SIGNED_LONG_INTEGER
    TestForDataLoss((n & 0xFF)); // Parentheses promotes the expression to a type of INTEGER
    TestForDataLoss(Low(n));     // Something weird is happening here with an implicit narrowing conversion... Works (?)

    // ... But what if we want the least significant 7 bytes?
    TestForDataLoss(Low(n & 0x7F));     // Works
    TestForDataLoss(LowWord(n & 0x7F)); // Works
    TestForDataLoss(n & 0x7F);          // Does NOT work
    TestForDataLoss((n & 0x7F));        // Works!
    TestForDataLoss((n));               // Should NOT work (but it does)
}

Note: In the code comments, “Works” indicates that the line of code compiles without any compiler warnings.

Without running the code, you can see the results from the comments beside each function call in the source code above. What is interesting, is the effect additional parentheses have on an expression. We can deduce that the expression with additional parentheses is being promoted from a type of SIGNED_LONG_INTEGER to INTEGER, and an expression without the additional parentheses is still recognized as a SIGNED_LONG_INTEGER since a compiler warning complains about that line.

The obvious solution to this problem is to use the built-in functions like Low() and LowWord(). LowWord() converts the type from the SIGNED_LONG_INTEGER passed into the function and returns the low 16 bits as an INTEGER. Low() does not convert the type at all; it takes an INTEGER as a function parameter and returns the least significant 8 bits as an INTEGER. This is important to understand, since passing a SIGNED_LONG_INTEGER to this function which takes in an INTEGER type, does not produce a compiler warning! This is potentially a very bad thing, since the compiler in all other circumstances would at least warn us about a potential loss of data from a narrowing conversion, but it lets you pass in a 32-bit SIGNED_LONG_INTEGER to a function which expects a 16-bit INTEGER no problem. A silent implicit narrowing type conversion like this can lead to inadvertent results that are more difficult to troubleshoot and resolve.

One of the main reasons I wanted to write this article was to show a shorthand for promoting the resultant type of an expression to an INTEGER to avoid the compiler warnings. I now see how this can also lead to unexpected results however, similar to how the compiler will not warn you about passing a SIGNED_LONG_INTEGER into a function which accepts a type of INTEGER. The implicit conversion seems to be based on parentheses and not the expression itself, which is even more concerning since an expression of ‘n’ in the code above–should remain a SIGNED_LONG_INTEGER, but it gets promoted to an INTEGER if you write ‘(n)’.

Note: The implicit type conversion only occurs if the type expected is of a different type than the declared type.

At least with languages like C and C++, you would normally have to explicitly cast for a narrowing conversion. This makes the intent a lot more readable, and the results more in line with what was expected.

Overview
In the following sections, we will explore different techniques to expand and enhance the functionality of your SIMPL Windows programs by harnessing the capabilities of SIMPL#. SIMPL# Pro is not covered in this edition, although a lot of the core principles and code examples can still be translated over to SIMPL# Pro.

Prerequisites
It is assumed that you have the following (as a minimum set of requirements):

❖ Microsoft Visual Studio 2008 Pro (SP1)
❖ Crestron SIMPL# Plugin
❖ SIMPL Windows and SIMPL+ language proficiency

Note: A basic understanding of the C# language (syntax) and .NET compact framework is recommended, although not required. It should be noted that this paper is not an alternative to learning how to program in C#.

Type Alias Helpers

You can use the using directive outside (or within) namespace scope to make the type aliases in your
SIMPL# library project source files. A set of those using directives for SIMPL+ type aliasing has been defined below:

using STRING = System.String; // string = STRING
using SSTRING = Crestron.SimplSharp.SimplSharpString; // SimplSharpString = STRING
using INTEGER = System.UInt16; // ushort = INTEGER (unsigned)
using SIGNED_INTEGER = System.Int16; // short = SIGNED_INTEGER
using SIGNED_LONG_INTEGER = System.Int32; // int = SIGNED_LONG_INTEGER
using LONG_INTEGER = System.UInt32; // uint = LONG_INTEGER (unsigned)

Now you can use these as normal identifiers in your code (which are synonymous with their underlying type):

private INTEGER Initialize()
{
    // TODO: ...
}

(Note: In the example above, the INTEGER return value is interchangeable with ushort. The compiler does not identify INTEGER as a separate type, and both INTEGER and ushort can be used in the same manner for this reason.)

I typically avoid using these for any of my classes that don’t need to interface with SIMPL+. The purpose of these aliases is to make SIMPL+ API’s create more similarities in comparing your C# source code.

Compiler Directives & Build Configurations

Using the build configurations provided by default can be beneficial even if you don’t create your own. You’ll notice two that are available by default; Debug and Release. Both of these configurations also have a predefined set of defined compiler constants that you can use to include or omit sections of code in the compiled output. There are other benefits to understanding default build configurations. The Release build for example, enables compiler optimization whereas the Debug configuration does not.

Let’s talk about the compiler for a bit though. Have you ever considered the fact that there is a slight performance impact in having diagnostic code in your compiled dll’s (i.e. console output messages, logging mechanisms, etc)? Even if you enable/disable your debug messages and logging functionality at runtime, there is conditional logic which determines whether to call your debug functions that still need to be evaluated by the CPU. This can be mitigated by telling your compiler to omit this code entirely for a certain build configuration like the Release configuration using conditional directives.

public INTEGER Load(STRING configFilepath)
{
#if DEBUG
    CrestronConsole.PrintLine("Loading configuration file");
#endif
    try
    {
        if (!File.Exists(configFilepath))
        {
#if DEBUG
            CrestronConsole.PrintLine("File does not exist: {0}", configFilepath);
#endif
            return 0;
        }
        return 1;
    }
    catch (ConfigurationLoadException ex)
    {
#if DEBUG
        CrestronConsole.PrintLine("Failed to load configuration: {0}\n\tReason: {1}", ex.Filepath, ex.Reason);
#endif
    }
    return 0;
}

This may be undesirable if you are writing a module that you need to support though, since you might
still want to allow the end-user to turn on debug messages at runtime so that you can help them troubleshoot. This feature may prove useful to selectively omit more verbose debug calls however that aren’t meant to be shown in your production build, and to limit your debug output and/or logging to the end-user level debug output.

The above code looks somewhat ugly too, and if we ended up writing our entire library like this, readability would probably suffer. You could create a function that internally calls CrestronConsole.PrintLine() and have a single pair of #if DEBUG / #endif around that call within the function, although the function call which does nothing is still compiled into the assembly; there is still a better alternative. There is the ConditionalAttribute class, which is already provided by the .NET framework. Using custom attributes can be very useful in C#, and this one allows us to have regular function calls in code, but omit the call itself if the specified directive is not defined.

public const string DEBUG_FLAG_DIRECTIVE = "DEBUG";
[Conditional(DEBUG_FLAG_DIRECTIVE)]
public static void WriteLine(string message, params object[] args)
{
    CrestronConsole.PrintLine(message, args);
}

If we put this into a class named Debug, an example call would look something like this:

Debug.WriteLine("Configuration load exception: {0}", ex.Message);

No need for any of that conditional directive bloat either which increases the line count and negatively impacts readability! In this case this function call is only compiled if we’re running the assembly which was compiled under the Debug configuration, and not the Release configuration. More information can be found here: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.conditionalattribute

Note: This is modern documentation, but the concept is still the same for us.

Cleaning Up the Public API

If you’re writing a library with more than a few classes, and you want to hide certain classes from the public API, you can mark things as internal. This accessibility modifier ensures that usage is still permitted from within the same assembly (or those external assemblies defined in the InternalsVisibleToAttribute definitions in the main assembly). Classes marked as internal hides them from the generated SIMPL+ API.

Passing Arrays to SIMPL+
You cannot return arrays directly back to SIMPL+ because there are no return types in SIMPL+ such as INTEGER_ARRAY_FUNCTION. You can return them via callbacks though. One somewhat “universal” solution involves utilizing inheritance and generics. You can even extend such functionality by expanding upon and deriving from the following class:

/// <summary>
/// Generic array wrapper class for SIMPL+ compatibility.
/// </summary>
/// <remarks>Inheritors of this type for typed array wrappers can be
/// passed back to SIMPL+ using callbacks but cannot be returned from
/// functions back to SIMPL+ for some reason.</remarks>
/// <typeparam name="T">Array element type</typeparam>
public abstract class CrestronArray<T>
{
    protected readonly T[] ObjArray;
    public T[] Elements { get { return ObjArray; } }
    public int Length { get { return ObjArray.Length; } }
    protected CrestronArray() { }
    protected CrestronArray(T[] array) { ObjArray = array; }
    public virtual int IndexOf(T item)
    {
        for (int i = 0; i < ObjArray.Length; i++)
        {
            if (ObjArray[i].Equals(item))
                return i;
        }
        return -1;
    }
}

This class is marked as abstract since it is not meant to be instantiated directly. It exposes an accessor for the Elements, as well as the Length of the encapsulated array. There is also a virtual function, IndexOf(), which is a useful function to have for all derived classes as a searching operation, but you might choose to add others depending on your requirements. Now for a derived class example:

/// <inheritdoc />
/// <summary>
/// String array for SIMPL+ compatibility.
/// </summary>
public sealed class StringArray : CrestronArray<string>
{
    [Obsolete("Provided only for S+ compatibility", false)]
    public StringArray() { }
    public StringArray(string[] array) : base(array) { }
}

Here we are implementing a string array wrapper class, and inheriting from the abstract generic class from above. For the parameterless constructor, we’ve marked it with the ObsoleteAttribute to help prevent unwanted usage from SIMPL# since we want to always have the object’s internal array properly initialized by only calling the constructor that passes an array of the specified type. The parameterless constructor is only necessary to keep the type compatible with SIMPL+. Generics are not directly supported in SIMPL+, which is the reason why we can’t just pass CrestronArray<string> directly back into the SIMPL+ layer. This solution works because SIMPL+ doesn’t care if the object inherits from a generic type. Usage in SIMPL+ is relatively straightforward:

a) Retrieve an element at index i

element = obj.Elements[i];

b) Get the array length

SIGNED_LONG_INTEGER n;
n = obj.Length;

c) Get the index of a particular element

SIGNED_LONG_INTEGER n;
n = obj.IndexOf("Name");

User-Interactive Callbacks
Say we had a module that needed to respond to a callback fired from SIMPL#. A typical example would be to acknowledge or decline a host key received during an SSH connection to a server. We could start the connection process from SIMPL+, but there’s no way to tell when we receive the host key other than an event in the SIMPL# SSH library that we can use to create a callback. To our benefit, SIMPL+ delegate callbacks can also be bi-directional. This would allow us to accept or decline the host key in response to the callback fired from SIMPL# (to proceed with, or abort the connection).

private bool OnValidateNewHostKey(byte[] key)
{
    var target = ValidateNewHostKey;
    if (target != null)
    {
        bool autoAcceptKey = target.Invoke(key.ToHexString()) != 0;
        if (!autoAcceptKey)
        {
            _hostKeyAcknowledgedEvent.Reset();
            if (!_hostKeyAcknowledgedEvent.Wait(10000))
                return false;
            return AcceptNewKey != 0;
        }
        return true;
    }
    return false;
}

In the code above, we’re using the callback to determine whether to auto-accept the key or wait for a manual trigger to check a flag that gets set to determine whether to accept or decline the new key. ValidateNewHostKey is the target callback that invokes the SIMPL+ callback, and this method is assigned to the event handler of the wrapper class I made for my SSH client which gets invoked by the event invocator in the code below (OnAcceptNewHostKey()).

– If the SIMPL+ callback returns TRUE (as a way to indicate that autoAcceptKey has been enabled on the SIMPL+ module, then we return true to accept the new key immediately.
– If the SIMPL+ callback returns FALSE (indicating that we aren’t automatically accepting new keys), then we wait to be signaled on the calling thread and check the AcceptNewKey property to see whether this new key should be accepted or not. The timeout on the Wait() method is there to allow the key to automatically be declined after a certain amount of time if the user hasn’t accepted or declined the key manually so we can free this thread from just sitting there doing nothing. (In this case we assume that the new host key cannot be trusted. If there is no user interaction, we default to declining the received host key.)

The signal to the waiting thread comes from the _hostKeyAcknowledgedEvent being Set() within the mutator on a property which is accessible from SIMPL+:

private INTEGER _acceptNewKey;
public INTEGER AcceptNewKey
{
    get { return _acceptNewKey; }
    set
    {
        _acceptNewKey = value;
        _hostKeyAcknowledgedEvent.Set();
    }
}

The following code is invoked from the SSH.NET library when we receive the host key from the server:

if (OnAcceptNewHostKey(e.FingerPrint))
{
    Debug.WriteInfo("Writing SSH key to file: '{0}'", filepath);
    using(var fs = new FileStream(filepath, FileMode.Create, FileAccess.Write))
    {
        fs.Write(string.Format("Fingerprint: {0}\nKey: {1}", fingerprint, key),
                 Encoding.UTF8);
    }
    e.CanTrust = true;
}
else
{
    e.CanTrust = false;
}

The corresponding SIMPL+ for the unique callback looks like this:

CALLBACK INTEGER_FUNCTION ValidateNewHostKeyHandler(STRING key)
{
    manual_host_key_validate_fb = FALSE;
    if (!auto_accept_new_keys)
    {
        // user must take action to validate received host key otherwise
        // the key will be automatically rejected after a timeout period.
        fingerprint$ = key;
        manual_host_key_validate_fb = TRUE;
        return (FALSE);
    }
    return (TRUE); // auto accept new host key
}

And the two SIMPL+ events that invoke to signal the thread are as follows:

PUSH accept_new_key
{
    ssh.AcceptNewKey = TRUE;
    fingerprint$ = "";
    manual_host_key_validate_fb = FALSE;
}
PUSH decline_new_key
{
    ssh.AcceptNewKey = FALSE;
    fingerprint$ = "";
    manual_host_key_validate_fb = FALSE;
}

manual_host_key_validate_fb is there as feedback to show a popup or something on the UI which allows the user to accept_new_key or decline_new_key respectively. In essence, it acts like our callback to SIMPL Windows, so the output signal can trigger conditional logic that tells the program that a new key needs to be either accepted or declined.

Inter-Symbol Communication (not Inter-Process Communication)
The most basic way is to use a static instance for talking between modules in SIMPL, without having to actually pass data back through SIMPL+. You can store all your information directly in the static instance, or implement a more advanced model like a publisher/subscriber implementation. In my case, I have a static publisher object that I can call upon to send notifications out to a defined set of subscribers, or globally, and this makes for a very dynamic but powerful solution over just a simple static class that stores all the information directly (although it definitely is a lot more work to set up so I won’t cover that here).

public class MyModule
{
    private static readonly Configuration _configuration = new Configuration("...");
    // ...
}

In the code above, we have a static readonly field that is implied to hold some kind of configuration. For every SIMPL+ module using this class, the important thing to remember here is that there is only a single instance, of which is shared across all other SIMPL+ modules. What you do with this is now up to your imagination. You can also create static events that will fire for every instance of your SIMPL+ module, it doesn’t necessarily need to be a static object to share information.

Note: SIMPL+ module events run from a threadpool, the other thing you have to keep in mind here is thread synchronization (which I’ll get to in the next example). Because this instance is shared, a lot more threads will be requesting access to this Configuration class, but threading is a problem we have to deal with regardless because of the way SIMPL+ was designed.

Thread Synchronization
For those unfamiliar with this concept, it is any mechanism that limits the number of threads that can access a shared resource at any given time. Unlike in SIMPL+ where you used a variable to restrict execution, SIMPL# has a lot more to offer.

Variable assignments and conditional checks are not really an acceptable way to deal with thread-safety in most contexts (including C#). In addition to having constructs that can block a thread for a timeout period, or indefinitely, there are also classes that allow you to implement thread synchronization in the following ways:

  1. Across program slots, or only within the same program.
  2. Events that get manually reset or reset automatically, which allow you to implement threads that must wait to be signaled. (This is very useful for initialization processes.)
  3. Semaphore’s which grant a specific number of threads access to a shared resource or pool of resources. (Note: This must be implemented or ported manually, or you can use one of the ports that others have provided to the programming community.)

The flexibility here is vastly greater than what is provided in SIMPL+, however, that doesn’t come without its set of responsibilities! You still must properly implement thread-safety, or you might find yourself spinning up threads in your modules that just sit there because you either forgot to release a lock. (You may have even created a deadlock!)

Note: It is important to avoid the lock statement as a means of thread synchronization. There is a known bug on the WinCE platform for 3-series regarding thread contention which may lead to a deadlock scenario.

Locks can also be expensive in some cases. If you are writing code that is performance critical, atomic instructions may be available to help you maintain the necessary performance, but keep your code thread-safe. One class that you can use to achieve this is the Interlocked class, which provides static methods to perform such atomic instructions to increment/decrement, exchange (conditionally and unconditionally), and more. (Some of the .NET BCL classes provide thread-safety entirely by using the Interlocked class for performance reasons, but this doesn’t mean get carried away with micro-optimization; only use it where it is necessary.) Some wrapper classes are also by default thread-safe for certain things, or fully thread-safe, so thread concurrency is already dealt with for you. In scope-based scenarios with things like CCricitalSection for example, you would call to Enter() to acquire the critical section lock, and Leave() to relinquish that lock which would allow another thread to obtain it. You should get into the habit of calling these functions within the try and finally branches of a try statement respectively. The reason for this is simply because even in the event of an exception that happens after the lock has been acquired, the finally block will ensure that the lock is released by executing Leave().

try
{
    criticalSection.Enter();
    // do work here
}
finally
{
    // this is called even if the work being done
    // after the Enter() call throws an exception.
    criticalSection.Leave();
}

IDisposable and the GC
You may have heard people talk about disposing objects via Dispose() explicitly or implicitly the using statement. The IDisposable interface is one that should be properly implemented to avoid problems down the road. For a managed code like .NET, we have what is called the garbage collector. The rules governing garbage collection can be a fairly lengthy topic so I will omit that discussion, perhaps until a very rainy day.

For simplicity, any object that implement the IDisposable interface will need to be disposed of accordingly in SIMPL#. If you create a wrapper that contains IDisposable members in your class, then you should also make your class implement IDisposable and create the proper dispose pattern to free your managed resource.

(There are other rules when you’re creating something designed to be inherited from, but again, I want to keep things simple for now.)

If P/Invoke was not hardcoded to be sandboxed for us, you would also have to do this for any unmanaged resources, but since it doesn’t apply to us (for 3-series) we’ll only be talking about managed resources. The idea is to ensure that you call Dispose() on any object that implements the IDisposable interface when you no longer need it so that unused resources can be freed up cleanly. Just like with thread synchronization objects, there is also a statement that is used to ensure Dispose() is called within a scope-based scenario, and that is the using statement.

using (var fs = File.OpenRead("file.txt"))
{
    // TODO: ...
}

There’s nothing special about this statement, it’s what we call syntactic sugar; specifically for a try-finally setup. The following is equivalent to what was written above:

{
    var fs = File.OpenRead("file.txt");
    try
    {
        // TODO: ...
    }
    finally
    {
        if (fs != null)
        ((IDisposable)fs).Dispose();
    }
}

The surrounding braces are there to define additional scope because just like in the using statement example above. The FileStream object is not accessible from outside of the statement’s scope. Failure to properly dispose of an object, especially if you’re creating many objects on the heap, will force the GC to do a clean sweep at some point to clean up all the garbage that you didn’t clean up properly. This is an expensive task, and negatively impacts performance across the entire program. This is also the reason why some developers implement object pools, especially in game development; sacrificing framerate is a sin.

Extension Methods
Extension methods can be useful. The syntax for calling an extension method is slightly different to a standard method call where you would just call the method and enclose all of the parameters within the parenthesis of the call. Extension methods are called as an extension of the first (this) parameter, hence the name “extension method.” Let’s show an example to demonstrate what I mean by that.

The following is a method I’ve used in some of my own libraries to ensure that I don’t pass a null string to a callback method for SIMPL+:

public static class Extensions
{
    public static SimplSharpString ToSimplSharpString(this string input)
    {
        return input ?? string.Empty;
    }
}

This method returns string.Empty if input is null, otherwise it returns the value of input if input is not null. Why do this? Because the regular implicit conversion operator for string to SimplSharpString will throw an exception if the string is null since it tries to calculate the length of the string. You can’t call upon the Length property if the string is null. The way you invoke an extension method is via the dot notation on the first parameter, negating the need to pass that variable in as a “normal” argument. See the following:

string data = "testing";
data.ToSimplSharpString();

Assembly Information & Reflection
I figured these topics kind of go hand in hand, especially with the example that I’ve prepared for this section, so I’ll talk with respect to both in this part. There is one particular file we’ll take a look at in this section as well, and that is the AssemblyInfo.cs file. If you take a look at your solution explorer, expand the project you’re working on, then open the “Properties” folder, the AssemblyInfo.cs file should be in there.

By default, that file should contain something like the following:

using System.Reflection;
[assembly: AssemblyTitle("MyProject")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MyProject")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyVersion("1.0.0.*")]

These are the pieces of information that get compiled into your assembly after a compile in the metadata storage streams of the file.

(Note: The wildcard asterisk in the AssemblyVersion line allows for the assembly version to be incrementally changed automatically with every new build output.)

Did you notice the using directive for System.Reflection in the top line? Well we’re going to see how to extract this information at runtime using reflection, because assembly metadata and reflection are closely related. Reflection is feature provided by .NET to find out information about managed items, as well as the ability to dynamically create, and invoke types. It is useful, but it can also be relatively expensive to use in your program, so use it sparingly! The following is an example that will retrieve the assembly version as seen above (with the current numeric value of the wildcard) at runtime:

var asmName = Assembly.GetExecutingAssembly().GetName();
CrestronConsole.PrintLine(asmName.Version.ToString());

There are other properties aside from Version that can be retrieved from the asmName variable as well.

There’s no Company or Product property here though so the way to access those is to get the custom attributes for the assembly, and find the appropriate one:

Assembly asm = Assembly.GetExecutingAssembly();
object[] attributes = asm.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
if (attributes.Length > 0)
    CrestronConsole.PrintLine(((AssemblyCompanyAttribute)attributes[0]).Company);

A few valid reasons why you might want this kind of information, would be licensing, logging, or perhaps even some kind of watermarking. Some obfuscators like Dotfuscator will do this.

Conclusion
The road to becoming proficient in SIMPL# is going to take some time. There are lots of resources available for .NET Compact Framework 3.5 and older C# 3.0 language version. As C# becomes increasingly more popular and the SIMPL# libraries mature, eventually .NET Compact Framework, Visual Studio 2008, older C# language and CLR versions will no longer be a limitation. At that time, the gap between modern C# tutorials and the relevant learning material for SIMPL# will be a lot smaller; making it easier to understand and use. Currently, there is no shortcut to learning how the Crestron libraries work though, and some of the wrapper classes have been modified in a way that MIcrosoft documentation incorrect for the equivalent SIMPL# classes provided by the SIMPL# libraries.

If you are aiming to use modern IDE’s for refactoring SIMPL# projects, or even building them, it is going to take some custom-built solution(s) to do this, and some modification to the modern environment in most cases, but it is possible. I personally use JetBrains Rider, and I know others have been able to use VS 2017. Building the project(s) still requires VS 2008 at this point because of the plugin, however, this can be automated if you know how to deal with COM, and can write your own software. That is a topic not within the scope of this paper however.

Resources

  1. Online SIMPL# Documentation: https://help.crestron.com/simpl_sharp/ (Note: you should also have a help file located within your filesystem.)
  2. Downloads for the SIMPL# plugin: https://crestron.com
  3. Microsoft Visual Studio 2008 Pro (SP1): https://microsoft.com (Note: You will need to obtain a license through a subscription or by some other means.)