brickremote 0.3.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.
- brickremote-0.3.0/.github/workflows/release.yml +41 -0
- brickremote-0.3.0/.github/workflows/test.yml +19 -0
- brickremote-0.3.0/.gitignore +6 -0
- brickremote-0.3.0/CHANGELOG.md +19 -0
- brickremote-0.3.0/LICENSE +21 -0
- brickremote-0.3.0/PKG-INFO +196 -0
- brickremote-0.3.0/README.md +170 -0
- brickremote-0.3.0/docs/img/car.jpg +0 -0
- brickremote-0.3.0/docs/img/path.jpg +0 -0
- brickremote-0.3.0/docs/img/ports.jpg +0 -0
- brickremote-0.3.0/docs/img/program.jpg +0 -0
- brickremote-0.3.0/docs/index.html +319 -0
- brickremote-0.3.0/examples/car_demo.py +16 -0
- brickremote-0.3.0/pyproject.toml +39 -0
- brickremote-0.3.0/run.sh +18 -0
- brickremote-0.3.0/src/brickremote/__init__.py +8 -0
- brickremote-0.3.0/src/brickremote/car.py +40 -0
- brickremote-0.3.0/src/brickremote/cli.py +131 -0
- brickremote-0.3.0/src/brickremote/module.py +208 -0
- brickremote-0.3.0/src/brickremote/protocol.py +90 -0
- brickremote-0.3.0/src/brickremote/script.py +159 -0
- brickremote-0.3.0/src/brickremote/tui.py +661 -0
- brickremote-0.3.0/src/brickremote/web/index.html +611 -0
- brickremote-0.3.0/src/brickremote/web.py +243 -0
- brickremote-0.3.0/tests/test_protocol.py +45 -0
- brickremote-0.3.0/tests/test_script.py +58 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Push a tag like v0.3.0 to build the package and publish it on PyPI.
|
|
4
|
+
# Publishing uses PyPI trusted publishing, so no token is stored in the repository.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.13"
|
|
17
|
+
- name: Check the tag matches the package version
|
|
18
|
+
run: |
|
|
19
|
+
version=$(grep -oP '__version__ = "\K[^"]+' src/brickremote/__init__.py)
|
|
20
|
+
[ "v$version" = "$GITHUB_REF_NAME" ] || { echo "tag $GITHUB_REF_NAME != version $version"; exit 1; }
|
|
21
|
+
- run: pip install build pytest aiohttp bleak && pip install -e . && pytest -q
|
|
22
|
+
- run: python -m build
|
|
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:
|
|
32
|
+
name: pypi
|
|
33
|
+
url: https://pypi.org/p/brickremote
|
|
34
|
+
permissions:
|
|
35
|
+
id-token: write
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/download-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: dist
|
|
40
|
+
path: dist/
|
|
41
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python: ["3.10", "3.12", "3.13"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: ${{ matrix.python }}
|
|
18
|
+
- run: pip install -e ".[web,test]"
|
|
19
|
+
- run: pytest -q
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- Renamed from `jmcble` to `brickremote` (package, command and config file; old settings are picked up).
|
|
6
|
+
- Remote control app: kid-friendly look, big steering wheel, mouse support, searching screen with
|
|
7
|
+
automatic reconnect, instant quit with `q`.
|
|
8
|
+
- Steering springs back as soon as the key is released; hold (`h`), pair (`p`) and reverse (`r`).
|
|
9
|
+
- `scan` prints modules as soon as they are seen and stops early.
|
|
10
|
+
- `JMCModule.detach()` for exiting without waiting for BlueZ to disconnect.
|
|
11
|
+
|
|
12
|
+
## 0.2.0
|
|
13
|
+
|
|
14
|
+
- Program and path scripts (`brickremote program`, `brickremote path`), port inversion and linking,
|
|
15
|
+
`JMCModule.run()`, first version of the terminal remote control.
|
|
16
|
+
|
|
17
|
+
## 0.1.0
|
|
18
|
+
|
|
19
|
+
- First release: protocol, `JMCModule`, `Car`, `scan` and `run` commands.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexandre Strube
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: brickremote
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Drive Lego-compatible Bluetooth motor bricks (JG_JMC / XP Block "Smart creative module") from a computer
|
|
5
|
+
Project-URL: Homepage, https://surak.github.io/brickremote/
|
|
6
|
+
Project-URL: Source, https://github.com/surak/brickremote
|
|
7
|
+
Project-URL: Changelog, https://github.com/surak/brickremote/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: Alexandre Strube <surak@surak.eti.br>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ble,bluetooth,building blocks,jg_jmc,kids,motor,remote control,smart creative module,xp block
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Education
|
|
17
|
+
Classifier: Topic :: Home Automation
|
|
18
|
+
Classifier: Topic :: System :: Hardware
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: bleak>=0.22
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest; extra == 'test'
|
|
23
|
+
Provides-Extra: web
|
|
24
|
+
Requires-Dist: aiohttp>=3.9; extra == 'web'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# brickremote
|
|
28
|
+
|
|
29
|
+
**Website for kids and parents: https://surak.github.io/brickremote/**
|
|
30
|
+
|
|
31
|
+
Python library for the cheap Lego-compatible Bluetooth LE motor bricks that advertise as
|
|
32
|
+
`JG_JMC-xxxx` and are normally driven by the **XP Block** phone app ("Smart creative module",
|
|
33
|
+
four motor ports A–D). Works anywhere [bleak](https://github.com/hbldh/bleak) does: Linux
|
|
34
|
+
(incl. Raspberry Pi), macOS and Windows.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
pip install brickremote # library and terminal app
|
|
38
|
+
pip install "brickremote[web]" # plus the web remote control
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or from a checkout, without installing anything yourself:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
./run.sh # creates .venv on first run, then starts the remote control app
|
|
45
|
+
./run.sh scan # any other command works the same way
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Remote control app
|
|
49
|
+
|
|
50
|
+
Run `brickremote` with no arguments for a terminal version of the phone app (everything except the
|
|
51
|
+
phone-only gyroscope and voice modes):
|
|
52
|
+
|
|
53
|
+
| Tab | Keys |
|
|
54
|
+
|-----|------|
|
|
55
|
+
| Car | arrows: forward/back, steer left/right (drive port A, steering port D; change with `--drive`/`--steer`) |
|
|
56
|
+
| Ports | like two game sticks: `W`/`S` port A, `A`/`D` port B, `I`/`K` port C, `J`/`L` port D |
|
|
57
|
+
| Program | `e` edit in `$EDITOR`, Enter run (see below) |
|
|
58
|
+
| Path | `e` edit, Enter run, `[` `]` / `{` `}` calibrate speed and turn rate |
|
|
59
|
+
|
|
60
|
+
Everywhere: `Tab` switch screen, `1`-`3` speed (60/120/255), `space` STOP, `q` quit, and
|
|
61
|
+
|
|
62
|
+
- `h` **hold**: motors keep running after you let go of the key (steering still springs back)
|
|
63
|
+
- `p` **pair**: one port copies another (off → A+B → A+C), for builds with two motors that
|
|
64
|
+
should always move together, like a tank
|
|
65
|
+
- `r` + port letter **reverse**: that port turns the other way round, for a motor that was
|
|
66
|
+
mounted backwards
|
|
67
|
+
|
|
68
|
+
Settings, programs and paths are saved in `~/.config/brickremote.json`.
|
|
69
|
+
|
|
70
|
+
Terminals report key presses but not releases, so a motor runs while its key auto-repeats and
|
|
71
|
+
stops a moment after you let go. Steering always springs back to centre, even with hold on.
|
|
72
|
+
|
|
73
|
+
## Web remote control
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
./run.sh web # or: brickremote web (needs `pip install 'brickremote[web]'`)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This starts a small web server and prints its address. Open it in any browser: on the same
|
|
80
|
+
computer, or on a tablet or laptop on the same Wi-Fi, which then becomes the remote control.
|
|
81
|
+
It has the same four screens as the terminal app, plus things a browser is good at:
|
|
82
|
+
|
|
83
|
+
- buttons you hold down, a steering wheel you can drag, and two joysticks for the ports
|
|
84
|
+
- program blocks you tap to build a program
|
|
85
|
+
- a grid where you draw the route for path mode
|
|
86
|
+
|
|
87
|
+
If the browser stops talking to the server (tab closed, Wi-Fi gone), the motors stop by themselves.
|
|
88
|
+
|
|
89
|
+
## Command line
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
brickremote scan # list modules nearby
|
|
93
|
+
brickremote run a=255 --time 2 # port A forward, full speed, 2 s
|
|
94
|
+
brickremote run a=-120 d=255 --time 1.5 # A reverse at medium speed, D forward
|
|
95
|
+
brickremote program dance.txt # run a program script
|
|
96
|
+
brickremote path route.txt --mps 0.6 # drive a path script
|
|
97
|
+
brickremote --invert d run d=255 # global options: --address, --slot, --invert, --drive, --steer
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Program scripts (the app's block programming)
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
gear 2 # speed for + and -: 1 = 60, 2 = 120, 3 = 255
|
|
104
|
+
repeat 3
|
|
105
|
+
a + 1.5 # port A forward for 1.5 s
|
|
106
|
+
ac - 1 # ports A and C reverse for 1 s
|
|
107
|
+
wait 0.5
|
|
108
|
+
end
|
|
109
|
+
d 200 # port D at speed 200, keep running
|
|
110
|
+
forever # until Ctrl-C / space
|
|
111
|
+
b + 1
|
|
112
|
+
b - 1
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Path scripts (the app's path mode)
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
forward 1.5 # metres
|
|
120
|
+
left 90 # degrees, driving forward with full steering
|
|
121
|
+
back 0.5
|
|
122
|
+
right 45
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Distances and angles become times through a calibration (`--mps`, `--dps`, or `[ ] { }` in the
|
|
126
|
+
app): measure how far the car goes in one second at full speed, and how many degrees it turns.
|
|
127
|
+
|
|
128
|
+
## Library
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
import asyncio
|
|
132
|
+
from brickremote import JMCModule
|
|
133
|
+
|
|
134
|
+
async def main():
|
|
135
|
+
async with JMCModule() as m: # connects to the first module found
|
|
136
|
+
await m.drive(a=255, d=-120) # speeds -255..255, negative = reverse
|
|
137
|
+
await asyncio.sleep(2)
|
|
138
|
+
await m.stop()
|
|
139
|
+
print("battery:", m.battery, "%")
|
|
140
|
+
|
|
141
|
+
asyncio.run(main())
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`JMCModule(address)` targets a specific module, `discover()` lists all of them. While connected,
|
|
145
|
+
the current state is resent every 0.2 s, which keeps the module from going to sleep.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
JMCModule(inverted="d", links={"a": "c"}) # reverse port D; C follows A
|
|
149
|
+
await m.run(2, a=255) # A forward for 2 s, then stop
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`brickremote.script` has the program and path engines (`parse_program`, `run_program`,
|
|
153
|
+
`parse_path`, `run_path`) if you want them from your own code.
|
|
154
|
+
|
|
155
|
+
For the stock car (drive motor on A, steering on D) there is a small wrapper:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from brickremote import Car, JMCModule
|
|
159
|
+
|
|
160
|
+
async with JMCModule() as m:
|
|
161
|
+
car = Car(m)
|
|
162
|
+
await car.left(speed=200) # forward while steering left
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Protocol
|
|
166
|
+
|
|
167
|
+
Reverse-engineered from the XP Block Android app. The module exposes service `FFF0`;
|
|
168
|
+
commands go to `FFF2` and **must be written with response** (write-without-response is
|
|
169
|
+
silently ignored):
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
5A 6B 02 00 05 dirAB speedAB dirCD speedCD slot SUM
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Field | Meaning |
|
|
176
|
+
|---------|---------|
|
|
177
|
+
| dirAB | A: `1` forward, `2` reverse; B: `+4` forward, `+8` reverse |
|
|
178
|
+
| speedAB | 0–255, shared by A and B (the app's gears are 60 / 120 / 255) |
|
|
179
|
+
| dirCD, speedCD | same for C and D |
|
|
180
|
+
| slot | 1–3, module slot (the app can drive three modules at once) |
|
|
181
|
+
| SUM | sum of all previous bytes, modulo 256 |
|
|
182
|
+
|
|
183
|
+
Six-port modules use length `08` and append `dirEF speedE speedF`.
|
|
184
|
+
|
|
185
|
+
The module reports on `FFF1` with `5A 6B 01 00 01 <battery %> SUM`. It switches itself off
|
|
186
|
+
after about ten minutes without commands.
|
|
187
|
+
|
|
188
|
+
## Limitations
|
|
189
|
+
|
|
190
|
+
- The two ports of a pair share one speed byte, so A and B (or C and D) cannot run at different
|
|
191
|
+
speeds at the same time; the faster one wins.
|
|
192
|
+
- Only tested with a four-port module. Six-port support follows the app's code but is unverified.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# brickremote
|
|
2
|
+
|
|
3
|
+
**Website for kids and parents: https://surak.github.io/brickremote/**
|
|
4
|
+
|
|
5
|
+
Python library for the cheap Lego-compatible Bluetooth LE motor bricks that advertise as
|
|
6
|
+
`JG_JMC-xxxx` and are normally driven by the **XP Block** phone app ("Smart creative module",
|
|
7
|
+
four motor ports A–D). Works anywhere [bleak](https://github.com/hbldh/bleak) does: Linux
|
|
8
|
+
(incl. Raspberry Pi), macOS and Windows.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
pip install brickremote # library and terminal app
|
|
12
|
+
pip install "brickremote[web]" # plus the web remote control
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or from a checkout, without installing anything yourself:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
./run.sh # creates .venv on first run, then starts the remote control app
|
|
19
|
+
./run.sh scan # any other command works the same way
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Remote control app
|
|
23
|
+
|
|
24
|
+
Run `brickremote` with no arguments for a terminal version of the phone app (everything except the
|
|
25
|
+
phone-only gyroscope and voice modes):
|
|
26
|
+
|
|
27
|
+
| Tab | Keys |
|
|
28
|
+
|-----|------|
|
|
29
|
+
| Car | arrows: forward/back, steer left/right (drive port A, steering port D; change with `--drive`/`--steer`) |
|
|
30
|
+
| Ports | like two game sticks: `W`/`S` port A, `A`/`D` port B, `I`/`K` port C, `J`/`L` port D |
|
|
31
|
+
| Program | `e` edit in `$EDITOR`, Enter run (see below) |
|
|
32
|
+
| Path | `e` edit, Enter run, `[` `]` / `{` `}` calibrate speed and turn rate |
|
|
33
|
+
|
|
34
|
+
Everywhere: `Tab` switch screen, `1`-`3` speed (60/120/255), `space` STOP, `q` quit, and
|
|
35
|
+
|
|
36
|
+
- `h` **hold**: motors keep running after you let go of the key (steering still springs back)
|
|
37
|
+
- `p` **pair**: one port copies another (off → A+B → A+C), for builds with two motors that
|
|
38
|
+
should always move together, like a tank
|
|
39
|
+
- `r` + port letter **reverse**: that port turns the other way round, for a motor that was
|
|
40
|
+
mounted backwards
|
|
41
|
+
|
|
42
|
+
Settings, programs and paths are saved in `~/.config/brickremote.json`.
|
|
43
|
+
|
|
44
|
+
Terminals report key presses but not releases, so a motor runs while its key auto-repeats and
|
|
45
|
+
stops a moment after you let go. Steering always springs back to centre, even with hold on.
|
|
46
|
+
|
|
47
|
+
## Web remote control
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
./run.sh web # or: brickremote web (needs `pip install 'brickremote[web]'`)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This starts a small web server and prints its address. Open it in any browser: on the same
|
|
54
|
+
computer, or on a tablet or laptop on the same Wi-Fi, which then becomes the remote control.
|
|
55
|
+
It has the same four screens as the terminal app, plus things a browser is good at:
|
|
56
|
+
|
|
57
|
+
- buttons you hold down, a steering wheel you can drag, and two joysticks for the ports
|
|
58
|
+
- program blocks you tap to build a program
|
|
59
|
+
- a grid where you draw the route for path mode
|
|
60
|
+
|
|
61
|
+
If the browser stops talking to the server (tab closed, Wi-Fi gone), the motors stop by themselves.
|
|
62
|
+
|
|
63
|
+
## Command line
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
brickremote scan # list modules nearby
|
|
67
|
+
brickremote run a=255 --time 2 # port A forward, full speed, 2 s
|
|
68
|
+
brickremote run a=-120 d=255 --time 1.5 # A reverse at medium speed, D forward
|
|
69
|
+
brickremote program dance.txt # run a program script
|
|
70
|
+
brickremote path route.txt --mps 0.6 # drive a path script
|
|
71
|
+
brickremote --invert d run d=255 # global options: --address, --slot, --invert, --drive, --steer
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Program scripts (the app's block programming)
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
gear 2 # speed for + and -: 1 = 60, 2 = 120, 3 = 255
|
|
78
|
+
repeat 3
|
|
79
|
+
a + 1.5 # port A forward for 1.5 s
|
|
80
|
+
ac - 1 # ports A and C reverse for 1 s
|
|
81
|
+
wait 0.5
|
|
82
|
+
end
|
|
83
|
+
d 200 # port D at speed 200, keep running
|
|
84
|
+
forever # until Ctrl-C / space
|
|
85
|
+
b + 1
|
|
86
|
+
b - 1
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Path scripts (the app's path mode)
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
forward 1.5 # metres
|
|
94
|
+
left 90 # degrees, driving forward with full steering
|
|
95
|
+
back 0.5
|
|
96
|
+
right 45
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Distances and angles become times through a calibration (`--mps`, `--dps`, or `[ ] { }` in the
|
|
100
|
+
app): measure how far the car goes in one second at full speed, and how many degrees it turns.
|
|
101
|
+
|
|
102
|
+
## Library
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import asyncio
|
|
106
|
+
from brickremote import JMCModule
|
|
107
|
+
|
|
108
|
+
async def main():
|
|
109
|
+
async with JMCModule() as m: # connects to the first module found
|
|
110
|
+
await m.drive(a=255, d=-120) # speeds -255..255, negative = reverse
|
|
111
|
+
await asyncio.sleep(2)
|
|
112
|
+
await m.stop()
|
|
113
|
+
print("battery:", m.battery, "%")
|
|
114
|
+
|
|
115
|
+
asyncio.run(main())
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`JMCModule(address)` targets a specific module, `discover()` lists all of them. While connected,
|
|
119
|
+
the current state is resent every 0.2 s, which keeps the module from going to sleep.
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
JMCModule(inverted="d", links={"a": "c"}) # reverse port D; C follows A
|
|
123
|
+
await m.run(2, a=255) # A forward for 2 s, then stop
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`brickremote.script` has the program and path engines (`parse_program`, `run_program`,
|
|
127
|
+
`parse_path`, `run_path`) if you want them from your own code.
|
|
128
|
+
|
|
129
|
+
For the stock car (drive motor on A, steering on D) there is a small wrapper:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from brickremote import Car, JMCModule
|
|
133
|
+
|
|
134
|
+
async with JMCModule() as m:
|
|
135
|
+
car = Car(m)
|
|
136
|
+
await car.left(speed=200) # forward while steering left
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Protocol
|
|
140
|
+
|
|
141
|
+
Reverse-engineered from the XP Block Android app. The module exposes service `FFF0`;
|
|
142
|
+
commands go to `FFF2` and **must be written with response** (write-without-response is
|
|
143
|
+
silently ignored):
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
5A 6B 02 00 05 dirAB speedAB dirCD speedCD slot SUM
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| Field | Meaning |
|
|
150
|
+
|---------|---------|
|
|
151
|
+
| dirAB | A: `1` forward, `2` reverse; B: `+4` forward, `+8` reverse |
|
|
152
|
+
| speedAB | 0–255, shared by A and B (the app's gears are 60 / 120 / 255) |
|
|
153
|
+
| dirCD, speedCD | same for C and D |
|
|
154
|
+
| slot | 1–3, module slot (the app can drive three modules at once) |
|
|
155
|
+
| SUM | sum of all previous bytes, modulo 256 |
|
|
156
|
+
|
|
157
|
+
Six-port modules use length `08` and append `dirEF speedE speedF`.
|
|
158
|
+
|
|
159
|
+
The module reports on `FFF1` with `5A 6B 01 00 01 <battery %> SUM`. It switches itself off
|
|
160
|
+
after about ten minutes without commands.
|
|
161
|
+
|
|
162
|
+
## Limitations
|
|
163
|
+
|
|
164
|
+
- The two ports of a pair share one speed byte, so A and B (or C and D) cannot run at different
|
|
165
|
+
speeds at the same time; the faster one wins.
|
|
166
|
+
- Only tested with a four-port module. Six-port support follows the app's code but is unverified.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|