Skip to content

MatthewRyanWeber/CrestronConvert

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CrestronConvert

Converts Crestron control-system project files into browsable HTML, working directly from the compiled files a processor and touch panel actually ship with. No SIMPL Windows or VTPro-e installation required.

Current state: working converter, SIMPL-to-C# generator, and HTML5 interface generator, verified against real project sets. Roadmap in NEXT.md.

SIMPL to C# (SIMPL# Pro)

py -3.12 run.py --session "Room 406" --csharp-only

Reads the .smw source program and emits a typed SIMPL# Pro project:

File Contents Completeness
Signals.cs Every named signal as a typed property (bool/ushort/string) Complete
Devices.cs Every device with transport, RS232 framing, addressing Complete
Logic/*.cs One class per symbol, inputs and outputs in SIMPL order See below
ControlSystem.cs SIMPL# Pro entry point wiring it together Complete
CONVERSION.md Per-symbol account of what translated and what did not

On a real production program:

Count
Signals, typed and event-driven 2,036
Devices with transports 230
Translated primitives 1,323 (343 top-level + 980 inside macros)
User macros expanded inline 18 (1,665 inner symbols)
SIMPL+ statements translated 1,948 of 1,986 (98.1%)

Execution model

This matters more than any individual symbol. SIMPL does not run top to bottom. Signals propagate: changing a signal re-evaluates every symbol with that signal on an input, which may change its outputs, which propagates onward until the graph settles.

A generated program that ran each symbol once in declaration order would compile, look plausible, and behave differently. So the emitted project ships Runtime/Signal.cs with change-event signals, and every symbol subscribes to its own inputs:

public NOT(Signals signals)
{
    _signals = signals;
    // SIMPL propagation: re-evaluate whenever an input changes.
    _signals.ConfigDSPEnable2.Changed += _ => Execute();
}

public void Execute() { Output1 = !Input1; }

Two fidelity details: a signal raises Changed only on an actual change, matching SIMPL, where rewriting the same value does not re-trigger downstream logic. And propagation depth is bounded, so a feedback loop in the source program is reported through Propagation.Runaway instead of blowing the stack on a live processor.

ControlSystem.Settle() evaluates every symbol once at startup to reach a consistent initial state. After that, logic runs by propagation, not polling.

What is real and what is not

Symbols fall into three tiers and the generator never blurs them:

  • Primitives translate to working C#. Buffer, NOT, AND, OR, Stepper, the one shots, and the analog family have documented, unambiguous behaviour. A NOT symbol becomes Output1 = !Input1; bound to the real signal names.
  • Structural symbols carry no logic. SUBSYSTEM, Logic folders, and device slots become device registration, not code.
  • External modules become documented stubs. .umc user modules and .usp SIMPL+ modules keep their behaviour outside the .smw. Each becomes a stub with its exact wiring preserved, and every one is listed in CONVERSION.md.

SIMPL+ statement translation

Module bodies are translated to C#, not just described. Across all 32 recovered modules that is 1,948 of 1,986 statements, 98.1%, with 14 modules at 100%. Every generated class states its own coverage, and anything outside the supported subset is emitted verbatim as // [untranslated] so nothing is silently dropped or guessed at.

Four traps, each of which silently produces wrong C# if missed:

SIMPL+ Meaning C#
If (x = 0) Equality, not assignment if (x == 0)
x <> y Inequality x != y
wait (100) { } Scheduled block, not a sleep scheduler callback, 1000 ms
INTEGER Unsigned 16-bit ushort

wait is the dangerous one: the body runs later while execution continues, so translating it as a blocking sleep would change behaviour on a live processor.

Recovering module sources

Module sources are not loose on disk, but SIMPL Windows bundles them into the *_archive.zip it writes beside each program. A real archive holds 32 .usp, 32 .ush, and 18 .umc files, so most stubs are not opaque after all.

When an archive is present the generator reads it and emits the module's declared interface alongside the wired signals:

// SIMPL+ module interface, as declared in TCP_Client.usp:
//   in    1  Digital  Connect
//   in    4  Analog   Port_Number
//   in    5  Serial   IP_Address$  (array)
//   out   1  Digital  ConnectF
// Positions above are the module's own numbering and are
// NOT assumed to line up with the wired inputs below.

plus the module's parameters, its SIMPL+ event handlers (PUSH Connect, SOCKETCONNECT tcpClient), its functions, and its help text verbatim.

A deliberate limitation. Members stay positional (Input1, Output2) rather than being renamed after SIMPL+ ports. The .smw wiring index and the SIMPL+ port index do not correspond by any rule that could be verified against these files, and a confidently wrong port name is worse than a positional one. The interface block above gives a human everything needed to match them deliberately.

Third-party compiled drivers such as Biamp Tesira and Vaddio RoboShot are the genuine exception: no source exists in any file here. For those the right move is calling the vendor's own SIMPL# library rather than porting anything.

This does not produce a drop-in replacement for a running program, and does not claim to. It produces a faithful typed skeleton with every signal, device, and connection intact and the mechanical logic already written.

Screenshots

The generated HTML5 interface in the semplice-blue theme, with every page recovered from the panel file:

Generated interface pages

Clicking a control emits the real join it would send over CH5. Here Vol+ sends digital join 1, exactly as it was wired in the original panel, with the mock bus echoing feedback:

Mock join bus

The analysis report, generated from the same source files:

Analysis report

One-click conversion

py -3.12 run.py --session "Hamilton 406" --only "Hamilton"

That single command creates a session folder on the Desktop and does all of this:

Hamilton 406/
  original crestron files/   verbatim copies, sha256-verified, read-only
  html5/<project>/
      preview.html           offline preview with a mock join bus
      ch5/index.html         deployable CH5 project
      ch5/project.json       the join contract, for diffing
      CHECKLIST.md           what needs a human before deployment
  reports/                   full analysis report
  session.json               provenance chain: source path, sha256, timestamps

Backups are written and verified before conversion starts. If the HTML5 interface misbehaves on hardware, the originals in original crestron files are what gets reloaded onto the processor and panel.

What it does today

Reads the compiled artifacts, cross-references them, and emits one self-contained HTML report per project set:

Input Format (verified by magic bytes) What is extracted
.smw ASCII INI-like records SIMPL source program: devices, manufacturers, serial port settings, and the full logic graph
.sig [RLSIG0001] or [LOGOSSIG001.000] Full signal table, named and symbol-scoped
.spz ZIP Fusion/RoomView join map from the embedded .rvi
.vtz ZIP Panel pages, controls, and every join from Environment.xml
.vtp OLE2 compound file Stream inventory (fallback when no .vtz)

The report has six tabs: Overview, Join crosswalk, Panel objects, Program signals, Fusion map, and Orphans. Everything is inline, so a report is a single file you can email or attach to a ticket.

The join crosswalk

This is the point of the tool. The panel side and the program side each know join numbers, but no Crestron tool shows them side by side. Joining them exposes:

  • panel-only joins: a control drives a join the program never declares
  • program-only joins: logic with no user interface
  • shared joins: one join driven by several objects, sometimes deliberate, sometimes a bug

Usage

py -3.12 run.py --list                      # show discovered project sets
py -3.12 run.py                             # convert everything under source_root
py -3.12 run.py --only "Hamilton"           # convert one project set
py -3.12 run.py --session "Room 406"        # session folder with verified backups
py -3.12 run.py --profile epson             # manufacturer string variant
py -3.12 run.py --no-html5                  # analysis report only
py -3.12 run.py --list-profiles             # available string profiles
py -3.12 run.py --list-sessions             # existing sessions
py -3.12 run.py --verify-session <path>     # re-check a session's backups
py -3.12 run.py --source "D:\Some\Other\Folder"

Paths live in config.json, never in code.

Why the HTML5 interface can work at all

A Crestron touch panel runs no logic. It exchanges joins with the processor: digital for buttons and feedback, analog for levels, serial for text. Every projector command, serial string, and IR burst lives in the processor program and keeps running untouched.

So converting a panel to HTML5 never reimplements device control. It reproduces the join contract, and the processor keeps driving displays exactly as before. That is why Vol+ in the screenshot above only needs to send digital join 1: the program already knows what to do with it.

The deployable output targets CH5, Crestron's supported HTML5 mechanism, and needs cr-com-lib.js from the CH5 toolchain alongside it. The preview.html is self-contained and needs nothing, which is what makes the conversion testable before any hardware is involved.

Themes

The original panel's appearance cannot be recovered by parsing. Its buttons and backgrounds are bitmap assets compiled into the project's SWF files; the theme XML carries only text colours. Verified on a real project set: all eight panels share one CT_Semplice_AAVS theme with byte-identical previews, and the XML holds just 472 colour entries, all of them text, against 1,495 ImageID references.

So the look of the generated interface is defined in html5/themes.py rather than extracted:

py -3.12 run.py --list-themes
py -3.12 run.py --session "Room 406" --theme semplice-blue
Theme Look
semplice-blue (default) Grey buttons on a deep blue background, the classroom house style
dark Neutral dark interface
light High-contrast light interface for bright rooms

The active-state colour #0098ca is the one accent that genuinely does appear in the original theme XML.

Manufacturer string variants

The same UI ships to rooms with different display brands. Profiles live in strings.json as data, not code:

py -3.12 run.py --session "Lab" --profile epson   # "Display" becomes "Projector"

Design rules this project follows

  • Dependency gate first. deps.py verifies Python 3.12+, installs missing packages, and aborts below 50 GB free disk. It never silently skips.
  • Streaming with checkpointing. Records go through the pipeline one at a time into SQLite and checkpoint on an interval. A crash at record 10,000 of 18,565 resumes at 10,001. Memory stays flat.
  • Fail loud. If more than 1% of signal records fail the grammar, the run aborts rather than shipping a partial report. This caught a real bug during development: 16.41% of records were being misread, and the threshold forced a correct fix instead of a quiet fallback.
  • Formats verified, never assumed. Every container is identified by magic bytes. Where a file states a value twice (the signal id appears as both trailing hex and a binary trailer), the parser cross-checks them and aborts on disagreement.

Format notes

Documented from real files, since Crestron publishes none of this.

.sig signal table

[RLSIG0001]          record text is cp1252
[LOGOSSIG001.000]    record text is UTF-16LE

per record:
  uint16 LE total_len      self-inclusive, counts these 2 bytes
  payload (total_len - 2)  encoded text + 6-byte trailer
      trailer[0:4]  uint32 LE signal id
      trailer[4]    flag_a
      trailer[5]    flag_b

Two record forms, both valid:

  • named: the programmer's own globals, e.g. Display-01_Power_On, TP_Room_Name$. A // prefix means declared but never driven.
  • symbol: scoped to a SIMPL symbol with a path and a repeated hex id, e.g. ::Trig$:S-1.1:S-3:S-8.10.2.00004FD1

.smw SIMPL source program

Plain ASCII, INI-like. Bracketed records of CRLF-separated Key=Value lines, with ObjTp naming the record type. Vocabulary confirmed against 40 real programs, all of which parse:

ObjTp Meaning Key fields
Sg Signal H handle, Nm name, SgTp type
Sm Symbol SmC class, PrH parent, I1..In inputs, O1..On outputs
Dv Device Nm, Ad address, ProdLine
Db Device database Mnf manufacturer, Mdl model, Tpe type
Cm Comm port Ptl protocol, BRt baud, Pty/DBt/SBt framing
Et / Cs Ethernet / Cresnet IPA, IPM
CED SmartGraphics control SGControlType, SGControlName

Signal typing was established from the data rather than guessed. The compiler emits reserved placeholder signals that name their own type, which pins the mapping exactly: SgTp absent is //__digital_reserved__, SgTp=2 is //__analog_reserved__, and SgTp=4 is //__serial_reserved__.

This is what backs the claim that the conversion retains all coding and physical sends. On a current production program the parser recovers 8,662 logic connections, 230 devices, the RS232 framing for each serial device, and the manufacturers driven (Crestron, DirecTV, Lutron).

.vtz panel archive

ZIP. swf/Environment.xml is UTF-16LE XML holding the whole project. An object is any element with a ControlName child; joins are any descendant tag matching *Join*, which covers the 31 distinct join tag names observed without hardcoding control types.

Verification

Ten project sets, three consecutive clean runs, exit 0, 0.00% unrecognized records across 130,000+ signal records.

About

One-click converter for Crestron control-system projects: parses compiled .sig/.spz/.vtz/.vtp files and emits a searchable HTML join crosswalk. No SIMPL Windows or VTPro-e required.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages