Add testing support for keyboard class
parent
735aa6eaa7
commit
3713dc7f23
|
|
@ -3,6 +3,7 @@
|
|||
import struct
|
||||
import json
|
||||
import lzma
|
||||
from collections import OrderedDict
|
||||
|
||||
from kle_serial import Serial as KleSerial
|
||||
from util import MSG_LEN, hid_send
|
||||
|
|
@ -20,10 +21,12 @@ CMD_VIAL_GET_DEFINITION = 0x02
|
|||
class Keyboard:
|
||||
""" Low-level communication with a vial-enabled keyboard """
|
||||
|
||||
def __init__(self, dev):
|
||||
def __init__(self, dev, usb_send=hid_send):
|
||||
self.dev = dev
|
||||
self.usb_send = usb_send
|
||||
|
||||
self.rowcol = set()
|
||||
# n.b. using OrderedDict here to make order of layout requests consistent for tests
|
||||
self.rowcol = OrderedDict()
|
||||
self.layout = dict()
|
||||
self.layers = 0
|
||||
self.keys = []
|
||||
|
|
@ -31,30 +34,30 @@ class Keyboard:
|
|||
def reload(self):
|
||||
""" Load information about the keyboard: number of layers, physical key layout """
|
||||
|
||||
self.rowcol = set()
|
||||
self.rowcol = OrderedDict()
|
||||
self.layout = dict()
|
||||
|
||||
self.reload_layers()
|
||||
self.reload_layout()
|
||||
self.reload_layers()
|
||||
self.reload_keymap()
|
||||
|
||||
def reload_layers(self):
|
||||
""" Get how many layers the keyboard has """
|
||||
|
||||
self.layers = hid_send(self.dev, struct.pack("B", CMD_VIA_GET_LAYER_COUNT))[1]
|
||||
self.layers = self.usb_send(self.dev, struct.pack("B", CMD_VIA_GET_LAYER_COUNT))[1]
|
||||
|
||||
def reload_layout(self):
|
||||
""" Requests layout data from the current device """
|
||||
|
||||
# get the size
|
||||
data = hid_send(self.dev, struct.pack("BB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_GET_SIZE))
|
||||
data = self.usb_send(self.dev, struct.pack("BB", CMD_VIA_VIAL_PREFIX, CMD_VIAL_GET_SIZE))
|
||||
sz = struct.unpack("<I", data[0:4])[0]
|
||||
|
||||
# get the payload
|
||||
payload = b""
|
||||
block = 0
|
||||
while sz > 0:
|
||||
data = hid_send(self.dev, struct.pack("<BBI", CMD_VIA_VIAL_PREFIX, CMD_VIAL_GET_DEFINITION, block))
|
||||
data = self.usb_send(self.dev, struct.pack("<BBI", CMD_VIA_VIAL_PREFIX, CMD_VIAL_GET_DEFINITION, block))
|
||||
if sz < MSG_LEN:
|
||||
data = data[:sz]
|
||||
payload += data
|
||||
|
|
@ -74,17 +77,17 @@ class Keyboard:
|
|||
row, col = int(row), int(col)
|
||||
key.row = row
|
||||
key.col = col
|
||||
self.rowcol.add((row, col))
|
||||
self.rowcol[(row, col)] = True
|
||||
|
||||
def reload_keymap(self):
|
||||
""" Load current key mapping from the keyboard """
|
||||
|
||||
for layer in range(self.layers):
|
||||
for row, col in self.rowcol:
|
||||
data = hid_send(self.dev, struct.pack("BBBB", CMD_VIA_GET_KEYCODE, layer, row, col))
|
||||
for row, col in self.rowcol.keys():
|
||||
data = self.usb_send(self.dev, struct.pack("BBBB", CMD_VIA_GET_KEYCODE, layer, row, col))
|
||||
keycode = struct.unpack(">H", data[4:6])[0]
|
||||
self.layout[(layer, row, col)] = keycode
|
||||
|
||||
def set_key(self, layer, row, col, code):
|
||||
hid_send(self.dev, struct.pack(">BBBBH", CMD_VIA_SET_KEYCODE, layer, row, col, code))
|
||||
self.usb_send(self.dev, struct.pack(">BBBBH", CMD_VIA_SET_KEYCODE, layer, row, col, code))
|
||||
self.layout[(layer, row, col)] = code
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
import unittest
|
||||
import lzma
|
||||
import struct
|
||||
|
||||
|
||||
from keyboard import Keyboard
|
||||
|
||||
|
||||
LAYOUT_2x2 = """
|
||||
{"name":"test","vendorId":"0x0000","productId":"0x1111","lighting":"none","matrix":{"rows":2,"cols":2},"layouts":{"keymap":[["0,0","0,1"],["1,0","1,1"]]}}
|
||||
"""
|
||||
|
||||
|
||||
def chunks(data, sz):
|
||||
for i in range(0, len(data), sz):
|
||||
yield data[i:i+sz]
|
||||
|
||||
|
||||
class SimulatedDevice:
|
||||
|
||||
def __init__(self):
|
||||
# sequence of keyboard communications, pairs of (request, response)
|
||||
self.expect_data = []
|
||||
# current index in communications
|
||||
self.expect_idx = 0
|
||||
|
||||
def expect(self, inp, out):
|
||||
if isinstance(inp, str):
|
||||
inp = bytes.fromhex(inp)
|
||||
if isinstance(out, str):
|
||||
out = bytes.fromhex(out)
|
||||
self.expect_data.append((inp, out))
|
||||
|
||||
def expect_layout(self, layout):
|
||||
compressed = lzma.compress(layout.encode("utf-8"))
|
||||
self.expect("FE01", struct.pack("<I", len(compressed)))
|
||||
for idx, chunk in enumerate(chunks(compressed, 32)):
|
||||
self.expect(
|
||||
struct.pack("<BBI", 0xFE, 0x02, idx),
|
||||
chunk
|
||||
)
|
||||
|
||||
def expect_layers(self, layers):
|
||||
self.expect("11", struct.pack("BB", 0x11, layers))
|
||||
|
||||
def expect_keymap(self, keymap):
|
||||
for l, layer in enumerate(keymap):
|
||||
for r, row in enumerate(layer):
|
||||
for c, col in enumerate(row):
|
||||
self.expect(struct.pack("BBBB", 4, l, r, c), struct.pack(">BBBBH", 4, l, r, c, col))
|
||||
|
||||
@staticmethod
|
||||
def sim_send(dev, data):
|
||||
if dev.expect_idx >= len(dev.expect_data):
|
||||
raise Exception("Trying to communicate more times ({}) than expected ({}); got data={}".format(
|
||||
dev.expect_idx + 1,
|
||||
len(dev.expect_data),
|
||||
data.hex()
|
||||
))
|
||||
inp, out = dev.expect_data[dev.expect_idx]
|
||||
if data != inp:
|
||||
raise Exception("Got unexpected data at index {}: expected={} got={}".format(
|
||||
dev.expect_idx,
|
||||
data.hex(),
|
||||
inp.hex()
|
||||
))
|
||||
dev.expect_idx += 1
|
||||
return out
|
||||
|
||||
def finish(self):
|
||||
if self.expect_idx != len(self.expect_data):
|
||||
raise Exception("Didn't communicate all the way, remaining data = {}".format(
|
||||
self.expect_data[self.expect_idx:]
|
||||
))
|
||||
|
||||
|
||||
class TestKeyboard(unittest.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def prepare_keyboard(layout, keymap):
|
||||
dev = SimulatedDevice()
|
||||
dev.expect_layout(layout)
|
||||
dev.expect_layers(len(keymap))
|
||||
dev.expect_keymap(keymap)
|
||||
|
||||
kb = Keyboard(dev, dev.sim_send)
|
||||
kb.reload()
|
||||
|
||||
return kb, dev
|
||||
|
||||
def test_keyboard_layout(self):
|
||||
kb, dev = self.prepare_keyboard(LAYOUT_2x2, [[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
|
||||
self.assertEqual(kb.layers, 2)
|
||||
self.assertEqual(kb.layout[(0, 0, 0)], 1)
|
||||
self.assertEqual(kb.layout[(0, 0, 1)], 2)
|
||||
self.assertEqual(kb.layout[(0, 1, 0)], 3)
|
||||
self.assertEqual(kb.layout[(0, 1, 1)], 4)
|
||||
self.assertEqual(kb.layout[(1, 0, 0)], 5)
|
||||
self.assertEqual(kb.layout[(1, 0, 1)], 6)
|
||||
self.assertEqual(kb.layout[(1, 1, 0)], 7)
|
||||
self.assertEqual(kb.layout[(1, 1, 1)], 8)
|
||||
dev.finish()
|
||||
Loading…
Reference in New Issue