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.
- brilliant_msg/__init__.py +24 -0
- brilliant_msg/brilliant_msg.py +184 -0
- brilliant_msg/lua/audio.lua +59 -0
- brilliant_msg/lua/audio.min.lua +1 -0
- brilliant_msg/lua/battery.lua +17 -0
- brilliant_msg/lua/battery.min.lua +1 -0
- brilliant_msg/lua/camera.lua +200 -0
- brilliant_msg/lua/camera.min.lua +1 -0
- brilliant_msg/lua/code.lua +11 -0
- brilliant_msg/lua/code.min.lua +1 -0
- brilliant_msg/lua/data.lua +128 -0
- brilliant_msg/lua/data.min.lua +1 -0
- brilliant_msg/lua/image_sprite_block.lua +92 -0
- brilliant_msg/lua/image_sprite_block.min.lua +1 -0
- brilliant_msg/lua/imu.lua +26 -0
- brilliant_msg/lua/imu.min.lua +1 -0
- brilliant_msg/lua/plain_text.lua +20 -0
- brilliant_msg/lua/plain_text.min.lua +1 -0
- brilliant_msg/lua/sprite.lua +40 -0
- brilliant_msg/lua/sprite.min.lua +1 -0
- brilliant_msg/lua/sprite_coords.lua +16 -0
- brilliant_msg/lua/sprite_coords.min.lua +1 -0
- brilliant_msg/lua/tap.lua +16 -0
- brilliant_msg/lua/tap.min.lua +1 -0
- brilliant_msg/lua/text_sprite_block.lua +44 -0
- brilliant_msg/lua/text_sprite_block.min.lua +1 -0
- brilliant_msg/rx_audio.py +161 -0
- brilliant_msg/rx_auto_exp_result.py +85 -0
- brilliant_msg/rx_imu.py +131 -0
- brilliant_msg/rx_metering_data.py +69 -0
- brilliant_msg/rx_photo.py +135 -0
- brilliant_msg/rx_tap.py +89 -0
- brilliant_msg/tx_auto_exp_settings.py +36 -0
- brilliant_msg/tx_capture_settings.py +30 -0
- brilliant_msg/tx_code.py +13 -0
- brilliant_msg/tx_image_sprite_block.py +79 -0
- brilliant_msg/tx_manual_exp_settings.py +30 -0
- brilliant_msg/tx_plain_text.py +44 -0
- brilliant_msg/tx_sprite.py +200 -0
- brilliant_msg/tx_sprite_coords.py +36 -0
- brilliant_msg/tx_text_sprite_block.py +73 -0
- brilliant_msg-7.0.0.dist-info/METADATA +121 -0
- brilliant_msg-7.0.0.dist-info/RECORD +45 -0
- brilliant_msg-7.0.0.dist-info/WHEEL +4 -0
- 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
|