spikeble 0.1__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.
- spikeble-0.1/.gitignore +5 -0
- spikeble-0.1/LICENSE +13 -0
- spikeble-0.1/PKG-INFO +126 -0
- spikeble-0.1/README.md +93 -0
- spikeble-0.1/pyproject.toml +65 -0
- spikeble-0.1/spikeble/__init__.py +71 -0
- spikeble-0.1/spikeble/_lib/cobs.py +125 -0
- spikeble-0.1/spikeble/_lib/connection.py +22 -0
- spikeble-0.1/spikeble/_lib/crc.py +11 -0
- spikeble-0.1/spikeble/_lib/enumeration.py +79 -0
- spikeble-0.1/spikeble/_lib/messages.py +290 -0
- spikeble-0.1/spikeble/_lib/readme +2 -0
- spikeble-0.1/spikeble/_utils.py +8 -0
- spikeble-0.1/spikeble/micropython/app/__init__.py +3 -0
- spikeble-0.1/spikeble/micropython/app/bargraph.py +24 -0
- spikeble-0.1/spikeble/micropython/app/display.py +39 -0
- spikeble-0.1/spikeble/micropython/app/linegraph.py +39 -0
- spikeble-0.1/spikeble/micropython/app/music.py +51 -0
- spikeble-0.1/spikeble/micropython/app/sound.py +18 -0
- spikeble-0.1/spikeble/micropython/color/__init__.py +14 -0
- spikeble-0.1/spikeble/micropython/color_matrix/__init__.py +18 -0
- spikeble-0.1/spikeble/micropython/color_sensor/__init__.py +16 -0
- spikeble-0.1/spikeble/micropython/device/__init__.py +27 -0
- spikeble-0.1/spikeble/micropython/distance_sensor/__init__.py +30 -0
- spikeble-0.1/spikeble/micropython/force_sensor/__init__.py +65 -0
- spikeble-0.1/spikeble/micropython/hub/__init__.py +13 -0
- spikeble-0.1/spikeble/micropython/hub/button.py +37 -0
- spikeble-0.1/spikeble/micropython/hub/light.py +38 -0
- spikeble-0.1/spikeble/micropython/hub/light_matrix.py +249 -0
- spikeble-0.1/spikeble/micropython/hub/motion_sensor.py +142 -0
- spikeble-0.1/spikeble/micropython/hub/port.py +25 -0
- spikeble-0.1/spikeble/micropython/hub/sound.py +74 -0
- spikeble-0.1/spikeble/micropython/motor/__init__.py +398 -0
- spikeble-0.1/spikeble/micropython/motor_pair/__init__.py +555 -0
- spikeble-0.1/spikeble/micropython/orientation/__init__.py +12 -0
- spikeble-0.1/spikeble/micropython/runloop/__init__.py +85 -0
- spikeble-0.1/spikeble/spike.py +243 -0
spikeble-0.1/.gitignore
ADDED
spikeble-0.1/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2025 Michael Gross
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
spikeble-0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spikeble
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: BLE interface for running Micropython on LEGO SPIKE devices
|
|
5
|
+
Project-URL: Homepage, https://github.com/MGross21/spikeble
|
|
6
|
+
Project-URL: Repository, https://github.com/MGross21/spikeble
|
|
7
|
+
Project-URL: Issues, https://github.com/MGross21/spikeble/issues
|
|
8
|
+
Author-email: Michael Gross <MGross21@users.noreply.github.com>
|
|
9
|
+
License: Copyright 2025 Michael Gross
|
|
10
|
+
|
|
11
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
you may not use this file except in compliance with the License.
|
|
13
|
+
You may obtain a copy of the License at
|
|
14
|
+
|
|
15
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
|
|
17
|
+
Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
See the License for the specific language governing permissions and
|
|
21
|
+
limitations under the License.
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Keywords: ble,bluetooth,lego,robotics,spike
|
|
24
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
25
|
+
Classifier: Operating System :: OS Independent
|
|
26
|
+
Classifier: Programming Language :: Python :: 3
|
|
27
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
29
|
+
Classifier: Topic :: System :: Hardware
|
|
30
|
+
Requires-Python: >=3.11
|
|
31
|
+
Requires-Dist: bleak>=1.0.0
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
<h1 align="center">
|
|
35
|
+
<img src="https://raw.githubusercontent.com/MGross21/spikeble/main/assets/spikeble_logo.png" alt="spikeble logo" width="300" />
|
|
36
|
+
</h1>
|
|
37
|
+
<p align="center">
|
|
38
|
+
<sub>(pronounced "spike-able")</sub>
|
|
39
|
+
<br>
|
|
40
|
+
<img src="https://raw.githubusercontent.com/MGross21/spikeble/main/assets/lego_spike.png" alt="Lego Spike"/>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
*From PyPI:*
|
|
46
|
+
|
|
47
|
+
```console
|
|
48
|
+
pip install spikeble
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
*From GitHub:*
|
|
52
|
+
|
|
53
|
+
```console
|
|
54
|
+
pip install git+https://github.com/MGross21/spikeble.git
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> **⚠️ Warning:**
|
|
58
|
+
> It is **highly recommended** to install and use this library within a Python virtual environment. Installing `spikeble` will expose all MicroPython modules (such as `app`, `color`, `color_matrix`, `color_sensor`, `device`, `distance_sensor`, `force_sensor`, `hub`, `motor`, `motor_pair`, `orientation`, and `runloop`) as direct imports in your environment. Using a virtual environment prevents conflicts with other Python projects and keeps your global Python installation clean.
|
|
59
|
+
>
|
|
60
|
+
> <details>
|
|
61
|
+
> <summary><strong>Making a Python Virtual Environment</strong></summary>
|
|
62
|
+
>
|
|
63
|
+
> ```bash
|
|
64
|
+
> python -m venv .venv
|
|
65
|
+
>
|
|
66
|
+
> # Activate the virtual environment
|
|
67
|
+
> # On Windows:
|
|
68
|
+
> venv\Scripts\activate
|
|
69
|
+
>
|
|
70
|
+
> # On macOS/Linux:
|
|
71
|
+
> source venv/bin/activate
|
|
72
|
+
> ```
|
|
73
|
+
>
|
|
74
|
+
> Once activated, you can install `spikeble` and other dependencies.
|
|
75
|
+
>
|
|
76
|
+
> </details>
|
|
77
|
+
|
|
78
|
+
## Running Code on SPIKE
|
|
79
|
+
|
|
80
|
+
To enable auto-complete for MicroPython APIs, place all MicroPython imports inside your `main()` function.
|
|
81
|
+
Use the template below as a starting point for your SPIKE code:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import spikeble
|
|
85
|
+
import asyncio
|
|
86
|
+
|
|
87
|
+
def main():
|
|
88
|
+
# from app import sound, bargraph, display, linegraph, music
|
|
89
|
+
import color
|
|
90
|
+
import color_matrix
|
|
91
|
+
import color_sensor
|
|
92
|
+
import device
|
|
93
|
+
import distance_sensor
|
|
94
|
+
import force_sensor
|
|
95
|
+
from hub import port, button, light, light_matrix, motion_sensor, sound
|
|
96
|
+
import motor
|
|
97
|
+
import motor_pair
|
|
98
|
+
import orientation
|
|
99
|
+
import runloop
|
|
100
|
+
|
|
101
|
+
### Insert Your Code Here ###
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
asyncio.run(spikeble.run(main))
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> **Note:**
|
|
108
|
+
> As of `SPIKE™ Firmware v1.8.149` and `RPC v1.0.47`, using `import app` or `from app import ...` will result in an import error. The `app` module is currently disabled until further notice.
|
|
109
|
+
|
|
110
|
+
### Other Send Methods
|
|
111
|
+
|
|
112
|
+
You can also run code on the SPIKE using the following methods:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
spikeble.run_file("path/to/your_script.py")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
spikeble.run_str("print('Hello from SPIKE!')")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Documentation
|
|
123
|
+
|
|
124
|
+
- [MicroPython Docs](https://spike.legoeducation.com/prime/modal/help/lls-help-python)
|
|
125
|
+
- [Communication Protocol](https://github.com/LEGO/spike-prime-docs)
|
|
126
|
+
<!-- - [Spike 3 Python Docs (Unofficial)](https://tuftsceeo.github.io/SPIKEPythonDocs/SPIKE3.html) -->
|
spikeble-0.1/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/MGross21/spikeble/main/assets/spikeble_logo.png" alt="spikeble logo" width="300" />
|
|
3
|
+
</h1>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<sub>(pronounced "spike-able")</sub>
|
|
6
|
+
<br>
|
|
7
|
+
<img src="https://raw.githubusercontent.com/MGross21/spikeble/main/assets/lego_spike.png" alt="Lego Spike"/>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
*From PyPI:*
|
|
13
|
+
|
|
14
|
+
```console
|
|
15
|
+
pip install spikeble
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
*From GitHub:*
|
|
19
|
+
|
|
20
|
+
```console
|
|
21
|
+
pip install git+https://github.com/MGross21/spikeble.git
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> **⚠️ Warning:**
|
|
25
|
+
> It is **highly recommended** to install and use this library within a Python virtual environment. Installing `spikeble` will expose all MicroPython modules (such as `app`, `color`, `color_matrix`, `color_sensor`, `device`, `distance_sensor`, `force_sensor`, `hub`, `motor`, `motor_pair`, `orientation`, and `runloop`) as direct imports in your environment. Using a virtual environment prevents conflicts with other Python projects and keeps your global Python installation clean.
|
|
26
|
+
>
|
|
27
|
+
> <details>
|
|
28
|
+
> <summary><strong>Making a Python Virtual Environment</strong></summary>
|
|
29
|
+
>
|
|
30
|
+
> ```bash
|
|
31
|
+
> python -m venv .venv
|
|
32
|
+
>
|
|
33
|
+
> # Activate the virtual environment
|
|
34
|
+
> # On Windows:
|
|
35
|
+
> venv\Scripts\activate
|
|
36
|
+
>
|
|
37
|
+
> # On macOS/Linux:
|
|
38
|
+
> source venv/bin/activate
|
|
39
|
+
> ```
|
|
40
|
+
>
|
|
41
|
+
> Once activated, you can install `spikeble` and other dependencies.
|
|
42
|
+
>
|
|
43
|
+
> </details>
|
|
44
|
+
|
|
45
|
+
## Running Code on SPIKE
|
|
46
|
+
|
|
47
|
+
To enable auto-complete for MicroPython APIs, place all MicroPython imports inside your `main()` function.
|
|
48
|
+
Use the template below as a starting point for your SPIKE code:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import spikeble
|
|
52
|
+
import asyncio
|
|
53
|
+
|
|
54
|
+
def main():
|
|
55
|
+
# from app import sound, bargraph, display, linegraph, music
|
|
56
|
+
import color
|
|
57
|
+
import color_matrix
|
|
58
|
+
import color_sensor
|
|
59
|
+
import device
|
|
60
|
+
import distance_sensor
|
|
61
|
+
import force_sensor
|
|
62
|
+
from hub import port, button, light, light_matrix, motion_sensor, sound
|
|
63
|
+
import motor
|
|
64
|
+
import motor_pair
|
|
65
|
+
import orientation
|
|
66
|
+
import runloop
|
|
67
|
+
|
|
68
|
+
### Insert Your Code Here ###
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
asyncio.run(spikeble.run(main))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
> **Note:**
|
|
75
|
+
> As of `SPIKE™ Firmware v1.8.149` and `RPC v1.0.47`, using `import app` or `from app import ...` will result in an import error. The `app` module is currently disabled until further notice.
|
|
76
|
+
|
|
77
|
+
### Other Send Methods
|
|
78
|
+
|
|
79
|
+
You can also run code on the SPIKE using the following methods:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
spikeble.run_file("path/to/your_script.py")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
spikeble.run_str("print('Hello from SPIKE!')")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Documentation
|
|
90
|
+
|
|
91
|
+
- [MicroPython Docs](https://spike.legoeducation.com/prime/modal/help/lls-help-python)
|
|
92
|
+
- [Communication Protocol](https://github.com/LEGO/spike-prime-docs)
|
|
93
|
+
<!-- - [Spike 3 Python Docs (Unofficial)](https://tuftsceeo.github.io/SPIKEPythonDocs/SPIKE3.html) -->
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27", "hatch-vcs>=0.4"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spikeble"
|
|
7
|
+
description = "BLE interface for running Micropython on LEGO SPIKE devices"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
authors = [{ name = "Michael Gross", email = "MGross21@users.noreply.github.com" }]
|
|
12
|
+
keywords = ["lego", "spike", "ble", "bluetooth", "robotics"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Software Development :: Libraries",
|
|
19
|
+
"Topic :: System :: Hardware",
|
|
20
|
+
]
|
|
21
|
+
dependencies = ["bleak>=1.0.0"]
|
|
22
|
+
dynamic = ["version"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/MGross21/spikeble"
|
|
26
|
+
Repository = "https://github.com/MGross21/spikeble"
|
|
27
|
+
Issues = "https://github.com/MGross21/spikeble/issues"
|
|
28
|
+
|
|
29
|
+
[tool.hatch.version]
|
|
30
|
+
source = "vcs"
|
|
31
|
+
fallback-version = "0.0.0"
|
|
32
|
+
raw-options = { local_scheme = "no-local-version" }
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
packages = ["spikeble"]
|
|
36
|
+
include = ["spikeble/py.typed"]
|
|
37
|
+
|
|
38
|
+
# Expose MicroPython folders as top-level install packages
|
|
39
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
40
|
+
"spikeble/micropython/app" = "app"
|
|
41
|
+
"spikeble/micropython/color" = "color"
|
|
42
|
+
"spikeble/micropython/color_matrix" = "color_matrix"
|
|
43
|
+
"spikeble/micropython/color_sensor" = "color_sensor"
|
|
44
|
+
"spikeble/micropython/device" = "device"
|
|
45
|
+
"spikeble/micropython/distance_sensor" = "distance_sensor"
|
|
46
|
+
"spikeble/micropython/force_sensor" = "force_sensor"
|
|
47
|
+
"spikeble/micropython/hub" = "hub"
|
|
48
|
+
"spikeble/micropython/motor" = "motor"
|
|
49
|
+
"spikeble/micropython/motor_pair" = "motor_pair"
|
|
50
|
+
"spikeble/micropython/orientation" = "orientation"
|
|
51
|
+
"spikeble/micropython/runloop" = "runloop"
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.sdist]
|
|
54
|
+
include = ["/spikeble", "/README.md", "/LICENSE*", "/pyproject.toml"]
|
|
55
|
+
|
|
56
|
+
# ---- Tooling ---------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
target-version = "py311"
|
|
60
|
+
line-length = 80
|
|
61
|
+
lint.select = ["E", "F", "I", "UP", "B", "C4", "SIM"]
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
addopts = "-ra -q"
|
|
65
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
async def run_fn(
|
|
4
|
+
program: Callable[[], None],
|
|
5
|
+
*,
|
|
6
|
+
slot: int = 0,
|
|
7
|
+
stay_connected: bool = False,
|
|
8
|
+
):
|
|
9
|
+
"""Run a Python function on the SPIKE Prime hub."""
|
|
10
|
+
from .spike import logger
|
|
11
|
+
from ._utils import fn_to_string
|
|
12
|
+
try:
|
|
13
|
+
# call function and see if any front-side errors trigger
|
|
14
|
+
program()
|
|
15
|
+
except Exception as e:
|
|
16
|
+
logger.error(f"Error occurred while running program: {e}")
|
|
17
|
+
await run_str(
|
|
18
|
+
fn_to_string(program),
|
|
19
|
+
slot=slot,
|
|
20
|
+
name=program.__name__ + ".py",
|
|
21
|
+
stay_connected=stay_connected
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
run = run_fn # shorthand alias default
|
|
25
|
+
|
|
26
|
+
async def run_file(
|
|
27
|
+
program_path: str,
|
|
28
|
+
*,
|
|
29
|
+
slot: int = 0,
|
|
30
|
+
stay_connected: bool = False,
|
|
31
|
+
):
|
|
32
|
+
"""Run a Python file on the SPIKE Prime hub."""
|
|
33
|
+
from .spike import logger
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
if not Path(program_path).exists():
|
|
36
|
+
logger.error(f"File not found: {program_path}")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
program_str = Path(program_path).read_text()
|
|
40
|
+
try:
|
|
41
|
+
compile(program_str, program_path, "exec")
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.error(f"Error compiling {program_path}: {e}")
|
|
44
|
+
return
|
|
45
|
+
await run_str(
|
|
46
|
+
program_str,
|
|
47
|
+
slot=slot,
|
|
48
|
+
name=Path(program_path).name,
|
|
49
|
+
stay_connected=stay_connected
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async def run_str(
|
|
53
|
+
program_str: str,
|
|
54
|
+
*,
|
|
55
|
+
slot: int = 0,
|
|
56
|
+
name: str = "program.py",
|
|
57
|
+
stay_connected: bool = False,
|
|
58
|
+
):
|
|
59
|
+
"""Run a Python string as code on the SPIKE Prime hub."""
|
|
60
|
+
from .spike import Spike
|
|
61
|
+
async with Spike(timeout=10, slot=slot) as hub:
|
|
62
|
+
await hub.get_info()
|
|
63
|
+
await hub.enable_notifications()
|
|
64
|
+
await hub.clear_slot()
|
|
65
|
+
await hub.upload_program(program_str.encode("utf-8"), name=name)
|
|
66
|
+
await hub.start_program()
|
|
67
|
+
if stay_connected:
|
|
68
|
+
try:
|
|
69
|
+
await hub.run_until_disconnect()
|
|
70
|
+
except KeyboardInterrupt:
|
|
71
|
+
await hub.disconnect()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Example implementation of the Consistent Overhead Byte Stuffing (COBS) algorithm
|
|
3
|
+
used by the SPIKE™ Prime BLE protocol.
|
|
4
|
+
|
|
5
|
+
This implementation prioritizes readability and simplicity over performance and
|
|
6
|
+
should be used for educational purposes only.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
DELIMITER = 0x02
|
|
10
|
+
"""Delimiter used to mark end of frame"""
|
|
11
|
+
|
|
12
|
+
NO_DELIMITER = 0xFF
|
|
13
|
+
"""Code word indicating no delimiter in block"""
|
|
14
|
+
|
|
15
|
+
COBS_CODE_OFFSET = DELIMITER
|
|
16
|
+
"""Offset added to code word"""
|
|
17
|
+
|
|
18
|
+
MAX_BLOCK_SIZE = 84
|
|
19
|
+
"""Maximum block size (incl. code word)"""
|
|
20
|
+
|
|
21
|
+
XOR = 3
|
|
22
|
+
"""XOR mask for encoding"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Source: https://lego.github.io/spike-prime-docs/encoding.html#cobs-encode
|
|
26
|
+
def encode(data: bytes):
|
|
27
|
+
"""
|
|
28
|
+
Encode data using COBS algorithm, such that no delimiters are present.
|
|
29
|
+
"""
|
|
30
|
+
buffer = bytearray()
|
|
31
|
+
code_index = block = 0
|
|
32
|
+
|
|
33
|
+
def begin_block():
|
|
34
|
+
"""Append code word to buffer and update code_index and block"""
|
|
35
|
+
nonlocal code_index, block
|
|
36
|
+
code_index = len(buffer) # index of incomplete code word
|
|
37
|
+
buffer.append(NO_DELIMITER) # updated later if delimiter is encountered
|
|
38
|
+
block = 1 # no. of bytes in block (incl. code word)
|
|
39
|
+
|
|
40
|
+
begin_block()
|
|
41
|
+
for byte in data:
|
|
42
|
+
if byte > DELIMITER:
|
|
43
|
+
# non-delimeter value, write as-is
|
|
44
|
+
buffer.append(byte)
|
|
45
|
+
block += 1
|
|
46
|
+
|
|
47
|
+
if byte <= DELIMITER or block > MAX_BLOCK_SIZE:
|
|
48
|
+
# block completed because size limit reached or delimiter found
|
|
49
|
+
if byte <= DELIMITER:
|
|
50
|
+
# reason for block completion is delimiter
|
|
51
|
+
# update code word to reflect block size
|
|
52
|
+
delimiter_base = byte * MAX_BLOCK_SIZE
|
|
53
|
+
block_offset = block + COBS_CODE_OFFSET
|
|
54
|
+
buffer[code_index] = delimiter_base + block_offset
|
|
55
|
+
# begin new block
|
|
56
|
+
begin_block()
|
|
57
|
+
|
|
58
|
+
# update final code word
|
|
59
|
+
buffer[code_index] = block + COBS_CODE_OFFSET
|
|
60
|
+
|
|
61
|
+
return buffer
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Source: https://lego.github.io/spike-prime-docs/encoding.html#cobs-decode
|
|
65
|
+
def decode(data: bytes):
|
|
66
|
+
"""
|
|
67
|
+
Decode data using COBS algorithm.
|
|
68
|
+
"""
|
|
69
|
+
buffer = bytearray()
|
|
70
|
+
|
|
71
|
+
def unescape(code: int):
|
|
72
|
+
"""Decode code word, returning value and block size"""
|
|
73
|
+
if code == 0xFF:
|
|
74
|
+
# no delimiter in block
|
|
75
|
+
return None, MAX_BLOCK_SIZE + 1
|
|
76
|
+
value, block = divmod(code - COBS_CODE_OFFSET, MAX_BLOCK_SIZE)
|
|
77
|
+
if block == 0:
|
|
78
|
+
# maximum block size ending with delimiter
|
|
79
|
+
block = MAX_BLOCK_SIZE
|
|
80
|
+
value -= 1
|
|
81
|
+
return value, block
|
|
82
|
+
|
|
83
|
+
value, block = unescape(data[0])
|
|
84
|
+
for byte in data[1:]: # first byte already processed
|
|
85
|
+
block -= 1
|
|
86
|
+
if block > 0:
|
|
87
|
+
buffer.append(byte)
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
# block completed
|
|
91
|
+
if value is not None:
|
|
92
|
+
buffer.append(value)
|
|
93
|
+
|
|
94
|
+
value, block = unescape(byte)
|
|
95
|
+
|
|
96
|
+
return buffer
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Source: https://lego.github.io/spike-prime-docs/encoding.html#cobs-pack
|
|
100
|
+
def pack(data: bytes):
|
|
101
|
+
"""
|
|
102
|
+
Encode and frame data for transmission.
|
|
103
|
+
"""
|
|
104
|
+
buffer = encode(data)
|
|
105
|
+
|
|
106
|
+
# XOR buffer to remove problematic ctrl+C
|
|
107
|
+
for i in range(len(buffer)):
|
|
108
|
+
buffer[i] ^= XOR
|
|
109
|
+
|
|
110
|
+
# add delimiter
|
|
111
|
+
buffer.append(DELIMITER)
|
|
112
|
+
return bytes(buffer)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Source: https://lego.github.io/spike-prime-docs/encoding.html#cobs-unpack
|
|
116
|
+
def unpack(frame: bytes):
|
|
117
|
+
"""
|
|
118
|
+
Unframe and decode frame.
|
|
119
|
+
"""
|
|
120
|
+
start = 0
|
|
121
|
+
if frame[0] == 0x01: # unused priority byte
|
|
122
|
+
start += 1
|
|
123
|
+
# unframe and XOR
|
|
124
|
+
unframed = bytes(map(lambda x: x ^ XOR, frame[start:-1]))
|
|
125
|
+
return bytes(decode(unframed))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class UUID:
|
|
2
|
+
"""
|
|
3
|
+
The LEGO SPIKE Prime Hub exposes a BLE GATT service with two characteristics:
|
|
4
|
+
- RX: for receiving data (from the hub's perspective)
|
|
5
|
+
- TX: for transmitting data (from the hub's perspective)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
_BASE = "0000FD02"
|
|
9
|
+
_TAIL = "1000-8000-00805F9B34FB"
|
|
10
|
+
SERVICE = f"{_BASE}-0000-{_TAIL}"
|
|
11
|
+
RX = f"{_BASE}-0001-{_TAIL}"
|
|
12
|
+
TX = f"{_BASE}-0002-{_TAIL}"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Hardware:
|
|
16
|
+
MAC_ADDR = "3C:E4:B0:AB:D3:3A"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Name:
|
|
20
|
+
HINTS = ["SPIKE", "Spike", "Prime", "Hub", "Lego"] + [
|
|
21
|
+
str(i) for i in range(1, 100)
|
|
22
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from binascii import crc32 as _crc32
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def crc(data: bytes, seed=0, align=4):
|
|
5
|
+
"""
|
|
6
|
+
Calculate the CRC32 of data with an optional seed and alignment.
|
|
7
|
+
"""
|
|
8
|
+
remainder = len(data) % align
|
|
9
|
+
if remainder:
|
|
10
|
+
data += b"\x00" * (align - remainder)
|
|
11
|
+
return _crc32(data, seed)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from enum import IntEnum, unique
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@unique
|
|
5
|
+
class Color(IntEnum):
|
|
6
|
+
BLACK = 0x00
|
|
7
|
+
MAGENTA = 0x01
|
|
8
|
+
PURPLE = 0x02
|
|
9
|
+
BLUE = 0x03
|
|
10
|
+
AZURE = 0x04
|
|
11
|
+
TURQUOISE = 0x05
|
|
12
|
+
GREEN = 0x06
|
|
13
|
+
YELLOW = 0x07
|
|
14
|
+
ORANGE = 0x08
|
|
15
|
+
RED = 0x09
|
|
16
|
+
WHITE = 0x0A
|
|
17
|
+
NONE = 0xFF
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@unique
|
|
21
|
+
class HubPort(IntEnum):
|
|
22
|
+
A = 0x00
|
|
23
|
+
B = 0x01
|
|
24
|
+
C = 0x02
|
|
25
|
+
D = 0x03
|
|
26
|
+
E = 0x04
|
|
27
|
+
F = 0x05
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@unique
|
|
31
|
+
class HubFace(IntEnum):
|
|
32
|
+
TOP = 0x00
|
|
33
|
+
FRONT = 0x01
|
|
34
|
+
RIGHT = 0x02
|
|
35
|
+
BOTTOM = 0x03
|
|
36
|
+
BACK = 0x04
|
|
37
|
+
LEFT = 0x05
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@unique
|
|
41
|
+
class ProgramAction(IntEnum):
|
|
42
|
+
START = 0x00
|
|
43
|
+
STOP = 0x01
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@unique
|
|
47
|
+
class ResponseStatus(IntEnum):
|
|
48
|
+
ACKNOWLEDGED = 0x00
|
|
49
|
+
NOT_ACKNOWLEDGED = 0x01
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@unique
|
|
53
|
+
class MotorEndState(IntEnum):
|
|
54
|
+
"""
|
|
55
|
+
Smart coast/brake: methods of stopping a motor that compensate for inaccuracies when following commands.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
COAST = 0x00
|
|
59
|
+
BRAKE = 0x01
|
|
60
|
+
HOLD = 0x02
|
|
61
|
+
CONTINUE = 0x03
|
|
62
|
+
COAST_SMART = 0x04
|
|
63
|
+
BRAKE_SMART = 0x05
|
|
64
|
+
DEFAULT = 0xFF
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@unique
|
|
68
|
+
class MotorMoveDirection(IntEnum):
|
|
69
|
+
CLOCKWISE = 0x00
|
|
70
|
+
COUNTERCLOCKWISE = 0x01
|
|
71
|
+
SHORTEST_PATH = 0x02
|
|
72
|
+
LONGEST_PATH = 0x03
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@unique
|
|
76
|
+
class MotorDeviceType(IntEnum):
|
|
77
|
+
MEDIUM = 0x30
|
|
78
|
+
LARGE = 0x31
|
|
79
|
+
SMALL = 0x41
|