brilliant-msg 7.0.0__py3-none-any.whl

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 (45) hide show
  1. brilliant_msg/__init__.py +24 -0
  2. brilliant_msg/brilliant_msg.py +184 -0
  3. brilliant_msg/lua/audio.lua +59 -0
  4. brilliant_msg/lua/audio.min.lua +1 -0
  5. brilliant_msg/lua/battery.lua +17 -0
  6. brilliant_msg/lua/battery.min.lua +1 -0
  7. brilliant_msg/lua/camera.lua +200 -0
  8. brilliant_msg/lua/camera.min.lua +1 -0
  9. brilliant_msg/lua/code.lua +11 -0
  10. brilliant_msg/lua/code.min.lua +1 -0
  11. brilliant_msg/lua/data.lua +128 -0
  12. brilliant_msg/lua/data.min.lua +1 -0
  13. brilliant_msg/lua/image_sprite_block.lua +92 -0
  14. brilliant_msg/lua/image_sprite_block.min.lua +1 -0
  15. brilliant_msg/lua/imu.lua +26 -0
  16. brilliant_msg/lua/imu.min.lua +1 -0
  17. brilliant_msg/lua/plain_text.lua +20 -0
  18. brilliant_msg/lua/plain_text.min.lua +1 -0
  19. brilliant_msg/lua/sprite.lua +40 -0
  20. brilliant_msg/lua/sprite.min.lua +1 -0
  21. brilliant_msg/lua/sprite_coords.lua +16 -0
  22. brilliant_msg/lua/sprite_coords.min.lua +1 -0
  23. brilliant_msg/lua/tap.lua +16 -0
  24. brilliant_msg/lua/tap.min.lua +1 -0
  25. brilliant_msg/lua/text_sprite_block.lua +44 -0
  26. brilliant_msg/lua/text_sprite_block.min.lua +1 -0
  27. brilliant_msg/rx_audio.py +161 -0
  28. brilliant_msg/rx_auto_exp_result.py +85 -0
  29. brilliant_msg/rx_imu.py +131 -0
  30. brilliant_msg/rx_metering_data.py +69 -0
  31. brilliant_msg/rx_photo.py +135 -0
  32. brilliant_msg/rx_tap.py +89 -0
  33. brilliant_msg/tx_auto_exp_settings.py +36 -0
  34. brilliant_msg/tx_capture_settings.py +30 -0
  35. brilliant_msg/tx_code.py +13 -0
  36. brilliant_msg/tx_image_sprite_block.py +79 -0
  37. brilliant_msg/tx_manual_exp_settings.py +30 -0
  38. brilliant_msg/tx_plain_text.py +44 -0
  39. brilliant_msg/tx_sprite.py +200 -0
  40. brilliant_msg/tx_sprite_coords.py +36 -0
  41. brilliant_msg/tx_text_sprite_block.py +73 -0
  42. brilliant_msg-7.0.0.dist-info/METADATA +121 -0
  43. brilliant_msg-7.0.0.dist-info/RECORD +45 -0
  44. brilliant_msg-7.0.0.dist-info/WHEEL +4 -0
  45. brilliant_msg-7.0.0.dist-info/licenses/LICENSE +28 -0
