From efe01be898018845f7e0241f2b1f59c87760eb0e Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 2 Jan 2018 19:25:34 +0100 Subject: [PATCH 01/20] Revert "Drop support for the Braillino for now" This reverts commit fdd9c410dee672a21d15a5a4be36a60db225b4cc. --- source/brailleDisplayDrivers/handyTech.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 4d6ca0f2a7c..a4d2990af43 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -66,6 +66,7 @@ "Active Star AS", "Basic Braille BB", "Braille Star 40 BS", + "Braillino BL", "Braille Wave BW", "Easy Braille EBR", } @@ -89,6 +90,7 @@ MODEL_BASIC_BRAILLE_64 = "\x86" MODEL_BASIC_BRAILLE_80 = "\x87" MODEL_BASIC_BRAILLE_160 = "\x8B" +MODEL_BRAILLINO = "\x72" MODEL_BRAILLE_STAR_40 = "\x74" MODEL_BRAILLE_STAR_80 = "\x78" MODEL_MODULAR_20 = "\x80" From 9d421111fc433b8ddc6b928665a1c29e05596158 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 2 Jan 2018 19:28:40 +0100 Subject: [PATCH 02/20] Basic support for old protocol --- source/brailleDisplayDrivers/handyTech.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index a4d2990af43..94fd2bf70ce 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -218,13 +218,23 @@ def display(self, cells): """Display cells on the braille display This is the modern protocol, which uses an extended packet to send braille - cells. Some very old displays use an older, simpler protocol - which is currently not implemented in this driver. + cells. Some displays use an older, simpler protocol. See OldProtocolMixin. """ self._display.sendExtendedPacket(HT_EXTPKT_BRAILLE, "".join(chr(cell) for cell in cells)) +class OldProtocolMixin(object): + "Mixin for displays using an older protocol to send braille cells and handle input" + def display(self, cells): + """Write cells to the display according to the old protocol + + This older protocol sends a simple packet starting with HT_PKT_BRAILLE, + followed by the cells. No model ID or lenghth are included. + """ + return self._display.sendPacket(HT_PKT_BRAILLE, [chr(cell) for cell in cells]) + + class AtcMixin(object): """Support for displays with Active Tactile Control (ATC)""" From 0adec27f242cf670cec4c71bfaf0783ce99410b2 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 2 Jan 2018 19:37:47 +0100 Subject: [PATCH 03/20] More work on old protocol --- source/brailleDisplayDrivers/handyTech.py | 38 ++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 94fd2bf70ce..1e7788d297f 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -223,16 +223,16 @@ def display(self, cells): self._display.sendExtendedPacket(HT_EXTPKT_BRAILLE, "".join(chr(cell) for cell in cells)) - class OldProtocolMixin(object): "Mixin for displays using an older protocol to send braille cells and handle input" + def display(self, cells): """Write cells to the display according to the old protocol This older protocol sends a simple packet starting with HT_PKT_BRAILLE, followed by the cells. No model ID or lenghth are included. """ - return self._display.sendPacket(HT_PKT_BRAILLE, [chr(cell) for cell in cells]) + self._display.sendPacket(HT_PKT_BRAILLE, "".join(chr(cell) for cell in cells)) class AtcMixin(object): @@ -405,6 +405,12 @@ class ActiveStar40(TimeSyncMixin, AtcMixin, TripleActionKeysMixin, Model): genericName = "Active Star" +class Braillino(TripleActionKeysMixin, OldProtocolMixin, Model): + deviceId = MODEL_BRAILLINO + numCells = 20 + genericName = name = 'Braillino' + + class BrailleWave(Model): deviceId = MODEL_BRAILLE_WAVE numCells = 40 @@ -460,7 +466,7 @@ class BrailleStar80(BrailleStar): numCells = 80 -class Modular(StatusCellMixin, TripleActionKeysMixin, Model): +class Modular(StatusCellMixin, TripleActionKeysMixin, OldProtocolMixin, Model): genericName = "Modular" def _get_name(self): @@ -713,6 +719,7 @@ def _handleKeyRelease(self): # being released, so they should be ignored. self._ignoreKeyReleases = True + # pylint: disable=R0912 # Pylint complains about many branches, might be worth refactoring def _onReceive(self, data): @@ -798,17 +805,7 @@ def _onReceive(self, data): elif packet[1] == HT_PKT_NAK: log.debugWarning("NAK received!") elif extPacketType == HT_EXTPKT_KEY: - key = ord(packet[1]) - release = (key & KEY_RELEASE) != 0 - if release: - key = key ^ KEY_RELEASE - self._handleKeyRelease() - self._keysDown.discard(key) - else: - # Press. - # This begins a new key combination. - self._ignoreKeyReleases = False - self._keysDown.add(key) + self._handleInput(ord(packet[1])) elif extPacketType == HT_EXTPKT_ATC_INFO: # Ignore ATC packets for now pass @@ -825,6 +822,19 @@ def _onReceive(self, data): log.debugWarning("Unhandled packet of type %r" % serPacketType) + def _handleInput(self, key): + release = (key & KEY_RELEASE) != 0 + if release: + key ^= KEY_RELEASE + self._handleKeyRelease() + self._keysDown.discard(key) + else: + # Press. + # This begins a new key combination. + self._ignoreKeyReleases = False + self._keysDown.add(key) + + def display(self, cells): # cells will already be padded up to numCells. self._model.display(cells) From 8680bfb239e8a14a30d17944f669dab86aae2d25 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 3 Jan 2018 16:30:03 +0100 Subject: [PATCH 04/20] Support the old protocol --- source/brailleDisplayDrivers/handyTech.py | 57 ++++++++++++----------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 1e7788d297f..b24641de985 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -111,7 +111,7 @@ KEY_LEFT_SPACE = 0x10 KEY_RIGHT_SPACE = 0x18 KEY_ROUTING = 0x20 -KEY_RELEASE = 0x80 +KEY_RELEASE_MASK = 0x80 # Braille dot mapping KEY_DOTS = { @@ -357,6 +357,7 @@ class ModularConnect88(TripleActionKeysMixin, Model): # pylint: disable=C0111 class ModularEvolution(AtcMixin, TripleActionKeysMixin, Model): +#class ModularEvolution(TripleActionKeysMixin, OldProtocolMixin, Model): genericName = "Modular Evolution" def _get_name(self): @@ -679,8 +680,6 @@ def _set_atc(self, state): def sendPacket(self, packetType, data=""): if type(data) == bool or type(data) == int: data = chr(data) - if self._model: - data = self._model.deviceId + data if self.isHid: self._sendHidPacket(packetType+data) else: @@ -693,6 +692,8 @@ def sendExtendedPacket(self, packetType, data=""): extType=packetType, data=data, length=chr(len(data) + len(packetType)) ) + if self._model: + packet = self._model.deviceId + packet self.sendPacket(HT_PKT_EXTENDED, packet) def _sendHidPacket(self, packet): @@ -728,22 +729,21 @@ def _onReceive(self, data): hidLength = ord(data[1]) self._hidSerialBuffer+=data[2:(2+hidLength)] currentBufferLength=len(self._hidSerialBuffer) - # We only support the extended packet based protocol - # Thus, the only non-extended packet we expect is the device identification, which is of type HT_PKT_OK and two bytes in size serPacketType = self._hidSerialBuffer[0] if serPacketType!=HT_PKT_EXTENDED: - if currentBufferLength>2: - stream = StringIO(self._hidSerialBuffer[:2]) - self._hidSerialBuffer = self._hidSerialBuffer[2:] - elif currentBufferLength==2: + packetLength = 2 if serPacketType==HT_PKT_OK else 1 + if currentBufferLength>packetLength: + stream = StringIO(self._hidSerialBuffer[:packetLength]) + self._hidSerialBuffer = self._hidSerialBuffer[packetLength:] + elif currentBufferLength==packetLength: stream = StringIO(self._hidSerialBuffer) self._hidSerialBuffer = "" else: # The packet is not yet complete return - # Extended packets are at least 5 bytes in size. elif serPacketType==HT_PKT_EXTENDED and currentBufferLength>=5: # Check whether our packet is complete + # Extended packets are at least 5 bytes in size. # The second byte is the model, the third byte is the data length, excluding the terminator packet_length = ord(self._hidSerialBuffer[2])+4 if len(self._hidSerialBuffer) Date: Wed, 3 Jan 2018 16:59:17 +0100 Subject: [PATCH 05/20] cleanup --- source/brailleDisplayDrivers/handyTech.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index b24641de985..dd921d4461e 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -357,7 +357,6 @@ class ModularConnect88(TripleActionKeysMixin, Model): # pylint: disable=C0111 class ModularEvolution(AtcMixin, TripleActionKeysMixin, Model): -#class ModularEvolution(TripleActionKeysMixin, OldProtocolMixin, Model): genericName = "Modular Evolution" def _get_name(self): From d01edb484a16dadb0e1d54b7d8ee99b82e7dd78c Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 1 Jan 2018 19:23:02 +0100 Subject: [PATCH 06/20] Support time synchronization and fix reconnecting for bluetooth. --- source/brailleDisplayDrivers/handyTech.py | 73 +++++++++++++++++++---- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index d11787b4360..9f285917ccf 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -3,8 +3,12 @@ #A part of NonVisual Desktop Access (NVDA) #This file is covered by the GNU General Public License. #See the file COPYING for more details. -#Copyright (C) 2008-2017 NV Access Limited, Bram Duvigneau, Leonard de Ruijter (Babbage B.V.), Felix Grützmacher (Handy Tech Elektronik GmbH) -"Braille display driver for Handy Tech braille displays" +#Copyright (C) 2008-2018 NV Access Limited, Bram Duvigneau, Leonard de Ruijter (Babbage B.V.), Felix Grützmacher (Handy Tech Elektronik GmbH) + +""" +Braille display driver for Handy Tech braille displays. +""" + from collections import OrderedDict from cStringIO import StringIO import serial # pylint: disable=E0401 @@ -18,7 +22,8 @@ from baseObject import ScriptableObject, AutoPropertyObject from globalCommands import SCRCAT_BRAILLE from logHandler import log - +import time +import datetime BAUD_RATE = 19200 PARITY = serial.PARITY_ODD @@ -219,12 +224,51 @@ def display(self, cells): class AtcMixin(object): + """Support for displays with Active Tactile Control (ATC)""" def postInit(self): super(AtcMixin, self).postInit() log.debug("Enabling ATC") self._display.sendExtendedPacket(HT_EXTPKT_SET_ATC_MODE, True) + +class TimeSyncMixin(object): + """Functionality for displays that support time synchronization.""" + + def postInit(self): + super(TimeSyncMixin, self).postInit() + log.debug("Request current display time") + self._display.sendExtendedPacket(HT_EXTPKT_GET_RTC) + + def handleTime(self, timeStr): + ords = map(ord, timeStr) + displayDateTime = datetime.datetime( + year=ords[0] << 8 | ords[1], + month=ords[2], + day=ords[3], + hour=ords[4], + minute=ords[5], + second=ords[6] + ) + localDateTime = datetime.datetime.today() + if abs((displayDateTime - localDateTime).total_seconds()) >= 5: + log.debugWarning("Display time out of sync: %s"%displayDateTime.isoformat()) + self.syncTime(localDateTime) + else: + log.debug("Time in sync. Display time %s"%displayDateTime.isoformat()) + + def syncTime(self, dt): + log.debug("Synchronizing braille display date and time...") + # Setting the time uses a swapped byte order for the year. + timeList = [ + dt.year & 0xFF, dt.year >> 8, + dt.month, dt.day, + dt.hour, dt.minute, dt.second + ] + timeStr = "".join(map(chr, timeList)) + self._display.sendExtendedPacket(HT_EXTPKT_SET_RTC, timeStr) + + class TripleActionKeysMixin(AutoPropertyObject): """Triple action keys @@ -322,26 +366,27 @@ class EasyBraille(Model): numCells = 40 genericName = name = "Easy Braille" -class ActiveBraille(AtcMixin, TripleActionKeysMixin, Model): + +class ActiveBraille(TimeSyncMixin, AtcMixin, TripleActionKeysMixin, Model): deviceId = MODEL_ACTIVE_BRAILLE numCells = 40 genericName = name = 'Active Braille' -class ConnectBraille40(TripleActionKeysMixin, Model): +class ConnectBraille40(TimeSyncMixin, TripleActionKeysMixin, Model): deviceId = MODEL_CONNECT_BRAILLE_40 numCells = 40 genericName = "Connect Braille" name = "Connect Braille 40" -class Actilino(AtcMixin, JoystickMixin, TripleActionKeysMixin, Model): +class Actilino(TimeSyncMixin, AtcMixin, JoystickMixin, TripleActionKeysMixin, Model): deviceId = MODEL_ACTILINO numCells = 16 genericName = name = "Actilino" -class ActiveStar40(AtcMixin, TripleActionKeysMixin, Model): +class ActiveStar40(TimeSyncMixin, AtcMixin, TripleActionKeysMixin, Model): deviceId = MODEL_ACTIVE_STAR_40 numCells = 40 name = "Active Star 40" @@ -595,6 +640,9 @@ def terminate(self): # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() + if not self.isHid: + # We must sleep after closing the COM port, as it takes some time for the device to disconnect. + time.sleep(self.timeout) def sendPacket(self, packetType, data=""): if type(data) == bool or type(data) == int: @@ -619,15 +667,12 @@ def _sendHidPacket(self, packet): assert self.isHid maxBlockSize = self._dev._writeSize-3 # When the packet length exceeds C{writeSize}, the packet is split up into several packets. - # These packets are of size C{blockSize}. # They contain C{HT_HID_RPT_InData}, the length of the data block, # the data block itself and a terminating null character. - bytesRemaining = packet - while bytesRemaining: - blockSize = min(maxBlockSize, len(bytesRemaining)) - hidPacket = HT_HID_RPT_InData + chr(blockSize) + bytesRemaining[:blockSize] + "\x00" + for offset in xrange(0, len(packet), maxBlockSize): + block = packet[offset:offset+maxBlockSize] + hidPacket = HT_HID_RPT_InData + chr(len(block)) + block + "\x00" self._dev.write(hidPacket) - bytesRemaining = bytesRemaining[blockSize:] def _handleKeyRelease(self): if self._ignoreKeyReleases or not self._keysDown: @@ -743,6 +788,8 @@ def _onReceive(self, data): pass elif extPacketType == HT_EXTPKT_GET_PROTOCOL_PROPERTIES: pass + elif extPacketType == HT_EXTPKT_GET_RTC and isinstance(self._model, TimeSyncMixin): + self._model.handleTime(packet[1:]) else: # Unknown extended packet, log it log.debugWarning("Unhandled extended packet of type %r: %r" % From 37c85522810575c5d43f80254f4fa5873dc26ef5 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 3 Jan 2018 19:25:38 +0100 Subject: [PATCH 07/20] Python3 compat --- source/brailleDisplayDrivers/handyTech.py | 124 +++++++++++----------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index dd921d4461e..01463bd8b91 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -73,29 +73,29 @@ # Model identifiers # pylint: disable=C0103 -MODEL_BRAILLE_WAVE = "\x05" -MODEL_MODULAR_EVOLUTION_64 = "\x36" -MODEL_MODULAR_EVOLUTION_88 = "\x38" -MODEL_MODULAR_CONNECT_88 = "\x3A" -MODEL_EASY_BRAILLE = "\x44" -MODEL_ACTIVE_BRAILLE = "\x54" -MODEL_CONNECT_BRAILLE_40 = "\x55" -MODEL_ACTILINO = "\x61" -MODEL_ACTIVE_STAR_40 = "\x64" -MODEL_BASIC_BRAILLE_16 = "\x81" -MODEL_BASIC_BRAILLE_20 = "\x82" -MODEL_BASIC_BRAILLE_32 = "\x83" -MODEL_BASIC_BRAILLE_40 = "\x84" -MODEL_BASIC_BRAILLE_48 = "\x8A" -MODEL_BASIC_BRAILLE_64 = "\x86" -MODEL_BASIC_BRAILLE_80 = "\x87" -MODEL_BASIC_BRAILLE_160 = "\x8B" -MODEL_BRAILLINO = "\x72" -MODEL_BRAILLE_STAR_40 = "\x74" -MODEL_BRAILLE_STAR_80 = "\x78" -MODEL_MODULAR_20 = "\x80" -MODEL_MODULAR_80 = "\x88" -MODEL_MODULAR_40 = "\x89" +MODEL_BRAILLE_WAVE = b"\x05" +MODEL_MODULAR_EVOLUTION_64 = b"\x36" +MODEL_MODULAR_EVOLUTION_88 = b"\x38" +MODEL_MODULAR_CONNECT_88 = b"\x3A" +MODEL_EASY_BRAILLE = b"\x44" +MODEL_ACTIVE_BRAILLE = b"\x54" +MODEL_CONNECT_BRAILLE_40 = b"\x55" +MODEL_ACTILINO = b"\x61" +MODEL_ACTIVE_STAR_40 = b"\x64" +MODEL_BASIC_BRAILLE_16 = b"\x81" +MODEL_BASIC_BRAILLE_20 = b"\x82" +MODEL_BASIC_BRAILLE_32 = b"\x83" +MODEL_BASIC_BRAILLE_40 = b"\x84" +MODEL_BASIC_BRAILLE_48 = b"\x8A" +MODEL_BASIC_BRAILLE_64 = b"\x86" +MODEL_BASIC_BRAILLE_80 = b"\x87" +MODEL_BASIC_BRAILLE_160 = b"\x8B" +MODEL_BRAILLINO = b"\x72" +MODEL_BRAILLE_STAR_40 = b"\x74" +MODEL_BRAILLE_STAR_80 = b"\x78" +MODEL_MODULAR_20 = b"\x80" +MODEL_MODULAR_80 = b"\x88" +MODEL_MODULAR_40 = b"\x89" # Key constants KEY_B1 = 0x03 @@ -232,7 +232,7 @@ def display(self, cells): This older protocol sends a simple packet starting with HT_PKT_BRAILLE, followed by the cells. No model ID or lenghth are included. """ - self._display.sendPacket(HT_PKT_BRAILLE, "".join(chr(cell) for cell in cells)) + self._display.sendPacket(HT_PKT_BRAILLE, b"".join(chr(cell) for cell in cells)) class AtcMixin(object): @@ -277,7 +277,7 @@ def syncTime(self, dt): dt.month, dt.day, dt.hour, dt.minute, dt.second ] - timeStr = "".join(map(chr, timeList)) + timeStr = b"".join(map(chr, timeList)) self._display.sendExtendedPacket(HT_EXTPKT_SET_RTC, timeStr) @@ -507,40 +507,40 @@ def _allSubclasses(cls): # Packet types -HT_PKT_BRAILLE = "\x01" -HT_PKT_EXTENDED = "\x79" -HT_PKT_NAK = "\x7D" -HT_PKT_ACK = "\x7E" -HT_PKT_OK = "\xFE" -HT_PKT_RESET = "\xFF" +HT_PKT_BRAILLE = b"\x01" +HT_PKT_EXTENDED = b"\x79" +HT_PKT_NAK = b"\x7D" +HT_PKT_ACK = b"\x7E" +HT_PKT_OK = b"\xFE" +HT_PKT_RESET = b"\xFF" HT_EXTPKT_BRAILLE = HT_PKT_BRAILLE -HT_EXTPKT_KEY = "\x04" -HT_EXTPKT_CONFIRMATION = "\x07" -HT_EXTPKT_SCANCODE = "\x09" -HT_EXTPKT_PING = "\x19" -HT_EXTPKT_SERIAL_NUMBER = "\x41" -HT_EXTPKT_SET_RTC = "\x44" -HT_EXTPKT_GET_RTC = "\x45" -HT_EXTPKT_BLUETOOTH_PIN = "\x47" -HT_EXTPKT_SET_ATC_MODE = "\x50" -HT_EXTPKT_SET_ATC_SENSITIVITY = "\x51" -HT_EXTPKT_ATC_INFO = "\x52" -HT_EXTPKT_SET_ATC_SENSITIVITY_2 = "\x53" -HT_EXTPKT_GET_ATC_SENSITIVITY_2 = "\x54" -HT_EXTPKT_READING_POSITION = "\x55" -HT_EXTPKT_SET_FIRMNESS = "\x60" -HT_EXTPKT_GET_FIRMNESS = "\x61" -HT_EXTPKT_GET_PROTOCOL_PROPERTIES = "\xC1" -HT_EXTPKT_GET_FIRMWARE_VERSION = "\xC2" +HT_EXTPKT_KEY = b"\x04" +HT_EXTPKT_CONFIRMATION = b"\x07" +HT_EXTPKT_SCANCODE = b"\x09" +HT_EXTPKT_PING = b"\x19" +HT_EXTPKT_SERIAL_NUMBER = b"\x41" +HT_EXTPKT_SET_RTC = b"\x44" +HT_EXTPKT_GET_RTC = b"\x45" +HT_EXTPKT_BLUETOOTH_PIN = b"\x47" +HT_EXTPKT_SET_ATC_MODE = b"\x50" +HT_EXTPKT_SET_ATC_SENSITIVITY = b"\x51" +HT_EXTPKT_ATC_INFO = b"\x52" +HT_EXTPKT_SET_ATC_SENSITIVITY_2 = b"\x53" +HT_EXTPKT_GET_ATC_SENSITIVITY_2 = b"\x54" +HT_EXTPKT_READING_POSITION = b"\x55" +HT_EXTPKT_SET_FIRMNESS = b"\x60" +HT_EXTPKT_GET_FIRMNESS = b"\x61" +HT_EXTPKT_GET_PROTOCOL_PROPERTIES = b"\xC1" +HT_EXTPKT_GET_FIRMWARE_VERSION = b"\xC2" # HID specific constants -HT_HID_RPT_OutData = "\x01" # receive data from device -HT_HID_RPT_InData = "\x02" # send data to device -HT_HID_RPT_InCommand = "\xFB" # run USB-HID firmware command -HT_HID_RPT_OutVersion = "\xFC" # get version of USB-HID firmware -HT_HID_RPT_OutBaud = "\xFD" # get baud rate of serial connection -HT_HID_RPT_InBaud = "\xFE" # set baud rate of serial connection -HT_HID_CMD_FlushBuffers = "\x01" # flush input and output buffers +HT_HID_RPT_OutData = b"\x01" # receive data from device +HT_HID_RPT_InData = b"\x02" # send data to device +HT_HID_RPT_InCommand = b"\xFB" # run USB-HID firmware command +HT_HID_RPT_OutVersion = b"\xFC" # get version of USB-HID firmware +HT_HID_RPT_OutBaud = b"\xFD" # get baud rate of serial connection +HT_HID_RPT_InBaud = b"\xFE" # set baud rate of serial connection +HT_HID_CMD_FlushBuffers = b"\x01" # flush input and output buffers class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): name = "handyTech" @@ -607,7 +607,7 @@ def __init__(self, port="auto"): self._ignoreKeyReleases = False self._keysDown = set() self.brailleInput = False - self._hidSerialBuffer = "" + self._hidSerialBuffer = b"" self._atc = False if port == "auto": @@ -687,7 +687,7 @@ def sendPacket(self, packetType, data=""): def sendExtendedPacket(self, packetType, data=""): if type(data) == bool or type(data) == int: data = chr(data) - packet = "{length}{extType}{data}\x16".format( + packet = b"{length}{extType}{data}\x16".format( extType=packetType, data=data, length=chr(len(data) + len(packetType)) ) @@ -703,7 +703,7 @@ def _sendHidPacket(self, packet): # the data block itself and a terminating null character. for offset in xrange(0, len(packet), maxBlockSize): block = packet[offset:offset+maxBlockSize] - hidPacket = HT_HID_RPT_InData + chr(len(block)) + block + "\x00" + hidPacket = HT_HID_RPT_InData + chr(len(block)) + block + b"\x00" self._dev.write(hidPacket) def _handleKeyRelease(self): @@ -736,7 +736,7 @@ def _onReceive(self, data): self._hidSerialBuffer = self._hidSerialBuffer[packetLength:] elif currentBufferLength==packetLength: stream = StringIO(self._hidSerialBuffer) - self._hidSerialBuffer = "" + self._hidSerialBuffer = b"" else: # The packet is not yet complete return @@ -755,7 +755,7 @@ def _onReceive(self, data): else: assert self._hidSerialBuffer.endswith("\x16") # Extended packets are terminated with \x16 stream = StringIO(self._hidSerialBuffer) - self._hidSerialBuffer = "" + self._hidSerialBuffer = b"" else: # The packet is not yet complete return @@ -796,7 +796,7 @@ def _onReceive(self, data): packet_length = ord(stream.read(1)) packet = stream.read(packet_length) terminator = stream.read(1) - assert terminator == "\x16" # Extended packets are terminated with \x16 + assert terminator == b"\x16" # Extended packets are terminated with \x16 extPacketType = packet[0] if extPacketType == HT_EXTPKT_CONFIRMATION: # Confirmation of a command. From cac7c1b1453851a1d5b6e3be6c307060bb080182 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 2 Jan 2018 19:22:12 +0100 Subject: [PATCH 08/20] Basic preparation for #7452 --- source/brailleDisplayDrivers/handyTech.py | 24 ++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 9f285917ccf..4d6ca0f2a7c 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -229,7 +229,7 @@ class AtcMixin(object): def postInit(self): super(AtcMixin, self).postInit() log.debug("Enabling ATC") - self._display.sendExtendedPacket(HT_EXTPKT_SET_ATC_MODE, True) + self._display.atc = True class TimeSyncMixin(object): @@ -588,8 +588,9 @@ def __init__(self, port="auto"): self._model = None self._ignoreKeyReleases = False self._keysDown = set() - self._brailleInput = False + self.brailleInput = False self._hidSerialBuffer = "" + self._atc = False if port == "auto": tryPorts = self._getAutoPorts(hwPortUtils.listComPorts(onlyAvailable=True)) @@ -644,6 +645,19 @@ def terminate(self): # We must sleep after closing the COM port, as it takes some time for the device to disconnect. time.sleep(self.timeout) + def _get_atc(self): + return self._atc + + def _set_atc(self, state): + if self._atc is state: + return + if isinstance(self._model,AtcMixin): + self.sendExtendedPacket(HT_EXTPKT_SET_ATC_MODE, state) + else: + log.debugWarning("Changing ATC setting for unsupported device %s"%self._model.name) + # Regardless whether this setting is supported or not, we want to safe its state. + self._atc = state + def sendPacket(self, packetType, data=""): if type(data) == bool or type(data) == int: data = chr(data) @@ -680,7 +694,7 @@ def _handleKeyRelease(self): # The first key released executes the key combination. try: inputCore.manager.executeGesture( - InputGesture(self._model, self._keysDown, self._brailleInput)) + InputGesture(self._model, self._keysDown, self.brailleInput)) except inputCore.NoInputGestureAction: pass # Any further releases are just the rest of the keys in the combination @@ -806,8 +820,8 @@ def display(self, cells): scriptCategory = SCRCAT_BRAILLE def script_toggleBrailleInput(self, _gesture): - self._brailleInput = not self._brailleInput - if self._brailleInput: + self.brailleInput = not self._brailleInput + if self.brailleInput: # Translators: message when braille input is enabled ui.message(_('Braille input enabled')) else: From cc6d7ffc52aa3f6ff0a302e9b3007a779d0b117e Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 15 Feb 2018 17:06:37 +0100 Subject: [PATCH 09/20] Comments --- source/brailleDisplayDrivers/handyTech.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 01463bd8b91..8a7efe35420 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -724,7 +724,7 @@ def _handleKeyRelease(self): # Pylint complains about many branches, might be worth refactoring def _onReceive(self, data): if self.isHidSerial: - # The HID serial converter seems to wrap one or two bytes into a single HID packet + # The HID serial converter wraps one or two bytes into a single HID packet hidLength = ord(data[1]) self._hidSerialBuffer+=data[2:(2+hidLength)] currentBufferLength=len(self._hidSerialBuffer) @@ -753,7 +753,7 @@ def _onReceive(self, data): stream = StringIO(self._hidSerialBuffer[:packet_length]) self._hidSerialBuffer = self._hidSerialBuffer[packet_length:] else: - assert self._hidSerialBuffer.endswith("\x16") # Extended packets are terminated with \x16 + assert(self._hidSerialBuffer.endswith("\x16"), "Extended packet termionator expected" stream = StringIO(self._hidSerialBuffer) self._hidSerialBuffer = b"" else: From 0d79b6fa3c8fbd66a2c9f67e797db96413302745 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 15 Feb 2018 17:08:16 +0100 Subject: [PATCH 10/20] Use old protocol for easy braille --- source/brailleDisplayDrivers/handyTech.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 8a7efe35420..48bdb7070f1 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -373,7 +373,7 @@ class ModularEvolution64(ModularEvolution): numCells = 64 -class EasyBraille(Model): +class EasyBraille(OldProtocolMixin, Model): deviceId = MODEL_EASY_BRAILLE numCells = 40 genericName = name = "Easy Braille" From d2f5047724fb40d9e57763ba08d0e104780eb2ae Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Fri, 16 Feb 2018 08:19:26 +0100 Subject: [PATCH 11/20] Fixes --- source/brailleDisplayDrivers/handyTech.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 48bdb7070f1..11ce7b14cb1 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -659,9 +659,8 @@ def terminate(self): # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() - if not self.isHid: - # We must sleep after closing the COM port, as it takes some time for the device to disconnect. - time.sleep(self.timeout) + # We must sleep after closing, as it sometimes takes some time for the device to disconnect. + time.sleep(self.timeout) def _get_atc(self): return self._atc @@ -753,7 +752,7 @@ def _onReceive(self, data): stream = StringIO(self._hidSerialBuffer[:packet_length]) self._hidSerialBuffer = self._hidSerialBuffer[packet_length:] else: - assert(self._hidSerialBuffer.endswith("\x16"), "Extended packet termionator expected" + assert self._hidSerialBuffer.endswith("\x16"), "Extended packet termionator expected" stream = StringIO(self._hidSerialBuffer) self._hidSerialBuffer = b"" else: From 66a65a03a8953c2a781c89a7212ed71244a39a8c Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Fri, 16 Feb 2018 17:26:21 +0100 Subject: [PATCH 12/20] Refactor onReceive --- source/brailleDisplayDrivers/handyTech.py | 72 ++++++++++++----------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 11ce7b14cb1..ecfe47ee709 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -373,7 +373,7 @@ class ModularEvolution64(ModularEvolution): numCells = 64 -class EasyBraille(OldProtocolMixin, Model): +class EasyBraille(Model): deviceId = MODEL_EASY_BRAILLE numCells = 40 genericName = name = "Easy Braille" @@ -620,16 +620,17 @@ def __init__(self, port="auto"): self.isHid = portType.startswith("USB HID") self.isHidSerial = portType == "USB HID serial converter" try: - if self.isHid: - self._dev = hwIo.Hid(port, onReceive=self._onReceive) - if self.isHidSerial: - # This is either the standalone HID adapter cable for older displays, - # or an older display with a HID - serial adapter built in - # Send a flush to open the serial channel - self._dev.write(HT_HID_RPT_InCommand + HT_HID_CMD_FlushBuffers) + if self.isHidSerial: + # This is either the standalone HID adapter cable for older displays, + # or an older display with a HID - serial adapter built in + self._dev = hwIo.Hid(port, onReceive=self._hidSerialOnReceive) + # Send a flush to open the serial channel + self._dev.write(HT_HID_RPT_InCommand + HT_HID_CMD_FlushBuffers) + elif self.isHid: + self._dev = hwIo.Hid(port, onReceive=self._hidOnReceive) else: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, parity=PARITY, - timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._onReceive) + timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._serialOnReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue @@ -721,21 +722,29 @@ def _handleKeyRelease(self): # pylint: disable=R0912 # Pylint complains about many branches, might be worth refactoring - def _onReceive(self, data): - if self.isHidSerial: - # The HID serial converter wraps one or two bytes into a single HID packet - hidLength = ord(data[1]) - self._hidSerialBuffer+=data[2:(2+hidLength)] + def _hidOnReceive(self, data): + # data contains the entire packet. + stream = StringIO(data) + serPacketType = data[2] + # Skip the header, so reading the stream will only give the rest of the data + stream.seek(3) + self._handleInputStream(serPacketType, stream) + + def _hidSerialOnReceive(self, data): + # The HID serial converter wraps one or two bytes into a single HID packet + hidLength = ord(data[1]) + self._hidSerialBuffer+=data[2:(2+hidLength)] + self._processHidSerialBuffer() + + def _processHidSerialBuffer(self): + while self._hidSerialBuffer: currentBufferLength=len(self._hidSerialBuffer) serPacketType = self._hidSerialBuffer[0] if serPacketType!=HT_PKT_EXTENDED: packetLength = 2 if serPacketType==HT_PKT_OK else 1 - if currentBufferLength>packetLength: + if currentBufferLength>=packetLength: stream = StringIO(self._hidSerialBuffer[:packetLength]) self._hidSerialBuffer = self._hidSerialBuffer[packetLength:] - elif currentBufferLength==packetLength: - stream = StringIO(self._hidSerialBuffer) - self._hidSerialBuffer = b"" else: # The packet is not yet complete return @@ -747,29 +756,22 @@ def _onReceive(self, data): if len(self._hidSerialBuffer)packet_length: - stream = StringIO(self._hidSerialBuffer[:packet_length]) - self._hidSerialBuffer = self._hidSerialBuffer[packet_length:] - else: + # We have a complete packet. + # We also isolate it from another packet that could have landed in the buffer, + stream = StringIO(self._hidSerialBuffer[:packet_length]) + self._hidSerialBuffer = self._hidSerialBuffer[packet_length:] + if len(self._hidSerialBuffer)==packet_length: assert self._hidSerialBuffer.endswith("\x16"), "Extended packet termionator expected" - stream = StringIO(self._hidSerialBuffer) - self._hidSerialBuffer = b"" else: # The packet is not yet complete return stream.seek(1) - elif self.isHid: - # data contains the entire packet. - stream = StringIO(data) - serPacketType = data[2] - # Skip the header, so reading the stream will only give the rest of the data - stream.seek(3) - else: - serPacketType = data - # data only contained the packet type. Read the rest from the device. - stream = self._dev + self._handleInputStream(serPacketType, stream) + + def _serialOnReceive(self, data): + self._handleInputStream(data, self._dev) + def _handleInputStream(self, serPacketType, stream): if serPacketType in (HT_PKT_OK, HT_PKT_EXTENDED): modelId = stream.read(1) if not self._model: From 42f01f60ab52fb27b25566e1f9991dbdbd1c84a8 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 2 Jan 2018 19:25:34 +0100 Subject: [PATCH 13/20] Added support for Braillino and the old protocol --- source/brailleDisplayDrivers/handyTech.py | 102 ++++++++++++++-------- 1 file changed, 64 insertions(+), 38 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 4d6ca0f2a7c..dd921d4461e 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -66,6 +66,7 @@ "Active Star AS", "Basic Braille BB", "Braille Star 40 BS", + "Braillino BL", "Braille Wave BW", "Easy Braille EBR", } @@ -89,6 +90,7 @@ MODEL_BASIC_BRAILLE_64 = "\x86" MODEL_BASIC_BRAILLE_80 = "\x87" MODEL_BASIC_BRAILLE_160 = "\x8B" +MODEL_BRAILLINO = "\x72" MODEL_BRAILLE_STAR_40 = "\x74" MODEL_BRAILLE_STAR_80 = "\x78" MODEL_MODULAR_20 = "\x80" @@ -109,7 +111,7 @@ KEY_LEFT_SPACE = 0x10 KEY_RIGHT_SPACE = 0x18 KEY_ROUTING = 0x20 -KEY_RELEASE = 0x80 +KEY_RELEASE_MASK = 0x80 # Braille dot mapping KEY_DOTS = { @@ -216,12 +218,22 @@ def display(self, cells): """Display cells on the braille display This is the modern protocol, which uses an extended packet to send braille - cells. Some very old displays use an older, simpler protocol - which is currently not implemented in this driver. + cells. Some displays use an older, simpler protocol. See OldProtocolMixin. """ self._display.sendExtendedPacket(HT_EXTPKT_BRAILLE, "".join(chr(cell) for cell in cells)) +class OldProtocolMixin(object): + "Mixin for displays using an older protocol to send braille cells and handle input" + + def display(self, cells): + """Write cells to the display according to the old protocol + + This older protocol sends a simple packet starting with HT_PKT_BRAILLE, + followed by the cells. No model ID or lenghth are included. + """ + self._display.sendPacket(HT_PKT_BRAILLE, "".join(chr(cell) for cell in cells)) + class AtcMixin(object): """Support for displays with Active Tactile Control (ATC)""" @@ -393,6 +405,12 @@ class ActiveStar40(TimeSyncMixin, AtcMixin, TripleActionKeysMixin, Model): genericName = "Active Star" +class Braillino(TripleActionKeysMixin, OldProtocolMixin, Model): + deviceId = MODEL_BRAILLINO + numCells = 20 + genericName = name = 'Braillino' + + class BrailleWave(Model): deviceId = MODEL_BRAILLE_WAVE numCells = 40 @@ -448,7 +466,7 @@ class BrailleStar80(BrailleStar): numCells = 80 -class Modular(StatusCellMixin, TripleActionKeysMixin, Model): +class Modular(StatusCellMixin, TripleActionKeysMixin, OldProtocolMixin, Model): genericName = "Modular" def _get_name(self): @@ -661,8 +679,6 @@ def _set_atc(self, state): def sendPacket(self, packetType, data=""): if type(data) == bool or type(data) == int: data = chr(data) - if self._model: - data = self._model.deviceId + data if self.isHid: self._sendHidPacket(packetType+data) else: @@ -675,6 +691,8 @@ def sendExtendedPacket(self, packetType, data=""): extType=packetType, data=data, length=chr(len(data) + len(packetType)) ) + if self._model: + packet = self._model.deviceId + packet self.sendPacket(HT_PKT_EXTENDED, packet) def _sendHidPacket(self, packet): @@ -701,6 +719,7 @@ def _handleKeyRelease(self): # being released, so they should be ignored. self._ignoreKeyReleases = True + # pylint: disable=R0912 # Pylint complains about many branches, might be worth refactoring def _onReceive(self, data): @@ -709,22 +728,21 @@ def _onReceive(self, data): hidLength = ord(data[1]) self._hidSerialBuffer+=data[2:(2+hidLength)] currentBufferLength=len(self._hidSerialBuffer) - # We only support the extended packet based protocol - # Thus, the only non-extended packet we expect is the device identification, which is of type HT_PKT_OK and two bytes in size serPacketType = self._hidSerialBuffer[0] if serPacketType!=HT_PKT_EXTENDED: - if currentBufferLength>2: - stream = StringIO(self._hidSerialBuffer[:2]) - self._hidSerialBuffer = self._hidSerialBuffer[2:] - elif currentBufferLength==2: + packetLength = 2 if serPacketType==HT_PKT_OK else 1 + if currentBufferLength>packetLength: + stream = StringIO(self._hidSerialBuffer[:packetLength]) + self._hidSerialBuffer = self._hidSerialBuffer[packetLength:] + elif currentBufferLength==packetLength: stream = StringIO(self._hidSerialBuffer) self._hidSerialBuffer = "" else: # The packet is not yet complete return - # Extended packets are at least 5 bytes in size. elif serPacketType==HT_PKT_EXTENDED and currentBufferLength>=5: # Check whether our packet is complete + # Extended packets are at least 5 bytes in size. # The second byte is the model, the third byte is the data length, excluding the terminator packet_length = ord(self._hidSerialBuffer[2])+4 if len(self._hidSerialBuffer) Date: Wed, 3 Jan 2018 19:25:38 +0100 Subject: [PATCH 14/20] Hopefully, some changes that ease python 3 porting in future --- source/brailleDisplayDrivers/handyTech.py | 128 +++++++++++----------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index dd921d4461e..8a7efe35420 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -73,29 +73,29 @@ # Model identifiers # pylint: disable=C0103 -MODEL_BRAILLE_WAVE = "\x05" -MODEL_MODULAR_EVOLUTION_64 = "\x36" -MODEL_MODULAR_EVOLUTION_88 = "\x38" -MODEL_MODULAR_CONNECT_88 = "\x3A" -MODEL_EASY_BRAILLE = "\x44" -MODEL_ACTIVE_BRAILLE = "\x54" -MODEL_CONNECT_BRAILLE_40 = "\x55" -MODEL_ACTILINO = "\x61" -MODEL_ACTIVE_STAR_40 = "\x64" -MODEL_BASIC_BRAILLE_16 = "\x81" -MODEL_BASIC_BRAILLE_20 = "\x82" -MODEL_BASIC_BRAILLE_32 = "\x83" -MODEL_BASIC_BRAILLE_40 = "\x84" -MODEL_BASIC_BRAILLE_48 = "\x8A" -MODEL_BASIC_BRAILLE_64 = "\x86" -MODEL_BASIC_BRAILLE_80 = "\x87" -MODEL_BASIC_BRAILLE_160 = "\x8B" -MODEL_BRAILLINO = "\x72" -MODEL_BRAILLE_STAR_40 = "\x74" -MODEL_BRAILLE_STAR_80 = "\x78" -MODEL_MODULAR_20 = "\x80" -MODEL_MODULAR_80 = "\x88" -MODEL_MODULAR_40 = "\x89" +MODEL_BRAILLE_WAVE = b"\x05" +MODEL_MODULAR_EVOLUTION_64 = b"\x36" +MODEL_MODULAR_EVOLUTION_88 = b"\x38" +MODEL_MODULAR_CONNECT_88 = b"\x3A" +MODEL_EASY_BRAILLE = b"\x44" +MODEL_ACTIVE_BRAILLE = b"\x54" +MODEL_CONNECT_BRAILLE_40 = b"\x55" +MODEL_ACTILINO = b"\x61" +MODEL_ACTIVE_STAR_40 = b"\x64" +MODEL_BASIC_BRAILLE_16 = b"\x81" +MODEL_BASIC_BRAILLE_20 = b"\x82" +MODEL_BASIC_BRAILLE_32 = b"\x83" +MODEL_BASIC_BRAILLE_40 = b"\x84" +MODEL_BASIC_BRAILLE_48 = b"\x8A" +MODEL_BASIC_BRAILLE_64 = b"\x86" +MODEL_BASIC_BRAILLE_80 = b"\x87" +MODEL_BASIC_BRAILLE_160 = b"\x8B" +MODEL_BRAILLINO = b"\x72" +MODEL_BRAILLE_STAR_40 = b"\x74" +MODEL_BRAILLE_STAR_80 = b"\x78" +MODEL_MODULAR_20 = b"\x80" +MODEL_MODULAR_80 = b"\x88" +MODEL_MODULAR_40 = b"\x89" # Key constants KEY_B1 = 0x03 @@ -232,7 +232,7 @@ def display(self, cells): This older protocol sends a simple packet starting with HT_PKT_BRAILLE, followed by the cells. No model ID or lenghth are included. """ - self._display.sendPacket(HT_PKT_BRAILLE, "".join(chr(cell) for cell in cells)) + self._display.sendPacket(HT_PKT_BRAILLE, b"".join(chr(cell) for cell in cells)) class AtcMixin(object): @@ -277,7 +277,7 @@ def syncTime(self, dt): dt.month, dt.day, dt.hour, dt.minute, dt.second ] - timeStr = "".join(map(chr, timeList)) + timeStr = b"".join(map(chr, timeList)) self._display.sendExtendedPacket(HT_EXTPKT_SET_RTC, timeStr) @@ -507,40 +507,40 @@ def _allSubclasses(cls): # Packet types -HT_PKT_BRAILLE = "\x01" -HT_PKT_EXTENDED = "\x79" -HT_PKT_NAK = "\x7D" -HT_PKT_ACK = "\x7E" -HT_PKT_OK = "\xFE" -HT_PKT_RESET = "\xFF" +HT_PKT_BRAILLE = b"\x01" +HT_PKT_EXTENDED = b"\x79" +HT_PKT_NAK = b"\x7D" +HT_PKT_ACK = b"\x7E" +HT_PKT_OK = b"\xFE" +HT_PKT_RESET = b"\xFF" HT_EXTPKT_BRAILLE = HT_PKT_BRAILLE -HT_EXTPKT_KEY = "\x04" -HT_EXTPKT_CONFIRMATION = "\x07" -HT_EXTPKT_SCANCODE = "\x09" -HT_EXTPKT_PING = "\x19" -HT_EXTPKT_SERIAL_NUMBER = "\x41" -HT_EXTPKT_SET_RTC = "\x44" -HT_EXTPKT_GET_RTC = "\x45" -HT_EXTPKT_BLUETOOTH_PIN = "\x47" -HT_EXTPKT_SET_ATC_MODE = "\x50" -HT_EXTPKT_SET_ATC_SENSITIVITY = "\x51" -HT_EXTPKT_ATC_INFO = "\x52" -HT_EXTPKT_SET_ATC_SENSITIVITY_2 = "\x53" -HT_EXTPKT_GET_ATC_SENSITIVITY_2 = "\x54" -HT_EXTPKT_READING_POSITION = "\x55" -HT_EXTPKT_SET_FIRMNESS = "\x60" -HT_EXTPKT_GET_FIRMNESS = "\x61" -HT_EXTPKT_GET_PROTOCOL_PROPERTIES = "\xC1" -HT_EXTPKT_GET_FIRMWARE_VERSION = "\xC2" +HT_EXTPKT_KEY = b"\x04" +HT_EXTPKT_CONFIRMATION = b"\x07" +HT_EXTPKT_SCANCODE = b"\x09" +HT_EXTPKT_PING = b"\x19" +HT_EXTPKT_SERIAL_NUMBER = b"\x41" +HT_EXTPKT_SET_RTC = b"\x44" +HT_EXTPKT_GET_RTC = b"\x45" +HT_EXTPKT_BLUETOOTH_PIN = b"\x47" +HT_EXTPKT_SET_ATC_MODE = b"\x50" +HT_EXTPKT_SET_ATC_SENSITIVITY = b"\x51" +HT_EXTPKT_ATC_INFO = b"\x52" +HT_EXTPKT_SET_ATC_SENSITIVITY_2 = b"\x53" +HT_EXTPKT_GET_ATC_SENSITIVITY_2 = b"\x54" +HT_EXTPKT_READING_POSITION = b"\x55" +HT_EXTPKT_SET_FIRMNESS = b"\x60" +HT_EXTPKT_GET_FIRMNESS = b"\x61" +HT_EXTPKT_GET_PROTOCOL_PROPERTIES = b"\xC1" +HT_EXTPKT_GET_FIRMWARE_VERSION = b"\xC2" # HID specific constants -HT_HID_RPT_OutData = "\x01" # receive data from device -HT_HID_RPT_InData = "\x02" # send data to device -HT_HID_RPT_InCommand = "\xFB" # run USB-HID firmware command -HT_HID_RPT_OutVersion = "\xFC" # get version of USB-HID firmware -HT_HID_RPT_OutBaud = "\xFD" # get baud rate of serial connection -HT_HID_RPT_InBaud = "\xFE" # set baud rate of serial connection -HT_HID_CMD_FlushBuffers = "\x01" # flush input and output buffers +HT_HID_RPT_OutData = b"\x01" # receive data from device +HT_HID_RPT_InData = b"\x02" # send data to device +HT_HID_RPT_InCommand = b"\xFB" # run USB-HID firmware command +HT_HID_RPT_OutVersion = b"\xFC" # get version of USB-HID firmware +HT_HID_RPT_OutBaud = b"\xFD" # get baud rate of serial connection +HT_HID_RPT_InBaud = b"\xFE" # set baud rate of serial connection +HT_HID_CMD_FlushBuffers = b"\x01" # flush input and output buffers class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject): name = "handyTech" @@ -607,7 +607,7 @@ def __init__(self, port="auto"): self._ignoreKeyReleases = False self._keysDown = set() self.brailleInput = False - self._hidSerialBuffer = "" + self._hidSerialBuffer = b"" self._atc = False if port == "auto": @@ -687,7 +687,7 @@ def sendPacket(self, packetType, data=""): def sendExtendedPacket(self, packetType, data=""): if type(data) == bool or type(data) == int: data = chr(data) - packet = "{length}{extType}{data}\x16".format( + packet = b"{length}{extType}{data}\x16".format( extType=packetType, data=data, length=chr(len(data) + len(packetType)) ) @@ -703,7 +703,7 @@ def _sendHidPacket(self, packet): # the data block itself and a terminating null character. for offset in xrange(0, len(packet), maxBlockSize): block = packet[offset:offset+maxBlockSize] - hidPacket = HT_HID_RPT_InData + chr(len(block)) + block + "\x00" + hidPacket = HT_HID_RPT_InData + chr(len(block)) + block + b"\x00" self._dev.write(hidPacket) def _handleKeyRelease(self): @@ -724,7 +724,7 @@ def _handleKeyRelease(self): # Pylint complains about many branches, might be worth refactoring def _onReceive(self, data): if self.isHidSerial: - # The HID serial converter seems to wrap one or two bytes into a single HID packet + # The HID serial converter wraps one or two bytes into a single HID packet hidLength = ord(data[1]) self._hidSerialBuffer+=data[2:(2+hidLength)] currentBufferLength=len(self._hidSerialBuffer) @@ -736,7 +736,7 @@ def _onReceive(self, data): self._hidSerialBuffer = self._hidSerialBuffer[packetLength:] elif currentBufferLength==packetLength: stream = StringIO(self._hidSerialBuffer) - self._hidSerialBuffer = "" + self._hidSerialBuffer = b"" else: # The packet is not yet complete return @@ -753,9 +753,9 @@ def _onReceive(self, data): stream = StringIO(self._hidSerialBuffer[:packet_length]) self._hidSerialBuffer = self._hidSerialBuffer[packet_length:] else: - assert self._hidSerialBuffer.endswith("\x16") # Extended packets are terminated with \x16 + assert(self._hidSerialBuffer.endswith("\x16"), "Extended packet termionator expected" stream = StringIO(self._hidSerialBuffer) - self._hidSerialBuffer = "" + self._hidSerialBuffer = b"" else: # The packet is not yet complete return @@ -796,7 +796,7 @@ def _onReceive(self, data): packet_length = ord(stream.read(1)) packet = stream.read(packet_length) terminator = stream.read(1) - assert terminator == "\x16" # Extended packets are terminated with \x16 + assert terminator == b"\x16" # Extended packets are terminated with \x16 extPacketType = packet[0] if extPacketType == HT_EXTPKT_CONFIRMATION: # Confirmation of a command. From 9b94a7162883e723e35d473d8f10309cb37b3156 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 15 Feb 2018 17:08:16 +0100 Subject: [PATCH 15/20] Use old protocol for easy braille --- source/brailleDisplayDrivers/handyTech.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 8a7efe35420..48bdb7070f1 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -373,7 +373,7 @@ class ModularEvolution64(ModularEvolution): numCells = 64 -class EasyBraille(Model): +class EasyBraille(OldProtocolMixin, Model): deviceId = MODEL_EASY_BRAILLE numCells = 40 genericName = name = "Easy Braille" From d47bd8220cd738142ba5bf85936072478002dcb6 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Fri, 16 Feb 2018 08:19:26 +0100 Subject: [PATCH 16/20] Last refactoring, including onReceive. Old protocol input is now dstable --- source/brailleDisplayDrivers/handyTech.py | 79 ++++++++++++----------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index 48bdb7070f1..af75339f763 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -620,16 +620,17 @@ def __init__(self, port="auto"): self.isHid = portType.startswith("USB HID") self.isHidSerial = portType == "USB HID serial converter" try: - if self.isHid: - self._dev = hwIo.Hid(port, onReceive=self._onReceive) - if self.isHidSerial: - # This is either the standalone HID adapter cable for older displays, - # or an older display with a HID - serial adapter built in - # Send a flush to open the serial channel - self._dev.write(HT_HID_RPT_InCommand + HT_HID_CMD_FlushBuffers) + if self.isHidSerial: + # This is either the standalone HID adapter cable for older displays, + # or an older display with a HID - serial adapter built in + self._dev = hwIo.Hid(port, onReceive=self._hidSerialOnReceive) + # Send a flush to open the serial channel + self._dev.write(HT_HID_RPT_InCommand + HT_HID_CMD_FlushBuffers) + elif self.isHid: + self._dev = hwIo.Hid(port, onReceive=self._hidOnReceive) else: self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, parity=PARITY, - timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._onReceive) + timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._serialOnReceive) except EnvironmentError: log.debugWarning("", exc_info=True) continue @@ -659,9 +660,8 @@ def terminate(self): # Make sure the device gets closed. # If it doesn't, we may not be able to re-open it later. self._dev.close() - if not self.isHid: - # We must sleep after closing the COM port, as it takes some time for the device to disconnect. - time.sleep(self.timeout) + # We must sleep after closing, as it sometimes takes some time for the device to disconnect. + time.sleep(self.timeout) def _get_atc(self): return self._atc @@ -722,21 +722,29 @@ def _handleKeyRelease(self): # pylint: disable=R0912 # Pylint complains about many branches, might be worth refactoring - def _onReceive(self, data): - if self.isHidSerial: - # The HID serial converter wraps one or two bytes into a single HID packet - hidLength = ord(data[1]) - self._hidSerialBuffer+=data[2:(2+hidLength)] + def _hidOnReceive(self, data): + # data contains the entire packet. + stream = StringIO(data) + serPacketType = data[2] + # Skip the header, so reading the stream will only give the rest of the data + stream.seek(3) + self._handleInputStream(serPacketType, stream) + + def _hidSerialOnReceive(self, data): + # The HID serial converter wraps one or two bytes into a single HID packet + hidLength = ord(data[1]) + self._hidSerialBuffer+=data[2:(2+hidLength)] + self._processHidSerialBuffer() + + def _processHidSerialBuffer(self): + while self._hidSerialBuffer: currentBufferLength=len(self._hidSerialBuffer) serPacketType = self._hidSerialBuffer[0] if serPacketType!=HT_PKT_EXTENDED: packetLength = 2 if serPacketType==HT_PKT_OK else 1 - if currentBufferLength>packetLength: + if currentBufferLength>=packetLength: stream = StringIO(self._hidSerialBuffer[:packetLength]) self._hidSerialBuffer = self._hidSerialBuffer[packetLength:] - elif currentBufferLength==packetLength: - stream = StringIO(self._hidSerialBuffer) - self._hidSerialBuffer = b"" else: # The packet is not yet complete return @@ -748,29 +756,22 @@ def _onReceive(self, data): if len(self._hidSerialBuffer)packet_length: - stream = StringIO(self._hidSerialBuffer[:packet_length]) - self._hidSerialBuffer = self._hidSerialBuffer[packet_length:] - else: - assert(self._hidSerialBuffer.endswith("\x16"), "Extended packet termionator expected" - stream = StringIO(self._hidSerialBuffer) - self._hidSerialBuffer = b"" + # We have a complete packet. + # We also isolate it from another packet that could have landed in the buffer, + stream = StringIO(self._hidSerialBuffer[:packet_length]) + self._hidSerialBuffer = self._hidSerialBuffer[packet_length:] + if len(self._hidSerialBuffer)==packet_length: + assert self._hidSerialBuffer.endswith("\x16"), "Extended packet termionator expected" else: # The packet is not yet complete return stream.seek(1) - elif self.isHid: - # data contains the entire packet. - stream = StringIO(data) - serPacketType = data[2] - # Skip the header, so reading the stream will only give the rest of the data - stream.seek(3) - else: - serPacketType = data - # data only contained the packet type. Read the rest from the device. - stream = self._dev + self._handleInputStream(serPacketType, stream) + + def _serialOnReceive(self, data): + self._handleInputStream(data, self._dev) + def _handleInputStream(self, serPacketType, stream): if serPacketType in (HT_PKT_OK, HT_PKT_EXTENDED): modelId = stream.read(1) if not self._model: @@ -846,7 +847,7 @@ def display(self, cells): scriptCategory = SCRCAT_BRAILLE def script_toggleBrailleInput(self, _gesture): - self.brailleInput = not self._brailleInput + self.brailleInput = not self.brailleInput if self.brailleInput: # Translators: message when braille input is enabled ui.message(_('Braille input enabled')) From 6bcdfb776fa8a83b310e27cf432700f5d31ad3d2 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Sat, 17 Feb 2018 12:42:16 +0100 Subject: [PATCH 17/20] Remove the com server --- miscDeps | 2 +- readme.md | 2 -- source/setup.py | 25 ------------------------- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/miscDeps b/miscDeps index 3707b8e4052..fe405850d39 160000 --- a/miscDeps +++ b/miscDeps @@ -1 +1 @@ -Subproject commit 3707b8e4052670c454343e32d8de3f0b8beab642 +Subproject commit fe405850d39cb2ca632d890bde67bd1ac5e4d5c0 diff --git a/readme.md b/readme.md index 5c3be5be027..2820d94e416 100644 --- a/readme.md +++ b/readme.md @@ -73,8 +73,6 @@ For reference, the following dependencies are included in Git submodules: * brlapi Python bindings, version 0.5.7 or later, distributed with [BRLTTY for Windows](http://brl.thefreecat.org/brltty/), version 4.2-2 * ALVA BC6 generic dll, version 3.0.4.1 * lilli.dll, version 2.1.0.0 -* [Handy Tech Braille SDK, version 1.4.2.0](ftp://ftp.handytech.de/public/Software/BrailleDriver/HTBrailleSDK_1420a.zip) -* Updated Handy Tech sbsupport.dll and dealers.dat received on 2014-09-09 * [pyserial](http://pypi.python.org/pypi/pyserial), version 2.7 * [Python interface to FTDI driver/chip](http://fluidmotion.dyndns.org/zenphoto/index.php?p=news&title=Python-interface-to-FTDI-driver-chip) * [Py2Exe](http://sourceforge.net/projects/py2exe/), version 0.6.9 diff --git a/source/setup.py b/source/setup.py index 8f9a137e0fd..f2c10c986d1 100755 --- a/source/setup.py +++ b/source/setup.py @@ -19,31 +19,6 @@ import imp MAIN_MANIFEST_EXTRA = r""" - - - - - - - - From e5b30b4decb68be37b1d0142c88965a43a19f539 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 20 Feb 2018 07:08:41 +0100 Subject: [PATCH 18/20] Review action --- source/brailleDisplayDrivers/handyTech.py | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index af75339f763..a8fe0418580 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -725,10 +725,10 @@ def _handleKeyRelease(self): def _hidOnReceive(self, data): # data contains the entire packet. stream = StringIO(data) - serPacketType = data[2] + htPacketType = data[2] # Skip the header, so reading the stream will only give the rest of the data stream.seek(3) - self._handleInputStream(serPacketType, stream) + self._handleInputStream(htPacketType, stream) def _hidSerialOnReceive(self, data): # The HID serial converter wraps one or two bytes into a single HID packet @@ -739,16 +739,16 @@ def _hidSerialOnReceive(self, data): def _processHidSerialBuffer(self): while self._hidSerialBuffer: currentBufferLength=len(self._hidSerialBuffer) - serPacketType = self._hidSerialBuffer[0] - if serPacketType!=HT_PKT_EXTENDED: - packetLength = 2 if serPacketType==HT_PKT_OK else 1 + htPacketType = self._hidSerialBuffer[0] + if htPacketType!=HT_PKT_EXTENDED: + packetLength = 2 if htPacketType==HT_PKT_OK else 1 if currentBufferLength>=packetLength: stream = StringIO(self._hidSerialBuffer[:packetLength]) self._hidSerialBuffer = self._hidSerialBuffer[packetLength:] else: # The packet is not yet complete return - elif serPacketType==HT_PKT_EXTENDED and currentBufferLength>=5: + elif htPacketType==HT_PKT_EXTENDED and currentBufferLength>=5: # Check whether our packet is complete # Extended packets are at least 5 bytes in size. # The second byte is the model, the third byte is the data length, excluding the terminator @@ -766,13 +766,13 @@ def _processHidSerialBuffer(self): # The packet is not yet complete return stream.seek(1) - self._handleInputStream(serPacketType, stream) + self._handleInputStream(htPacketType, stream) def _serialOnReceive(self, data): self._handleInputStream(data, self._dev) - def _handleInputStream(self, serPacketType, stream): - if serPacketType in (HT_PKT_OK, HT_PKT_EXTENDED): + def _handleInputStream(self, htPacketType, stream): + if htPacketType in (HT_PKT_OK, HT_PKT_EXTENDED): modelId = stream.read(1) if not self._model: if not modelId in MODELS: @@ -786,14 +786,14 @@ def _handleInputStream(self, serPacketType, stream): # plugged in the same (already open) serial port. self.terminate() - if serPacketType==HT_PKT_OK: + if htPacketType==HT_PKT_OK: pass - elif serPacketType == HT_PKT_ACK: + elif htPacketType == HT_PKT_ACK: # This is unexpected, but we need to make sure that we handle old style ack self._handleAck() - elif serPacketType == HT_PKT_NAK: + elif htPacketType == HT_PKT_NAK: log.debugWarning("NAK received!") - elif serPacketType == HT_PKT_EXTENDED: + elif htPacketType == HT_PKT_EXTENDED: packet_length = ord(stream.read(1)) packet = stream.read(packet_length) terminator = stream.read(1) @@ -819,12 +819,12 @@ def _handleInputStream(self, serPacketType, stream): log.debugWarning("Unhandled extended packet of type %r: %r" % (extPacketType, packet)) else: - serPacketOrd = ord(serPacketType) + serPacketOrd = ord(htPacketType) if isinstance(self._model, OldProtocolMixin) and serPacketOrd&~KEY_RELEASE_MASK < HT_PKT_EXTENDED: self._handleInput(serPacketOrd) else: # Unknown packet type, log it - log.debugWarning("Unhandled packet of type %r" % serPacketType) + log.debugWarning("Unhandled packet of type %r" % htPacketType) def _handleInput(self, key): From 6ca6642fbdea12c3f6f544331ea170dd1326654f Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 20 Feb 2018 08:38:08 +0100 Subject: [PATCH 19/20] Use Old Protocol for Braille Wave --- source/brailleDisplayDrivers/handyTech.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index a8fe0418580..f9eb22729e8 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -411,7 +411,7 @@ class Braillino(TripleActionKeysMixin, OldProtocolMixin, Model): genericName = name = 'Braillino' -class BrailleWave(Model): +class BrailleWave(OldProtocolMixin, Model): deviceId = MODEL_BRAILLE_WAVE numCells = 40 genericName = name = "Braille Wave" From 4e55bc57ed5fc782293d4d67f9df5c9e5b1d684e Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 6 Mar 2018 11:42:15 +0100 Subject: [PATCH 20/20] Catch ValueError for Handy Tech time sync datetime.datetime --- source/brailleDisplayDrivers/handyTech.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/source/brailleDisplayDrivers/handyTech.py b/source/brailleDisplayDrivers/handyTech.py index f9eb22729e8..b7c9c7c254a 100644 --- a/source/brailleDisplayDrivers/handyTech.py +++ b/source/brailleDisplayDrivers/handyTech.py @@ -254,14 +254,18 @@ def postInit(self): def handleTime(self, timeStr): ords = map(ord, timeStr) - displayDateTime = datetime.datetime( - year=ords[0] << 8 | ords[1], - month=ords[2], - day=ords[3], - hour=ords[4], - minute=ords[5], - second=ords[6] - ) + try: + displayDateTime = datetime.datetime( + year=ords[0] << 8 | ords[1], + month=ords[2], + day=ords[3], + hour=ords[4], + minute=ords[5], + second=ords[6] + ) + except ValueError: + log.debugWarning("Invalid time/date of Handy Tech display: %r"%timeStr) + return localDateTime = datetime.datetime.today() if abs((displayDateTime - localDateTime).total_seconds()) >= 5: log.debugWarning("Display time out of sync: %s"%displayDateTime.isoformat())