Repository: nvaccess/nvda

Issue:
Native driver for Optelec braille displays
Is it possible to start work on native driver for Optelec displays such as BC6 series?

Here are some benefits:
- many users use these displays (BC640/680, BC640 Comfort (USB and also emulate them with protocol converter attached to Alva Satellite or Tieman Voyager displays.
- It would be possible to use advanced braille features which are coming to NVDA.

Unfortunately I don't have enough skills to develop it but if I can help you on the other way, please tell me.

Thanks,
support for Optelec convertcard 
Hi. I have an braille wouager and a Optelec convertecard wich makes it possible to use braille voyager for newer computers. I am not expert, but as i understand it, the card convert  braille voyager and even other older braildisplays from Tieman to a newer protocols like alva.
However this card is working on jaws, Windows eyes, and other screenreaders. However nvda does not work with this card. I believe it is more then me who have this issue, a new brailledisplay is not the cheepest eithem you can buy. so support for this would be an good idea. I am not a programer, but i'm not believe this is an bigger task to fix. That could be integrated in that allready alva drivers wich is allready included in NVDA.   


Discussion:
CC @dkager, who also has such a display and has some decent programming skills. I will see whether I could get an old device myself.

I have to note though, that it would be very helpful to have the data protocol for these displays, especially to implement the converter and Comfort part.
Hi,

shell it help you if I sent you a protocol converter via postal service or DHL? my display is currently on a maintenance and repairment so I do not need it now.
If you guide or tech me I will also be glad to send you other materials to successfully develop a native driver for BC6 series.

regards, Jožef
The most important thing is the protocol, so if you could assist in getting hold of that, that would be awesome.
I might have time to work on this around christmas, but I don't have a protocol to work with, only a BC640, without the comfort part. Unless I have a protocol document, I probably have to work with what BRLTTY and some device monitoring tools will tell me.
Hello,

I contacted our distributer a month ago, but unfortunately I have not
got a document yet.

anyway thanks for your good work.

regards, Jožef

2017-12-18 19:21 GMT+01.00, Leonard de Ruijter <notifications@github.com>:
> I might have time to work on this around christmas, but I don't have a
> protocol to work with, only a BC640, without the comfort part. Unless I have
> a protocol document, I probably have to work with what BRLTTY and some
> device monitoring tools will tell me.
>
> --
> You are receiving this because you authored the thread.
> Reply to this email directly or view it on GitHub:
> https://github.com/nvaccess/nvda/issues/7733#issuecomment-352514611

I've had contact with @jcsteh and might be able to start with a native driver very shortly. @gregjozk, your testing would of course be greatly appreciated. I will start posting try builds here whenever they are available, probably within the first or second week of January.
Interesting, I do not know this device. CC @dkager
What display do you have to select in other screen readers in order to 
get the voyager working with this convert device?

Hi. In jaws i select something call Optelec Alva BC6. I dont know, but 
there is even drivers for this card in order to use with jawsand other 
screanrederscan  of curse sen it to some one if it's helpful.

Alexander
Den 2017-01-14 kl. 09:36, skrev Leonard de Ruijter:
> Interesting, I do not know this device. CC @dkager
> What display do you have to select in other screen readers in order to
> get the voyager working with this convert device?
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub 
> <https://github.com/nvaccess/nvda/issues/6731#issuecomment-272610642>, 
> or mute the thread 
> <https://github.com/notifications/unsubscribe-auth/AX86WeSt5ML8ayb9SiVmBLqk7-fsr7qMks5rSIkGgaJpZM4LjjHb>.
>


I have heard of this card and even seem to recall there being a reason why it it not in NVDA. But I don't know what device ID it uses. We could ping Optelec, unless @jcsteh can shed some light.
Hi, at this time, besides Jamie, you should contact VFO to see what’s up. Thanks.

 

From: Davy Kager [mailto:notifications@github.com] 
Sent: Saturday, January 14, 2017 3:29 AM
To: nvaccess/nvda <nvda@noreply.github.com>
Cc: Subscribed <subscribed@noreply.github.com>
Subject: Re: [nvaccess/nvda] support for Optelec convertcard (#6731)

 

I have heard of this card and even seem to recall there being a reason why it it not in NVDA. But I don't know what device ID it uses. We could ping Optelec, unless @jcsteh <https://github.com/jcsteh>  can shed some light.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub <https://github.com/nvaccess/nvda/issues/6731#issuecomment-272618358> , or mute the thread <https://github.com/notifications/unsubscribe-auth/AHgLkNZiacr1wba8WYc2ai19Sa2jhcEhks5rSLFzgaJpZM4LjjHb> .


As I understand it, this should work with our ALVA driver. What happens if
you select "ALVA BC640/680 series" from the braille displaylist?

It say something like can not initiate the brailledisplay. And om my 
voyager i can read the velcome tex "braille voyager conected".




Den 2017-01-14 kl. 22:28, skrev James Teh:
> As I understand it, this should work with our ALVA driver. What happens if
> you select "ALVA BC640/680 series" from the braille displaylist?
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub 
> <https://github.com/nvaccess/nvda/issues/6731#issuecomment-272654470>, 
> or mute the thread 
> <https://github.com/notifications/unsubscribe-auth/AX86WU76u3bRGxEhxut1mpmPX7K586Tuks5rST4KgaJpZM4LjjHb>.
>


Could you share the log file:
- NVDA menu
- Tools
- View log
- Copy the bottom lines, which should say something about an error.

I wonder if our alvaw32.dll is too old. Upgrading this library would add a lot of dependencies if I remember correctly.
Is this ok?

ERROR - braille.BrailleHandler.setDisplayByName (12:04:39):
Error initializing display driver
Traceback (most recent call last):
   File "braille.pyc", line 1502, in setDisplayByName
   File "brailleDisplayDrivers\alvaBC6.pyc", line 58, in __init__
RuntimeError: No ALVA display found
INFO - braille.BrailleHandler.setDisplayByName (12:04:39):
Loaded braille display driver noBraille, current display has 0 cells.




Den 2017-01-15 kl. 11:57, skrev Davy Kager:
>
> Could you share the log file:
>
>   * NVDA menu
>   * Tools
>   * View log
>   * Copy the bottom lines, which should say something about an error.
>
> I wonder if our alvaw32.dll is too old. Upgrading this library would 
> add a lot of dependencies if I remember correctly.
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub 
> <https://github.com/nvaccess/nvda/issues/6731#issuecomment-272687773>, 
> or mute the thread 
> <https://github.com/notifications/unsubscribe-auth/AX86WVLvNgl0_rgHOXUM4061UXgLTCpzks5rSfuMgaJpZM4LjjHb>.
>


Yeah, it looks to me like the library from Optelec doesn't find the display.
Yes, it's so i understand it also. An no profecional gues, could it have 
something to do that i run 64 bit pc? But on other hand, the 
brailldisplay is not dead, it wake up when i turn on the computer so my 
unprofecional gues that it must have something to do with drivers for 
nvda. There is maybe some one who from optelec or have conection with 
them and can find out how this card works?????




Den 2017-01-15 kl. 12:13, skrev Davy Kager:
>
> Yeah, it looks to me like the library from Optelec doesn't find the 
> display.
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub 
> <https://github.com/nvaccess/nvda/issues/6731#issuecomment-272688493>, 
> or mute the thread 
> <https://github.com/notifications/unsubscribe-auth/AX86WWYKiX1_Ee353jjbs3yiIWxG8Zy1ks5rSf9wgaJpZM4LjjHb>.
>


Was the converter connected to the computer via USB, serial or Bluetooth?

It's possible that the older version of the ALVA dll we use doesn't support
this, but that surprises me because I thought this converter was released
long before the new dll was released. We definitely can't update to the
newer dll; it introduces a lot of large dependencies. We'd either need to
write a new driver using the raw protocol or drop the ALVA driver from NVDA
itself and distribute it as an add-on with the new dll. We don't have
access to one of these converters, though, so there's no guarantee we'd get
it right if we wrote a new driver.

Hi,

discussion about problems with protocol converter can be found at issue #5493, where has been already told, that a newest library is too big to be included in NVDA core... Whatever I still have a problem but as work around I have begun using a BRLTTY also on 64-bit systems when I wanted to use an ALVA Satellite. When I use other screen readers, I use Satellite with protocol converter.
I'm refferencing this to #5493, because the protocol converter supports both Braille Voyager braille displays and Satellite braille displays.
Fair enough, but is the old (current) library supposed to support the converter?
Is the raw protocol documented anywhere? This might be tricky to get right on Bluetooth.
This old displays does not support bluetooth. And personaly i think an 
solotion would be better to find without BRLTTY, it's for an mayority of 
users very tricky and only for tech-.persons. As i understand Linux is 
prio 1 for them. I hope you people who has an knowlegde about this, will 
find and solotion without brltty.  to



Den 2017-01-16 kl. 15:48, skrev Davy Kager:
>
> Fair enough, but is the old (current) library supposed to support the 
> converter?
> Is the raw protocol documented anywhere? This might be tricky to get 
> right on Bluetooth.
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub 
> <https://github.com/nvaccess/nvda/issues/6731#issuecomment-272881470>, 
> or mute the thread 
> <https://github.com/notifications/unsubscribe-auth/AX86WUyUUcbhivybq-BdsteIW4qbH98Gks5rS4MhgaJpZM4LjjHb>.
>


Hi,

here we talk about usb connection. the usb protocol converter presents
Satellite and Voyager displays as BC6 displays. unfortunately, when a
display is connected to a computer via this hardware interface, NVDA
says "could not load BC6 display" even a device is plugged in and
display is ready.

oh, how protocol converter connects? a device has to connectors:
- mini usb "female" connector, where a usb cable from computer is connected.
- clasical usb "b" "male" connector which is connected to a display.

regards, Jožef

2017-01-16 16:07 GMT+01.00, Bamze66 <notifications@github.com>:
> This old displays does not support bluetooth. And personaly i think an
> solotion would be better to find without BRLTTY, it's for an mayority of
> users very tricky and only for tech-.persons. As i understand Linux is
> prio 1 for them. I hope you people who has an knowlegde about this, will
> find and solotion without brltty.  to
>
>
>
> Den 2017-01-16 kl. 15:48, skrev Davy Kager:
>>
>> Fair enough, but is the old (current) library supposed to support the
>> converter?
>> Is the raw protocol documented anywhere? This might be tricky to get
>> right on Bluetooth.
>>
>> —
>> You are receiving this because you authored the thread.
>> Reply to this email directly, view it on GitHub
>> <https://github.com/nvaccess/nvda/issues/6731#issuecomment-272881470>,
>> or mute the thread
>> <https://github.com/notifications/unsubscribe-auth/AX86WUyUUcbhivybq-BdsteIW4qbH98Gks5rS4MhgaJpZM4LjjHb>.
>>
>
>
>
> --
> You are receiving this because you commented.
> Reply to this email directly or view it on GitHub:
> https://github.com/nvaccess/nvda/issues/6731#issuecomment-272886399

[**@dkager**](https://github.com/dkager) commented on [17 Jan. 2017, 12:48 am AEST](https://github.com/nvaccess/nvda/issues/6731#issuecomment-272881470 "2017-01-16T14:48:00Z - Replied by Github Reply Comments"):
> Fair enough, but is the old (current) library supposed to support the converter?

I don't know. I assumed so, but that seems not to be the case.

I do know that the converter has a different USB product id. That is, it doesn't show up as a 640 or a 680; it has its own id. I guess the older dll mustn't know about this id.

> Is the raw protocol documented anywhere?

I do have the documentation from Optelec, yes. It's not publicly available, though. (Don't get me started on how ridiculous that is.) If someone wants to work on this, I can probably provide it.

> This might be tricky to get right on Bluetooth.

I don't think the converter supports Bluetooth? As for the BC640/680, I also have the Bluetooth protocol documentation. Unfortunately, it's a completely different protocol to the one they use for USB.
Yes, protocol converter supports only USB, cause this works with displays (Voyager and Satellite), which do not have a BT interface.
I have a BC6, so I could give this a shot. But a few remarks first:
- It is not completely certain that the DLL is the culprit. I could ask Optelec about it, we also had contact last year when I extended the BC6 keymap.
- I am not experienced enough in this to do it without looking at an existing driver. Maybe the one for the Brailliant BI?
- Or is the user base small enough to justify an addon with latest DLL? In The Netherlands there are still quite a few users of this display, so that would increase the threshold a bit for them.
An add-on with dll doesn't have to mean that we have to throw away the 
driver in core. The add-on can just replace the driver in core IMO.

[**@dkager**](https://github.com/dkager) commented on [17 Jan. 2017, 5:32 pm AEST](https://github.com/nvaccess/nvda/issues/6731#issuecomment-273041639 "2017-01-17T07:32:09Z - Replied by Github Reply Comments"):
> - It is not completely certain that the DLL is the culprit. I could ask Optelec about it, we also had contact last year when I extended the BC6 keymap.

I do have contacts at Optelec, but we haven't had any contact since the merge with VFO. I can try poking them if need be.

> - I am not experienced enough in this to do it without looking at an existing driver. Maybe the one for the Brailliant BI?

brailliantB is probably the best driver to start with. It has to support both HID and Bluetooth serial and uses the new hwIo stuff for both.

> - Or is the user base small enough to justify an addon with latest DLL? In The Netherlands there are still quite a few users of this display, so that would increase the threshold a bit for them.

It isn't an ideal solution, but nor is the current situation. I'll discuss with others here. We do have a BC640, though it's not currently with me at the moment.

[**@leonardder**](https://github.com/leonardder) commented on [17 Jan. 2017, 5:34 pm AEST](https://github.com/nvaccess/nvda/issues/6731#issuecomment-273041953 "2017-01-17T07:34:12Z - Replied by Github Reply Comments"):
> An add-on with dll doesn't have to mean that we have to throw away the 
> driver in core. The add-on can just replace the driver in core IMO.

I disagree. It is difficult to explain, will confuse users and will thereby create additional support load.
I wonder if there would be any other benefits to a Python-based driver. For me as BC640 user support for the converter card isn't very interesting. But maybe something like automatic reconnect or thread safety.
If nothing else I'll gladly test the new driver if it is realized. Maybe poking VFO is a good idea. Your contacts are probably better than mine. :)
Hi,

for existing BC6 users, there is no benefit, but benefit is for users,
who use Satellite series displays (544 Pro, 570, 584 Pro and 544
Traveller) and Voyager series. In South slavic countries there are
many users who use these display especially Satellite series.

Regards, Jožef

2017-01-18 10:39 GMT+01.00, Davy Kager <notifications@github.com>:
> I wonder if there would be any other benefits to a Python-based driver. For
> me as BC640 user support for the converter card isn't very interesting. But
> maybe something like automatic reconnect or thread safety.
> If nothing else I'll gladly test the new driver if it is realized. Maybe
> poking VFO is a good idea. Your contacts are probably better than mine. :)
>
> --
> You are receiving this because you commented.
> Reply to this email directly or view it on GitHub:
> https://github.com/nvaccess/nvda/issues/6731#issuecomment-273428273

Auto reconnect is not something that should be done in individual drivers.
It will happen as part of braille display detection (#1271).

The other thing moving to a Python based driver gives us is direct access
to the braille keys so NVDA can use its own braille input code, rather than
the ALVA's own braille translator. Right now, that's probably not a massive
win, but it will be once we have uncontracted and contracted input.

I may get a chance to look at this some time in the next few months, but
I'm not certain.

Related issue: #7733.
This will be covered by #7733. I'm not treating this issue as duplicate though, since it covers a specific case which is currently not available in NVDA, whereas other Alva users can already use most if not all functions of their displays.

Resolving patch:
diff --git a/source/braille.py b/source/braille.py
--- a/source/braille.py
+++ b/source/braille.py
@@ -3,7 +3,7 @@
 #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, Joseph Lee, Babbage B.V., Davy Kager, Bram Duvigneau
+#Copyright (C) 2008-2018 NV Access Limited, Joseph Lee, Babbage B.V., Davy Kager, Bram Duvigneau
 
 import sys
 import itertools
@@ -1930,7 +1930,8 @@ def func(cls):
 
 #: Maps old braille display driver names to new drivers that supersede old drivers.
 RENAMED_DRIVERS = {
-	"syncBraille":"hims"
+	"syncBraille":"hims",
+	"alvaBc6":"alva"
 }
 
 def initialize():
diff --git a/source/brailleDisplayDrivers/alva.py b/source/brailleDisplayDrivers/alva.py
new file mode 100644
--- /dev/null
+++ b/source/brailleDisplayDrivers/alva.py
@@ -0,0 +1,453 @@
+#brailleDisplayDrivers/alva.py
+#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) 2009-2018 NV Access Limited, Davy Kager, Leonard de Ruijter, Optelec B.V.
+
+import serial
+import hwPortUtils
+import braille
+from logHandler import log
+import inputCore
+import brailleInput
+import hwIo
+from collections import OrderedDict
+from globalCommands import SCRCAT_BRAILLE
+import ui
+from baseObject import ScriptableObject
+import wx
+import time
+import datetime
+
+ALVA_RELEASE_MASK = 0x80
+ALVA_2ND_CR_MASK = 0x80
+ALVA_BRAILLE_OUTPUT_REPORT = b"\x02"
+ALVA_BRAILLE_OUTPUT_MAX_SIZE = 40
+ALVA_KEY_REPORT = b"\x04"
+ALVA_KEY_REPORT_KEY_POS = 1
+ALVA_KEY_REPORT_KEY_GROUP_POS = 2
+ALVA_KEY_RAW_INPUT_MASK = 0x30
+ALVA_DISPLAY_SETTINGS_REPORT = b"\x05"
+ALVA_DISPLAY_SETTINGS_CELL_COUNT_POS = 6
+ALVA_KEY_SETTINGS_REPORT = b"\x06"
+ALVA_KEY_SETTINGS_POS = 1 # key settings are stored as bits in 1 byte
+ALVA_RTC_REPORT = b"\x0a"
+ALVA_RTC_STR_LENGTH = 7
+ALVA_RTC_MAX_DRIFT = 5
+
+ALVA_MODEL_IDS = {
+	0x40: "BC640",
+	0x80: "BC680",
+	0x99: "ProtocolConverter",
+}
+
+ALVA_SER_CMD_LENGTHS = {
+	b"T": 2, # Status cells
+	b"S": 1, # Splitpoint
+	b"D": 1, # Dot pressure
+	b"O": 1, # Output Braille table
+	b"I": 1, # Input Braille table
+	b"F": 4, # Key feedback
+	b"P": 1, # Key repeat
+	b"2": 3, # 2nd CR row emulation parameters
+	b"L": 3, # System language
+	b"R": 1, # Bluetooth enable/disable
+	b"V": 5, # Hardware and firmware versions
+	b"G": 9, # Battery status
+	b"H": ALVA_RTC_STR_LENGTH, # Time
+	b"?": 1, # Device ID
+	b"E": 1, # Braille cell count
+	b"N": 12, # Serial numbers
+	b"X": 1, # Bluetooth connection state
+	b"k": 1, # Standard HID keyboard interface enable/disable
+	b"r": 1, # Raw keyboard messages enable/disable
+	b"h": 8, # Serial HID input messages
+	b"K": 2, # Keys message
+}
+
+ALVA_CR_GROUP = 0x74
+ALVA_FEATURE_PACK_GROUP = 0x78
+ALVA_SPECIAL_KEYS_GROUP = 0x01
+ALVA_SPECIAL_SETTINGS_CHANGED = 0x01
+
+ALVA_KEYS = {
+	# Thumb keys (FRONT_GROUP)
+	0x71: ("t1", "t2", "t3", "t4", "t5",
+		# Only for BC680
+		"t1", "t2", "t3", "t4", "t5"),
+	# eTouch keys (ETOUCH_GROUP)
+	0x72: ("etouch1", "etouch2", "etouch3", "etouch4"),
+	# Smartpad keys (PDA_GROUP)
+	0x73: ("sp1", "sp2", "spLeft", "spEnter", "spUp", "spDown", "spRight", "sp3", "sp4",
+		# Only for BC680
+		"sp1", "sp2", "spLeft", "spEnter", "spUp", "spDown", "spRight", "sp3", "sp4"),
+	# Feature pack keys.
+	# Numbers start at 0x01, therefore the first string is an empty placeholder.
+	ALVA_FEATURE_PACK_GROUP: ("", "dot1", "dot2", "dot3", "dot4", "dot5", "dot6", "dot7", "dot8",
+		"control", "windows", "space", "alt", "enter"),
+}
+
+USB_IDS = {
+	"VID_0798&PID_0640", # BC640
+	"VID_0798&PID_0680", # BC680
+	"VID_0798&PID_0699", # USB protocol converter
+}
+
+class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject):
+	name = "alva"
+	# Translators: The name of a braille display.
+	description = _("Optelec ALVA 6 series/protocol converter")
+	isThreadSafe = True
+	timeout = 0.2
+
+	@classmethod
+	def check(cls):
+		return True
+
+	@classmethod
+	def getPossiblePorts(cls):
+		ports = OrderedDict()
+		comPorts = list(hwPortUtils.listComPorts(onlyAvailable=True))
+		try:
+			next(cls._getAutoPorts(comPorts))
+			ports.update((cls.AUTOMATIC_PORT,))
+		except StopIteration:
+			pass
+		for portInfo in comPorts:
+			if not portInfo.get("bluetoothName","").startswith("ALVA "):
+				continue
+			# Translators: Name of a bluetooth serial communications port.
+			ports[portInfo["port"]] = _("Bluetooth serial: {portName}").format(portName=portInfo["friendlyName"])
+		return ports
+
+	@classmethod
+	def _getAutoPorts(cls, comPorts):
+		for portInfo in hwPortUtils.listHidDevices():
+			if portInfo.get("usbID","") in USB_IDS:
+				yield portInfo["devicePath"], "USB HID", portInfo["usbID"]
+		for portInfo in comPorts:
+			if not portInfo.get("bluetoothName","").startswith("ALVA "):
+				continue
+			yield portInfo["port"], "bluetooth", portInfo["bluetoothName"]
+
+	def _get_model(self):
+		if not self._deviceId:
+			return ""
+		self.model = ALVA_MODEL_IDS[self._deviceId]
+		return self.model
+
+	def _updateSettings(self):
+		oldNumCells = self.numCells
+		if self.isHid:
+			displaySettings = self._dev.getFeature(ALVA_DISPLAY_SETTINGS_REPORT)
+			self.numCells = ord(displaySettings[ALVA_DISPLAY_SETTINGS_CELL_COUNT_POS])
+			timeStr = self._dev.getFeature(ALVA_RTC_REPORT)[1:ALVA_RTC_STR_LENGTH+1]
+			self._handleTime(timeStr)
+			keySettings = self._dev.getFeature(ALVA_KEY_SETTINGS_REPORT)[ALVA_KEY_SETTINGS_POS]
+			self._rawKeyboardInput = bool(ord(keySettings) & ALVA_KEY_RAW_INPUT_MASK)
+		else:
+			# Get cell count
+			self._ser6SendMessage(b"E", b"?")
+			for i in xrange(3):
+				self._dev.waitForRead(self.timeout)
+				if self.numCells: # Display responded
+					break
+			else: # No response from display, do not send the other requests.
+				return
+			# Get device date and time
+			self._ser6SendMessage(b"H", b"?")
+			# Get HID keyboard input state
+			self._ser6SendMessage(b"r", b"?")
+		if oldNumCells not in (0, self.numCells):
+			# In case of splitpoint changes, we need to update the braille handler as well
+			braille.handler.displaySize = self.numCells
+
+	def __init__(self, port="auto"):
+		super(BrailleDisplayDriver,self).__init__()
+		self.numCells = 0
+		self._rawKeyboardInput = False
+		self._deviceId = None
+		if port == "auto":
+			tryPorts = self._getAutoPorts(hwPortUtils.listComPorts(onlyAvailable=True))
+		else:
+			tryPorts = ((port, "bluetooth", "ALVA"),)
+		for port, portType, identifier in tryPorts:
+			self.isHid = portType == "USB HID"
+			# Try talking to the display.
+			try:
+				if self.isHid:
+					self._dev = hwIo.Hid(port, onReceive=self._hidOnReceive)
+					self._deviceId = int(identifier[-2:],16)
+				else:
+					self._dev = hwIo.Serial(port, timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._ser6OnReceive)
+					# Get the device ID
+					self._ser6SendMessage(b"?", b"?")
+					for i in xrange(3):
+						self._dev.waitForRead(self.timeout)
+						if self._deviceId: # Display responded
+							break
+					else: # No response from display
+						continue
+			except EnvironmentError:
+				continue
+			self._updateSettings()
+			if self.numCells:
+				# A display responded.
+				log.info("Found display with {cells} cells connected via {type} ({port})".format(
+					cells=self.numCells, type=portType, port=port))
+				break
+			self._dev.close()
+
+		else:
+			raise RuntimeError("No display found")
+
+		self._keysDown = set()
+		self._ignoreKeyReleases = False
+
+	def terminate(self):
+		try:
+			super(BrailleDisplayDriver, self).terminate()
+		finally:
+			# 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 _ser6SendMessage(self, cmd, value=""):
+		if isinstance(value, (int, bool)):
+			value = chr(value)
+		self._dev.write(b"\x1b{cmd}{value}".format(cmd=cmd, value=value,))
+
+	def _ser6OnReceive(self, data):
+		if data != b"\x1b": # Escape
+			return
+		cmd = self._dev.read(1)
+		value = self._dev.read(ALVA_SER_CMD_LENGTHS[cmd])
+
+		if cmd == b"K": # Input
+			self._handleInput(ord(value[0]), ord(value[1]))
+		elif cmd == b"E": # Braille cell count
+			self.numCells = ord(value)
+		elif cmd == b"?": # Device ID
+			self._deviceId = ord(value)
+		elif cmd == b"r": # Raw keyboard messages enable/disable
+			self._rawKeyboardInput = bool(ord(value))
+		elif cmd == b"H": # Time
+			self._handleTime(value)
+
+	def _hidOnReceive(self, data):
+		reportID = data[0]
+		if reportID == ALVA_KEY_REPORT:
+			self._handleInput(ord(data[ALVA_KEY_REPORT_KEY_GROUP_POS]), ord(data[ALVA_KEY_REPORT_KEY_POS]))
+
+	def _handleInput(self, group, number):
+		if group == ALVA_SPECIAL_KEYS_GROUP:
+			# ALVA displays communicate setting changes as input messages.
+			if number == ALVA_SPECIAL_SETTINGS_CHANGED:
+				# Some internal settings have changed.
+				# For example, split point could have been set, in which case the number of cells changed.
+				# We must handle these properly.
+				# Call this on the main thread, to make sure that we can wait for reads when in non-HID mode.
+				# This can probably be changed when #1271 is implemented.
+				wx.CallAfter(self._updateSettings)
+			return
+		isRelease = bool(group & ALVA_RELEASE_MASK)
+		group = group & ~ALVA_RELEASE_MASK
+		if isRelease:
+			if not self._ignoreKeyReleases and self._keysDown:
+				try:
+					inputCore.manager.executeGesture(InputGesture(self.model, self._keysDown, brailleInput=self._rawKeyboardInput))
+				except inputCore.NoInputGestureAction:
+					pass
+				# Any further releases are just the rest of the keys in the combination being released,
+				# so they should be ignored.
+				self._ignoreKeyReleases = True
+			self._keysDown.discard((group, number))
+		else: # Press
+			self._keysDown.add((group, number))
+			# This begins a new key combination.
+			self._ignoreKeyReleases = False
+
+	def _hidDisplay(self, cells):
+		for offset in xrange(0, len(cells), ALVA_BRAILLE_OUTPUT_MAX_SIZE):
+			cellsToWrite = cells[offset:offset+ALVA_BRAILLE_OUTPUT_MAX_SIZE]
+			self._dev.write("{id}{offset}{count}{cells}".format(
+				id=ALVA_BRAILLE_OUTPUT_REPORT,
+				offset=chr(offset),
+				count=chr(len(cellsToWrite)),
+				cells=cellsToWrite
+			))
+
+	def _ser6Display(self, cells):
+		self._ser6SendMessage(b"B", chr(0)+chr(len(cells))+cells)
+
+	def display(self, cells):
+		# cells will already be padded up to numCells.
+		cells = b"".join(map(chr, cells))
+		if self.isHid:
+			self._hidDisplay(cells)
+		else:
+			self._ser6Display(cells)
+
+	def _handleTime(self, timeStr):
+		ords = map(ord, timeStr)
+		displayDateTime = datetime.datetime(
+			year=ords[0] | ords[1] << 8,
+			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()) >= ALVA_RTC_MAX_DRIFT:
+			log.debugWarning("Display time out of sync: %s"%displayDateTime.isoformat())
+			self._syncTime(localDateTime)
+		else:
+			log.debug("Time not synchronized. Display time %s"%displayDateTime.isoformat())
+
+	def _syncTime(self, dt):
+		log.debug("Synchronizing braille display date and time...")
+		timeList = [
+			dt.year & 0xFF, dt.year >> 8,
+			dt.month, dt.day,
+			dt.hour, dt.minute, dt.second
+		]
+		timeStr = b"".join(map(chr, timeList))
+		if self.isHid:
+			self._dev.setFeature(ALVA_RTC_REPORT + timeStr)
+		else:
+			self._ser6SendMessage(b"H", timeStr)
+
+	def _get_hidKeyboardInput(self):
+		return not self._rawKeyboardInput
+
+	def _set_hidKeyboardInput(self, state):
+		rawState = not state
+		if self.isHid:
+			# Make sure the device settings are up to date.
+			keySettings = self._dev.getFeature(ALVA_KEY_SETTINGS_REPORT)[ALVA_KEY_SETTINGS_POS]
+			# Try to update the state
+			if rawState:
+				newKeySettings = chr(ord(keySettings) | ALVA_KEY_RAW_INPUT_MASK)
+			elif ord(keySettings) & ALVA_KEY_RAW_INPUT_MASK:
+				newKeySettings = chr(ord(keySettings) ^ ALVA_KEY_RAW_INPUT_MASK)
+			else:
+				newKeySettings = keySettings
+			self._dev.setFeature(ALVA_KEY_SETTINGS_REPORT + newKeySettings)
+			# Check whether the state has been changed successfully.
+			# If not, this device does not support this feature.
+			keySettings = self._dev.getFeature(ALVA_KEY_SETTINGS_REPORT)[ALVA_KEY_SETTINGS_POS]
+			# Save the new state
+			self._rawKeyboardInput = bool(ord(keySettings) & ALVA_KEY_RAW_INPUT_MASK)
+		else:
+			self._ser6SendMessage(b"r", rawState)
+			self._ser6SendMessage(b"r", b"?")
+			for i in xrange(3):
+				self._dev.waitForRead(self.timeout)
+				if rawState is self._rawKeyboardInput:
+					break
+
+	scriptCategory = SCRCAT_BRAILLE
+	def script_toggleHidKeyboardInput(self, gesture):
+		oldHidKeyboardInput = self.hidKeyboardInput
+		self.hidKeyboardInput = not self.hidKeyboardInput
+		if self.hidKeyboardInput is oldHidKeyboardInput:
+			# Translators: Message when setting HID keyboard simulation failed.
+			ui.message(_("Setting HID keyboard simulation not supported"))
+		elif self.hidKeyboardInput:
+			# Translators: Message when HID keyboard simulation is enabled.
+			ui.message(_("HID keyboard simulation enabled"))
+		else:
+			# Translators: Message when HID keyboard simulation is disabled.
+			ui.message(_("HID keyboard simulation disabled"))
+	# Translators: Description of the script that toggles HID keyboard simulation.
+	script_toggleHidKeyboardInput.__doc__ = _("Toggles HID keyboard simulation")
+
+	__gestures = {
+		"br(alva):t1+spEnter": "toggleHidKeyboardInput",
+	}
+
+	gestureMap = inputCore.GlobalGestureMap({
+		"globalCommands.GlobalCommands": {
+			"braille_scrollBack": ("br(alva):t1","br(alva):etouch1"),
+			"braille_previousLine": ("br(alva):t2",),
+			"braille_toFocus": ("br(alva):t3",),
+			"braille_nextLine": ("br(alva):t4",),
+			"braille_scrollForward": ("br(alva):t5","br(alva):etouch3"),
+			"braille_routeTo": ("br(alva):routing",),
+			"review_top": ("br(alva):t1+t2",),
+			"review_bottom": ("br(alva):t4+t5",),
+			"braille_toggleTether": ("br(alva):t1+t3",),
+			"braille_cycleCursorShape": ("br(alva):t1+t4",),
+			"braille_toggleShowCursor": ("br(alva):t2+t5",),
+			"title": ("br(alva):etouch2",),
+			"reportStatusLine": ("br(alva):etouch4",),
+			"kb:shift+tab": ("br(alva):sp1",),
+			"kb:alt": ("br(alva):sp2",),
+			"kb:escape": ("br(alva):sp3",),
+			"kb:tab": ("br(alva):sp4",),
+			"kb:upArrow": ("br(alva):spUp",),
+			"kb:downArrow": ("br(alva):spDown",),
+			"kb:leftArrow": ("br(alva):spLeft",),
+			"kb:rightArrow": ("br(alva):spRight",),
+			"kb:enter": ("br(alva):spEnter","br(alva):enter",),
+			"dateTime": ("br(alva):sp1+sp2",),
+			"showGui": ("br(alva):sp1+sp3",),
+			"kb:windows+d": ("br(alva):sp1+sp4",),
+			"kb:windows+b": ("br(alva):sp3+sp4",),
+			"kb:windows": ("br(alva):sp2+sp3","br(alva):windows",),
+			"kb:alt+tab": ("br(alva):sp2+sp4",),
+			"kb:control+home": ("br(alva):t3+spUp",),
+			"kb:control+end": ("br(alva):t3+spDown",),
+			"kb:home": ("br(alva):t3+spLeft",),
+			"kb:end": ("br(alva):t3+spRight",),
+			"kb:alt": ("br(alva):alt",),
+			"kb:control": ("br(alva):control",),
+		}
+	})
+
+class InputGesture(braille.BrailleDisplayGesture, brailleInput.BrailleInputGesture):
+	source = BrailleDisplayDriver.name
+
+	def __init__(self, model, keys, brailleInput=False):
+		super(InputGesture, self).__init__()
+		# Model identifiers should not contain spaces.
+		self.model = model.replace(" ", "")
+		assert(self.model.isalnum())
+		self.keyCodes = set(keys)
+		self.keyNames = names = []
+		dots = 0
+		space = False
+		for group, number in self.keyCodes:
+			if group == ALVA_CR_GROUP:
+				if number & ALVA_2ND_CR_MASK:
+					names.append("secondRouting")
+					self.routingIndex = number & ~ALVA_2ND_CR_MASK
+				else:
+					names.append("routing")
+					self.routingIndex = number
+			else:
+				try:
+					names.append(ALVA_KEYS[group][number])
+				except (KeyError, IndexError):
+					log.debugWarning("Unknown key with group %d and number %d" % (group, number))
+
+			# Braille input
+			if brailleInput:
+				if group == ALVA_FEATURE_PACK_GROUP:
+					if ALVA_KEYS[group][number] == "space":
+						space = True
+					elif number <= 8:
+						dots |= 1 << (number-1)
+					else:
+						brailleInput = False
+				else:
+					brailleInput = False
+
+		self.id = "+".join(names)
+		if brailleInput:
+			self.dots = dots
+			self.space = space
diff --git a/source/brailleDisplayDrivers/alvaBC6.py b/source/brailleDisplayDrivers/alvaBC6.py
deleted file mode 100644
--- a/source/brailleDisplayDrivers/alvaBC6.py
+++ /dev/null
@@ -1,176 +0,0 @@
-#brailleDisplayDrivers/alvaBC6.py
-#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) 2009-2011 Optelec B.V. <http://www.optelec.com/>, James Teh <jamie@jantrid.net>
-
-import braille
-import queueHandler
-from logHandler import log
-from ctypes import *
-from ctypes.wintypes import *
-import time
-import config
-import inputCore
-
-ALVA_RELEASE_MASK = 0x8000
-ALVA_2ND_CR_MASK = 0x80
-
-ALVA_KEYS = {
-	# Thumb keys (FRONT_GROUP)
-	0x71: ("t1", "t2", "t3", "t4", "t5",
-		# Only for BC680
-		"t1", "t2", "t3", "t4", "t5"),
-	# eTouch keys (ETOUCH_GROUP)
-	0x72: ("etouch1", "etouch2", "etouch3", "etouch4"),
-	# Smartpad keys (PDA_GROUP)
-	0x73: ("sp1", "sp2", "spLeft", "spEnter", "spUp", "spDown", "spRight", "sp3", "sp4",
-		# Only for BC680
-		"sp1", "sp2", "spLeft", "spEnter", "spUp", "spDown", "spRight", "sp3", "sp4")
-}
-ALVA_CR_GROUP = 0x74
-ALVA_MODIFIER_GROUP = 0x75
-ALVA_ASCII_GROUP = 0x76
-
-ALVA_PKEYCALLBACK = CFUNCTYPE(BOOL, c_int, USHORT, c_void_p)
-
-#Try to load alvaw32.dll
-try:
-	AlvaLib=windll[r"brailleDisplayDrivers\alvaw32.dll"]
-except:
-	AlvaLib=None
-
-class BrailleDisplayDriver(braille.BrailleDisplayDriver):
-	name = "alvaBC6"
-	# Translators: The name of a braille display.
-	description = _("ALVA BC640/680 series")
-
-	@classmethod
-	def check(cls):
-		return bool(AlvaLib)
-
-	def __init__(self):
-		super(BrailleDisplayDriver,self).__init__()
-		log.debug("ALVA BC6xx Braille init")
-		_AlvaNumDevices=c_int(0)
-		AlvaLib.AlvaScanDevices(byref(_AlvaNumDevices))
-		if _AlvaNumDevices.value==0:
-			raise RuntimeError("No ALVA display found")
-		log.debug("%d devices found" %_AlvaNumDevices.value)
-		AlvaLib.AlvaOpen(0)
-		self._alva_NumCells = 0
-		self._keysDown = set()
-		self._ignoreKeyReleases = False
-		self._keyCallbackInst = ALVA_PKEYCALLBACK(self._keyCallback)
-		AlvaLib.AlvaSetKeyCallback(0, self._keyCallbackInst, None)
-
-	def terminate(self):
-		super(BrailleDisplayDriver, self).terminate()
-		AlvaLib.AlvaClose(1)
-		# Drop the ctypes function instance for the key callback,
-		# as it is holding a reference to an instance method, which causes a reference cycle.
-		self._keyCallbackInst = None
-
-	def _get_numCells(self):
-		if self._alva_NumCells==0:
-			NumCells = c_int(0)
-			AlvaLib.AlvaGetCells(0, byref(NumCells))
-			if NumCells.value==0:
-				raise RuntimeError("Cannot obtain number of cells")
-			self._alva_NumCells = NumCells.value
-			log.info("ALVA BC6xx has %d cells" %self._alva_NumCells)
-		return self._alva_NumCells
-
-	def display(self, cells):
-		cells="".join([chr(x) for x in cells])
-		AlvaLib.AlvaSendBraille(0, cells, 0, len(cells))
-
-	def _keyCallback(self, dev, key, userData):
-		group = (key >> 8) & 0x7F
-		number = key & 0xFF
-		if key & ALVA_RELEASE_MASK:
-			# Release.
-			if not self._ignoreKeyReleases and self._keysDown:
-				try:
-					inputCore.manager.executeGesture(InputGesture(self._keysDown))
-				except inputCore.NoInputGestureAction:
-					pass
-				# Any further releases are just the rest of the keys in the combination being released,
-				# so they should be ignored.
-				self._ignoreKeyReleases = True
-			self._keysDown.discard((group, number))
-		else:
-			# Press.
-			if group == ALVA_CR_GROUP:
-				# Execute routing keys when pressed instead of released.
-				try:
-					inputCore.manager.executeGesture(InputGesture(((group, number),)))
-				except inputCore.NoInputGestureAction:
-					pass
-			else:
-				self._keysDown.add((group, number))
-				# This begins a new key combination.
-				self._ignoreKeyReleases = False
-		return False
-
-	gestureMap = inputCore.GlobalGestureMap({
-		"globalCommands.GlobalCommands": {
-			"braille_scrollBack": ("br(alvaBC6):t1","br(alvaBC6):etouch1"),
-			"braille_previousLine": ("br(alvaBC6):t2",),
-			"braille_toFocus": ("br(alvaBC6):t3",),
-			"braille_nextLine": ("br(alvaBC6):t4",),
-			"braille_scrollForward": ("br(alvaBC6):t5","br(alvaBC6):etouch3"),
-			"braille_routeTo": ("br(alvaBC6):routing",),
-			"review_top": ("br(alvaBC6):t1+t2",),
-			"review_bottom": ("br(alvaBC6):t4+t5",),
-			"braille_toggleTether": ("br(alvaBC6):t1+t3",),
-			"braille_cycleCursorShape": ("br(alvaBC6):t1+t4",),
-			"braille_toggleShowCursor": ("br(alvaBC6):t2+t5",),
-			"title": ("br(alvaBC6):etouch2",),
-			"reportStatusLine": ("br(alvaBC6):etouch4",),
-			"kb:shift+tab": ("br(alvaBC6):sp1",),
-			"kb:alt": ("br(alvaBC6):sp2",),
-			"kb:escape": ("br(alvaBC6):sp3",),
-			"kb:tab": ("br(alvaBC6):sp4",),
-			"kb:upArrow": ("br(alvaBC6):spUp",),
-			"kb:downArrow": ("br(alvaBC6):spDown",),
-			"kb:leftArrow": ("br(alvaBC6):spLeft",),
-			"kb:rightArrow": ("br(alvaBC6):spRight",),
-			"kb:enter": ("br(alvaBC6):spEnter",),
-			"dateTime": ("br(alvaBC6):sp1+sp2",),
-			"showGui": ("br(alvaBC6):sp1+sp3",),
-			"kb:windows+d": ("br(alvaBC6):sp1+sp4",),
-			"kb:windows+b": ("br(alvaBC6):sp3+sp4",),
-			"kb:windows": ("br(alvaBC6):sp2+sp3",),
-			"kb:alt+tab": ("br(alvaBC6):sp2+sp4",),
-			"kb:control+home": ("br(alvaBC6):t3+spUp",),
-			"kb:control+end": ("br(alvaBC6):t3+spDown",),
-			"kb:home": ("br(alvaBC6):t3+spLeft",),
-			"kb:end": ("br(alvaBC6):t3+spRight",),
-		}
-	})
-
-class InputGesture(braille.BrailleDisplayGesture):
-
-	source = BrailleDisplayDriver.name
-
-	def __init__(self, keys):
-		super(InputGesture, self).__init__()
-		self.keyCodes = set(keys)
-
-		self.keyNames = names = []
-		for group, number in self.keyCodes:
-			if group == ALVA_CR_GROUP:
-				if number & ALVA_2ND_CR_MASK:
-					names.append("routing2")
-					self.routingIndex = number & ~ALVA_2ND_CR_MASK
-				else:
-					names.append("routing")
-					self.routingIndex = number
-			else:
-				try:
-					names.append(ALVA_KEYS[group][number])
-				except (KeyError, IndexError):
-					log.debugWarning("Unknown key with group %d and number %d" % (group, number))
-
-		self.id = "+".join(names)


Test patch: