Codec API
Tier: Stable Core
Encode and decode Click clipboard binary and program files.
laddercodec.encode
Ladder rung encoder — unified pipeline.
Definitions
Rung
One logical unit of ladder logic. A rung has 1..32 rows, each with 31 condition columns (A..AE) and 1 output column (AF). The rung may also carry a single plain-text comment.
Row
A horizontal slice of the rung grid. Row 0 is the topmost visible row. The grid starts at absolute offset 0x0A60 with a stride of 0x800 per row (32 columns x 64 bytes per cell).
Cell
A 64-byte (0x40) block within the grid. Addressed by (row, column). Contains wire flags, structural control bytes, and (for instruction- bearing rungs) stream-placement metadata.
Wire topology
The arrangement of horizontal wires ("-"), vertical pass-throughs ("|"), and junction-down points ("T") across the condition grid. Encoded via three per-cell flag bytes: +0x19 (segment), +0x1D (right), +0x21 (down). Conditions also set these flags (like "-").
Condition
An instruction placed on a condition column (A..AE). Includes
Contact (NO/NC/edge) and CompareContact (GT/GE/LT/LE/EQ/NE).
Condition cells are variable-length: a 0x25-byte header (wire flags,
column, row, instruction index) followed by an instruction blob
(class name, type marker, operand, func_code). Passed as
Contact or CompareContact objects in the condition_rows grid.
NOP
The simplest AF-column instruction. At most one per rung. Encoded via a minimal byte model: col31 +0x1D = 1 (all rows), plus col0 +0x15 = 1 for non-first rows. Does not require an instruction stream entry.
Page
A 0x1000 (4096) byte allocation unit. Buffer size is: pad_to_page(0x0A60 + logical_rows * 0x800 + payload_len).
Program header
A single 0x40-byte structure at 0x0254. Contains the row-count word (+0x00/+0x01 = (rows+1)*0x20) and other GUI state. Not a 32-entry table — the range 0x0294–0x0A5F is the payload region.
Supported checklist
Verified in Click (encode → paste → copy back → decode round-trip):
[x] Empty rung, 1..32 rows
[x] Wire topology, 1..32 rows (-, |, T in any valid position)
[x] NOP on any row (with col0 +0x15 enable for non-first rows)
[x] Plain comment, 1-row (empty, wire, NOP, max 1400 bytes)
[x] Plain comment, 2-row (empty, NOP, wire incl. col-A, max 1324)
[x] Plain comment, 3-row (empty, NOP, wire, mixed, max 1400)
[x] Plain comment, 4..32 rows (wire combos, scaling)
[x] Multi-line comment (\n → \par; verified 2026-03-12)
[x] Styled comments (bold/italic/underline via markdown → RTF groups; verified 2026-03-12)
[x] Contacts (NO, NC, edge, immediate — via Contact objects in condition_rows)
[x] Coils (out, latch, reset, immediate, range — via Coil objects in af_tokens)
[x] Comparison contacts (GT, GE, LT, LE, EQ, NE — via CompareContact objects)
[x] Timers (on_delay, off_delay, retentive — via Timer objects in af_tokens)
[ ] Full AF instruction set (counters, math, etc.)
Pipeline steps
1. Header — load from synthesize_empty_multirow (includes row_word)
2. Grid — build 32 cell objects per row (wire flags + NOP baked
in), concatenate to form the grid bytes
3. Assemble — header[:0x0A60] + grid_bytes
4. Comment — assemble RTF, insert at 0x0298, push grid forward
5. Pad — to next 0x1000 page boundary
Cell objects are bytes blobs built by ClickCell.to_bytes().
Wire cells are 0x40 bytes. Instruction cells (contacts, coils, timers)
are larger, and the concatenation model handles variable-length cells
naturally — no fixed-offset assumptions in the grid.
encode_rung
encode_rung(
logical_rows: int,
condition_rows: Sequence[Sequence[ConditionToken]],
af_tokens: Sequence[AfToken],
comment: str | None = None,
*,
show_nicknames: bool = False,
) -> bytes
Encode a ladder rung to binary payload.
Parameters
logical_rows:
Number of rung rows (1..32).
condition_rows:
Row-major token grid. Each row has 31 condition-column entries.
Supported: "" blank, "-" horizontal wire,
"|" vertical pass-through, "T" junction-down,
or a Contact object.
af_tokens:
One per row. "NOP" encodes the NOP instruction on the AF
column; "" leaves it blank; a Coil object encodes the
coil instruction.
comment:
Optional comment text (max 1400 bytes after RTF encoding). Stored
as an RTF envelope inserted at 0x0298; the cell grid is pushed
forward by payload_len bytes automatically.
Inline styles (markdown syntax):
``**text**`` → bold
``__text__`` → underline
``*text*`` → italic (asterisk)
``_text_`` → italic (underscore)
Line breaks: ``\n`` becomes an RTF paragraph break (``\par``).
``\r\n`` and bare ``\r`` are normalized to ``\n`` first.
Returns
bytes Encoded binary payload ready for the target environment.
encode
encode(rungs, *, show_nicknames: bool = False)
Encode one or more rungs to clipboard binary.
Parameters
rungs:
A single Rung object (single-rung encode) or a sequence of
Rung objects (multi-rung encode).
show_nicknames:
When True, sets the nickname display flag on math instructions
so Click shows project-level tag names instead of raw addresses.
The nicknames must already be loaded in the Click project before
pasting.
Returns
bytes Encoded binary payload.
laddercodec.decode
Ladder rung decoder — binary clipboard buffer to structured data.
Reads a Click clipboard binary and produces the same data structures
that feed encode_rung() / encode_rungs().
Public API
decode_rung(data) -> Rung
decode_rungs(data) -> list[Rung]
Round-trip identity:
decode_rung(encode_rung(lr, cr, af, cmt))
.logical_rows == lr
.conditions == cr
.instructions == af
.comment == cmt
Instruction cells
Contacts and coils are composite: a horizontal wire ((1,1,0)) with
instruction data layered on top. The instruction payload starts at
cell offset +0x25 (UTF-16LE class name, type marker, operand, func
code). Wire-only cells are exactly 0x40 bytes; instruction cells
are larger.
Known instruction types are decoded into Contact (condition columns)
or Coil (AF column) domain objects from model.py. Unrecognised
cells fall back to UnknownCondition / UnknownInstruction with
raw bytes preserved.
DecodeError
Bases: ValueError
Raised when a clipboard binary cannot be decoded.
UnknownCondition
dataclass
Instruction cell on a condition column (A..AE).
The wire flags (always (1,1,0) for contacts) are implicit.
raw carries the instruction-specific bytes from cell offset
+0x25 to the cell boundary.
UnknownInstruction
dataclass
Instruction cell on the AF column.
raw carries the instruction-specific bytes from cell offset
+0x25 to the cell boundary.
Rung
dataclass
Structured rung data — used for both decode output and encode input.
Attributes
logical_rows:
Number of rung rows (1..32).
conditions:
Row-major token grid. Each row has 31 condition-column entries.
Wire-only cells are strings ("" blank, "-" horizontal,
"|" vertical, "T" junction-down). Contacts are
Contact objects; unrecognised cells are
UnknownCondition.
instructions:
One per row. "NOP" or "" for wire-only cells.
Coils are Coil objects; unrecognised cells are
UnknownInstruction.
comment:
Markdown text (for CSV export), or None.
comment_rtf:
Raw RTF payload bytes, or None. Preserves byte-exact
fidelity for re-encoding.
CellDump
dataclass
Raw byte dump of a single cell, for RE/debugging.
Attributes
rung: Rung index (0-based).
row: Visual row within the rung (0-based).
col: Column letter ("A".."AE" or "AF").
offset: Absolute byte offset in the buffer.
size: Cell size in bytes (0x40 for wire-only, larger for instructions).
raw: Raw cell bytes.
flags: Flags (segment, right, down) read from +0x19/+0x1D/+0x21.
token: Decoded wire/instruction token, or None if not decoded.
hex
hex(cols: int = 16) -> str
Return a formatted hex dump with offset labels.
decode
decode(data: bytes) -> Rung | list[Rung]
Decode a clipboard binary. Returns Rung for single-rung, list[Rung] for multi-rung.
Parameters
data:
Raw clipboard bytes (page-aligned, starts with CLICK magic).
Returns
Rung | list[Rung]
A single Rung for single-rung buffers, or a list of Rung
objects for multi-rung buffers.
Raises
DecodeError If the buffer is invalid.
decode_rung
decode_rung(data: bytes) -> Rung
Decode a single-rung clipboard binary.
Parameters
data:
Raw clipboard bytes (page-aligned, starts with CLICK magic).
Returns
Rung
Decoded rung data matching encode_rung() input contract.
Raises
DecodeError If the buffer is invalid or contains multiple rungs.
decode_rungs
decode_rungs(data: bytes) -> list[Rung]
Decode a multi-rung clipboard binary.
Parameters
data:
Raw clipboard bytes (page-aligned, starts with CLICK magic).
Returns
list[Rung] One entry per rung, in order.
Raises
DecodeError If the buffer is invalid or contains only a single rung.
inspect_cells
inspect_cells(
data: bytes, cells: list[tuple[int, int, str]]
) -> list[CellDump]
Dump raw bytes for specific cells in a clipboard binary.
Parameters
data:
Raw clipboard bytes.
cells:
List of (rung_index, visual_row, column_letter) tuples.
Column is "A".."AE" for conditions or "AF" for output.
Returns
list[CellDump] One entry per requested cell, in the same order as cells.
Example::
dumps = inspect_cells(raw, [(0, 1, "A"), (0, 1, "B")])
for d in dumps:
print(d)
laddercodec.decode_program
Program file decoder — Scr*.tmp binary to structured Rung data.
Reads Click Programming Software's internal temp files and produces
the same Rung objects as the clipboard decoder.
Public API
decode_program(data) -> Program
The SCR format is compact (~17x smaller than clipboard) and represents the full program as stored on disk. Instruction tag IDs and operand values are identical to clipboard format — only the framing differs.
decode_program
decode_program(data: bytes) -> Program
Decode an SC-SCR temp file into a Program.
Parameters
data:
Raw bytes of a Scr*.tmp file (starts with SC-SCR magic).
Returns
Program Program with name, prog_idx, and rungs parsed from the file.
Raises
ValueError If the file cannot be parsed.
laddercodec.Rung
dataclass
Structured rung data — used for both decode output and encode input.
Attributes
logical_rows:
Number of rung rows (1..32).
conditions:
Row-major token grid. Each row has 31 condition-column entries.
Wire-only cells are strings ("" blank, "-" horizontal,
"|" vertical, "T" junction-down). Contacts are
Contact objects; unrecognised cells are
UnknownCondition.
instructions:
One per row. "NOP" or "" for wire-only cells.
Coils are Coil objects; unrecognised cells are
UnknownInstruction.
comment:
Markdown text (for CSV export), or None.
comment_rtf:
Raw RTF payload bytes, or None. Preserves byte-exact
fidelity for re-encoding.
laddercodec.Program
dataclass
A single PLC program (main or subroutine) with its rungs.
laddercodec.Project
dataclass
A complete PLC project: main program plus subroutines.
Comment
Plain-text annotation on a rung. Stored as an RTF envelope (fixed prefix + cp1252 body + fixed suffix) in the payload region at 0x0298. The 4-byte payload length sits at 0x0294. The cell grid (always at 0x0A60 in a no-payload buffer) is pushed forward by payload_len bytes after insertion. Max 1400 bytes.