Platform index / Video presentation switcher

Analog Way Midra series

Six models, one firmware, one set of bitstreams. The layer engine lives in FPGA fabric that is configured identically on every unit in the range, so the tier a machine sells as is decided by an integer it reads at boot.

DocumentedIntroduced 2013MidraLPC43xx3 × FPGAADV7842HDBaseTOne image, six modelsTCP 10500562 control variables

The load-bearing finding. The updater flashes one CAMELYA.hex, one FELINDRA.hex, one TATIANA.hex and one MCU application to every model in the family, with no per-model branching anywhere in the script. The compositor is in those bitstreams — so a Pulse2 is running the identical silicon configuration to an Eikos2.

Verdict. Within a platform the model tier is enforced in software, from a device-type integer read at boot; between the base and -H boards it is genuinely different hardware. The evidence for the first half is the update script, which has no per-model branching and ships exactly one bitstream per FPGA for the whole family, plus a single MCU application that carries the complete FE1–FE6 chip map and the complete sixteen-model table. The evidence for the second half is in the same package: the Chtiderman build replaces the analog RGBHV output with Valens VS2000 HDBaseT transmitters and receivers, which is a different board, not a different setting. What is really built differently between the base models is the connector and daughter-card population — the two optional SDI cards, and which of the six front ends and their EDID EEPROMs are fitted. A Pulse2 does not have fewer layers because it has less silicon. The control plane recovered since is consistent with all of it: one variable table for the whole family, dimensions declared per model, and a device that hands you its own device-type integer the moment you connect.

Method

Two bases, deliberately kept apart. The silicon findings are static analysis of one vendor updater package, cross-read against the vendor's own control client — no unit was opened, and the package's own manufacturer update log from a real PLS350 is their observation of a live machine, not ours. The control-plane findings are not file-only: the client's AVM2 bytecode was decompiled and its variable-table constructor replayed to reconstruct the whole control model, and on 2026-08-06 a real Pulse2 on our LAN was read and written through our own client, with RCS2's own session against it captured through a logging proxy.

Artifacts examined

  • midra_updater_v02_02_02.exe — Inno Setup 5.5 installer containing Device.xml, Script.xml, six module scripts and every firmware component as plaintext Intel HEX
  • Logs.txt, shipped inside that package — a 2013 update session against a PLS350, naming the components a physical unit answered for
  • MDR_launcher.swf — the RCS2 control client; its AVM2 constant pool carries the complete DEV_*, CARD_*, LAYER_* and plug enumerations
  • RCS2.app — the Midra client, an Adobe AIR application (MDR.RCS v2.2.03). The MDR_launcher.swf inside it is a CWS (zlib) container whose Vars::VarCom initialiser carries the entire control model: 562 variables in 49 groups, each constructed with 15–16 positional arguments — name, group, min, max, default, dimension list, request and answer mnemonics, and flags
  • A logging proxy between RCS2 and a live Pulse2, recording the exact command sequence the vendor client sends to assign a layer source — the one operation the recovered table alone does not explain

No manufacturer firmware, binaries or documentation are redistributed here. Artifacts are referenced by name and version only. See the method and legal statement for the basis on which this analysis was performed and the boundaries it observes.

Hardware architecture

