-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcalibrate.py
More file actions
117 lines (102 loc) · 4.07 KB
/
Copy pathcalibrate.py
File metadata and controls
117 lines (102 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
KeyAxis CALIBRATOR — learn which matrix (row,col) is which physical key.
Run this ONCE. It arms the travel stream and walks you through the board,
key by key. Press the key it names; it records the coordinate and moves on.
Writes calibration.json next to this script. Then relaunch KeyAxis.
CLOSE KeyAxis first (only one app can hold the keyboard).
Requires: python -m pip install hidapi
Notes:
* The letters will appear in this console as you press — ignore them.
* If a key won't register, wait ~7s and it auto-skips (or press it harder).
* Ctrl+C at any time saves what you've done so far.
"""
import sys, time, json, os
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
try:
import hid
except ImportError:
sys.exit("Missing lib: python -m pip install hidapi")
VID, PID = 0x0416, 0x7372
ARM = bytes([0x21, 0, 0, 0, 0x18, 0x02,
0x3e, 0x26, 0x3e, 0x1e, 0x1e, 0x1e, 0x3e, 0x1e, 0x1e, 0x3e,
0x1e, 0x3e, 0x2e, 0x10, 0x2e, 0x30, 0x3e]) + bytes(40)
DISARM = bytes([0x21, 0, 0, 0, 0x18, 0x03]) + bytes(57)
PRESS, RELEASE = 20, 6 # depth thresholds
# Physical M68 keys, in the order we'll ask for them (labels match the UI board).
KEYS = [
"Esc","1","2","3","4","5","6","7","8","9","0","-","=","Back","Del",
"Tab","Q","W","E","R","T","Y","U","I","O","P","[","]","\\","`",
"Caps","A","S","D","F","G","H","J","K","L",";","'","Enter","PgUp",
"LShift","Z","X","C","V","B","N","M",",",".","/","RShift","Up","PgDn",
"LCtrl","LWin","LAlt","Space","RAlt","Fn","RCtrl","Left","Down","Right",
]
HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "calibration.json")
def open_board():
paths = [d["path"] for d in hid.enumerate(VID, PID)
if d.get("usage_page", 0) == 0xFF1B]
if not paths:
sys.exit("M68 vendor interface not found. Plugged in? KeyAxis / browser closed?")
h = hid.device(); h.open_path(paths[0]); h.set_nonblocking(1)
return h
def read_frame(h):
d = h.read(64)
if d and len(d) >= 10 and d[1] == 0x21 and d[5] == 0x03:
return d[7], d[8], d[9] # row, col, depth
return None
def main():
h = open_board()
h.write(bytes([0x01]) + ARM)
print("Calibration armed. Follow the prompts — press the named key fully.\n")
time.sleep(0.2)
mapping = {} # "row,col" -> key name
used = set()
try:
for name in KEYS:
sys.stdout.write(" Press [ %-6s ] ... " % name)
sys.stdout.flush()
# wait for release baseline
captured = None
deadline = time.time() + 7.0
while time.time() < deadline:
f = read_frame(h)
if f:
r, c, depth = f
key = "%d,%d" % (r, c)
if depth >= PRESS and key not in used:
captured = key
break
time.sleep(0.002)
if captured is None:
print("skipped (no press)")
continue
mapping[captured] = name
used.add(captured)
print("row %s -> %s" % (captured, name))
# wait for release so the next prompt doesn't grab the same key
rel = time.time() + 3.0
while time.time() < rel:
f = read_frame(h)
if f and f[0] == int(captured.split(",")[0]) and \
f[1] == int(captured.split(",")[1]) and f[2] < RELEASE:
break
time.sleep(0.002)
except KeyboardInterrupt:
print("\n(interrupted — saving what we have)")
finally:
try:
h.write(bytes([0x01]) + DISARM)
except Exception:
pass
h.close()
with open(OUT, "w", encoding="utf-8") as f:
json.dump(mapping, f, indent=2)
print("\nSaved %d keys -> %s" % (len(mapping), OUT))
print("Now relaunch KeyAxis. (Tell Claude 'calibrated' and it bakes the map in.)")
if __name__ == "__main__":
main()