brilliant-msg 7.0.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.
- brilliant_msg-7.0.0/.gitignore +22 -0
- brilliant_msg-7.0.0/LICENSE +28 -0
- brilliant_msg-7.0.0/PKG-INFO +121 -0
- brilliant_msg-7.0.0/README.md +93 -0
- brilliant_msg-7.0.0/pyproject.toml +55 -0
- brilliant_msg-7.0.0/src/brilliant_msg/__init__.py +24 -0
- brilliant_msg-7.0.0/src/brilliant_msg/brilliant_msg.py +184 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/audio.lua +59 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/audio.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/battery.lua +17 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/battery.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/camera.lua +200 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/camera.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/code.lua +11 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/code.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/data.lua +128 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/data.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/image_sprite_block.lua +92 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/image_sprite_block.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/imu.lua +26 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/imu.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/plain_text.lua +20 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/plain_text.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/sprite.lua +40 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/sprite.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/sprite_coords.lua +16 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/sprite_coords.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/tap.lua +16 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/tap.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/text_sprite_block.lua +44 -0
- brilliant_msg-7.0.0/src/brilliant_msg/lua/text_sprite_block.min.lua +1 -0
- brilliant_msg-7.0.0/src/brilliant_msg/rx_audio.py +161 -0
- brilliant_msg-7.0.0/src/brilliant_msg/rx_auto_exp_result.py +85 -0
- brilliant_msg-7.0.0/src/brilliant_msg/rx_imu.py +131 -0
- brilliant_msg-7.0.0/src/brilliant_msg/rx_metering_data.py +69 -0
- brilliant_msg-7.0.0/src/brilliant_msg/rx_photo.py +135 -0
- brilliant_msg-7.0.0/src/brilliant_msg/rx_tap.py +89 -0
- brilliant_msg-7.0.0/src/brilliant_msg/tx_auto_exp_settings.py +36 -0
- brilliant_msg-7.0.0/src/brilliant_msg/tx_capture_settings.py +30 -0
- brilliant_msg-7.0.0/src/brilliant_msg/tx_code.py +13 -0
- brilliant_msg-7.0.0/src/brilliant_msg/tx_image_sprite_block.py +79 -0
- brilliant_msg-7.0.0/src/brilliant_msg/tx_manual_exp_settings.py +30 -0
- brilliant_msg-7.0.0/src/brilliant_msg/tx_plain_text.py +44 -0
- brilliant_msg-7.0.0/src/brilliant_msg/tx_sprite.py +200 -0
- brilliant_msg-7.0.0/src/brilliant_msg/tx_sprite_coords.py +36 -0
- brilliant_msg-7.0.0/src/brilliant_msg/tx_text_sprite_block.py +73 -0
- brilliant_msg-7.0.0/tests/__init__.py +0 -0
- brilliant_msg-7.0.0/tests/test_rx_messages.py +413 -0
- brilliant_msg-7.0.0/tests/test_tx_simple.py +177 -0
- brilliant_msg-7.0.0/tests/test_tx_sprite.py +445 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.so
|
|
5
|
+
.Python
|
|
6
|
+
build/
|
|
7
|
+
develop-eggs/
|
|
8
|
+
dist/
|
|
9
|
+
downloads/
|
|
10
|
+
eggs/
|
|
11
|
+
.eggs/
|
|
12
|
+
lib/
|
|
13
|
+
lib64/
|
|
14
|
+
parts/
|
|
15
|
+
sdist/
|
|
16
|
+
var/
|
|
17
|
+
wheels/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
.installed.cfg
|
|
20
|
+
*.egg
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.venv/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, CitizenOneX
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brilliant-msg
|
|
3
|
+
Version: 7.0.0
|
|
4
|
+
Summary: Handles various message types for Brilliant Labs Frame and Halo devices
|
|
5
|
+
Project-URL: Homepage, https://github.com/brilliantlabsAR/brilliant_sdk/tree/main/python/packages/brilliant_msg
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/brilliantlabsAR/brilliant_sdk/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/brilliantlabsAR/brilliant_sdk/blob/main/python/packages/brilliant_msg/CHANGELOG.md
|
|
8
|
+
Author: luke-brilliant
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: brilliant-ble>=3.0.0
|
|
15
|
+
Requires-Dist: lz4<5.0.0,>=4.4.3
|
|
16
|
+
Requires-Dist: numpy<3.0.0,>=2.2.3
|
|
17
|
+
Requires-Dist: pillow<12.0.0,>=11.1.0
|
|
18
|
+
Provides-Extra: docs
|
|
19
|
+
Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == 'docs'
|
|
20
|
+
Requires-Dist: sphinx>=7.0.0; extra == 'docs'
|
|
21
|
+
Provides-Extra: tests
|
|
22
|
+
Requires-Dist: opencv-python>=4.0.0; extra == 'tests'
|
|
23
|
+
Requires-Dist: pvspeaker>=1.0.0; extra == 'tests'
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'tests'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'tests'
|
|
26
|
+
Requires-Dist: qoi>=0.7.0; extra == 'tests'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Brilliant Message Package
|
|
30
|
+
|
|
31
|
+
A Python package for handling rich application-level messages for [Brilliant Labs](https://brilliant.xyz/) Frame and Halo devices, including sprites, text, audio, IMU data, and photos.
|
|
32
|
+
|
|
33
|
+
[Frame SDK documentation](https://docs.brilliant.xyz/frame/frame-sdk/).
|
|
34
|
+
|
|
35
|
+
[Examples on GitHub](https://github.com/brilliantlabsAR/brilliant_sdk/tree/main/python/packages/brilliant_msg/examples).
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv add brilliant-msg
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import asyncio
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
from brilliant_msg import BrilliantMsg, TxSprite
|
|
50
|
+
|
|
51
|
+
async def main():
|
|
52
|
+
"""
|
|
53
|
+
Displays sample images on the Frame display.
|
|
54
|
+
|
|
55
|
+
The images are indexed (palette) PNG images, in 2, 4, and 16 colors (that is, 1-, 2- and 4-bits-per-pixel).
|
|
56
|
+
"""
|
|
57
|
+
frame = BrilliantMsg()
|
|
58
|
+
try:
|
|
59
|
+
await frame.connect()
|
|
60
|
+
|
|
61
|
+
# Let the user know we're starting
|
|
62
|
+
await frame.print_short_text('Loading...')
|
|
63
|
+
|
|
64
|
+
# send the std lua files to Frame that handle data accumulation and sprite parsing
|
|
65
|
+
await frame.upload_stdlua_libs(lib_names=['data', 'sprite'])
|
|
66
|
+
|
|
67
|
+
# Send the main lua application from this project to Frame that will run the app
|
|
68
|
+
await frame.upload_frame_app(local_filename="lua/sprite_frame_app.lua")
|
|
69
|
+
|
|
70
|
+
# attach the print response handler so we can see stdout from Frame Lua print() statements
|
|
71
|
+
frame.attach_print_response_handler()
|
|
72
|
+
|
|
73
|
+
# "require" the main frame_app lua file to run it, and block until it has started.
|
|
74
|
+
await frame.start_frame_app()
|
|
75
|
+
|
|
76
|
+
# send the 1-bit image to Frame in chunks
|
|
77
|
+
sprite = TxSprite.from_indexed_png_bytes(Path("images/logo_1bit.png").read_bytes())
|
|
78
|
+
await frame.send_message(0x20, sprite.pack())
|
|
79
|
+
|
|
80
|
+
# send a 2-bit image
|
|
81
|
+
sprite = TxSprite.from_indexed_png_bytes(Path("images/street_2bit.png").read_bytes())
|
|
82
|
+
await frame.send_message(0x20, sprite.pack())
|
|
83
|
+
|
|
84
|
+
# send a 4-bit image
|
|
85
|
+
sprite = TxSprite.from_indexed_png_bytes(Path("images/hotdog_4bit.png").read_bytes())
|
|
86
|
+
await frame.send_message(0x20, sprite.pack())
|
|
87
|
+
|
|
88
|
+
await asyncio.sleep(5.0)
|
|
89
|
+
|
|
90
|
+
# unhook the print handler
|
|
91
|
+
frame.detach_print_response_handler()
|
|
92
|
+
|
|
93
|
+
# break out of the frame app loop and reboot Frame
|
|
94
|
+
await frame.stop_frame_app()
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print(f"An error occurred: {e}")
|
|
98
|
+
finally:
|
|
99
|
+
# clean disconnection
|
|
100
|
+
await frame.disconnect()
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
asyncio.run(main())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Running Tests
|
|
107
|
+
|
|
108
|
+
From the workspace root:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cd python
|
|
112
|
+
uv sync --all-packages --extra tests
|
|
113
|
+
uv run pytest packages/brilliant_msg/tests/
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The test suite covers all Tx message packing (TxCode, TxPlainText, TxSpriteCoords,
|
|
117
|
+
TxCaptureSettings, TxAutoExpSettings, TxSprite bit-packing and factory methods,
|
|
118
|
+
TxImageSpriteBlock, TxTextSpriteBlock) and Rx message parsing (RxAudio WAV
|
|
119
|
+
conversion, RxMeteringData, RxAutoExpResult, RxIMU including IMUData pitch/roll
|
|
120
|
+
and SensorBuffer smoothing, RxTap, and BrilliantMsg handler dispatch). No hardware
|
|
121
|
+
is required.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Brilliant Message Package
|
|
2
|
+
|
|
3
|
+
A Python package for handling rich application-level messages for [Brilliant Labs](https://brilliant.xyz/) Frame and Halo devices, including sprites, text, audio, IMU data, and photos.
|
|
4
|
+
|
|
5
|
+
[Frame SDK documentation](https://docs.brilliant.xyz/frame/frame-sdk/).
|
|
6
|
+
|
|
7
|
+
[Examples on GitHub](https://github.com/brilliantlabsAR/brilliant_sdk/tree/main/python/packages/brilliant_msg/examples).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv add brilliant-msg
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import asyncio
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from brilliant_msg import BrilliantMsg, TxSprite
|
|
22
|
+
|
|
23
|
+
async def main():
|
|
24
|
+
"""
|
|
25
|
+
Displays sample images on the Frame display.
|
|
26
|
+
|
|
27
|
+
The images are indexed (palette) PNG images, in 2, 4, and 16 colors (that is, 1-, 2- and 4-bits-per-pixel).
|
|
28
|
+
"""
|
|
29
|
+
frame = BrilliantMsg()
|
|
30
|
+
try:
|
|
31
|
+
await frame.connect()
|
|
32
|
+
|
|
33
|
+
# Let the user know we're starting
|
|
34
|
+
await frame.print_short_text('Loading...')
|
|
35
|
+
|
|
36
|
+
# send the std lua files to Frame that handle data accumulation and sprite parsing
|
|
37
|
+
await frame.upload_stdlua_libs(lib_names=['data', 'sprite'])
|
|
38
|
+
|
|
39
|
+
# Send the main lua application from this project to Frame that will run the app
|
|
40
|
+
await frame.upload_frame_app(local_filename="lua/sprite_frame_app.lua")
|
|
41
|
+
|
|
42
|
+
# attach the print response handler so we can see stdout from Frame Lua print() statements
|
|
43
|
+
frame.attach_print_response_handler()
|
|
44
|
+
|
|
45
|
+
# "require" the main frame_app lua file to run it, and block until it has started.
|
|
46
|
+
await frame.start_frame_app()
|
|
47
|
+
|
|
48
|
+
# send the 1-bit image to Frame in chunks
|
|
49
|
+
sprite = TxSprite.from_indexed_png_bytes(Path("images/logo_1bit.png").read_bytes())
|
|
50
|
+
await frame.send_message(0x20, sprite.pack())
|
|
51
|
+
|
|
52
|
+
# send a 2-bit image
|
|
53
|
+
sprite = TxSprite.from_indexed_png_bytes(Path("images/street_2bit.png").read_bytes())
|
|
54
|
+
await frame.send_message(0x20, sprite.pack())
|
|
55
|
+
|
|
56
|
+
# send a 4-bit image
|
|
57
|
+
sprite = TxSprite.from_indexed_png_bytes(Path("images/hotdog_4bit.png").read_bytes())
|
|
58
|
+
await frame.send_message(0x20, sprite.pack())
|
|
59
|
+
|
|
60
|
+
await asyncio.sleep(5.0)
|
|
61
|
+
|
|
62
|
+
# unhook the print handler
|
|
63
|
+
frame.detach_print_response_handler()
|
|
64
|
+
|
|
65
|
+
# break out of the frame app loop and reboot Frame
|
|
66
|
+
await frame.stop_frame_app()
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"An error occurred: {e}")
|
|
70
|
+
finally:
|
|
71
|
+
# clean disconnection
|
|
72
|
+
await frame.disconnect()
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
asyncio.run(main())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Running Tests
|
|
79
|
+
|
|
80
|
+
From the workspace root:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
cd python
|
|
84
|
+
uv sync --all-packages --extra tests
|
|
85
|
+
uv run pytest packages/brilliant_msg/tests/
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The test suite covers all Tx message packing (TxCode, TxPlainText, TxSpriteCoords,
|
|
89
|
+
TxCaptureSettings, TxAutoExpSettings, TxSprite bit-packing and factory methods,
|
|
90
|
+
TxImageSpriteBlock, TxTextSpriteBlock) and Rx message parsing (RxAudio WAV
|
|
91
|
+
conversion, RxMeteringData, RxAutoExpResult, RxIMU including IMUData pitch/roll
|
|
92
|
+
and SensorBuffer smoothing, RxTap, and BrilliantMsg handler dispatch). No hardware
|
|
93
|
+
is required.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "brilliant-msg"
|
|
7
|
+
version = "7.0.0"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"lz4>=4.4.3,<5.0.0",
|
|
10
|
+
"numpy>=2.2.3,<3.0.0",
|
|
11
|
+
"pillow>=11.1.0,<12.0.0",
|
|
12
|
+
"brilliant-ble>=3.0.0"
|
|
13
|
+
]
|
|
14
|
+
authors = [
|
|
15
|
+
{ name = "luke-brilliant" },
|
|
16
|
+
]
|
|
17
|
+
description = "Handles various message types for Brilliant Labs Frame and Halo devices"
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
requires-python = ">=3.10"
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"License :: OSI Approved :: BSD License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
tests = [
|
|
28
|
+
"pytest>=8.0",
|
|
29
|
+
"pytest-asyncio>=0.23",
|
|
30
|
+
"pvspeaker>=1.0.0",
|
|
31
|
+
"opencv-python>=4.0.0",
|
|
32
|
+
"qoi>=0.7.0",
|
|
33
|
+
]
|
|
34
|
+
docs = [
|
|
35
|
+
"sphinx>=7.0.0",
|
|
36
|
+
"sphinx-rtd-theme>=2.0.0",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
asyncio_mode = "auto"
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/brilliant_msg"]
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.sdist]
|
|
47
|
+
include = [
|
|
48
|
+
"/src",
|
|
49
|
+
"/tests",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
"Homepage" = "https://github.com/brilliantlabsAR/brilliant_sdk/tree/main/python/packages/brilliant_msg"
|
|
54
|
+
"Bug Tracker" = "https://github.com/brilliantlabsAR/brilliant_sdk/issues"
|
|
55
|
+
"Changelog" = "https://github.com/brilliantlabsAR/brilliant_sdk/blob/main/python/packages/brilliant_msg/CHANGELOG.md"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
brilliant_msg - Message Package defines Transmit- and Receive-related message classes and their associated Lua handlers for Brilliant Labs Frame and Halo devices (https://brilliant.xyz/)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "7.0.0"
|
|
6
|
+
|
|
7
|
+
from .brilliant_msg import BrilliantMsg
|
|
8
|
+
|
|
9
|
+
from .tx_auto_exp_settings import TxAutoExpSettings
|
|
10
|
+
from .tx_capture_settings import TxCaptureSettings
|
|
11
|
+
from .tx_code import TxCode
|
|
12
|
+
from .tx_image_sprite_block import TxImageSpriteBlock
|
|
13
|
+
from .tx_manual_exp_settings import TxManualExpSettings
|
|
14
|
+
from .tx_plain_text import TxPlainText
|
|
15
|
+
from .tx_sprite import TxSprite
|
|
16
|
+
from .tx_sprite_coords import TxSpriteCoords
|
|
17
|
+
from .tx_text_sprite_block import TxTextSpriteBlock
|
|
18
|
+
|
|
19
|
+
from .rx_audio import RxAudio
|
|
20
|
+
from .rx_auto_exp_result import RxAutoExpResult
|
|
21
|
+
from .rx_imu import RxIMU
|
|
22
|
+
from .rx_metering_data import RxMeteringData
|
|
23
|
+
from .rx_photo import RxPhoto
|
|
24
|
+
from .rx_tap import RxTap
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from importlib.resources import files
|
|
3
|
+
|
|
4
|
+
from brilliant_ble import BrilliantBle, BrilliantDeviceType
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
class BrilliantMsg:
|
|
8
|
+
"""
|
|
9
|
+
A high-level library for interacting with Brilliant Labs Frame by passing structured messages
|
|
10
|
+
between a Frameside app and a hostside app.
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initialize the BrilliantMsg class with a new BrilliantBle instance and a dictionary for registered data response handlers."""
|
|
15
|
+
self.ble = BrilliantBle()
|
|
16
|
+
self.data_response_handlers = {}
|
|
17
|
+
|
|
18
|
+
async def connect(self, initialize:bool=True):
|
|
19
|
+
"""
|
|
20
|
+
Connect to the Frame device and optionally run the initialization sequence.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
initialize (bool): If True, runs the break/reset/break sequence after connecting.
|
|
24
|
+
Defaults to True.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
bool: True if connection and initialization were successful
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
Any exceptions from the underlying BrilliantBle connection
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
await self.ble.connect(data_response_handler=self._handle_data_response)
|
|
34
|
+
|
|
35
|
+
if initialize:
|
|
36
|
+
# Send break signal in case an application loop is running
|
|
37
|
+
await self.ble.send_break_signal()
|
|
38
|
+
|
|
39
|
+
# Reset signal to restart Lua VM and initialize memory
|
|
40
|
+
await self.ble.send_reset_signal()
|
|
41
|
+
|
|
42
|
+
# Another break signal in case of auto-starting main.lua
|
|
43
|
+
await self.ble.send_break_signal()
|
|
44
|
+
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
# If anything fails during connection or initialization,
|
|
49
|
+
# ensure we're disconnected and re-raise the exception
|
|
50
|
+
if self.ble.is_connected():
|
|
51
|
+
await self.ble.disconnect()
|
|
52
|
+
raise e
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def disconnect(self):
|
|
56
|
+
"""Disconnect from the Frame device"""
|
|
57
|
+
if self.ble.is_connected():
|
|
58
|
+
await self.ble.disconnect()
|
|
59
|
+
|
|
60
|
+
def is_connected(self):
|
|
61
|
+
"""Check if currently connected to the Frame device."""
|
|
62
|
+
return self.ble.is_connected()
|
|
63
|
+
|
|
64
|
+
async def print_short_text(self, text:str=''):
|
|
65
|
+
"""
|
|
66
|
+
Convenience wrapper around `frame.display.text()` that can only be used
|
|
67
|
+
prior to the main frame_app starting (e.g. immediately after connection).
|
|
68
|
+
"""
|
|
69
|
+
sanitized_text = text.replace("'", "\\'").replace("\n", "")
|
|
70
|
+
lua_command = ""
|
|
71
|
+
if (self.ble.type == BrilliantDeviceType.FRAME):
|
|
72
|
+
lua_command = f"frame.display.text('{sanitized_text}',1,1);frame.display.show();print(0)"
|
|
73
|
+
else:
|
|
74
|
+
lua_command = f"frame.display.clear();frame.display.text('{sanitized_text}',50,50);print(0)"
|
|
75
|
+
|
|
76
|
+
await self.ble.send_lua(lua_command, await_print=True)
|
|
77
|
+
|
|
78
|
+
async def upload_stdlua_libs(self, lib_names: List[str]=['data'], minified: bool=True):
|
|
79
|
+
"""Send the specified standard frame-msg Lua files to Frame that are used by the frame_app, e.g. ['data', 'camera'] """
|
|
80
|
+
for stdlua in lib_names:
|
|
81
|
+
suffix = ".min" if minified else ""
|
|
82
|
+
await self.ble.upload_file_from_string(files("brilliant_msg").joinpath(f"lua/{stdlua}{suffix}.lua").read_text(), f"{stdlua}{suffix}.lua")
|
|
83
|
+
|
|
84
|
+
async def upload_frame_app(self, local_filename: str, frame_filename: str='frame_app.lua'):
|
|
85
|
+
""" Send the main lua application from this project to Frame that will run the app (but doesn't run the file)"""
|
|
86
|
+
# We rename the file slightly when we copy it, although it isn't necessary
|
|
87
|
+
await self.ble.upload_file(local_filename, frame_filename)
|
|
88
|
+
|
|
89
|
+
async def start_frame_app(self, frame_app_name:str='frame_app', await_print:bool=True):
|
|
90
|
+
"""
|
|
91
|
+
'require' the main lua file to run it
|
|
92
|
+
|
|
93
|
+
Note: This require() doesn't return - frame_app.lua has a main loop,
|
|
94
|
+
so we can't put a 'print(0)' after the require() statement and wait for it to print,
|
|
95
|
+
however if our main loop prints something (even a byte) once it has started up,
|
|
96
|
+
then the await_print can be used to determine that the frameside app is ready
|
|
97
|
+
rather than waiting for an app-dependent amount of time,
|
|
98
|
+
or sending messages to Frame too early.
|
|
99
|
+
Set await_print to False if the Frame app should be asynchronously started
|
|
100
|
+
and without waiting for any printed confirmation that the frameside app is ready.
|
|
101
|
+
"""
|
|
102
|
+
await self.ble.send_lua(f"require('{frame_app_name}')", await_print=await_print)
|
|
103
|
+
|
|
104
|
+
async def stop_frame_app(self, reset=True):
|
|
105
|
+
"""
|
|
106
|
+
Sends a break signal to terminate the running main loop on Frame, if applicable.
|
|
107
|
+
A custom app may prefer to send a specific TxCode to the Frameside app to instruct it
|
|
108
|
+
to shut down cleanly, but a break signal will also be caught by the exception handler
|
|
109
|
+
of the main loop and is enough to clean up the display, release memory etc.
|
|
110
|
+
|
|
111
|
+
If `reset` is True (default), then also send a reset signal that will reinitialize the Lua VM
|
|
112
|
+
and boot into a saved `main.lua`, if present.
|
|
113
|
+
"""
|
|
114
|
+
await self.ble.send_break_signal()
|
|
115
|
+
if reset:
|
|
116
|
+
await self.ble.send_reset_signal()
|
|
117
|
+
|
|
118
|
+
def attach_print_response_handler(self, handler=print):
|
|
119
|
+
"""Attach the print response handler so we can see stdout from Frame Lua print() statements"""
|
|
120
|
+
self.ble._user_print_response_handler = handler
|
|
121
|
+
|
|
122
|
+
def detach_print_response_handler(self):
|
|
123
|
+
"""Detach the print response handler so we no longer see stdout from Frame Lua print() statements"""
|
|
124
|
+
self.ble._user_print_response_handler = None
|
|
125
|
+
|
|
126
|
+
async def send_message(self, msg_code: int, payload: bytes, show_me: bool=False) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Sends a structured message from hostside to the Frameside app, identified by the specified msg_code.
|
|
129
|
+
For example, if the frame_app is expecting a TxCaptureSettings message on msg_code 0x0d
|
|
130
|
+
to initiate a photo capture, you might send:
|
|
131
|
+
`frame.send_message(0x0d, TxCaptureSettings(resolution=720).pack())`
|
|
132
|
+
Wraps the brilliant_ble function of the same name
|
|
133
|
+
"""
|
|
134
|
+
await self.ble.send_message(msg_code, payload, show_me)
|
|
135
|
+
|
|
136
|
+
def register_data_response_handler(self, subscriber, msg_codes: List[int], handler: Callable[[bytes], None]):
|
|
137
|
+
"""
|
|
138
|
+
Register a handler for a subscriber that is interested in specific msg codes.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
subscriber: The subscriber object.
|
|
142
|
+
msg_codes (List[int]): List of single byte msg codes the subscriber is interested in.
|
|
143
|
+
handler: The handler function to receive the data.
|
|
144
|
+
"""
|
|
145
|
+
for code in msg_codes:
|
|
146
|
+
if code not in self.data_response_handlers:
|
|
147
|
+
self.data_response_handlers[code] = []
|
|
148
|
+
self.data_response_handlers[code].append((subscriber, handler))
|
|
149
|
+
|
|
150
|
+
def unregister_data_response_handler(self, subscriber):
|
|
151
|
+
"""
|
|
152
|
+
Unregister a subscriber from receiving data responses.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
subscriber: The subscriber object to unregister.
|
|
156
|
+
"""
|
|
157
|
+
for code in list(self.data_response_handlers.keys()):
|
|
158
|
+
self.data_response_handlers[code] = [
|
|
159
|
+
(sub, handler) for sub, handler in self.data_response_handlers[code] if sub != subscriber
|
|
160
|
+
]
|
|
161
|
+
if not self.data_response_handlers[code]:
|
|
162
|
+
del self.data_response_handlers[code]
|
|
163
|
+
|
|
164
|
+
async def _handle_data_response(self, data: bytes):
|
|
165
|
+
"""
|
|
166
|
+
Internal method to handle incoming data responses and dispatch to the appropriate handlers.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
data (bytes): The incoming data response.
|
|
170
|
+
"""
|
|
171
|
+
if data:
|
|
172
|
+
msg_code = data[0]
|
|
173
|
+
if msg_code in self.data_response_handlers:
|
|
174
|
+
for subscriber, handler in self.data_response_handlers[msg_code]:
|
|
175
|
+
# not awaited, synchronous call
|
|
176
|
+
handler(data)
|
|
177
|
+
|
|
178
|
+
def __getattr__(self, name):
|
|
179
|
+
"""
|
|
180
|
+
Delegate any unknown attributes to the underlying BrilliantBle instance.
|
|
181
|
+
This allows direct access to all BrilliantBle methods while keeping the
|
|
182
|
+
wrapper transparent.
|
|
183
|
+
"""
|
|
184
|
+
return getattr(self.ble, name)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
-- Module for sending raw PCM audio Data from Frame's microphone
|
|
2
|
+
local _M = {}
|
|
3
|
+
|
|
4
|
+
-- Frame to Host flags
|
|
5
|
+
local AUDIO_DATA_FINAL_MSG = 0x06
|
|
6
|
+
local AUDIO_DATA_NON_FINAL_MSG = 0x05
|
|
7
|
+
|
|
8
|
+
-- 1 byte reserved for flag
|
|
9
|
+
local MTU = frame.bluetooth.max_length() - 1
|
|
10
|
+
-- data buffer needs to be even for reading from microphone
|
|
11
|
+
if MTU % 2 == 1 then MTU = MTU - 1 end
|
|
12
|
+
|
|
13
|
+
function _M.start(args)
|
|
14
|
+
-- if args provides a sample_rate or a bit_depth use them otherwise use the defaults
|
|
15
|
+
local rate = (args and args.sample_rate) or 8000
|
|
16
|
+
local depth = (args and args.bit_depth) or 8
|
|
17
|
+
|
|
18
|
+
pcall(frame.microphone.start, {sample_rate=rate, bit_depth=depth})
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
function _M.stop()
|
|
22
|
+
pcall(frame.microphone.stop)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
-- reads an MTU-sized amount of audio data and sends it to the host
|
|
26
|
+
-- ensure this function is called frequently enough to keep up with realtime audio
|
|
27
|
+
-- as the Frame buffer is ~32k
|
|
28
|
+
function _M.read_and_send_audio()
|
|
29
|
+
audio_data = frame.microphone.read(MTU)
|
|
30
|
+
|
|
31
|
+
-- If frame.microphone.stop() is called, a nil will be read() here
|
|
32
|
+
if audio_data == nil then
|
|
33
|
+
-- send an end-of-stream message back to the host
|
|
34
|
+
while true do
|
|
35
|
+
-- If the Bluetooth is busy, this simply tries again until it gets through
|
|
36
|
+
if (pcall(frame.bluetooth.send, string.char(AUDIO_DATA_FINAL_MSG))) then
|
|
37
|
+
break
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
return nil
|
|
42
|
+
|
|
43
|
+
-- send the data that was read
|
|
44
|
+
elseif audio_data ~= '' then
|
|
45
|
+
while true do
|
|
46
|
+
-- If the Bluetooth is busy, this simply tries again until it gets through
|
|
47
|
+
if (pcall(frame.bluetooth.send, string.char(AUDIO_DATA_NON_FINAL_MSG) .. audio_data)) then
|
|
48
|
+
break
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
return string.len(audio_data)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
-- no data read, no data sent
|
|
56
|
+
return 0
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return _M
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
local a={}local b=0x06;local c=0x05;local d=frame.bluetooth.max_length()-1;if d%2==1 then d=d-1 end;function a.start(e)local f=e and e.sample_rate or 8000;local g=e and e.bit_depth or 8;pcall(frame.microphone.start,{sample_rate=f,bit_depth=g})end;function a.stop()pcall(frame.microphone.stop)end;function a.read_and_send_audio()audio_data=frame.microphone.read(d)if audio_data==nil then while true do if pcall(frame.bluetooth.send,string.char(b))then break end end;return nil elseif audio_data~=''then while true do if pcall(frame.bluetooth.send,string.char(c)..audio_data)then break end end;return string.len(audio_data)end;return 0 end;return a
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Module handling periodic battery level reports on a standard message code
|
|
2
|
+
local _M = {}
|
|
3
|
+
|
|
4
|
+
-- Frame to phone flags
|
|
5
|
+
local BATTERY_MSG = 0x0c
|
|
6
|
+
|
|
7
|
+
function _M.send_batt_if_elapsed(prev, interval)
|
|
8
|
+
local t = frame.time.utc()
|
|
9
|
+
if ((prev == 0) or ((t - prev) > interval)) then
|
|
10
|
+
pcall(frame.bluetooth.send, string.char(BATTERY_MSG) .. string.char(math.floor(frame.battery_level())))
|
|
11
|
+
return t
|
|
12
|
+
else
|
|
13
|
+
return prev
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return _M
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
local _M={}local a=0x0c;function _M.send_batt_if_elapsed(b,c)local d=frame.time.utc()if b==0 or d-b>c then pcall(frame.bluetooth.send,string.char(a)..string.char(math.floor(frame.battery_level())))return d else return b end end;return _M
|