Destinations were one flat list, so building a second set meant overwriting the
first. They now live in named groups, listed down the left: click a group and
its destinations appear.
This is not a new concept bolted on. The .td5 header carries a company count
with room for twelve, each with its own name and its own pointer table, the
.tp5 has "Company Sum:", and TP5's manual has you create a company before you
can add any destinations. A group is that company.
- a saved project (.tp5) holds every group, which the old software can still
open, and reloading one restores them all
- a .td5 is written from the selected group, since one file is what goes on
one SD card. That keeps every exported file the exact shape already proven
against the bus, rather than a multi-company layout with no reference file
to check against
- deleting a group takes its destinations with it, so it is undoable too
state.destinations and state.company are now accessors onto the active group,
so everything built on the flat list keeps working untouched.
Also fixes the custom sign size inputs showing when a preset was selected:
.field sets display:flex, which beats the [hidden] attribute's display:none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Importing a sheet guessed which column was the controller name and which was
the sign text, then silently acted on the guess. On a sheet whose headers it
did not recognise, the guess fell through to "first column", which is usually
the row number — so the destinations came out named 1, 2, 3.
Now the import opens a preview: the two columns are dropdowns, seeded with the
guess and showing a sample of each, above a table of exactly what will be
created. Nothing is imported until it looks right.
The guessing itself is better too:
- a top row of words above rows containing numbers is treated as a header
even when none of its labels are recognised
- with no recognisable columns, the column carrying the longest words wins
- one recognised column stands in for the other, since a destination's name
is usually exactly what the sign shows, rather than picking at random
- a truncated name no longer keeps a trailing space, which would also stop it
matching an existing destination on a later import
Also adds custom sign sizes, for boards that are not one of the three fitted
models. Widths snap to a multiple of 8 since the record stores width in bytes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TP5 is a 2012 Windows application supplied with the Guangzhou-Tongda LED
destination signs fitted to Yutong buses. It has no preview, so building a
destination list is guess-and-check, and it only runs on Windows.
The bus never talks to TP5 — the sign controller reads a .td5 file off an SD
card, and that is the whole interface. So this replaces the software without
touching any hardware or protocol: it just has to write byte-correct .td5.
Formats reverse-engineered from the sample files and TP5(En).exe, then verified
byte-for-byte:
.td5 the file the bus reads. Fixed-layout binary; each destination block
carries a CRC-16/ARC over block[3..len] and a rand() block id, which
together looked like one 4-byte field because RAND_MAX is 0x7fff.
.tp5 the editable project. Line-based text, UTF-16BE hex strings.
.font the sign's own bitmap fonts, each glyph row XORed with its char code.
The app is one self-contained HTML file: live LED preview at the real sign size
with real scrolling, spreadsheet/CSV import, multi-page destinations, undoable
delete, and export to both .td5 and .tp5.
Verified:
- rebuilds a real 46,080-byte TP5 export byte-for-byte with a recomputed CRC
- all 36 stored CRCs verify against the implementation
- driven through its own UI, re-exporting the real file differs in 7 bytes,
all of them the export timestamp
- running on a real bus: signs and driver's controller both correct
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>