Show Yourself
The goal that started this whole scanner project was small and stubborn: point at any character on the SDS150’s screen and say what color it’s drawn in. That takes two things. It takes the colors, which the radio hides from its serial port and which I had to pry out of the menus in “Ask the Screen”, and it takes the geometry, which area of the screen owns which character cell. The colors are per-radio settings, so they have to be read live. The geometry is the firmware’s: the areas never move and never resize, no menu changes them, so it can live in a table I build once. The only problem was that the radio will not hand you that table. There is no command that says “the system name occupies these cells.” I had to recover 280 coordinates from a machine that had no intention of telling me any of them.
The editor highlights whatever you’re standing on
The way in is the layout editor under Customize. It doesn’t show you a list of area names, it draws the actual screen artwork, and it draws the one area you’re parked on in reverse video. The status command reports the screen as two parallel strings, the text and a per-cell attribute string, one character of attribute for every character on screen, and reverse video shows up in that second string as a run of asterisks. So the selected area announces its own rectangle:
text: " SYSTEM DEPT CHANNEL" attr: "********* ********** *********"
Step the knob to the next area, read the new run of asterisks, write down where it starts and how long it is, and repeat. That gives area -> (line, column, length) straight off the glass, about eighteen seconds to walk a whole layout. The reason it works at all is that the display is a grid of character cells with an attribute on each one, the same model a VT100 terminal has used since 1978 and every curses text UI since the early 80s, where the layout simply is the cell grid and you locate a field by where it draws. Reverse video marking the current selection is a convention as old as those terminals. Uniden’s editor leans on it cleanly, drawing real artwork with the selection inverted, and that one design choice is what made the whole extraction possible. Give the engineers their due: I’ve reverse-engineered uglier things that were trying to be helpful.
Seven editors, but only three screens
The Customize menu offers seven layout editors, and my first assumption was seven maps to build. That was wrong, and pleasantly so. The seven collapse into three distinct geometries: a Simple screen of forty areas, a Detail screen of fifty, and a Search screen of forty-five shared by the search, weather and tone-out modes. Within each group the positions are identical, area for area. The conventional and trunk editors of a pair differ only in the colors somebody set on them, which is the entire reason they exist as separate editors: one screen, two color schemes, one for each kind of system. So walking two of the seven confirmed what the other five had already said, and the map got built in a fraction of the knob-turning I’d braced for.
The dimension I forgot to measure
My first map was wrong, and it was wrong in a way I want junior engineers to see coming, because I didn’t. I recorded where each area started, the line and the column, and quietly assumed every area was one row tall. Nine of them are not. The system, department and channel names on the Simple screen are drawn in a large font that occupies two display rows each, and the Search screen’s detail area is four rows tall. An area recorded as one row when it’s really two paints the bottom half of itself in whatever colors happen to surround it, and nothing anywhere reports the mistake. It just quietly draws wrong.
What caught it was the verification command, the one that re-walks the editors and compares them against the stored table, on its very first run. Every single start position from the first pass survived that check. The only thing missing was height. Here’s the lesson I keep relearning: write the checker before you trust the table, not after, because the table you build by hand is a hypothesis and the checker is the experiment. Geometry that agrees with itself in two dimensions can still be silently broken in the third, and the only thing that finds the third dimension is a tool that measures all three and complains when they don’t match.
The one row that refuses to be a table
The bottom row broke the whole method, and the way it broke is instructive. It holds the three soft-key labels, and it is drawn entirely in reverse video, all the time. In the editor that means the selection is invisible: an inverted area sitting on an already-inverted row leaves no run of asterisks to distinguish it from its neighbors. There is nothing to record. The soft keys have no entry in any of my tables, and they can’t.
They don’t need one, because the live row describes itself. On a real screen it comes across as three runs of reverse video separated by single-character gaps, which splits cleanly into three buttons and their exact widths at the moment you read it. That turns out to be mandatory rather than merely convenient, because the labels change with the mode, so their widths change too, and any width I’d frozen into a table would have been wrong the next time the mode changed. It’s the same discipline that runs through “Ask Again”: geometry that varies with state cannot be precomputed, it has to be read from the screen each time, and read after the screen has settled. Most of this radio’s layout is a table. This one row is a function, and pretending otherwise would have hosed it.
The rest of the pipeline is unglamorous and I’ll spare you the full walk, which lives in screen-map.md: read the screen keeping the raw attribute strings, work out which of the three geometries is on screen, find the area whose rectangle contains each cell, draw the text in that area’s colors, and swap them wherever the attribute says reverse video. The one trap that bites is that the attribute string can run longer than the trimmed text, because a highlight bar paints across the empty tail of a row, so you pad the text out to the attribute length before you index into either. I lost time to that one more than once.
Nobody drew me a floor plan of this screen. I got one anyway, by making every room light up as I walked past.