ItemFindingConfidence
Host MCUOne micro, codenamed CALIMERO. Dual-bank flash at 0x1A000000 and 0x1B000000 with the application 32 kB into bank A, 512 kB per bank, and an SRAM stub executed at 0x20000000 — that is the NXP LPC18xx / LPC43xx memory map. Product.xml corroborates with an NXPBootStartTimeout parameter. The exact part was not identifiedInferred
Flash layoutApplication code 0x1A0080000x1A07D7CF (481,232 B); const data 0x1B0000000x1B02EB0B (191,244 B); factory-test data 0x1B030000; factory-test code 0x1B040000. Every string, chip name and lookup table lives in the const image, not the code imageVerified
FPGAsThree, codenamed CAMELYA, FELINDRA and TATIANA, each addressed as its own daughter card (CARD_CF_CAMELYA, CARD_CF_FELINDRA, CARD_CF_TATIANA). One bitstream file eachVerified
Bitstream containerIntel HEX wrapping a 43-byte proprietary header at 0x20 and a payload from 0x33F. Payloads are 3,222,997 / 3,348,122 / 3,279,834 bytes at 6.50 / 6.61 / 6.58 bits per byte — uncompressed structured data, i.e. raw configuration bitstreamsVerified
FPGA vendorNot identified. No Xilinx sync word, .bit header or Altera/Lattice preamble survives any bit rotation, byte reversal or inversion of the payload. Three different lengths for uncompressed bitstreams implies three different parts rather than one part carrying three designsUnknown
Input siliconFE1–FE4 Analog Devices ADV7842 — HDMI receiver, analog AFE, colour processor, SD decoder, InfoFrame and HDCP repeater, each a separate I²C sub-address. FE5–FE6 ADV7611, HDMI only. Read from the firmware's own I²C device-name tableVerified
Output siliconBE1 and BE2 each carry a Silicon Image SiI9024 HDMI transmitter, a TI THS8200 video DAC and a Silicon Labs Si570 programmable pixel clock. BE3 is an ADV7301 or ADV7341 for the monitoring outputVerified
AudioCirrus Logic CS8422 × 2 (asynchronous sample-rate converter / AES receiver) and CS4265 × 2 (codec)Verified
EDIDOne EEPROM per connector, not per input — 18 individually addressable positions spanning EDID_ANA1..4, EDID_DVI1..4, EDID_HDMI1..6 and EDID_HDBT1..4. The 2013 PLS350 log shows a real unit answering for twelve of themVerified
Daughter cardsCARD_MOTHERBOARD, the three FPGA cards, CARD_CF_AUDIO, CARD_CF_FAV, CARD_CF_VIDEO_OUT, CARD_CF_SDI_IN_1, CARD_CF_SDI_IN_2, CARD_XPORT. SDI input is two optional cards — "CF" is carte filleVerified

Software architecture

ItemFindingConfidence
Operating systemNo FreeRTOS, ThreadX, µC/OS, embOS or Linux string appears in either image, and the client's microcontroller enum has exactly one entry (MIC_CALIMERO). A single bare-metal micro drives the whole machineVerified
Model tableA flat 16-entry, 12-byte-stride string table at 0x1B02808C: MDRdbg, six base models, MNMdbg, six -H models and two -H LE models. Device type is 256 + i across the first block and 280 + j across the secondVerified
Models the updater acceptsTwelve, in Device.xml: 257 EKS550 (Eikos2), 258 SPX450 (Saphyr), 259 PLS350 (Pulse2), 260 SMX250 (SmartMatriX2), 261 QMX150 (QuickMatriX), 262 QVU150 (QuickVu), then 282–285 and 287–288 for the -H and -H LE variantsVerified
Models the firmware knowsSixteen. 281 EKS550-H and 286 QVU150-H exist in the firmware model table and in the control client's DEV_* list, but are absent from the updater's compatibility list — built into the code and, apparently, never shippedVerified
Two platforms, one packageThe updater carries two complete MCU applications — Calimero_* ("MDR") and Chtiderman_* ("MNM") — and flashes both unconditionally. Only the Chtiderman model table contains the two LE SKUsVerified
Layer modelPer screen: LAYER_FRAME background, LAYER_PIP_1..4, LAYER_LOGO_1..2 and LAYER_AUDIO. Two screens (SCREEN_S1, SCREEN_S2), two outputs, main and preview. Keying is luma or chroma, each with a downstream variantVerified
Protocol spaceThe superset, not any one model: 10 inputs across 5 plug types (ANALOG_HD15, DVI, SDI, HDMI, HDBASET), 2 outputs across 8 plug types, 8 still frames and 8 logos, 8 preset memory slots, 3 case fansVerified
Control interfacesRS-232 at 115200 8N1, USB CDC, and TCP 10500. Product.xml gives all three, with a factory default of 192.168.2.140Verified
Control protocolA terse ASCII protocol on TCP 10500, and it is asymmetric: an outbound command puts the mnemonic last (1,2,5PMinp sets, 1,2,PMinp requests) while the device's reply puts it first (PMinp1,2,5), with the final comma-separated field as the value. Midra's client appends CRLF itself; the device answers CRLF. Mnemonics are five characters, with three debug specials whose request and answer differ — ?DEV, @ADBG, >DDBGMeasured
The variable table562 variables in 49 groups, 190 of them read-only status. Every one declares its own dimension list, minimum, maximum and default — but those are the vendor's declarations, read out of the client, not limits anyone has measured. Treat them as strong hints; the device is the authorityVerified
Device identity on the wireMidra has no PDEV — asking for it draws E10. It pushes DEV on connect (259 on our Pulse2) and answers DIdsn serial (2165), VEvar version (13) and DIdre reference (240215). A Midra model map keys on DEV; the LiveCore generation's keys on PDEV, and a client that assumes one shape sees the other generation as a dead socketMeasured
Screen and layer model, as a Pulse2 reports itUp to 2 screens; SCmly maximum layers is read-only 0–4 and reads 2 for screen 0 on this unit; layers are indexed [screen(2), ctx(3), layer(8)]; INava[10] available inputs reads 8. Geometry carries the same +32768 centre bias as LiveCore — a centred 1080p layer reads PRpoh=33728, PRpov=33308, PRsih=1920, PRsiv=1080 — so the two generations share their positioning arithmetic even where they share no mnemonicsMeasured
Take and layout verbsGCtak[screen] takes one screen, GCtal takes all, GCtba is the T-bar, with GCtav/GCtio as status. GCqly[screen,ctx] applies one of 27 built-in layouts and reads back 0, because it is a momentary trigger rather than stored state, and it sets geometry only — sources are assigned separately. There is no selected-layer variable at all, which is a Midra/LiveCore differenceMeasured
Programme is protected; preview is notGeometry writes are accepted, echoed back and raise GCtav (TAKE_AVAILABLE): the device holds the edit as a pending change until a take commits it. A source write aimed at the programme context is echoed back as the old value with a CTsto pulse — the device declines it. The vendor client's own path, captured through the proxy, writes the source to the preview context with preset-update-mode on; a programme-context edit re-baselined with PIwur sticks too. Both were confirmed on the unitMeasured
Memory modelEight preset slots. PMpst[slot] is the used flag and the stored content is readable per slot but not writable — a direct write to stored content reverts the same way a protected live write does. Save and erase are request variables. Recall is not a device command at all: the vendor client reads the stored slot and re-applies it to the live preset, so on this generation a memory recall is client-side arithmetic, and any controller that expects a one-command recall is waiting for a reply that never comesMeasured
HTTP surfaceThe device serves HTTP on port 80 alongside the control socket, including per-input thumbnails as BMPs cache-busted by timestamp (/assets/Snapshots/capture_in_8.bmp?time=…). Source previews in a third-party controller cost nothing to buildMeasured

