rt82display 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rt82display-0.1.0/.github/workflows/publish.yml +41 -0
- rt82display-0.1.0/.gitignore +51 -0
- rt82display-0.1.0/AGENTS.md +183 -0
- rt82display-0.1.0/LICENSE +674 -0
- rt82display-0.1.0/PKG-INFO +161 -0
- rt82display-0.1.0/PROTOCOL.md +237 -0
- rt82display-0.1.0/QGIF.md +452 -0
- rt82display-0.1.0/README.md +133 -0
- rt82display-0.1.0/pyproject.toml +44 -0
- rt82display-0.1.0/qgif_native.py +202 -0
- rt82display-0.1.0/rt82display/__init__.py +3 -0
- rt82display-0.1.0/rt82display/cli.py +734 -0
- rt82display-0.1.0/rt82display/hid_device.py +268 -0
- rt82display-0.1.0/rt82display/image.py +214 -0
- rt82display-0.1.0/rt82display/protocol.py +280 -0
- rt82display-0.1.0/rt82display/qgif.py +273 -0
- rt82display-0.1.0/rt82display/theme.py +93 -0
- rt82display-0.1.0/udev/99-rt82.rules +16 -0
- rt82display-0.1.0/wasm2c_runtime/.gitignore +5 -0
- rt82display-0.1.0/wasm2c_runtime/build.sh +64 -0
- rt82display-0.1.0/wasm2c_runtime/qgif_generated.c +38062 -0
- rt82display-0.1.0/wasm2c_runtime/qgif_generated.h +92 -0
- rt82display-0.1.0/wasm2c_runtime/test_qgif.c +84 -0
- rt82display-0.1.0/wasm2c_runtime/wasm-rt-exceptions-impl.c +73 -0
- rt82display-0.1.0/wasm2c_runtime/wasm-rt-impl-tableops.inc +87 -0
- rt82display-0.1.0/wasm2c_runtime/wasm-rt-impl.c +401 -0
- rt82display-0.1.0/wasm2c_runtime/wasm-rt-impl.h +68 -0
- rt82display-0.1.0/wasm2c_runtime/wasm-rt-mem-impl-helper.inc +197 -0
- rt82display-0.1.0/wasm2c_runtime/wasm-rt-mem-impl.c +161 -0
- rt82display-0.1.0/wasm2c_runtime/wasm-rt.h +710 -0
- rt82display-0.1.0/wasm2c_runtime/wasm_syscalls.c +290 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.12"
|
|
16
|
+
|
|
17
|
+
- name: Install build tools
|
|
18
|
+
run: pip install build
|
|
19
|
+
|
|
20
|
+
- name: Build sdist and wheel
|
|
21
|
+
run: python -m build
|
|
22
|
+
|
|
23
|
+
- uses: actions/upload-artifact@v4
|
|
24
|
+
with:
|
|
25
|
+
name: dist
|
|
26
|
+
path: dist/
|
|
27
|
+
|
|
28
|
+
publish:
|
|
29
|
+
needs: build
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment: pypi
|
|
32
|
+
permissions:
|
|
33
|
+
id-token: write
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/download-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
|
|
40
|
+
- name: Publish to PyPI
|
|
41
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.so
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
.eggs/
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
|
|
15
|
+
# IDE
|
|
16
|
+
.idea/
|
|
17
|
+
.vscode/
|
|
18
|
+
*.swp
|
|
19
|
+
*.swo
|
|
20
|
+
|
|
21
|
+
# OS
|
|
22
|
+
.DS_Store
|
|
23
|
+
Thumbs.db
|
|
24
|
+
|
|
25
|
+
# Testing
|
|
26
|
+
.pytest_cache/
|
|
27
|
+
.coverage
|
|
28
|
+
htmlcov/
|
|
29
|
+
.tox/
|
|
30
|
+
.mypy_cache/
|
|
31
|
+
|
|
32
|
+
# Environment
|
|
33
|
+
.env
|
|
34
|
+
.envrc
|
|
35
|
+
|
|
36
|
+
# WASM decode (not working yet)
|
|
37
|
+
qgif.js
|
|
38
|
+
qgif.wasm
|
|
39
|
+
qgif_encoder.js
|
|
40
|
+
encode_qgif.mjs
|
|
41
|
+
node_modules/
|
|
42
|
+
package.json
|
|
43
|
+
package-lock.json
|
|
44
|
+
|
|
45
|
+
# Test files and captures
|
|
46
|
+
*.qgif
|
|
47
|
+
*.gif
|
|
48
|
+
*.png
|
|
49
|
+
capture_log.json
|
|
50
|
+
CAPTURE_*.js
|
|
51
|
+
make_test_gif.py
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# RT82 Display - Agent Instructions
|
|
2
|
+
|
|
3
|
+
Instructions for AI agents working on this project.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd rt82display
|
|
9
|
+
source ../.venv/bin/activate # or use uv
|
|
10
|
+
rt82display upload <file.qgif>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Troubleshooting
|
|
14
|
+
|
|
15
|
+
### Device Not Detected
|
|
16
|
+
|
|
17
|
+
**Symptom**: `Could not find keyboard interface (0x36B0)` or `LCD interface (0x1919) did not appear after init`
|
|
18
|
+
|
|
19
|
+
**Cause**: The RT82 has TWO USB devices:
|
|
20
|
+
- `0x36B0:0x30A3` - Always visible (keyboard)
|
|
21
|
+
- `0x1919:0x1919` - Only appears AFTER init commands sent to 0x36B0
|
|
22
|
+
|
|
23
|
+
**Cross-OS note**: On **macOS**, `hid.enumerate()` reports the `usage_page` field correctly (e.g. `0xFF60` for the keyboard interface). On **Linux** with the `libusb` backend (common default), `usage_page` is returned as `0` for all interfaces. The `0x36B0` device exposes three interfaces:
|
|
24
|
+
- IF=0: Keyboard (usage page 0x0001)
|
|
25
|
+
- IF=1: **Raw HID** (usage page 0xFF60) -- **this is the one needed for init**
|
|
26
|
+
- IF=2: Mouse/consumer (usage page 0x0001)
|
|
27
|
+
|
|
28
|
+
The CLI handles this by: (1) preferring the correct `usage_page` when available, (2) falling back to `interface_number == 1` (the standard QMK raw HID interface), (3) falling back to the first match as last resort.
|
|
29
|
+
|
|
30
|
+
Additionally, USB re-enumeration is slower on Linux (~1-2 seconds) than macOS (~300ms). The CLI polls for the `0x1919` device with retries rather than a single fixed sleep.
|
|
31
|
+
|
|
32
|
+
**Solution**: The CLI should automatically handle this, but if it fails:
|
|
33
|
+
|
|
34
|
+
1. **Check devices are visible**:
|
|
35
|
+
```bash
|
|
36
|
+
rt82display list
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. **(Linux only) Install udev rules** so non-root users can access the HID device:
|
|
40
|
+
```bash
|
|
41
|
+
sudo cp udev/99-rt82.rules /etc/udev/rules.d/
|
|
42
|
+
sudo udevadm control --reload-rules
|
|
43
|
+
sudo udevadm trigger
|
|
44
|
+
```
|
|
45
|
+
Then unplug and replug the keyboard.
|
|
46
|
+
|
|
47
|
+
3. **Manually activate LCD interface**:
|
|
48
|
+
```python
|
|
49
|
+
import hid
|
|
50
|
+
import time
|
|
51
|
+
|
|
52
|
+
# Send init to 0x36B0 — prefer usage_page 0xFF60, then IF=1, then first
|
|
53
|
+
devices = list(hid.enumerate(0x36B0, 0x30A3))
|
|
54
|
+
target = None
|
|
55
|
+
for d in devices:
|
|
56
|
+
if d.get('usage_page') == 0xFF60:
|
|
57
|
+
target = d
|
|
58
|
+
break
|
|
59
|
+
if target is None:
|
|
60
|
+
for d in devices:
|
|
61
|
+
if d.get('interface_number') == 1:
|
|
62
|
+
target = d
|
|
63
|
+
break
|
|
64
|
+
if target is None and devices:
|
|
65
|
+
target = devices[0]
|
|
66
|
+
|
|
67
|
+
if target:
|
|
68
|
+
dev = hid.device()
|
|
69
|
+
dev.open_path(target['path'])
|
|
70
|
+
dev.set_nonblocking(True)
|
|
71
|
+
dev.write(bytes([0xAA, 0xE2] + [0]*62))
|
|
72
|
+
time.sleep(0.05)
|
|
73
|
+
for _ in range(5):
|
|
74
|
+
dev.write(bytes([0xAA, 0xE0] + [0]*62))
|
|
75
|
+
time.sleep(0.05)
|
|
76
|
+
dev.close()
|
|
77
|
+
|
|
78
|
+
# Poll for 0x1919 (may take 1-2s on Linux)
|
|
79
|
+
for _ in range(10):
|
|
80
|
+
time.sleep(0.3)
|
|
81
|
+
found = list(hid.enumerate(0x1919, 0x1919))
|
|
82
|
+
if found:
|
|
83
|
+
for d in found:
|
|
84
|
+
print(f"Found: IF={d.get('interface_number')} UP=0x{d.get('usage_page', 0):04X}")
|
|
85
|
+
break
|
|
86
|
+
else:
|
|
87
|
+
print("0x1919 did not appear")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
4. **If still not working**: Unplug and replug the keyboard
|
|
91
|
+
|
|
92
|
+
### Screen Stuck in "Downloading"
|
|
93
|
+
|
|
94
|
+
**Cause**: Transfer started but didn't complete properly.
|
|
95
|
+
|
|
96
|
+
**Solution**:
|
|
97
|
+
1. Unplug keyboard
|
|
98
|
+
2. Wait 5 seconds
|
|
99
|
+
3. Replug keyboard
|
|
100
|
+
4. Try upload again
|
|
101
|
+
|
|
102
|
+
### Garbled/Wrong Colors
|
|
103
|
+
|
|
104
|
+
**Cause**: Data format issue
|
|
105
|
+
|
|
106
|
+
**Possible fixes**:
|
|
107
|
+
- Ensure using QGIF format (not raw RGB565)
|
|
108
|
+
- Check byte order (should be Little Endian)
|
|
109
|
+
- Verify dimensions are 240×135
|
|
110
|
+
|
|
111
|
+
### Upload Succeeds But No Image
|
|
112
|
+
|
|
113
|
+
**Cause**: QGIF format might not match what firmware expects
|
|
114
|
+
|
|
115
|
+
**Debug steps**:
|
|
116
|
+
1. Capture a working QGIF from web tool using browser console
|
|
117
|
+
2. Compare header bytes with generated QGIF
|
|
118
|
+
3. Try the captured QGIF to verify protocol works
|
|
119
|
+
|
|
120
|
+
## Device IDs
|
|
121
|
+
|
|
122
|
+
| Device | VID | PID | Usage Page | Interface | Purpose |
|
|
123
|
+
|--------|-----|-----|------------|-----------|---------|
|
|
124
|
+
| Keyboard HID | 0x36B0 | 0x30A3 | 0x0001 | 0 | Standard keyboard |
|
|
125
|
+
| **Raw HID** | 0x36B0 | 0x30A3 | **0xFF60** | **1** | **Init commands** |
|
|
126
|
+
| Mouse/Consumer | 0x36B0 | 0x30A3 | 0x0001 | 2 | Media keys etc. |
|
|
127
|
+
| LCD | 0x1919 | 0x1919 | 0xFF | 0,1 | Data transfer |
|
|
128
|
+
|
|
129
|
+
**Note**: On Linux (libusb backend), `usage_page` is reported as `0x0000` for all interfaces. Use `interface_number` to distinguish them.
|
|
130
|
+
|
|
131
|
+
## Protocol Summary
|
|
132
|
+
|
|
133
|
+
1. **Init** (on 0x36B0:0x30A3, IF=1, UP=0xFF60):
|
|
134
|
+
- Send `AA E2` + `AA E0` ×5
|
|
135
|
+
- Poll for 0x1919 device (appears in ~300ms on macOS, ~1-2s on Linux)
|
|
136
|
+
- On Linux, `usage_page` may be 0; select interface by `interface_number == 1`
|
|
137
|
+
|
|
138
|
+
2. **Download Mode** (on 0x1919:0x1919, UP=0xFF):
|
|
139
|
+
- Query commands (`AA 10`, `AA 17`, etc.)
|
|
140
|
+
- Trigger download: `AA E3 00 00 00 01 00 00 01`
|
|
141
|
+
- Screen shows "Downloading"
|
|
142
|
+
|
|
143
|
+
3. **Data Transfer**:
|
|
144
|
+
- Setup packets (`AA 15`, `AA 16`, `AA 18`)
|
|
145
|
+
- Data packets: `AA 19 [offset_L] [offset_H] 00 38 00 00 [56 bytes]`
|
|
146
|
+
|
|
147
|
+
4. **Finalize**:
|
|
148
|
+
- Status: `AA 1C`
|
|
149
|
+
- End: `AA 1A`
|
|
150
|
+
|
|
151
|
+
## File Formats
|
|
152
|
+
|
|
153
|
+
### QGIF (Recommended)
|
|
154
|
+
- Compressed RLE format
|
|
155
|
+
- 32-byte header + frame blocks
|
|
156
|
+
- Generated by `rt82display.qgif.encode_qgif()`
|
|
157
|
+
|
|
158
|
+
### Raw RGB565 (Experimental)
|
|
159
|
+
- Uncompressed 16-bit pixels
|
|
160
|
+
- May not work on all firmware versions
|
|
161
|
+
- Use `--raw` flag
|
|
162
|
+
|
|
163
|
+
## Key Files
|
|
164
|
+
|
|
165
|
+
| File | Purpose |
|
|
166
|
+
|------|---------|
|
|
167
|
+
| `cli.py` | Main CLI application |
|
|
168
|
+
| `qgif.py` | QGIF encoder |
|
|
169
|
+
| `hid_device.py` | USB HID communication |
|
|
170
|
+
| `protocol.py` | Packet builders |
|
|
171
|
+
| `udev/99-rt82.rules` | Linux udev rules for non-root HID access |
|
|
172
|
+
| `PROTOCOL.md` | Full protocol documentation |
|
|
173
|
+
| `QGIF.md` | QGIF format specification |
|
|
174
|
+
|
|
175
|
+
## Common Issues for AI Agents
|
|
176
|
+
|
|
177
|
+
1. **Don't prepend Report ID** - Just send 64-byte packets directly
|
|
178
|
+
2. **Two-step connection** - Must init 0x36B0 before 0x1919 appears
|
|
179
|
+
3. **Correct interface on 0x36B0** - Init must go to IF=1 (raw HID, UP=0xFF60), not IF=0 (keyboard) or IF=2 (mouse). On Linux, `usage_page` is 0 so select by `interface_number == 1`
|
|
180
|
+
4. **Poll for 0x1919** - USB re-enumeration takes ~1-2s on Linux; use a retry loop, not a fixed sleep
|
|
181
|
+
5. **Little Endian** - All multi-byte values are LE
|
|
182
|
+
6. **56-byte chunks** - Data packets have 8-byte header + 56 data
|
|
183
|
+
7. **QGIF required** - Raw RGB565 usually doesn't display correctly
|