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.
Files changed (50) hide show
  1. brilliant_msg-7.0.0/.gitignore +22 -0
  2. brilliant_msg-7.0.0/LICENSE +28 -0
  3. brilliant_msg-7.0.0/PKG-INFO +121 -0
  4. brilliant_msg-7.0.0/README.md +93 -0
  5. brilliant_msg-7.0.0/pyproject.toml +55 -0
  6. brilliant_msg-7.0.0/src/brilliant_msg/__init__.py +24 -0
  7. brilliant_msg-7.0.0/src/brilliant_msg/brilliant_msg.py +184 -0
  8. brilliant_msg-7.0.0/src/brilliant_msg/lua/audio.lua +59 -0
  9. brilliant_msg-7.0.0/src/brilliant_msg/lua/audio.min.lua +1 -0
  10. brilliant_msg-7.0.0/src/brilliant_msg/lua/battery.lua +17 -0
  11. brilliant_msg-7.0.0/src/brilliant_msg/lua/battery.min.lua +1 -0
  12. brilliant_msg-7.0.0/src/brilliant_msg/lua/camera.lua +200 -0
  13. brilliant_msg-7.0.0/src/brilliant_msg/lua/camera.min.lua +1 -0
  14. brilliant_msg-7.0.0/src/brilliant_msg/lua/code.lua +11 -0
  15. brilliant_msg-7.0.0/src/brilliant_msg/lua/code.min.lua +1 -0
  16. brilliant_msg-7.0.0/src/brilliant_msg/lua/data.lua +128 -0
  17. brilliant_msg-7.0.0/src/brilliant_msg/lua/data.min.lua +1 -0
  18. brilliant_msg-7.0.0/src/brilliant_msg/lua/image_sprite_block.lua +92 -0
  19. brilliant_msg-7.0.0/src/brilliant_msg/lua/image_sprite_block.min.lua +1 -0
  20. brilliant_msg-7.0.0/src/brilliant_msg/lua/imu.lua +26 -0
  21. brilliant_msg-7.0.0/src/brilliant_msg/lua/imu.min.lua +1 -0
  22. brilliant_msg-7.0.0/src/brilliant_msg/lua/plain_text.lua +20 -0
  23. brilliant_msg-7.0.0/src/brilliant_msg/lua/plain_text.min.lua +1 -0
  24. brilliant_msg-7.0.0/src/brilliant_msg/lua/sprite.lua +40 -0
  25. brilliant_msg-7.0.0/src/brilliant_msg/lua/sprite.min.lua +1 -0
  26. brilliant_msg-7.0.0/src/brilliant_msg/lua/sprite_coords.lua +16 -0
  27. brilliant_msg-7.0.0/src/brilliant_msg/lua/sprite_coords.min.lua +1 -0
  28. brilliant_msg-7.0.0/src/brilliant_msg/lua/tap.lua +16 -0
  29. brilliant_msg-7.0.0/src/brilliant_msg/lua/tap.min.lua +1 -0
  30. brilliant_msg-7.0.0/src/brilliant_msg/lua/text_sprite_block.lua +44 -0
  31. brilliant_msg-7.0.0/src/brilliant_msg/lua/text_sprite_block.min.lua +1 -0
  32. brilliant_msg-7.0.0/src/brilliant_msg/rx_audio.py +161 -0
  33. brilliant_msg-7.0.0/src/brilliant_msg/rx_auto_exp_result.py +85 -0
  34. brilliant_msg-7.0.0/src/brilliant_msg/rx_imu.py +131 -0
  35. brilliant_msg-7.0.0/src/brilliant_msg/rx_metering_data.py +69 -0
  36. brilliant_msg-7.0.0/src/brilliant_msg/rx_photo.py +135 -0
  37. brilliant_msg-7.0.0/src/brilliant_msg/rx_tap.py +89 -0
  38. brilliant_msg-7.0.0/src/brilliant_msg/tx_auto_exp_settings.py +36 -0
  39. brilliant_msg-7.0.0/src/brilliant_msg/tx_capture_settings.py +30 -0
  40. brilliant_msg-7.0.0/src/brilliant_msg/tx_code.py +13 -0
  41. brilliant_msg-7.0.0/src/brilliant_msg/tx_image_sprite_block.py +79 -0
  42. brilliant_msg-7.0.0/src/brilliant_msg/tx_manual_exp_settings.py +30 -0
  43. brilliant_msg-7.0.0/src/brilliant_msg/tx_plain_text.py +44 -0
  44. brilliant_msg-7.0.0/src/brilliant_msg/tx_sprite.py +200 -0
  45. brilliant_msg-7.0.0/src/brilliant_msg/tx_sprite_coords.py +36 -0
  46. brilliant_msg-7.0.0/src/brilliant_msg/tx_text_sprite_block.py +73 -0
  47. brilliant_msg-7.0.0/tests/__init__.py +0 -0
  48. brilliant_msg-7.0.0/tests/test_rx_messages.py +413 -0
  49. brilliant_msg-7.0.0/tests/test_tx_simple.py +177 -0
  50. 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