@@ -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
@@ -0,0 +1,200 @@
1
+ -- Module to encapsulate taking and sending photos as simple frame app messages
2
+ local _M = {}
3
+
4
+ -- Frame to phone flags
5
+ local IMAGE_MSG = 0x07
6
+ local IMAGE_FINAL_MSG = 0x08
7
+ local AUTOEXP_DATA_MSG = 0x11
8
+ local METERING_DATA_MSG = 0x12
9
+
10
+ -- local state to capture the stateful camera settings
11
+ local quality_values = {'VERY_LOW', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH'}
12
+ local metering_values = {'SPOT', 'CENTER_WEIGHTED', 'AVERAGE'}
13
+
14
+ -- default settings
15
+ local auto_exp_settings = {
16
+ metering = 'CENTER_WEIGHTED',
17
+ exposure = 0.1,
18
+ exposure_speed = 0.45,
19
+ shutter_limit = 16383,
20
+ analog_gain_limit = 16.0,
21
+ white_balance_speed = 0.5,
22
+ rgb_gain_limit = 287
23
+ }
24
+
25
+ local manual_exp_settings = {
26
+ shutter = 4096,
27
+ analog_gain = 1,
28
+ red_gain = 121,
29
+ green_gain = 64,
30
+ blue_gain = 140
31
+ }
32
+
33
+ -- auto/manual status, accessible outside the module
34
+ _M.is_auto_exp = true
35
+
36
+ -- helper function to update settings if they are present (otherwise keep defaults)
37
+ function update_if_present(settings, updates)
38
+ for k, v in pairs(updates) do
39
+ if v ~= nil then
40
+ settings[k] = v
41
+ end
42
+ end
43
+ end
44
+
45
+ -- Update the saved auto exposure settings with the provided args
46
+ -- The caller still needs to _M.run_auto_exposure() every 100ms after this
47
+ function _M.set_auto_exp_settings(args)
48
+ update_if_present(auto_exp_settings, args)
49
+ _M.is_auto_exp = true
50
+ end
51
+
52
+ -- Turns off auto exposure flag if present and updates the manual exposure settings with the provided args
53
+ -- Sets the manual exposure settings into the camera using the low level functions
54
+ function _M.set_manual_exp_settings(args)
55
+ _M.is_auto_exp = false
56
+ update_if_present(manual_exp_settings, args)
57
+
58
+ -- actually update the state of the camera
59
+ frame.camera.set_shutter(manual_exp_settings.shutter)
60
+ frame.camera.set_gain(manual_exp_settings.analog_gain)
61
+ frame.camera.set_white_balance(manual_exp_settings.red_gain, manual_exp_settings.green_gain, manual_exp_settings.blue_gain)
62
+ end
63
+
64
+ -- parse the auto exposure settings message from the host into a table we can use with set_auto_exp_settings()
65
+ function _M.parse_auto_exp_settings(data)
66
+ local settings = {}
67
+
68
+ settings.metering = metering_values[string.byte(data, 1) + 1]
69
+ settings.exposure = string.byte(data, 2) / 255.0
70
+ settings.exposure_speed = string.byte(data, 3) / 255.0
71
+ settings.shutter_limit = string.byte(data, 4) << 8 | string.byte(data, 5) & 0x3FFF
72
+ settings.analog_gain_limit = string.byte(data, 6) & 0xFF
73
+ settings.white_balance_speed = string.byte(data, 7) / 255.0
74
+ settings.rgb_gain_limit = string.byte(data, 8) << 8 | string.byte(data, 9) & 0x3FF
75
+
76
+ return settings
77
+ end
78
+
79
+ -- parse the manual exposure settings message from the host into a table we can use with set_manual_exp_settings()
80
+ function _M.parse_manual_exp_settings(data)
81
+ local settings = {}
82
+
83
+ settings.shutter = string.byte(data, 1) << 8 | string.byte(data, 2) & 0x3FFF
84
+ settings.analog_gain = string.byte(data, 3) & 0xFF
85
+ settings.red_gain = string.byte(data, 4) << 8 | string.byte(data, 5) & 0x3FF
86
+ settings.green_gain = string.byte(data, 6) << 8 | string.byte(data, 7) & 0x3FF
87
+ settings.blue_gain = string.byte(data, 8) << 8 | string.byte(data, 9) & 0x3FF
88
+
89
+ return settings
90
+ end
91
+
92
+ -- parse the capture settings message from the host into a table we can use with the capture_and_send function
93
+ function _M.parse_capture_settings(data)
94
+
95
+ local settings = {}
96
+ -- quality and metering mode are indices into arrays of values (0-based phoneside; 1-based in Lua)
97
+ settings.quality = quality_values[string.byte(data, 1) + 1]
98
+ local half_res = string.byte(data, 2) << 8 | string.byte(data, 3)
99
+ settings.resolution = half_res * 2
100
+ local pan_shifted = string.byte(data, 4) << 8 | string.byte(data, 5)
101
+ settings.pan = pan_shifted - 140
102
+ settings.raw = string.byte(data, 6) > 0
103
+
104
+ return settings
105
+ end
106
+
107
+ -- send data with retries and no sleeps, bail after 2 seconds
108
+ function send_data(data)
109
+ local sent = false
110
+
111
+ -- 2 second time limit for this packet else bail out
112
+ local try_until = frame.time.utc() + 2
113
+
114
+ while frame.time.utc() < try_until do
115
+ if pcall(frame.bluetooth.send, data) then
116
+ sent = true
117
+ break
118
+ end
119
+ end
120
+
121
+ if not sent then
122
+ error('Error sending photo data')
123
+ end
124
+ end
125
+
126
+ -- Runs the auto exposure algorithm with the current settings (call this every 100ms)
127
+ function _M.run_auto_exposure()
128
+ -- pass the table into the function with parentheses, don't wrap again in curly braces, it's already a table
129
+ return frame.camera.auto(auto_exp_settings)
130
+ end
131
+
132
+ function _M.send_autoexp_result(autoexp)
133
+ -- Pack the message code as an unsigned byte (B)
134
+ -- followed by 16 floats (f) in little-endian format
135
+ local data = string.pack("<Bffffffffffffffff",
136
+ AUTOEXP_DATA_MSG,
137
+ -- Top level values
138
+ autoexp['error'],
139
+ autoexp['shutter'],
140
+ autoexp['analog_gain'],
141
+ autoexp['red_gain'],
142
+ autoexp['green_gain'],
143
+ autoexp['blue_gain'],
144
+
145
+ -- Brightness values
146
+ autoexp['brightness']['center_weighted_average'],
147
+ autoexp['brightness']['scene'],
148
+
149
+ -- Brightness matrix values
150
+ autoexp['brightness']['matrix']['r'],
151
+ autoexp['brightness']['matrix']['g'],
152
+ autoexp['brightness']['matrix']['b'],
153
+ autoexp['brightness']['matrix']['average'],
154
+
155
+ -- Brightness spot values
156
+ autoexp['brightness']['spot']['r'],
157
+ autoexp['brightness']['spot']['g'],
158
+ autoexp['brightness']['spot']['b'],
159
+ autoexp['brightness']['spot']['average']
160
+ )
161
+
162
+ -- Total size: 1 byte for message code + (16 floats * 4 bytes) = 65 bytes
163
+ send_data(data)
164
+ end
165
+
166
+ -- send the current spot and matrix rgb values from the FPGA (as used by the auto exposure algorithm)
167
+ function _M.send_metering_data()
168
+ send_data(string.char(METERING_DATA_MSG) .. frame.fpga_read(0x25, 6))
169
+ end
170
+
171
+ -- takes a capture_settings table and sends the image data to the host
172
+ function _M.capture_and_send(args)
173
+ frame.camera.capture { resolution=args.resolution, quality=args.quality, pan=args.pan }
174
+
175
+ -- wait until the capture is finished and the image is ready before continuing
176
+ while not frame.camera.image_ready() do
177
+ frame.sleep(0.005)
178
+ end
179
+
180
+ local data = ''
181
+ local raw = args.raw
182
+
183
+ while true do
184
+ -- skip the 623 byte header if the caller requested raw data
185
+ if (raw) then
186
+ data = frame.camera.read_raw(frame.bluetooth.max_length() - 1)
187
+ else
188
+ data = frame.camera.read(frame.bluetooth.max_length() - 1)
189
+ end
190
+
191
+ if (data ~= nil) then
192
+ send_data(string.char(IMAGE_MSG) .. data)
193
+ else
194
+ send_data(string.char(IMAGE_FINAL_MSG))
195
+ break
196
+ end
197
+ end
198
+ end
199
+
200
+ return _M
@@ -0,0 +1 @@
1
+ local a={}local b=0x07;local c=0x08;local d=0x11;local e=0x12;local f={'VERY_LOW','LOW','MEDIUM','HIGH','VERY_HIGH'}local g={'SPOT','CENTER_WEIGHTED','AVERAGE'}local h={metering='CENTER_WEIGHTED',exposure=0.1,exposure_speed=0.45,shutter_limit=16383,analog_gain_limit=16.0,white_balance_speed=0.5,rgb_gain_limit=287}local i={shutter=4096,analog_gain=1,red_gain=121,green_gain=64,blue_gain=140}a.is_auto_exp=true;function update_if_present(j,k)for l,m in pairs(k)do if m~=nil then j[l]=m end end end;function a.set_auto_exp_settings(n)update_if_present(h,n)a.is_auto_exp=true end;function a.set_manual_exp_settings(n)a.is_auto_exp=false;update_if_present(i,n)frame.camera.set_shutter(i.shutter)frame.camera.set_gain(i.analog_gain)frame.camera.set_white_balance(i.red_gain,i.green_gain,i.blue_gain)end;function a.parse_auto_exp_settings(o)local j={}j.metering=g[string.byte(o,1)+1]j.exposure=string.byte(o,2)/255.0;j.exposure_speed=string.byte(o,3)/255.0;j.shutter_limit=string.byte(o,4)<<8|(string.byte(o,5)&0x3FFF)j.analog_gain_limit=string.byte(o,6)&0xFF;j.white_balance_speed=string.byte(o,7)/255.0;j.rgb_gain_limit=string.byte(o,8)<<8|(string.byte(o,9)&0x3FF)return j end;function a.parse_manual_exp_settings(o)local j={}j.shutter=string.byte(o,1)<<8|(string.byte(o,2)&0x3FFF)j.analog_gain=string.byte(o,3)&0xFF;j.red_gain=string.byte(o,4)<<8|(string.byte(o,5)&0x3FF)j.green_gain=string.byte(o,6)<<8|(string.byte(o,7)&0x3FF)j.blue_gain=string.byte(o,8)<<8|(string.byte(o,9)&0x3FF)return j end;function a.parse_capture_settings(o)local j={}j.quality=f[string.byte(o,1)+1]local p=string.byte(o,2)<<8|string.byte(o,3)j.resolution=p*2;local q=string.byte(o,4)<<8|string.byte(o,5)j.pan=q-140;j.raw=string.byte(o,6)>0;return j end;function send_data(o)local r=false;local s=frame.time.utc()+2;while frame.time.utc()<s do if pcall(frame.bluetooth.send,o)then r=true;break end end;if not r then error('Error sending photo data')end end;function a.run_auto_exposure()return frame.camera.auto(h)end;function a.send_autoexp_result(t)local o=string.pack("<Bffffffffffffffff",d,t['error'],t['shutter'],t['analog_gain'],t['red_gain'],t['green_gain'],t['blue_gain'],t['brightness']['center_weighted_average'],t['brightness']['scene'],t['brightness']['matrix']['r'],t['brightness']['matrix']['g'],t['brightness']['matrix']['b'],t['brightness']['matrix']['average'],t['brightness']['spot']['r'],t['brightness']['spot']['g'],t['brightness']['spot']['b'],t['brightness']['spot']['average'])send_data(o)end;function a.send_metering_data()send_data(string.char(e)..frame.fpga_read(0x25,6))end;function a.capture_and_send(n)frame.camera.capture{resolution=n.resolution,quality=n.quality,pan=n.pan}while not frame.camera.image_ready()do frame.sleep(0.005)end;local o=''local u=n.raw;while true do if u then o=frame.camera.read_raw(frame.bluetooth.max_length()-1)else o=frame.camera.read(frame.bluetooth.max_length()-1)end;if o~=nil then send_data(string.char(b)..o)else send_data(string.char(c))break end end end;return a
@@ -0,0 +1,11 @@
1
+ -- Module to parse message codes sent from phoneside app as TxCode messages
2
+ local _M = {}
3
+
4
+ -- Parse the TxCode message raw data, which is a single byte
5
+ function _M.parse_code(data)
6
+ local code = {}
7
+ code.value = string.byte(data, 1)
8
+ return code
9
+ end
10
+
11
+ return _M
@@ -0,0 +1 @@
1
+ local _M={}function _M.parse_code(a)local b={}b.value=string.byte(a,1)return b end;return _M
@@ -0,0 +1,128 @@
1
+ -- Module containing generic data handling code.
2
+ -- BLE packets with a specific message code are accumulated and concatenated,
3
+ -- then enqueued in arrival order for the caller to parse and dispatch.
4
+ local _M = {}
5
+
6
+ -- accumulates chunks of input message into this table
7
+ local app_data_accum = {}
8
+ _M.app_data_accum = app_data_accum
9
+
10
+ -- ordered queue of completed messages {msg_flag, block_data} in arrival order
11
+ local app_data_queue = {}
12
+ local app_data_queue_len = 0
13
+
14
+ -- Data Handler: called when data arrives, must execute quickly.
15
+ -- Update the app_data_accum item based on the contents of the current packet
16
+ -- The first byte of the packet indicates the message type, and the item's key
17
+ -- The first packet also has a Uint16 message length field right after the message type
18
+ -- If the key is not present, initialise a new app data item
19
+ -- Accumulate chunks of data of the specified type, for later processing
20
+ -- The message codes and message length fields are not included in the accumulated chunks.
21
+ -- When the message is fully received, the completed message is appended to the ordered queue
22
+ -- so no need to pass on the length or message type in the payload
23
+ -- TODO add reliability features (packet acknowledgement or dropped packet retransmission requests, message and packet sequence numbers)
24
+ function _M.update_app_data_accum(data)
25
+ rc, err = pcall(
26
+ function()
27
+ local msg_flag = string.byte(data, 1)
28
+ local item = _M.app_data_accum[msg_flag]
29
+ if item == nil or next(item) == nil then
30
+ item = { chunk_table = {}, num_chunks = 0, size = 0, recv_bytes = 0 }
31
+ _M.app_data_accum[msg_flag] = item
32
+ end
33
+
34
+ if item.num_chunks == 0 then
35
+ -- first chunk of new data contains size (Uint16)
36
+ item.size = string.byte(data, 2) << 8 | string.byte(data, 3)
37
+ item.chunk_table[1] = string.sub(data, 4)
38
+ item.num_chunks = 1
39
+ item.recv_bytes = string.len(data) - 3
40
+
41
+ if item.recv_bytes == item.size then
42
+ app_data_queue_len = app_data_queue_len + 1
43
+ app_data_queue[app_data_queue_len] = {msg_flag, item.chunk_table[1]}
44
+ item.size = 0
45
+ item.recv_bytes = 0
46
+ item.num_chunks = 0
47
+ item.chunk_table[1] = nil
48
+ app_data_accum[msg_flag] = item
49
+ end
50
+ else
51
+ item.chunk_table[item.num_chunks + 1] = string.sub(data, 2)
52
+ item.num_chunks = item.num_chunks + 1
53
+ item.recv_bytes = item.recv_bytes + string.len(data) - 1
54
+
55
+ -- if all bytes are received, concat and enqueue the completed message
56
+ if item.recv_bytes == item.size then
57
+ collectgarbage('collect')
58
+ app_data_queue_len = app_data_queue_len + 1
59
+ app_data_queue[app_data_queue_len] = {msg_flag, table.concat(item.chunk_table)}
60
+ for k, v in pairs(item.chunk_table) do item.chunk_table[k] = nil end
61
+ collectgarbage('collect')
62
+ item.size = 0
63
+ item.recv_bytes = 0
64
+ item.num_chunks = 0
65
+ _M.app_data_accum[msg_flag] = item
66
+ end
67
+ end
68
+
69
+ -- send some data back as an ACK for receiver-paced flow control
70
+ -- and send_message() must use await_data=True
71
+ while true do
72
+ -- If the Bluetooth is busy, this simply tries again until it gets through
73
+ -- data/ack/success
74
+ if (pcall(frame.bluetooth.send, '\x01\x00\x00')) then
75
+ break
76
+ end
77
+ frame.sleep(0.0025)
78
+ end
79
+
80
+ end
81
+ )
82
+ if rc == false then
83
+ -- send the error back on the stdout stream otherwise the data handler thread fails silently
84
+ print('Error in data accumulator: ' .. err)
85
+ while true do
86
+ -- If the Bluetooth is busy, this simply tries again until it gets through
87
+ -- data/ack/failure
88
+ if (pcall(frame.bluetooth.send, '\x01\x00\x01')) then
89
+ break
90
+ end
91
+ frame.sleep(0.0025)
92
+ end
93
+ -- rethrow the error, especially important to propagate the break signal to stop execution
94
+ error(err)
95
+ end
96
+ end
97
+
98
+ -- register the handler as a callback for all data sent from the host
99
+ frame.bluetooth.receive_callback(_M.update_app_data_accum)
100
+
101
+ -- Drains the ordered message queue.
102
+ -- Returns an array of {flag, raw_block} pairs in arrival order.
103
+ -- The caller is responsible for parsing and dispatching each item.
104
+ function _M.process_raw_items()
105
+ local items = {}
106
+ local items_len = 0
107
+ rc, err = pcall(
108
+ function()
109
+ collectgarbage('collect')
110
+
111
+ for i = 1, app_data_queue_len do
112
+ items_len = items_len + 1
113
+ items[items_len] = app_data_queue[i]
114
+ app_data_queue[i] = nil
115
+ end
116
+
117
+ app_data_queue_len = 0
118
+ end
119
+ )
120
+ if rc == false then
121
+ print('Error processing raw items: ' .. err)
122
+ error(err)
123
+ end
124
+
125
+ return items
126
+ end
127
+
128
+ return _M
@@ -0,0 +1 @@
1
+ local a={}local b={}a.app_data_accum=b;local c={}local d=0;function a.update_app_data_accum(e)rc,err=pcall(function()local f=string.byte(e,1)local g=a.app_data_accum[f]if g==nil or next(g)==nil then g={chunk_table={},num_chunks=0,size=0,recv_bytes=0}a.app_data_accum[f]=g end;if g.num_chunks==0 then g.size=string.byte(e,2)<<8|string.byte(e,3)g.chunk_table[1]=string.sub(e,4)g.num_chunks=1;g.recv_bytes=string.len(e)-3;if g.recv_bytes==g.size then d=d+1;c[d]={f,g.chunk_table[1]}g.size=0;g.recv_bytes=0;g.num_chunks=0;g.chunk_table[1]=nil;b[f]=g end else g.chunk_table[g.num_chunks+1]=string.sub(e,2)g.num_chunks=g.num_chunks+1;g.recv_bytes=g.recv_bytes+string.len(e)-1;if g.recv_bytes==g.size then collectgarbage('collect')d=d+1;c[d]={f,table.concat(g.chunk_table)}for h,i in pairs(g.chunk_table)do g.chunk_table[h]=nil end;collectgarbage('collect')g.size=0;g.recv_bytes=0;g.num_chunks=0;a.app_data_accum[f]=g end end;while true do if pcall(frame.bluetooth.send,'\x01\x00\x00')then break end;frame.sleep(0.0025)end end)if rc==false then print('Error in data accumulator: '..err)while true do if pcall(frame.bluetooth.send,'\x01\x00\x01')then break end;frame.sleep(0.0025)end;error(err)end end;frame.bluetooth.receive_callback(a.update_app_data_accum)function a.process_raw_items()local j={}local k=0;rc,err=pcall(function()collectgarbage('collect')for l=1,d do k=k+1;j[k]=c[l]c[l]=nil end;d=0 end)if rc==false then print('Error processing raw items: '..err)error(err)end;return j end;return a