Update path & security model

ItemFindingConfidence
Image signingNone. No RSA, SHA, AES, HMAC or ECDSA string appears in either image, and every firmware component ships as plaintext Intel HEX — no container, no compression, no obfuscationVerified
Update validationA version check, a transfer and a read-back verify per component (Kd, Th, Kf in the script grammar). There is no signature step in the grammar at allVerified
BootloaderTwo-stage. A resident bootloader at node address 1 exposes only MICRO_RAM; the updater pushes a 40,900-byte stub into AHB SRAM, jumps to it with the script's Ga 0x20000000 opcode, and that stub performs every flash operationVerified
EntitlementNone found. No licence, unlock, activation, option-key or dongle string in either MCU image or in the control client. This generation has no feature-key system — unlike the LiveCore generation that followed it, which has oneVerified
Control-plane authenticationNone observed. The control port accepts a session with no credential step and starts pushing state immediately — the Pulse2 pushes its own DEV device code on connect — and the socket that reads is the socket that writes. Concurrency is the unmeasured half: Analog Way ship AWGateway, an application whose entire purpose is to hold one session to the device and re-serve port 10500 to several clients, which is what you build when a device accepts very few. The limit itself was never measuredMeasured
Rejected commandsA rejected command is answered EE10 for a mnemonic not in the table, E12 for the wrong number of index fields, E13 for a bad index count. An empty line draws no response at all. Nothing in the error path distinguishes not permitted from not understood, because there is no permission model to reportMeasured

These rows describe how a platform validates a firmware image, because that is a structural fact about its architecture. They are not a vulnerability disclosure and no exploit, bypass or circumvention technique is published here. Licensing and entitlement mechanisms are out of scope throughout — seescope boundaries.

