######################################### Telesensory PowerBraille Display Protocol ######################################### This started out as my attempt to reconstruct, from existing open code bases and through testing of a unit in hand, the TeleSensory ScreenPower PowerBraille serial wire protocol. While I had feared that, as with so many historic devices, documentation had been lost, Freedom Scientific was willing to provide me with their engineering document about the protocol! That's amazing. Unfortunately, this document appears to be incomplete, or perhaps just describing an earlier version of the protocol than that spoken by the unit I have. Thus, some additional probing has been required. In the interest of having an available public reference, this page represents, to the best of my knowledge and ability, enough to write a functional driver for a PowerBraille 80. This protocol is nominally shared by several other devices, but with per-device nuance that I cannot verify and so do not attempt to reproduce here. Please understand that there may be mistakes in the following and it is still mostly the result of educated guesses. There are no warranties associated with this text, not even implied ones. The Manual ########## The user manual for this device is still quietly accessible at https://support.freedomscientific.com/Content/Documents/Manuals/Legacy/PowerBraille/PB_User_Manual.pdf but it is of limited utility for understanding the device at a software level. Notably, the manual does not even document the acceptable voltage ranges for DC power input, and The Internet as a whole also seems to have forgotten. There appears to be some suggestion that 9V or 12V is the intended answer. I originally ran mine on 5V to be safe, but have recently supplied 12V instead with no ill effects. Some limited REing of the PSU circuitry on the board (in the fullness of time, I mean to capture the whole schematic) suggests a fairly wide range of acceptable inputs. Despite that the PB80 does not claim to offer battery support, the circuitry is absolutely there (and so is the battery holder!), and there will be periodic reports of low battery in the serial stream. So much for documentation, eh? Existing Open-Source Drivers ############################ There are (were) several open-source accessibility solutions that speak (spoke) something claiming to be TeleSensory's protocol, but how much of it is RE'd vs. as described in documentation I don't know. Anyway, because they are useful references, I've included them here: * https://git.savannah.gnu.org/cgit/screen.git/log/src/braille_tsi.c -- GNU ``screen`` had support, but it was removed in 2015. * https://github.com/brltty/brltty/tree/master/Drivers/Braille/TSI -- ``brltty`` can drive these devices through a native driver * https://github.com/brltty/brltty/tree/master/Drivers/Braille/Baum/braille.c -- ``brltty`` also includes a *second* implementation of TSI's protocol apparently as spoken by some Baum devices. * https://github.com/brltty/brltty/tree/master/Drivers/Braille/Seika/braille.c -- ``brltty`` also includes a *third* implementation of TSI's protocol apparently as spoken by some Seika devices. Messages From The Device ######################## Structured device messages are variable length, with the most-significant three bits of the first byte determining the type and nature of any following bytes: +-----+-------------------------------------------------------------------+ | 000 | Miscellaneous reporting; see :ref:`msg_low_batt`, :ref:`msg_id`, | | | and :ref:`msg_cursor_keys`. | +-----+-------------------------------------------------------------------+ | 100 | Never observed | +-----+-------------------------------------------------------------------+ The remaining 6 values are all used for buttons. .. _msg_side_keys: Non-Cursor Buttons ================== The non-cursor-routing buttons are reported as a series of 5-bit bitmaps. Keys are reported on key-up or by the typematic mechanism's timeout, but note that multiple key presses, if not sufficiently simultaneous for the device's preference, may cancel typematic timeouts. Multiple keys down during any reporting period will be reported by logical OR. There will be no reports asserting that all non-cursor buttons are released; that is, of the six messages detailed below, at least one in any batch thereof will contain a set key bit. The non-cursor buttons come in several flavors: * Two large buttons on the front, one convex (on the left, CVX) and one concave (on the right, CCV). * Small rocker switches on the front panel. Two on the left and two on the right. Each can be momentarily pushed into one of two positions, up or down. Left-to-right, we denote these buttons as F0D, F0U, F1D, F1U, ..., F3U. * Two long rocker bars on the front panel, also with momentary up and down positions. The short (FSD, FSU) is on the left while the long (FLD, FLU) is on the right. * Four small momentary buttons on the top; two on the left and two on the right. Left to right, we denote these T0 through T3. * Four long bar momentary buttons on the top; left to right, these are TL0 through TL3. * The engineering document mentions, but does not describe, a keyboard detection flag, noted KBD below. It has never been observed in testing, but I lack a real AT keyaboard port and keyboard both. The bitmaps are coded as per the following table. The device reports in this somewhat curious order (the leading triplets are 010, 110, 001, 101, 011, 111; this is counting up from 2 to 7 with the bits reversed), and the assignments to buttons seems somewhat haphazard. +---------+-----------+-----------+-----------+-----------+-----------+ | | ``10000`` | ``01000`` | ``00100`` | ``00010`` | ``00001`` | +---------+-----------+-----------+-----------+-----------+-----------+ | ``010`` | | F1D | F1U | F0D | F0U | +---------+-----------+-----------+-----------+-----------+-----------+ | ``110`` | KBD | F3D | F3U | F2D | F2U | +---------+-----------+-----------+-----------+-----------+-----------+ | ``001`` | | | TL3 | | TL2 | +---------+-----------+-----------+-----------+-----------+-----------+ | ``101`` | | | T3 | | T2 | +---------+-----------+-----------+-----------+-----------+-----------+ | ``011`` | CCV | FLD | TL1 | FLU | TL0 | +---------+-----------+-----------+-----------+-----------+-----------+ | ``111`` | CVX | FSD | T1 | FSU | T0 | +---------+-----------+-----------+-----------+-----------+-----------+ Some drivers, confusingly, always read these messages two bytes at a time. Fortunately, the device appears to always send them as such, but it does not appear to be required by the protocol. .. _msg_cursor_keys: Cursor Routing Buttons ====================== With each Braille cell there is a momentary push button. These are reported as a single multi-byte message: * ``0x00`` ``0x08`` -- message header. * A length byte (``n``) * ``n`` bytes of switch status, one bit per switch; ``0`` for up, ``1`` for down. Nominally, for a PB80, there are 15 sensor bytes reported, with 4 for "vertical" cursor routing keys and 11 for "horizontal", but only the horizontal ones carry any meaning, as this display has only one row. It appears that TeleSensory was ambitious! Unlike the non-cursor buttons above, a report will be issued on every *transition*, so one can expect a terminal message with all bit positions 0 once the operator has released all keys. .. _msg_test: Test Result Messages ==================== After initiating a device self test (0x0B), the device should eventually report success with ``0x00 0x06`` or failure with ``0x00 0x07``. .. _msg_id: Device Identification Message ============================= Emitted in response to an identify command (``0x0A``; see below). * ``0x00`` ``0x05`` -- message header. * 1 byte cell count; for PB80, that's ``0x51`` (the device has 81 cells, despite its name). * 1 byte dot count; either 6 or 8, one assumes; I've only seen 8. * A 4 byte version field. * A 4 byte checksum (of what?). .. _msg_low_batt: Low Battery Notice ================== * The two byte sequence ``0x00`` ``0x01``. As the PB80 is not intended to be run from batteries, it's somewhat mystifying that this gets sent at all, but it does, by default. See :ref:`cmd_low_batt`. Unused Message Headers ====================== There appears to be a curious gap in message headers, as ``0x00 0x02``, ``0x00 0x03``, and ``0x00 0x04`` are skipped and undocumented. Perhaps they were reserved for a future use that never came. Similarly, ``0x00 0x09`` through ``0x00 0xFF`` and ``0x80`` through ``0x9F`` are seemingly unused. Messages To The Device ###################### Messages to the device always begin with two ``0xFF`` bytes. A single byte command determines the type and length of the rest of the payload. So far, we believe: +----------+-------------+----------------------------------------------------+ | Command | Payload Len | Interpretation | +==========+=============+====================================================+ | ``0x00`` | 0 | Ignored? | +----------+-------------+----------------------------------------------------+ | ``0x01`` | 40 | :ref:`20-cell write to display? ` | +----------+-------------+----------------------------------------------------+ | ``0x02`` | 80 | :ref:`40-cell write to display? ` | +----------+-------------+----------------------------------------------------+ | ``0x03`` | 160 | :ref:`80-cell write to display? ` | +----------+-------------+----------------------------------------------------+ | ``0x04`` | Variable | :ref:`Write to display ` | +----------+-------------+----------------------------------------------------+ | ``0x05`` | 1 | :ref:`Set UART parameter ` | +----------+-------------+----------------------------------------------------+ | ``0x06`` | 8 | :ref:`Set Vibration Parameters ` | +----------+-------------+----------------------------------------------------+ | ``0x07`` | 1 | Set display off timer; 1 second per count | +----------+-------------+----------------------------------------------------+ | ``0x08`` | 1 | :ref:`Display Size ` | +----------+-------------+----------------------------------------------------+ | ``0x09`` | 0 | :ref:`Elicit debug report ` | +----------+-------------+----------------------------------------------------+ | ``0x0A`` | 0 | Elicit :ref:`msg_id` | +----------+-------------+----------------------------------------------------+ | ``0x0B`` | 0 | Trigger cell test; see :ref:`msg_test` | +----------+-------------+----------------------------------------------------+ | ``0x0C`` | 0 | Trigger full diagnostics loop; power cycle req'd! | +----------+-------------+----------------------------------------------------+ | ``0x0D`` | 2 | Set typematic rate parameters | +----------+-------------+----------------------------------------------------+ | ``0x0E`` | 1 | Set debounce time; 10ms/count | +----------+-------------+----------------------------------------------------+ | ``0x0F`` | 1 | :ref:`Set Echo Mode ` | +----------+-------------+----------------------------------------------------+ | ``0x10`` | 1 | :ref:`Keypad Setup Mode byte ` | +----------+-------------+----------------------------------------------------+ | ``0x11`` | 1 | :ref:`Set Active Display Mode ` | +----------+-------------+----------------------------------------------------+ | ``0x12`` | 1 | :ref:`Set Low Battery Tx Mode ` | +----------+-------------+----------------------------------------------------+ | ``0x13`` | 1 | Unknown; uncorrelated with :ref:`0x09 ` | +----------+-------------+----------------------------------------------------+ | ``0x14`` | 3 | :ref:`Cursor Status ` | +----------+-------------+----------------------------------------------------+ | ``0x15`` | 1 | Unknown; reset? crash? | +----------+-------------+----------------------------------------------------+ | ``0x16`` | 1 | Unknown; uncorrelated with :ref:`0x09 ` | +----------+-------------+----------------------------------------------------+ | ``0x17`` | 0 | Ignored? | +----------+-------------+----------------------------------------------------+ | ... | 0 | Ignored? | +----------+-------------+----------------------------------------------------+ | ``0x30`` | 0 | Ignored? | +----------+-------------+----------------------------------------------------+ | ... | ? | Unprobed | +----------+-------------+----------------------------------------------------+ | ``0x80`` | 0 | Ignored? | +----------+-------------+----------------------------------------------------+ | ... | ? | Unprobed | +----------+-------------+----------------------------------------------------+ | ``0xF0`` | 0 | Ignored? | +----------+-------------+----------------------------------------------------+ | ... | ? | Unprobed | +----------+-------------+----------------------------------------------------+ | ``0xFE`` | 0 | Ignored? | +----------+-------------+----------------------------------------------------+ | ``0xFF`` | 0 | Ignored? | +----------+-------------+----------------------------------------------------+ .. _cmd_write: Writes to Display ================= It is unclear why all of 0x01, 0x02, 0x03, 0x04 exist, especially with at least the last of these being variable length. Apparently ``0x04`` was added later and subsumed the earlier commands. * 0x01 is like 0x04 with the payload length byte set to 0x28 and the start position set to 0x00 and neither transmitted. * 0x02 is like 0x04 with the payload length byte set to 0x50 and the start position set to 0x00 and neither transmitted. * 0x03 is like 0x04 with the payload length byte set to 0xA0 and the start position set to 0x00 and neither transmitted. .. _cmd_write_4: 0x04 Write ========== This variable-length Write command takes the following payload structure: * Mode byte; the logical OR of the following values: +------+--------------------------------------------------------------------+ | 0x01 | Cursor display (1 = shown, 0 = hidden) | +------+--------------------------------------------------------------------+ | 0x02 | Enable first vibration parameters in cells | +------+--------------------------------------------------------------------+ | 0x04 | Enable second vibration parameters in cells | +------+--------------------------------------------------------------------+ | 0x08 | Enable third vibration parameters in cells | +------+--------------------------------------------------------------------+ | 0x10 | Enable fourth vibration parameters in cells | +------+--------------------------------------------------------------------+ Bits 0xE0 appear ignored. * Cursor position column (0 = leftmost, 1 its right neighbor, etc.); values beyond the number of cells on the display cause the cursor to appear hidden. * Cursor type byte. It appears that only the least significant nibble matters: +---+--------------------------------------------------------------------+ | 0 | All dots up | +---+--------------------------------------------------------------------+ | 1 | :ref:`Cursor status configuration ` | +---+--------------------------------------------------------------------+ | 2 | All dots but 1 up, dot 1 vibrating using first vibration parameters| +---+--------------------------------------------------------------------+ | 3 | Dots 6 and 7 up | +---+--------------------------------------------------------------------+ | n | All dots up | +---+--------------------------------------------------------------------+ * payload length byte (``n``) in bytes of attr/data byte pairs field * start position (0 = leftmost, 1 = its right neighbor, etc.) * attr/data byte pairs. Attribute byte first. Braille dots are encoded using bit... :: 0 3 1 4 2 5 6 7 The attribute byte is one of the following; other values have not yet been probed. Only ``0x00``, ``0x02``, and ``0x04`` are specified in the manual; ``0x06`` and ``0x08`` were found by experimentation and appear intended, while ``0x0A``, ``0x0C``, and ``0x0E`` appear to just happen to work the way they do. +------+--------------------------------------------------------------------+ | 0x00 | Character steady on | +------+--------------------------------------------------------------------+ | 0x02 | Character using first vibration parameters, if enabled in mode | +------+--------------------------------------------------------------------+ | 0x04 | Character using second vibration parameters, if enabled in mode | +------+--------------------------------------------------------------------+ | 0x06 | Character using third vibration parameters, if enabled in mode | +------+--------------------------------------------------------------------+ | 0x08 | Character using fourth vibration parameters, if enabled in mode | +------+ | | 0x0A | | +------+ | | 0x0C | | +------+ | | 0x0E | | +------+--------------------------------------------------------------------+ Attribute bits ``0xF1`` appear to be ignored. It is unclear what happens if one specifies an odd number of bytes for a field composed of pairs... probably unwise. .. _cmd_set_uart: 0x05 UART Parameters ==================== ``0x05`` takes a 1-byte payload that appears to configure the UART. +-----+------------------------------+ | 0 | RTS/CTS off, both UARTs | +-----+------------------------------+ | 1 | RTS/CTS on, both UARTs | +-----+------------------------------+ | 2 | Set current UART 4800 baud | +-----+------------------------------+ | 3 | Set current UART 9600 baud | +-----+------------------------------+ | 4 | Set current UART 19200 baud | +-----+------------------------------+ On power-up, the device defaults to 9600 baud on UART 1, 4800 baud on UART 2, and with RTS/CTS disabled on both UARTs. It seems that 8n1 is always expected. It does not appear to be possible to change the baud rate of the UART other than the one targeted, in something of an odd contrast to :ref:`Echo Mode `. .. _cmd_set_vibe: 0x06 Vibration Parameters ========================= There are four vibration sets on the PB65/81. Each defines a square wave using two parameters: the *on* duration and *off* duration, so the ``0x06`` command naturally takes eight bytes of payload: ``1-On 1-Off 2-On ... 4-Off``. Each counter has units of centiseconds, and is rounded to the nearest multiple of 5. Zero (and so byte values 0, 1, and 2) is to be interpreted as ``0x104`` (that is, 2.6 seconds) rather than as no time. The default setting is ``0x0A 0x05 0x23 0x0A 0x41 0x0A 0x5F 0x0A``. That is: +------+---------------+------------+ | Mode | Period (msec) | Duty cycle | +======+===============+============+ | 1 | 150 | 2/3 | +------+---------------+------------+ | 2 | 450 | 7/8 | +------+---------------+------------+ | 3 | 750 | 13/15 | +------+---------------+------------+ | 4 | 1050 | 19/21 | +------+---------------+------------+ .. _cmd_display_size: 0x08 Display Size ================= This somewhat erroneously named command can be used to split the display, displaying both UART's screens. The argument is the size of UART 1's portion of the screen, and is on the left. .. _cmd_debug: 0x09 Debug Report ================= Upon receipt of the ``0x09`` command byte, my unit reports something like :: TELESENSORY BRAILLE 65/81 STATUS REPORT BRAILLE 65/81 FIRMWARE, VERSION 1.0A, JUNE 22, 1995, BATTERY USAGE 07:00:07 NUMBER OF DISPLAY = 51H DISPLAY SIZE = 51H NUMBER OF DOTS = 8 DISPLAY OFF TIMER, 1SEC/CT = F0H CURSOR STATUS UART 1 C_POS C_MODE UP_BIT ON_BIT VIB_BITS = 00H 00H FFH C0H 00H CURSOR STATUS UART 2 C_POS C_MODE UP_BIT ON_BIT VIB_BITS = 00H 00H FFH C0H 00H VIBRATION TIMERS, 10MS/CT ONX OFFX 1-4 = 0AH 05H 23H 0AH 41H 0AH 5FH 0AH UART 1,2 HAND SHAKE RTS/CTS = OFF UART 1 BAUD RATE = 9600 UART 2 BAUD RATE = 4800 ECHO MODE = 00H ACTIVE DISPLAY MODE = 01H KEYPAD SETUP, 10MS/CT DEBOUNSE START CONTINUE MODE = 02H 32H 0AH 03H LOW BATTERY TRANSMISION MODE = 03H CHECK SUM = 077EH CHECK SUM = 077EH There appear to be no framing bytes or anything around this, so programmatic use is unlikely to be all that easy. The second ``CHECK SUM`` line has appreciable delay after the ``=`` and before the result, so likely the unit is processing during that time. .. _cmd_echo: 0x0F Set Echo Mode ================== +-----+-------------------------------------------------------------------+ | 0x0 | Disable echo mode | +-----+-------------------------------------------------------------------+ | 0x1 | Bridge UART 1 to UART 2; UART 2 in command mode | +-----+-------------------------------------------------------------------+ | 0x2 | Bridge UART 2 to UART 1; UART 1 in command mode | +-----+-------------------------------------------------------------------+ | 0x3 | Bridge UARTs together; neither in command mode | +-----+-------------------------------------------------------------------+ When a UART is bridged, a BREAK condition will take it out of bridge mode and lower the corresponding bit (0x01 for UART 1, 0x02 for UART 2) of the bridge mode setting. Bridges are not interpreted by the PowerBraille device; that is, they are electrical, and so the sender must vary their baud rate to match the target of the device. For sending a command from the terminal connected to UART 1 to a device connected to UART 2, for example, the terminal should... * Set echo mode to 0x01. * Adjust its baud rate to match that of the device. * Send bytes. * Place a BREAK condition on the bus. * Restore its baud rate to that of the PowerBraille. If a response is expected, the likely procedure is a little more complex: * Set echo mode to 0x03. * Adjust its baud rate to match that of the device. * Transact with the device; the PowerBraille is entirely passive. * Place a BREAK condition on the bus; echo mode will be 0x02. * Restore its baud rate to that of the PowerBraille. * Restore the echo mode to 0x00. .. _cmd_keypad_mode: 0x10 Set Keypad Mode ==================== Select which UARTs receive :ref:`side ` and :ref:`cursor ` keypress events. Bitwise OR of 0x01 for UART 1, 0x02 for UART 2. Default is 0x03. Other values silence reporting. .. _cmd_display_mode: 0x11 Set Active Display Mode ============================ Mode 0x01 displays writes as received from UART 1, while 0x02 from UART 2. Setting to any other value appears to have no effect; that is, the display behaves as though the mode were in its previous state. .. _cmd_low_batt: 0x12 Set Low Battery Transmission Mode ====================================== Select which UARTs receive :ref:`low battery notifications `. Bitwise OR of 0x01 for UART 1, 0x02 for UART 2. Default is 0x03. .. _cmd_cursor_status: 0x14 Cursor Status ================== This command influences the behavior of cursor "type" 1. It takes three bytes: * The "up" dots: these dots are up if the underlying display has them up. * The "on" bits: these dots are forced up. * The "vibrating" bits: these dots are vibrating if they are up by either of the prior two mechanisms. So, for example, with a cell displaying 0xA0, an up dots setting of 0xC0, an on dots setting of 0x18, and a vibrating setting of 0x0C, the cell has 0x90 always on and 0x08 vibrating. The default setting is 0xFF up, 0xC0 on, and 0x00 vibrating. That is, the display unconditionally sets the bottom two dots and passes the six upper dots through (in fact it also passes the bottom two dots through, but they are then overridden). Open Questions ############## * What are commands 0x13, 0x15, and 0x16? * Are there commands we've missed? messages? * Is there a firmware dump anywhere?