blob: 2ddc0b6d905e1dac5c2093e8b0d5900cb8edbd53 [file] [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>.
*/
/* TSI/braille.c - Braille display driver for TSI displays
*
* Written by Stéphane Doyon (s.doyon@videotron.ca)
*
* It attempts full support for Navigator 20/40/80 and Powerbraille 40/65/80.
* It is designed to be compiled into BRLTTY version 3.5.
*
* History:
* Version 2.74 apr2004: use message() to report low battery condition.
* Version 2.73 jan2004: Fix key bindings for speech commands for PB80.
* Add CMD_SPKHOME to help.
* Version 2.72 jan2003: brl->buffer now allocated by core.
* Version 2.71: Added CMD_LEARN, BRL_CMD_NXPROMPT/CMD_PRPROMPT and CMD_SIXDOTS.
* Version 2.70: Added CR_CUTAPPEND, BRL_BLK_CUTLINE, BRL_BLK_SETMARK, BRL_BLK_GOTOMARK
* and CR_SETLEFT. Changed binding for NXSEARCH.. Adjusted PB80 cut&paste
* bindings. Replaced CMD_CUT_BEG/CMD_CUT_END by CR_CUTBEGIN/CR_CUTRECT,
* and CMD_CSRJMP by CR_ROUTE+0. Adjusted cut_cursor for new cut&paste
* bindings (untested).
* Version 2.61: Adjusted key bindings for preferences menu.
* Version 2.60: Use TCSADRAIN when closing serial port. Slight API and
* name changes for BRLTTY 3.0. Argument to readbrl now ignore, instead
* of being validated.
* Version 2.59: Added bindings for CMD_LNBEG/LNEND.
* Version 2.58: Added bindings for CMD_BACK and CR_MSGATTRIB.
* Version 2.57: Fixed help screen/file for Nav80. We finally have a
* user who confirms it works!
* Version 2.56: Added key binding for NXSEARCH.
* Version 2.55: Added key binding for NXINDENT and NXBLNKLNS.
* Version 2.54: Added key binding for switchvt.
* Version 2.53: The IXOFF bit in the termios setting was inverted?
* Version 2.52: Changed LOG_NOTICE to LOG_INFO. Was too noisy.
* Version 2.51: Added CMD_RESTARTSPEECH.
* Version 2.5: Added CMD_SPKHOME, sacrificed LNBEG and LNEND.
* Version 2.4: Refresh display even if unchanged after every now and then so
* that it will clear up if it was garbled. Added speech key bindings (had
* to change a few bindings to make room). Added SKPEOLBLNK key binding.
* Version 2.3: Reset serial port attributes at each detection attempt in
* initbrl. This should help BRLTTY recover if another application (such
* as kudzu) scrambles the serial port while BRLTTY is running.
* Unnumbered version: Fixes for dynmically loading drivers (declare all
* non-exported functions and variables static).
* Version 2.2beta3: Option to disable CTS checking. Apparently, Vario
* does not raise CTS when connected.
* Version 2.2beta1: Exploring problems with emulators of TSI (PB40): BAUM
* and mdv mb408s. See if we can provide timing options for more flexibility.
* Version 2.1: Help screen fix for new keys in preferences menu.
* Version 2.1beta1: Less delays in writing braille to display for
* nav20/40 and pb40, delays still necessary for pb80 on probably for nav80.
* Additional routing keys for navigator. Cut&paste binding that combines
* routing key and normal key.
* Version 2.0: Tested with Nav40 PB40 PB80. Support for functions added
* in BRLTTY 2.0: added key bindings for new fonctions (attributes and
* routing). Support for PB at 19200baud. Live detection of display, checks
* both at 9600 and 19200baud. RS232 wire monitoring. Ping when idle to
* detect when display turned off and issue a CMD_RESTARTBRL.
* Version 1.2 (not released) introduces support for PB65/80. Rework of key
* binding mechanism and readbrl(). Slight modifications to routing keys
* support, + corrections. May have broken routing key support for PB40.
* Version 1.1 worked on nav40 and was reported to work on pb40.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "log.h"
#include "parse.h"
#include "io_generic.h"
#include "message.h"
typedef enum {
PARM_HIGH_BAUD,
PARM_SET_BAUD
} DriverParameter;
#define BRLPARMS "highbaud", "setbaud"
#include "brl_driver.h"
#include "braille.h"
#include "brldefs-ts.h"
BEGIN_KEY_NAME_TABLE(routing)
KEY_GROUP_ENTRY(TS_GRP_RoutingKeys, "RoutingKey"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLE(nav_small)
KEY_NAME_ENTRY(TS_KEY_CursorLeft, "CursorLeft"),
KEY_NAME_ENTRY(TS_KEY_CursorRight, "CursorRight"),
KEY_NAME_ENTRY(TS_KEY_CursorUp, "CursorUp"),
KEY_NAME_ENTRY(TS_KEY_CursorDown, "CursorDown"),
KEY_NAME_ENTRY(TS_KEY_NavLeft, "NavLeft"),
KEY_NAME_ENTRY(TS_KEY_NavRight, "NavRight"),
KEY_NAME_ENTRY(TS_KEY_NavUp, "NavUp"),
KEY_NAME_ENTRY(TS_KEY_NavDown, "NavDown"),
KEY_NAME_ENTRY(TS_KEY_ThumbLeft, "ThumbLeft"),
KEY_NAME_ENTRY(TS_KEY_ThumbRight, "ThumbRight"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLE(nav_large)
KEY_NAME_ENTRY(TS_KEY_CursorLeft, "CursorLeft"),
KEY_NAME_ENTRY(TS_KEY_CursorRight, "CursorRight"),
KEY_NAME_ENTRY(TS_KEY_CursorUp, "CursorUp"),
KEY_NAME_ENTRY(TS_KEY_CursorDown, "CursorDown"),
KEY_NAME_ENTRY(TS_KEY_NavLeft, "LeftOuter"),
KEY_NAME_ENTRY(TS_KEY_NavRight, "RightOuter"),
KEY_NAME_ENTRY(TS_KEY_NavUp, "LeftInner"),
KEY_NAME_ENTRY(TS_KEY_NavDown, "RightInner"),
KEY_NAME_ENTRY(TS_KEY_ThumbLeft, "LeftThumb"),
KEY_NAME_ENTRY(TS_KEY_ThumbRight, "RightThumb"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLE(pb_small)
KEY_NAME_ENTRY(TS_KEY_CursorUp, "LeftRockerUp"),
KEY_NAME_ENTRY(TS_KEY_CursorDown, "LeftRockerDown"),
KEY_NAME_ENTRY(TS_KEY_NavLeft, "Backward"),
KEY_NAME_ENTRY(TS_KEY_NavRight, "Forward"),
KEY_NAME_ENTRY(TS_KEY_NavUp, "RightRockerUp"),
KEY_NAME_ENTRY(TS_KEY_NavDown, "RightRockerDown"),
KEY_NAME_ENTRY(TS_KEY_ThumbLeft, "Convex"),
KEY_NAME_ENTRY(TS_KEY_ThumbRight, "Concave"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLE(pb_large)
KEY_NAME_ENTRY(TS_KEY_Button1, "Button1"),
KEY_NAME_ENTRY(TS_KEY_Button2, "Button2"),
KEY_NAME_ENTRY(TS_KEY_Button3, "Button3"),
KEY_NAME_ENTRY(TS_KEY_Button4, "Button4"),
KEY_NAME_ENTRY(TS_KEY_Bar1, "Bar1"),
KEY_NAME_ENTRY(TS_KEY_Bar2, "Bar2"),
KEY_NAME_ENTRY(TS_KEY_Bar3, "Bar3"),
KEY_NAME_ENTRY(TS_KEY_Bar4, "Bar4"),
KEY_NAME_ENTRY(TS_KEY_Switch1Up, "Switch1Up"),
KEY_NAME_ENTRY(TS_KEY_Switch1Down, "Switch1Down"),
KEY_NAME_ENTRY(TS_KEY_Switch2Up, "Switch2Up"),
KEY_NAME_ENTRY(TS_KEY_Switch2Down, "Switch2Down"),
KEY_NAME_ENTRY(TS_KEY_Switch3Up, "Switch3Up"),
KEY_NAME_ENTRY(TS_KEY_Switch3Down, "Switch3Down"),
KEY_NAME_ENTRY(TS_KEY_Switch4Up, "Switch4Up"),
KEY_NAME_ENTRY(TS_KEY_Switch4Down, "Switch4Down"),
KEY_NAME_ENTRY(TS_KEY_LeftRockerUp, "LeftRockerUp"),
KEY_NAME_ENTRY(TS_KEY_LeftRockerDown, "LeftRockerDown"),
KEY_NAME_ENTRY(TS_KEY_RightRockerUp, "RightRockerUp"),
KEY_NAME_ENTRY(TS_KEY_RightRockerDown, "RightRockerDown"),
KEY_NAME_ENTRY(TS_KEY_Convex, "Convex"),
KEY_NAME_ENTRY(TS_KEY_Concave, "Concave"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLES(nav20)
KEY_NAME_TABLE(nav_small),
END_KEY_NAME_TABLES
BEGIN_KEY_NAME_TABLES(nav40)
KEY_NAME_TABLE(nav_small),
END_KEY_NAME_TABLES
BEGIN_KEY_NAME_TABLES(nav80)
KEY_NAME_TABLE(nav_large),
KEY_NAME_TABLE(routing),
END_KEY_NAME_TABLES
BEGIN_KEY_NAME_TABLES(pb40)
KEY_NAME_TABLE(pb_small),
KEY_NAME_TABLE(routing),
END_KEY_NAME_TABLES
BEGIN_KEY_NAME_TABLES(pb65)
KEY_NAME_TABLE(pb_large),
KEY_NAME_TABLE(routing),
END_KEY_NAME_TABLES
BEGIN_KEY_NAME_TABLES(pb80)
KEY_NAME_TABLE(pb_large),
KEY_NAME_TABLE(routing),
END_KEY_NAME_TABLES
DEFINE_KEY_TABLE(nav20)
DEFINE_KEY_TABLE(nav40)
DEFINE_KEY_TABLE(nav80)
DEFINE_KEY_TABLE(pb40)
DEFINE_KEY_TABLE(pb65)
DEFINE_KEY_TABLE(pb80)
BEGIN_KEY_TABLE_LIST
&KEY_TABLE_DEFINITION(nav20),
&KEY_TABLE_DEFINITION(nav40),
&KEY_TABLE_DEFINITION(nav80),
&KEY_TABLE_DEFINITION(pb40),
&KEY_TABLE_DEFINITION(pb65),
&KEY_TABLE_DEFINITION(pb80),
END_KEY_TABLE_LIST
/* Stabilization delay after changing baud rate */
#define BAUD_DELAY 100
/* for routing keys */
#define ROUTING_BYTES_VERTICAL 4
#define ROUTING_BYTES_MAXIMUM 11
#define ROUTING_BYTES_40 9
#define ROUTING_BYTES_80 14
#define ROUTING_BYTES_81 15
/* Description of reply to query */
#define IDENTITY_H1 0X00
#define IDENTITY_H2 0X05
/* Routing keys information (2 bytes header) */
#define ROUTING_H1 0x00
#define ROUTING_H2 0x08
/* input codes signaling low battery power (2bytes) */
#define BATTERY_H1 0x00
#define BATTERY_H2 0x01
/* Bit definition of key codes returned by the display.
* Navigator and pb40 return 2 bytes, pb65/80 returns 6. Each byte has a
* different specific mask/signature in the 3 most significant bits.
* Other bits indicate whether a specific key is pressed.
* See readbrl().
*/
/* We combine all key bits into one KeyNumberSet. Each byte is masked by the
* corresponding "mask" to extract valid bits then those are shifted by
* "shift" and or'ed into the 32bits "code".
*/
/* bits to take into account when checking each byte's signature */
#define KEYS_BYTE_SIGNATURE_MASK 0XE0
/* how we describe each byte */
typedef struct {
unsigned char signature; /* it's signature */
unsigned char mask; /* bits that do represent keys */
unsigned char shift; /* where to shift them into "code" */
} KeysByteDescriptor;
/* Description of bytes for navigator and pb40. */
static const KeysByteDescriptor keysDescriptor_Navigator[] = {
{.signature=0X60, .mask=0X1F, .shift=0},
{.signature=0XE0, .mask=0X1F, .shift=5}
};
/* Description of bytes for pb65/80 */
static const KeysByteDescriptor keysDescriptor_PowerBraille[] = {
{.signature=0X40, .mask=0X0F, .shift=10},
{.signature=0XC0, .mask=0X0F, .shift=14},
{.signature=0X20, .mask=0X05, .shift=18},
{.signature=0XA0, .mask=0X05, .shift=21},
{.signature=0X60, .mask=0X1F, .shift=24},
{.signature=0XE0, .mask=0X1F, .shift=5}
};
typedef struct {
const char *modelName;
const KeyTableDefinition *keyTableDefinition;
unsigned char routingBytes;
signed char routingKeyCount;
unsigned slowUpdate:2;
unsigned highBaudSupported:1;
} ModelEntry;
static const ModelEntry modelNavigator20 = {
.modelName = "Navigator 20",
.routingBytes = ROUTING_BYTES_40,
.routingKeyCount = 20,
.keyTableDefinition = &KEY_TABLE_DEFINITION(nav20)
};
static const ModelEntry modelNavigator40 = {
.modelName = "Navigator 40",
.routingBytes = ROUTING_BYTES_40,
.routingKeyCount = 40,
.slowUpdate = 1,
.keyTableDefinition = &KEY_TABLE_DEFINITION(nav40)
};
static const ModelEntry modelNavigator80 = {
.modelName = "Navigator 80",
.routingBytes = ROUTING_BYTES_80,
.routingKeyCount = 80,
.slowUpdate = 2,
.keyTableDefinition = &KEY_TABLE_DEFINITION(nav80)
};
static const ModelEntry modelPowerBraille40 = {
.modelName = "Power Braille 40",
.routingBytes = ROUTING_BYTES_40,
.routingKeyCount = 40,
.highBaudSupported = 1,
.keyTableDefinition = &KEY_TABLE_DEFINITION(pb40)
};
static const ModelEntry modelPowerBraille65 = {
.modelName = "Power Braille 65",
.routingBytes = ROUTING_BYTES_81,
.routingKeyCount = 65,
.slowUpdate = 2,
.highBaudSupported = 1,
.keyTableDefinition = &KEY_TABLE_DEFINITION(pb65)
};
static const ModelEntry modelPowerBraille80 = {
.modelName = "Power Braille 80",
.routingBytes = ROUTING_BYTES_81,
.routingKeyCount = 81,
.slowUpdate = 2,
.highBaudSupported = 1,
.keyTableDefinition = &KEY_TABLE_DEFINITION(pb80)
};
typedef enum {
IPT_IDENTITY,
IPT_ROUTING,
IPT_BATTERY,
IPT_KEYS
} InputPacketType;
typedef struct {
union {
unsigned char bytes[1];
struct {
unsigned char header[2];
unsigned char columns;
unsigned char dots;
char version[4];
unsigned char checksum[4];
} identity;
struct {
unsigned char header[2];
unsigned char count;
unsigned char vertical[ROUTING_BYTES_VERTICAL];
unsigned char horizontal[0X100 - 4];
} routing;
unsigned char keys[6];
} fields;
InputPacketType type;
union {
struct {
unsigned char count;
} routing;
struct {
const KeysByteDescriptor *descriptor;
unsigned char count;
} keys;
} data;
} InputPacket;
struct BrailleDataStruct {
const ModelEntry *model;
SerialParameters serialParameters;
unsigned char routingKeys[ROUTING_BYTES_MAXIMUM];
unsigned char forceWrite;
unsigned char cellCount;
unsigned char cells[0XFF];
struct {
unsigned char major;
unsigned char minor;
} version;
/* Type of delay the display requires after sending it a command.
* 0 -> no delay, 1 -> drain only, 2 -> drain + wait for SEND_DELAY.
*/
unsigned char slowUpdate;
};
static ssize_t
writeBytes (BrailleDisplay *brl, const void *data, size_t size) {
brl->writeDelay += brl->data->slowUpdate * 24;
return writeBraillePacket(brl, NULL, data, size);
}
static BraillePacketVerifierResult
verifyPacket (
BrailleDisplay *brl,
unsigned char *bytes, size_t size,
size_t *length, void *data
) {
InputPacket *packet = data;
const off_t index = size - 1;
const unsigned char byte = bytes[index];
if (size == 1) {
switch (byte) {
case IDENTITY_H1:
packet->type = IPT_IDENTITY;
*length = 2;
break;
default:
if ((byte & KEYS_BYTE_SIGNATURE_MASK) == keysDescriptor_Navigator[0].signature) {
packet->data.keys.descriptor = keysDescriptor_Navigator;
packet->data.keys.count = ARRAY_COUNT(keysDescriptor_Navigator);
goto isKeys;
}
if ((byte & KEYS_BYTE_SIGNATURE_MASK) == keysDescriptor_PowerBraille[0].signature) {
packet->data.keys.descriptor = keysDescriptor_PowerBraille;
packet->data.keys.count = ARRAY_COUNT(keysDescriptor_PowerBraille);
goto isKeys;
}
return BRL_PVR_INVALID;
isKeys:
packet->type = IPT_KEYS;
*length = packet->data.keys.count;
break;
}
} else {
switch (packet->type) {
case IPT_IDENTITY:
if (size == 2) {
switch (byte) {
case IDENTITY_H2:
*length = sizeof(packet->fields.identity);
break;
case ROUTING_H2:
packet->type = IPT_ROUTING;
*length = 3;
break;
case BATTERY_H2:
packet->type = IPT_BATTERY;
break;
default:
return BRL_PVR_INVALID;
}
}
break;
case IPT_ROUTING:
if (size == 3) {
packet->data.routing.count = byte;
*length += packet->data.routing.count;
}
break;
case IPT_KEYS:
if ((byte & KEYS_BYTE_SIGNATURE_MASK) != packet->data.keys.descriptor[index].signature) return BRL_PVR_INVALID;
break;
default:
break;
}
}
return BRL_PVR_INCLUDE;
}
static size_t
readPacket (BrailleDisplay *brl, InputPacket *packet) {
return readBraillePacket(brl, NULL, &packet->fields, sizeof(packet->fields), verifyPacket, packet);
}
static int
getIdentity (BrailleDisplay *brl, InputPacket *reply) {
static const unsigned char request[] = {0xFF, 0xFF, 0x0A};
if (writeBytes(brl, request, sizeof(request))) {
if (awaitBrailleInput(brl, 100)) {
size_t count = readPacket(brl, reply);
if (count > 0) {
if (reply->type == IPT_IDENTITY) return 1;
logUnexpectedPacket(reply->fields.bytes, count);
}
} else {
logMessage(LOG_DEBUG, "no response");
}
}
return 0;
}
static int
setAutorepeatProperties (BrailleDisplay *brl, int on, int delay, int interval) {
const unsigned char request[] = {
0XFF, 0XFF, 0X0D,
on? ((delay + 9) / 10) /* 10ms */: 0XFF /* long delay */,
on? ((interval + 9) / 10) /* 10ms */: 0XFF /* long interval */
};
return writeBytes(brl, request, sizeof(request));
}
static int
setLocalBaud (BrailleDisplay *brl, unsigned int baud) {
SerialParameters *parameters = &brl->data->serialParameters;
logMessage(LOG_DEBUG, "trying at %u baud", baud);
if (parameters->baud == baud) return 1;
parameters->baud = baud;
return gioReconfigureResource(brl->gioEndpoint, parameters);
}
static int
setRemoteBaud (BrailleDisplay *brl, unsigned int baud) {
unsigned char request[] = {0xFF, 0xFF, 0x05, 0};
unsigned char *byte = &request[sizeof(request) - 1];
switch (baud) {
case TS_BAUD_LOW:
*byte = 2;
break;
case TS_BAUD_NORMAL:
*byte = 3;
break;
case TS_BAUD_HIGH:
*byte = 4;
break;
default:
logMessage(LOG_WARNING, "display does not support %u baud", baud);
return 0;
}
logMessage(LOG_WARNING, "switching display to %u baud", baud);
return writeBraillePacket(brl, NULL, request, sizeof(request));
}
static int
connectResource (BrailleDisplay *brl, const char *identifier) {
static const SerialParameters serialParameters = {
SERIAL_DEFAULT_PARAMETERS
};
GioDescriptor descriptor;
gioInitializeDescriptor(&descriptor);
descriptor.serial.parameters = &serialParameters;
if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
brl->data->serialParameters = serialParameters;
return 1;
}
return 0;
}
static void
disconnectResource (BrailleDisplay *brl) {
disconnectBrailleResource(brl, NULL);
}
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));
if (connectResource(brl, device)) {
InputPacket reply;
unsigned int allowHighBaud = 1;
unsigned int oldBaud;
unsigned int newBaud;
{
const char *parameter = parameters[PARM_HIGH_BAUD];
if (parameter && *parameter) {
if (!validateYesNo(&allowHighBaud, parameter)) {
logMessage(LOG_WARNING, "unsupported high baud setting: %s", parameter);
}
}
}
{
static const unsigned int bauds[] = {
TS_BAUD_NORMAL, TS_BAUD_HIGH, 0
};
const unsigned int *baud = bauds;
while (1) {
if (!*baud) goto failure;
oldBaud = *baud++;
if (allowHighBaud || (oldBaud <= TS_BAUD_NORMAL)) {
if (setLocalBaud(brl, oldBaud)) {
if (getIdentity(brl, &reply)) {
break;
}
}
}
}
}
brl->data->cellCount = reply.fields.identity.columns;
brl->data->version.major = reply.fields.identity.version[1] - '0';
brl->data->version.minor = reply.fields.identity.version[3] - '0';
logMessage(LOG_INFO, "display replied: %u cells, version %u.%u",
brl->data->cellCount,
brl->data->version.major,
brl->data->version.minor);
switch (brl->data->cellCount) {
case 20:
brl->data->model = &modelNavigator20;
break;
case 40:
brl->data->model = (brl->data->version.major > 3)?
&modelPowerBraille40:
&modelNavigator40;
break;
case 80:
brl->data->model = &modelNavigator80;
break;
case 65:
brl->data->model = &modelPowerBraille65;
break;
case 81:
brl->data->model = &modelPowerBraille80;
break;
default:
logMessage(LOG_ERR, "unrecognized braille display size: %u", brl->data->cellCount);
goto failure;
}
logMessage(LOG_INFO, "detected %s", brl->data->model->modelName);
brl->data->slowUpdate = brl->data->model->slowUpdate;
#ifdef FORCE_DRAIN_AFTER_SEND
brl->data->slowUpdate = 1;
#endif /* FORCE_DRAIN_AFTER_SEND */
#ifdef FORCE_FULL_SEND_DELAY
brl->data->slowUpdate = 2;
#endif /* FORCE_FULL_SEND_DELAY */
newBaud = oldBaud;
if (allowHighBaud && brl->data->model->highBaudSupported) newBaud = TS_BAUD_HIGH;
{
const char *parameter = parameters[PARM_SET_BAUD];
if (parameter && *parameter) {
static const int minimum = 1;
int value;
if (validateInteger(&value, parameter, &minimum, NULL)) {
newBaud = value;
} else {
logMessage(LOG_WARNING, "unsupported set baud setting: %s", parameter);
}
}
}
if (newBaud != oldBaud) {
if (!setRemoteBaud(brl, newBaud)) goto failure;
drainBrailleOutput(brl, BAUD_DELAY);
if (!setLocalBaud(brl, newBaud)) goto failure;
logMessage(LOG_DEBUG, "now using %u baud - checking if display followed", newBaud);
if (getIdentity(brl, &reply)) {
logMessage(LOG_DEBUG, "display responded at %u baud", newBaud);
} else {
logMessage(LOG_INFO,
"display did not respond at %u baud"
" - going back to %u baud",
newBaud, oldBaud);
if (!setLocalBaud(brl, oldBaud)) goto failure;
drainBrailleOutput(brl, BAUD_DELAY);
if (getIdentity(brl, &reply)) {
logMessage(LOG_INFO, "found display again at %u baud", oldBaud);
} else {
logMessage(LOG_ERR, "display lost after baud switch");
goto failure;
}
}
}
setBrailleKeyTable(brl, brl->data->model->keyTableDefinition);
makeOutputTable(dotsTable_ISO11548_1);
brl->textColumns = brl->data->cellCount; /* initialise size of display */
brl->setAutorepeatProperties = setAutorepeatProperties;
memset(brl->data->routingKeys, 0, sizeof(brl->data->routingKeys));
brl->data->forceWrite = 1;
return 1;
failure:
disconnectResource(brl);
}
free(brl->data);
} else {
logMallocError();
}
return 0;
}
static void
brl_destruct (BrailleDisplay *brl) {
disconnectResource(brl);
if (brl->data) {
free(brl->data);
brl->data = NULL;
}
}
static int
writeCells (BrailleDisplay *brl, unsigned int from, unsigned int to) {
static const unsigned char header[] = {
0XFF, 0XFF, 0X04, 0X00, 0X99, 0X00
};
unsigned int length = to - from;
unsigned char packet[sizeof(header) + 2 + (length * 2)];
unsigned char *byte = packet;
unsigned int i;
byte = mempcpy(byte, header, sizeof(header));
*byte++ = 2 * length;
*byte++ = from;
for (i=0; i<length; i+=1) {
*byte++ = 0;
*byte++ = translateOutputCell(brl->data->cells[from + i]);
}
/* Some displays apparently don't like rapid updating. Most or all apprently
* don't do flow control. If we update the display too often and too fast,
* then the packets queue up in the send queue, the info displayed is not up
* to date, and the info displayed continues to change after we stop
* updating while the queue empties (like when you release the arrow key and
* the display continues changing for a second or two). We also risk
* overflows which put garbage on the display, or often what happens is that
* some cells from previously displayed lines will remain and not be cleared
* or replaced; also the pinging fails and the display gets
* reinitialized... To expose the problem skim/scroll through a long file
* (with long lines) holding down the up/down arrow key on the PC keyboard.
*
* pb40 has no problems: it apparently can take whatever we throw at
* it. Nav40 is good but we drain just to be safe.
*
* pb80 (twice larger but twice as fast as nav40) cannot take a continuous
* full speed flow. There is no flow control: apparently not supported
* properly on at least pb80. My pb80 is recent yet the hardware version is
* v1.0a, so this may be a hardware problem that was fixed on pb40. There's
* some UART handshake mode that might be relevant but only seems to break
* everything (on both pb40 and pb80)...
*
* Nav80 is untested but as it receives at 9600, we probably need to
* compensate there too.
*
* Finally, some TSI emulators (at least the mdv mb408s) may have timing
* limitations.
*
* I no longer have access to a Nav40 and PB80 for testing: I only have a
* PB40.
*/
return writeBytes(brl, packet, (byte - packet));
}
static int
brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
unsigned int from, to;
if (cellsHaveChanged(brl->data->cells, brl->buffer, brl->data->cellCount,
&from, &to, &brl->data->forceWrite)) {
if (!writeCells(brl, from, to)) return 0;
}
return 1;
}
static int
handleInputPacket (BrailleDisplay *brl, const InputPacket *packet) {
switch (packet->type) {
case IPT_KEYS: {
KeyNumberSet keys = 0;
unsigned int i;
for (i=0; i<packet->data.keys.count; i+=1) {
const KeysByteDescriptor *kbd = &packet->data.keys.descriptor[i];
keys |= (packet->fields.keys[i] & kbd->mask) << kbd->shift;
}
enqueueKeys(brl, keys, TS_GRP_NavigationKeys, 0);
return 1;
}
case IPT_ROUTING: {
if (packet->data.routing.count != brl->data->model->routingBytes) return 0;
enqueueUpdatedKeyGroup(brl, brl->data->model->routingKeyCount,
packet->fields.routing.horizontal,
brl->data->routingKeys,
TS_GRP_RoutingKeys);
return 1;
}
case IPT_BATTERY:
message(NULL, gettext("battery low"), 0);
return 1;
default:
return 0;
}
}
static int
brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
/* Key press codes come in pairs of bytes for nav and pb40, in 6bytes
* for pb65/80. Each byte has bits representing individual keys + a special
* mask/signature in the most significant 3bits.
*
* The low battery warning from the display is a specific 2bytes code.
*
* Finally, the routing keys have a special 2bytes header followed by 9, 14
* or 15 bytes of info (1bit for each routing key). The first 4bytes describe
* vertical routing keys and are ignored in this driver.
*
* We might get a query reply, since we send queries when we don't get
* any keys in a certain time. That a 2byte header + 10 more bytes ignored.
*/
InputPacket packet;
size_t size;
while ((size = readPacket(brl, &packet))) {
if (!handleInputPacket(brl, &packet)) {
logUnexpectedPacket(packet.fields.bytes, size);
}
}
return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
}