blob: 8f8169eda4bd9a2fdb698a2e0f431eff2abd8514 [file] [log] [blame] [edit]
/*
* 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-2023 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 <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include <errno.h>
#include "log.h"
#include "crc_generate.h"
#include "async_handle.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 force;
unsigned char haveOldCells:1;
unsigned char haveNewCells:1;
unsigned char *oldCells;
unsigned char newCells[];
} 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; index<brl->textRows; index+=1) {
RowEntry **row = &(*rowEntries)[index];
size_t rowLength = brl->textColumns;
size_t size = sizeof(**row) + (rowLength * 2);
if (!(*row = malloc(size))) {
logMallocError();
deallocateRowEntries(brl, (index + 1));
return 0;
}
memset(*row, 0, size);
(*row)->force = 1;
(*row)->oldCells = (*row)->newCells + rowLength;
}
return 1;
}
static void
setRowHasChanged (BrailleDisplay *brl, unsigned int index) {
getRowEntry(brl, index)->haveNewCells = 1;
logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "row has changed: %u", index);
if (index < brl->data->window.firstChangedRow) {
logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "first changed row: %u", index);
brl->data->window.firstChangedRow = index;
}
}
static void
resendRow (BrailleDisplay *brl) {
logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "resending row: %u", brl->data->window.lastRowSent);
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; index<brl->textRows; index+=1) {
RowEntry *row = getRowEntry(brl, index);
if (cellsHaveChanged(row->newCells, 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; index<brl->textRows; index+=1) {
RowEntry *row = getRowEntry(brl, index);
row->haveNewCells = 1;
row->haveOldCells = 0;
}
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->haveNewCells) {
unsigned int length = brl->textColumns;
if (row->haveOldCells) {
if (memcmp(row->newCells, row->oldCells, length) == 0) {
row->haveNewCells = 0;
}
}
if (row->haveNewCells) {
unsigned char packet[2 + length];
unsigned char *byte = packet;
*byte++ = CN_CMD_SEND_ROW;
*byte++ = brl->data->window.firstChangedRow;
byte = translateOutputCells(byte, row->newCells, length);
size_t size = byte - packet;
logBytes(LOG_CATEGORY(BRAILLE_DRIVER), "sending row: %u", packet, size, brl->data->window.firstChangedRow);
if (writePacket(brl, packet, size)) {
row->haveNewCells = 0;
brl->data->window.lastRowSent = brl->data->window.firstChangedRow++;
memcpy(row->oldCells, row->newCells, length);
}
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: {
RowEntry *row = getRowEntry(brl, brl->data->window.lastRowSent);
if (row->haveOldCells) {
motorsTime = ROW_UPDATE_TIME;
} else {
row->haveOldCells = 1;
}
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;
}