/* * BRLTTY - A background process providing access to the console screen (when in * text mode) for a blind person using a refreshable braille display. * * Copyright (C) 1995-2021 by The BRLTTY Developers. * * BRLTTY comes with ABSOLUTELY NO WARRANTY. * * This is free software, placed under the terms of the * GNU Lesser General Public License, as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any * later version. Please see the file LICENSE-LGPL for details. * * Web Page: http://brltty.app/ * * This software is maintained by Dave Mielke . */ #include "prologue.h" #include #include #include "log.h" #include "crc_generate.h" #include "async.h" #include "async_alarm.h" #include "timing.h" #include "brl_driver.h" #include "brldefs-cn.h" #define PROBE_RETRY_LIMIT 0 #define PROBE_RESPONSE_TIMEOUT 1000 #define COMMAND_RESPONSE_TIMEOUT 10000 #define MAXIMUM_RESPONSE_SIZE 0X100 #define KEYS_POLL_INTERVAL 100 #define MOTORS_POLL_INTERVAL 400 #define ROW_UPDATE_TIME 1200 #define CELLS_RESET_TIME 14000 #define UPDATE_RETRY_DELAY 5000 BEGIN_KEY_NAME_TABLE(navigation) KEY_NAME_ENTRY(CN_KEY_Help, "Help"), KEY_NAME_ENTRY(CN_KEY_Refresh, "Refresh"), KEY_NAME_ENTRY(CN_KEY_Line1, "Line1"), KEY_NAME_ENTRY(CN_KEY_Line2, "Line2"), KEY_NAME_ENTRY(CN_KEY_Line3, "Line3"), KEY_NAME_ENTRY(CN_KEY_Line4, "Line4"), KEY_NAME_ENTRY(CN_KEY_Line5, "Line5"), KEY_NAME_ENTRY(CN_KEY_Line6, "Line6"), KEY_NAME_ENTRY(CN_KEY_Line7, "Line7"), KEY_NAME_ENTRY(CN_KEY_Line8, "Line8"), KEY_NAME_ENTRY(CN_KEY_Line9, "Line9"), KEY_NAME_ENTRY(CN_KEY_Back, "Back"), KEY_NAME_ENTRY(CN_KEY_Menu, "Menu"), KEY_NAME_ENTRY(CN_KEY_Forward, "Forward"), END_KEY_NAME_TABLE BEGIN_KEY_NAME_TABLES(all) KEY_NAME_TABLE(navigation), END_KEY_NAME_TABLES DEFINE_KEY_TABLE(all) BEGIN_KEY_TABLE_LIST &KEY_TABLE_DEFINITION(all), END_KEY_TABLE_LIST typedef struct { unsigned char hasChanged:1; unsigned char force; unsigned char cells[]; } RowEntry; typedef BrailleResponseResult ProbeResponseHandler ( BrailleDisplay *brl, const unsigned char *response, size_t size ); struct BrailleDataStruct { CRCGenerator *crcGenerator; AsyncHandle keysPollerAlarm; struct { ProbeResponseHandler *responseHandler; unsigned int protocolVersion; } probe; struct { TimePeriod timeout; unsigned char command; unsigned char waiting:1; } response; struct { TimePeriod retryDelay; RowEntry **rowEntries; unsigned int firstChangedRow; unsigned int lastRowSent; unsigned char resetCells:1; } window; struct { TimePeriod delay; CN_PacketInteger flags; } status; struct { KeyNumberSet pressed; } keys; }; static crc_t makePacketChecksum (BrailleDisplay *brl, const void *packet, size_t size) { CRCGenerator *crc = brl->data->crcGenerator; crcResetGenerator(crc); crcAddData(crc, packet, size); return crcGetChecksum(crc); } typedef enum { PVS_WAITING, PVS_STARTED, PVS_DONE } PacketVerificationState; typedef struct { PacketVerificationState state; unsigned escaped:1; } PacketVerificationData; static BraillePacketVerifierResult verifyPacket ( BrailleDisplay *brl, unsigned char *bytes, size_t size, size_t *length, void *data ) { PacketVerificationData *pvd = data; unsigned char *byte = &bytes[size-1]; if (*byte == CN_PACKET_FRAMING_BYTE) { if ((pvd->state += 1) == PVS_DONE) { if (pvd->escaped) return BRL_PVR_INVALID; *length = size - 1; } else { *length = MAXIMUM_RESPONSE_SIZE; } return BRL_PVR_EXCLUDE; } if (pvd->state == PVS_WAITING) { return BRL_PVR_INVALID; } if (*byte == CN_PACKET_ESCAPE_BYTE) { if (pvd->escaped) return BRL_PVR_INVALID; pvd->escaped = 1; return BRL_PVR_EXCLUDE; } if (pvd->escaped) { pvd->escaped = 0; *byte ^= CN_PACKET_ESCAPE_BIT; } return BRL_PVR_INCLUDE; } static size_t readPacket (BrailleDisplay *brl, void *packet, size_t size) { while (1) { PacketVerificationData pvd = { .state = PVS_WAITING }; size_t length = readBraillePacket(brl, NULL, packet, size, verifyPacket, &pvd); if (length > 0) { if (length < 3) { logShortPacket(packet, length); continue; } { crc_t expected = CN_getResponseInteger(packet, (length -= 2)); crc_t actual = makePacketChecksum(brl, packet, length); if (actual != expected) { logBytes(LOG_WARNING, "input packet checksum mismatch:" " Actual:%"PRIcrc " Expected:%"PRIcrc, packet, length, actual, expected ); continue; } } { const unsigned char *bytes = packet; size_t expected = 0; switch (bytes[0]) { case CN_CMD_COLUMN_COUNT: case CN_CMD_ROW_COUNT: case CN_CMD_PROTOCOL_VERSION: case CN_CMD_FIRMWARE_VERSION: case CN_CMD_DEVICE_STATUS: case CN_CMD_PRESSED_KEYS: case CN_CMD_SEND_ROW: case CN_CMD_RESET_CELLS: expected = 3; break; default: logUnexpectedPacket(packet, length); continue; } if (length < expected) { logTruncatedPacket(packet, length); continue; } } } return length; } } static inline void addByteToPacket (unsigned char **target, unsigned char byte) { if ((byte == CN_PACKET_ESCAPE_BYTE) || (byte == CN_PACKET_FRAMING_BYTE)) { *(*target)++ = CN_PACKET_ESCAPE_BYTE; byte ^= CN_PACKET_ESCAPE_BIT; } *(*target)++ = byte; } static int writePacket (BrailleDisplay *brl, const unsigned char *packet, size_t size) { logBytes(LOG_CATEGORY(OUTPUT_PACKETS), "raw", packet, size); unsigned char buffer[1 + ((size + 2) * 2) + 1]; unsigned char *target = buffer; *target++ = CN_PACKET_FRAMING_BYTE; { const unsigned char *source = packet; const unsigned char *end = source + size; while (source < end) addByteToPacket(&target, *source++); } { uint16_t checksum = makePacketChecksum(brl, packet, size); addByteToPacket(&target, (checksum & UINT8_MAX)); addByteToPacket(&target, (checksum >> 8)); } *target++ = CN_PACKET_FRAMING_BYTE; size = target - buffer; int ok = writeBraillePacket(brl, NULL, buffer, size); if (ok) { brl->data->response.waiting = 1; startTimePeriod(&brl->data->response.timeout, COMMAND_RESPONSE_TIMEOUT); brl->data->response.command = packet[0]; } else { brl->hasFailed = 1; } return ok; } static int writeSimpleCommand (BrailleDisplay *brl, unsigned char command) { const unsigned char packet[] = {command}; return writePacket(brl, packet, sizeof(packet)); } static RowEntry * getRowEntry (BrailleDisplay *brl, unsigned int index) { return brl->data->window.rowEntries[index]; } static void deallocateRowEntries (BrailleDisplay *brl, unsigned int count) { RowEntry ***rowEntries = &brl->data->window.rowEntries; if (*rowEntries) { while (count > 0) free(getRowEntry(brl, --count)); free(*rowEntries); *rowEntries = NULL; } } static int allocateRowEntries (BrailleDisplay *brl) { RowEntry ***rowEntries = &brl->data->window.rowEntries; if (!(*rowEntries = malloc(ARRAY_SIZE(*rowEntries, brl->textRows)))) { logMallocError(); return 0; } for (unsigned int index=0; indextextRows; index+=1) { RowEntry **row = &(*rowEntries)[index]; size_t size = sizeof(**row) + brl->textColumns; if (!(*row = malloc(size))) { logMallocError(); deallocateRowEntries(brl, (index + 1)); return 0; } memset(*row, 0, size); (*row)->force = 1; } return 1; } static void setRowHasChanged (BrailleDisplay *brl, unsigned int index) { getRowEntry(brl, index)->hasChanged = 1; if (index < brl->data->window.firstChangedRow) { brl->data->window.firstChangedRow = index; } } static void resendRow (BrailleDisplay *brl) { setRowHasChanged(brl, brl->data->window.lastRowSent); } static int refreshAllRows (BrailleDisplay *brl) { brl->data->window.resetCells = 1; return 1; } static int refreshRow (BrailleDisplay *brl, int row) { return refreshAllRows(brl); // for now } ASYNC_ALARM_CALLBACK(CN_keysPoller) { BrailleDisplay *brl = parameters->data; if (!brl->data->response.waiting) { writeSimpleCommand(brl, CN_CMD_PRESSED_KEYS); } else if (afterTimePeriod(&brl->data->response.timeout, NULL)) { unsigned char command = brl->data->response.command; logMessage(LOG_WARNING, "command response timeout: Cmd:0X%02X", command); switch (command) { case CN_CMD_SEND_ROW: resendRow(brl); break; case CN_CMD_RESET_CELLS: brl->data->window.resetCells = 1;; break; default: break; } writeSimpleCommand(brl, CN_CMD_DEVICE_STATUS); } } static void stopKeysPoller (BrailleDisplay *brl) { AsyncHandle *alarm = &brl->data->keysPollerAlarm; if (*alarm) { asyncCancelRequest(*alarm); *alarm = NULL; } } static int startKeysPoller (BrailleDisplay *brl) { AsyncHandle alarm = brl->data->keysPollerAlarm; if (alarm) return 1; if (asyncNewRelativeAlarm(&alarm, 0, CN_keysPoller, brl)) { if (asyncResetAlarmInterval(alarm, KEYS_POLL_INTERVAL)) { brl->data->keysPollerAlarm = alarm; return 1; } asyncCancelRequest(alarm); } return 0; } static BrailleResponseResult isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) { brl->data->response.waiting = 0; ProbeResponseHandler *handler = brl->data->probe.responseHandler; brl->data->probe.responseHandler = NULL; return handler(brl, packet, size); } static BrailleResponseResult writeProbeCommand (BrailleDisplay *brl, unsigned char command, ProbeResponseHandler *handler) { if (!writeSimpleCommand(brl, command)) return 0; brl->data->probe.responseHandler = handler; return 1; } static BrailleResponseResult writeNextProbeCommand (BrailleDisplay *brl, unsigned char command, ProbeResponseHandler *handler) { return writeProbeCommand(brl, command, handler)? BRL_RSP_CONTINUE: BRL_RSP_FAIL; } static BrailleResponseResult handleDeviceStatus (BrailleDisplay *brl, const unsigned char *response, size_t size) { if (response[0] != CN_CMD_DEVICE_STATUS) return BRL_RSP_UNEXPECTED; brl->data->status.flags = CN_getResponseResult(response); return BRL_RSP_DONE; } static BrailleResponseResult handleFirmwareVersion (BrailleDisplay *brl, const unsigned char *response, size_t size) { if (response[0] != CN_CMD_FIRMWARE_VERSION) return BRL_RSP_UNEXPECTED; response += 1; size -= 1; logMessage(LOG_INFO, "Firmware Version: %.*s", (int)size, response); return writeNextProbeCommand(brl, CN_CMD_DEVICE_STATUS, handleDeviceStatus); } static BrailleResponseResult handleProtocolVersion (BrailleDisplay *brl, const unsigned char *response, size_t size) { if (response[0] != CN_CMD_PROTOCOL_VERSION) return BRL_RSP_UNEXPECTED; brl->data->probe.protocolVersion = CN_getResponseResult(response); logMessage(LOG_INFO, "Protocol Version: %u", brl->data->probe.protocolVersion); return writeNextProbeCommand(brl, CN_CMD_FIRMWARE_VERSION, handleFirmwareVersion); } static BrailleResponseResult handleRowCount (BrailleDisplay *brl, const unsigned char *response, size_t size) { if (response[0] != CN_CMD_ROW_COUNT) return BRL_RSP_UNEXPECTED; brl->textRows = CN_getResponseResult(response); return writeNextProbeCommand(brl, CN_CMD_PROTOCOL_VERSION, handleProtocolVersion); } static BrailleResponseResult handleColumnCount (BrailleDisplay *brl, const unsigned char *response, size_t size) { if (response[0] != CN_CMD_COLUMN_COUNT) return BRL_RSP_UNEXPECTED; brl->textColumns = CN_getResponseResult(response); return writeNextProbeCommand(brl, CN_CMD_ROW_COUNT, handleRowCount); } static int writeIdentifyRequest (BrailleDisplay *brl) { return writeProbeCommand(brl, CN_CMD_COLUMN_COUNT, handleColumnCount); } static int connectResource (BrailleDisplay *brl, const char *identifier) { static const SerialParameters serialParameters = { SERIAL_DEFAULT_PARAMETERS, .baud = 9600 }; BEGIN_USB_STRING_LIST(usbManufacturers_16C0_05E1) "bristolbraille.co.uk", END_USB_STRING_LIST BEGIN_USB_STRING_LIST(usbProducts_16C0_05E1) "Canute 360", END_USB_STRING_LIST BEGIN_USB_CHANNEL_DEFINITIONS { /* all models */ .vendor=0X16C0, .product=0X05E1, .manufacturers = usbManufacturers_16C0_05E1, .products = usbProducts_16C0_05E1, .configuration=1, .interface=1, .alternative=0, .inputEndpoint=3, .outputEndpoint=2, .serial = &serialParameters, .resetDevice = 1 }, END_USB_CHANNEL_DEFINITIONS GioDescriptor descriptor; gioInitializeDescriptor(&descriptor); descriptor.serial.parameters = &serialParameters; descriptor.usb.channelDefinitions = usbChannelDefinitions; if (connectBrailleResource(brl, identifier, &descriptor, NULL)) { return 1; } return 0; } static int brl_construct (BrailleDisplay *brl, char **parameters, const char *device) { if ((brl->data = malloc(sizeof(*brl->data)))) { memset(brl->data, 0, sizeof(*brl->data)); brl->data->crcGenerator = NULL; brl->data->keysPollerAlarm = NULL; brl->data->probe.responseHandler = NULL; brl->data->probe.protocolVersion = 0; brl->data->response.waiting = 0; startTimePeriod(&brl->data->window.retryDelay, 0); brl->data->window.rowEntries = NULL; brl->data->window.resetCells = 0; brl->data->keys.pressed = 0; { static const CRCAlgorithm algorithm = { .primaryName = CN_CRC_ALGORITHM_NAME, .checksumWidth = CN_CRC_CHECKSUM_WIDTH, .reflectData = CN_CRC_REFLECT_DATA, .reflectResult = CN_CRC_REFLECT_RESULT, .generatorPolynomial = UINT16_C(CN_CRC_GENERATOR_POLYNOMIAL), .initialValue = UINT16_C(CN_CRC_INITIAL_VALUE), .xorMask = UINT16_C(CN_CRC_XOR_MASK), .checkValue = UINT16_C(CN_CRC_CHECK_VALUE), .residue = UINT16_C(CN_CRC_RESIDUE), }; brl->data->crcGenerator = crcNewGenerator(&algorithm); } if (brl->data->crcGenerator) { if (connectResource(brl, device)) { unsigned char response[MAXIMUM_RESPONSE_SIZE]; if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_RESPONSE_TIMEOUT, writeIdentifyRequest, readPacket, &response, sizeof(response), isIdentityResponse)) { if (allocateRowEntries(brl)) { brl->refreshBrailleDisplay = refreshAllRows; brl->refreshBrailleRow = refreshRow; brl->cellSize = 6; setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all)); makeOutputTable(dotsTable_ISO11548_1); if (startKeysPoller(brl)) { return 1; } deallocateRowEntries(brl, brl->textRows); } } disconnectBrailleResource(brl, NULL); } crcDestroyGenerator(brl->data->crcGenerator); } free(brl->data); } else { logMallocError(); } return 0; } static void brl_destruct (BrailleDisplay *brl) { stopKeysPoller(brl); disconnectBrailleResource(brl, NULL); deallocateRowEntries(brl, brl->textRows); crcDestroyGenerator(brl->data->crcGenerator); free(brl->data); brl->data = NULL; } static int brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) { unsigned int length = brl->textColumns; const unsigned char *cells = brl->buffer; for (unsigned int index=0; indextextRows; index+=1) { RowEntry *row = getRowEntry(brl, index); if (cellsHaveChanged(row->cells, cells, length, NULL, NULL, &row->force)) { setRowHasChanged(brl, index); } cells += length; } return 1; } static int startUpdate (BrailleDisplay *brl) { if (!afterTimePeriod(&brl->data->window.retryDelay, NULL)) return 0; if (brl->data->window.resetCells) { brl->data->window.resetCells = 0; brl->data->window.firstChangedRow = 0; for (unsigned int index=0; indextextRows; index+=1) { getRowEntry(brl, index)->hasChanged = 1; } writeSimpleCommand(brl, CN_CMD_RESET_CELLS); return 1; } while (brl->data->window.firstChangedRow < brl->textRows) { RowEntry *row = getRowEntry(brl, brl->data->window.firstChangedRow); if (row->hasChanged) { unsigned int length = brl->textColumns; unsigned char packet[2 + length]; unsigned char *byte = packet; *byte++ = CN_CMD_SEND_ROW; *byte++ = brl->data->window.firstChangedRow; byte = translateOutputCells(byte, row->cells, length); if (writePacket(brl, packet, (byte - packet))) { row->hasChanged = 0; brl->data->window.lastRowSent = brl->data->window.firstChangedRow++; } return 1; } brl->data->window.firstChangedRow += 1; } return 0; } static void startNextCommand (BrailleDisplay *brl) { if (!(brl->data->status.flags & CN_STATUS_MOTORS_ACTIVE)) { startUpdate(brl); } else if (afterTimePeriod(&brl->data->status.delay, NULL)) { startTimePeriod(&brl->data->status.delay, MOTORS_POLL_INTERVAL); writeSimpleCommand(brl, CN_CMD_DEVICE_STATUS); } } static int brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) { unsigned char packet[MAXIMUM_RESPONSE_SIZE]; size_t size; while ((size = readPacket(brl, packet, sizeof(packet)))) { brl->data->response.waiting = 0; brl->writeDelay = 0; unsigned char command = packet[0]; CN_PacketInteger result = CN_getResponseResult(packet); unsigned int motorsTime = 0; switch (command) { case CN_CMD_PRESSED_KEYS: enqueueUpdatedKeys(brl, result, &brl->data->keys.pressed, CN_GRP_NavigationKeys, 0); startNextCommand(brl); continue; case CN_CMD_DEVICE_STATUS: brl->data->status.flags = result; continue; case CN_CMD_SEND_ROW: motorsTime = ROW_UPDATE_TIME; break; case CN_CMD_RESET_CELLS: motorsTime = CELLS_RESET_TIME; break; default: logUnexpectedPacket(packet, size); continue; } if (result) { logMessage(LOG_WARNING, "command failed: Cmd:0X%02X Err:0X%02X", command, result ); switch (command) { case CN_CMD_SEND_ROW: resendRow(brl); goto UPDATE_FAILED; case CN_CMD_RESET_CELLS: brl->data->window.resetCells = 1; goto UPDATE_FAILED; UPDATE_FAILED: startTimePeriod(&brl->data->window.retryDelay, UPDATE_RETRY_DELAY); continue; default: continue;; } } else if (motorsTime) { brl->data->status.flags |= CN_STATUS_MOTORS_ACTIVE; startTimePeriod(&brl->data->status.delay, motorsTime); } } return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL; }