Add testing support for keyboard class

main
Ilya Zhuravlev 2020-10-17 05:28:49 -04:00
parent 735aa6eaa7
commit 3713dc7f23
3 changed files with 116 additions and 11 deletions

View File

@ -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

View File

View File

@ -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()