What is programmable

  1. 1The control protocol

    ASCII over TCP 10500 or RS-232, and no longer merely recoverable: the full 562-variable table has been reconstructed from the vendor's own client and the framing, identity, geometry, take and memory paths confirmed on a real Pulse2. The client is an Adobe AIR application and the platform is long out of support, so a modern replacement controller is the obvious and entirely legitimate project — and it needs nothing from the firmware.

  2. 2EDID management

    One EEPROM per connector, individually addressable through the documented update path, with the stock EDIDs shipped as readable 256-byte HEX. This is the one part of the machine's behaviour a third party can change through a supported mechanism.

  3. 3The FPGA fabric — in principle only

    The compositor is entirely in the three bitstreams, so nothing about the video path can be changed without them. They are unencrypted, but the container is proprietary and the parts are unidentified, which puts any real work here behind two unsolved problems and a bench.

Traps

Mistakes this analysis actually made, or came close to making. They are recorded because each one produces a plausible-looking wrong answer rather than an obvious failure.

  • The first 80 bytes of every bitstream payload contain only 0x00 and 0x80, which is exactly what a bitstream expanded to one configuration bit per byte for GPIO bit-banging looks like. Decoding on that assumption produced 0f f0 0f f0 — very close to the Xilinx .bit magic — and a confident wrong answer. The byte histogram of the whole payload killed it: 0x44, 0x22, 0x81 and 0x88 are all common further in. A structured header region can imitate an encoding for as long as you only look at the header.
  • Midra_Simulator.app is not the Midra series. Its EDIDs are MDR4K_* and ALTA4K_* — it belongs to the later, unrelated Midra 4K / Alta 4K line, and it is an HTML5 application where this generation's client is Flash. Analysing it answers a different question entirely.
  • Reading "one firmware package" as "one machine". The single-image evidence proves the software is common across the family; the very same package also carries two entirely different MCU applications for two different I/O boards. Stopping at the first conclusion would have missed the -H platform completely.
  • The vendor's own update log inside the package is the most valuable file in it and looks like debris. It is the only record here of a physical unit's responses — which device type it reported, and which of the eighteen EDID positions actually existed.
  • Reading the first reverting writes as a locked device. A source write to a layer comes back as the old value, which looked like the device refusing remote source assignment outright — and this entry nearly said so. It was two separate things: the programme context is protected while the preview context is not, and the device declines to place an input carrying no signal on a layer, so with nothing patched in every real input reverts while an always-available internal source sticks. The mechanism was right the whole time; the test was wrong twice over.
  • Putting a comma before the mnemonic on a set. 1,2,5PMinp sets the variable; 1,2,5,PMinp is read as a get of an over-indexed variable and is silently ignored or answered E13. The value is glued to the mnemonic with no separator, which reads like a typo and is the protocol.

Open questions

  • Which FPGAs these actually are. The bitstreams are raw and unencrypted but sit in an Analog Way container with no vendor magic, and the three differing lengths point at three different parts. A board photograph or a JTAG IDCODE settles it in seconds and nothing in the files will.
  • External frame memory. One bitstream can address different DDR depths, so it remains possible that the higher tiers are stuffed with more frame RAM and the firmware limit merely tracks what is fitted. This is the single caveat on the software-tiering conclusion and it cannot be closed from files.
  • Where the device-type integer is stored. The update log shows the unit reporting Detected Device Type [259] alongside a device ID, but the storage location was not located and was not looked for.
  • What the 43-byte bitstream header encodes. It is identical for the first eight bytes across all three FPGAs and diverges after, with the last two bytes differing — consistent with an identifier plus a checksum, but not decoded.
  • The exact on-air semantics. A take commits a pending change, but the per-layer status variables read as small stable enums that did not flip under one, so what is actually on the programme output cannot be settled from the wire alone. It needs a monitor on the output while the socket is watched.
  • How many concurrent control sessions the device accepts. AWGateway's existence implies very few, possibly one. It was never measured, and it decides whether a third-party controller can coexist with the vendor client at all.
  • Whether a set outside a variable's declared range is clamped, rejected or accepted. Every range on this page is the client's declaration, and a declaration is not a limit.
  • The rest of the preset-memory group. Thirty-one variables are enumerated and only the used flag, the per-slot content and the save and erase requests have been exercised on hardware.

Status of this entry. A written teardown exists and its claims carry evidence citations. Rows labelled Measured were read off a running unit; everything else on this page is derived from files and has not been checked against hardware.

← Back to the platform index