feat(ble)!: stream observed advertisements
This commit is contained in:
@@ -28,4 +28,4 @@ Firmware commits dirty display content and owns panel refresh policy. Binding an
|
||||
marked generated. Callbacks are fields on the table an app returns, so a `@lua-app` block generates
|
||||
a class an app composes (`---@class PaintApp : App, TouchHandlers`) rather than global functions. Each `@lua-module`/`@lua-augment` directive sits immediately above the
|
||||
`luaL_Reg` table it describes, which is how one source declares several namespaces. This repository owns portable `lua/lib/` modules; shared UI owns theme application and persistence. Use `ble` for BLE/GATT
|
||||
and reserve `bt` for a future Classic Bluetooth contract.
|
||||
and reserve `bt` for a future Classic Bluetooth contract. `ble.observe(filter)` returns a reusable cursor iterator that retrieves one buffered advert at a time; runtime teardown ends the observation.
|
||||
|
||||
+4
-13
@@ -2,12 +2,7 @@
|
||||
|
||||
-- Generated from native/src/bindings/core/ble.cpp. Do not edit.
|
||||
|
||||
---@class BleObservation
|
||||
---@field address string
|
||||
---@field name string
|
||||
---@field rssi integer
|
||||
---@field payload string Raw advertisement bytes.
|
||||
---@field lastSeenMs integer sys.getMillis() at last sighting.
|
||||
---@alias BleObservationIterator fun(state: nil, after: string|nil): address: string|nil, name: string|nil, rssi: integer|nil, payload: string|nil, lastSeenMs: integer|nil
|
||||
|
||||
---@class BleFilter
|
||||
---@field services? string[] Service-data UUIDs to keep.
|
||||
@@ -29,23 +24,19 @@ function ble.deinit() end
|
||||
---@return boolean
|
||||
function ble.isInitialized() end
|
||||
|
||||
---Starts passively observing advertisements, coalesced per device.
|
||||
---Starts passively observing advertisements, coalesced per device. The returned iterator is reusable; observation stops with its Lua state.
|
||||
---@param filter? BleFilter Keep only matching adverts; nil keeps all.
|
||||
---@return true? ok
|
||||
---@return BleObservationIterator? observations
|
||||
---@return string? error
|
||||
function ble.observe(filter) end
|
||||
|
||||
---Stops observing and clears the snapshot.
|
||||
---Stops observing and clears the buffered devices.
|
||||
function ble.unobserve() end
|
||||
|
||||
---Whether advertisement observation is running.
|
||||
---@return boolean
|
||||
function ble.isObserving() end
|
||||
|
||||
---The current snapshot of observed devices.
|
||||
---@return BleObservation[] devices
|
||||
function ble.observed() end
|
||||
|
||||
---Connects to a peripheral.
|
||||
---@param address string
|
||||
---@return true? ok
|
||||
|
||||
@@ -275,7 +275,8 @@ public:
|
||||
virtual Status observe(const BleFilter& filter) = 0;
|
||||
virtual void unobserve() = 0;
|
||||
virtual bool isObserving() const = 0;
|
||||
virtual void observed(std::vector<BleObservation>& out) = 0;
|
||||
virtual bool nextObservation(const std::string* after,
|
||||
BleObservation& out) = 0;
|
||||
virtual Status connect(const std::string& address) = 0;
|
||||
virtual void disconnect() = 0;
|
||||
virtual bool isConnected() const = 0;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// @lua-module ble BleLib
|
||||
// @lua-preamble ---@class BleObservation
|
||||
// @lua-preamble ---@field address string
|
||||
// @lua-preamble ---@field name string
|
||||
// @lua-preamble ---@field rssi integer
|
||||
// @lua-preamble ---@field payload string Raw advertisement bytes.
|
||||
// @lua-preamble ---@field lastSeenMs integer sys.getMillis() at last sighting.
|
||||
// @lua-preamble ---@alias BleObservationIterator fun(state: nil, after:
|
||||
// string|nil): address: string|nil, name: string|nil, rssi: integer|nil,
|
||||
// payload: string|nil, lastSeenMs: integer|nil
|
||||
// @lua-preamble
|
||||
// @lua-preamble ---@class BleFilter
|
||||
// @lua-preamble ---@field services? string[] Service-data UUIDs to keep.
|
||||
@@ -66,13 +63,45 @@ void readIntArray(lua_State* state, int index, const char* key,
|
||||
lua_pop(state, 1);
|
||||
}
|
||||
|
||||
int nextObservation(lua_State* state) {
|
||||
BleProvider& provider = Runtime::from(state)->ble();
|
||||
if (!provider.isObserving())
|
||||
return 0;
|
||||
|
||||
std::string after;
|
||||
const std::string* cursor = nullptr;
|
||||
if (!lua_isnoneornil(state, 2)) {
|
||||
after = checkString(state, 2);
|
||||
cursor = &after;
|
||||
}
|
||||
|
||||
BleObservation observation;
|
||||
if (!provider.nextObservation(cursor, observation))
|
||||
return 0;
|
||||
pushString(state, observation.address);
|
||||
pushString(state, observation.name);
|
||||
lua_pushinteger(state, observation.rssi);
|
||||
pushString(state, observation.payload);
|
||||
lua_pushinteger(state, observation.lastSeenMs);
|
||||
return 5;
|
||||
}
|
||||
|
||||
int observe(lua_State* state) {
|
||||
BleProvider& provider = Runtime::from(state)->ble();
|
||||
if (provider.isObserving())
|
||||
return pushError(state, "already observing");
|
||||
|
||||
BleFilter filter;
|
||||
if (lua_istable(state, 1)) {
|
||||
readStringArray(state, 1, "services", filter.services);
|
||||
readIntArray(state, 1, "manufacturers", filter.manufacturers);
|
||||
}
|
||||
return pushStatus(state, Runtime::from(state)->ble().observe(filter));
|
||||
|
||||
const Status status = provider.observe(filter);
|
||||
if (!status.ok)
|
||||
return pushError(state, status.error);
|
||||
lua_pushcfunction(state, nextObservation);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int unobserve(lua_State* state) {
|
||||
@@ -85,23 +114,6 @@ int isObserving(lua_State* state) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int observed(lua_State* state) {
|
||||
std::vector<BleObservation> devices;
|
||||
Runtime::from(state)->ble().observed(devices);
|
||||
|
||||
lua_createtable(state, static_cast<int>(devices.size()), 0);
|
||||
for (size_t at = 0; at < devices.size(); at++) {
|
||||
lua_createtable(state, 0, 5);
|
||||
setField(state, "address", devices[at].address);
|
||||
setField(state, "name", devices[at].name);
|
||||
setField(state, "rssi", devices[at].rssi);
|
||||
setField(state, "payload", devices[at].payload);
|
||||
setField(state, "lastSeenMs", devices[at].lastSeenMs);
|
||||
lua_rawseti(state, -2, static_cast<lua_Integer>(at + 1));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int connect(lua_State* state) {
|
||||
const std::string address = checkString(state, 1);
|
||||
return pushStatus(state, Runtime::from(state)->ble().connect(address));
|
||||
@@ -161,18 +173,16 @@ const luaL_Reg FUNCTIONS[] = {
|
||||
// @return boolean
|
||||
{"isInitialized", isInitialized},
|
||||
// --- Starts passively observing advertisements, coalesced per device.
|
||||
// The returned iterator is reusable; observation stops with its Lua state.
|
||||
// @param filter BleFilter|nil Keep only matching adverts; nil keeps all.
|
||||
// @return true|nil ok
|
||||
// @return BleObservationIterator|nil observations
|
||||
// @return string|nil error
|
||||
{"observe", observe},
|
||||
// --- Stops observing and clears the snapshot.
|
||||
// --- Stops observing and clears the buffered devices.
|
||||
{"unobserve", unobserve},
|
||||
// --- Whether advertisement observation is running.
|
||||
// @return boolean
|
||||
{"isObserving", isObserving},
|
||||
// --- The current snapshot of observed devices.
|
||||
// @return BleObservation[] devices
|
||||
{"observed", observed},
|
||||
// --- Connects to a peripheral.
|
||||
// @param address string
|
||||
// @return true|nil ok
|
||||
|
||||
@@ -105,6 +105,7 @@ void Runtime::close() {
|
||||
if (!state_)
|
||||
return;
|
||||
cancelAllTimers();
|
||||
providers_.ble->unobserve();
|
||||
lua_close(state_);
|
||||
state_ = nullptr;
|
||||
mainRef_ = 0;
|
||||
|
||||
@@ -294,9 +294,16 @@ struct Ble : BleProvider {
|
||||
}
|
||||
void unobserve() override { observing = false; }
|
||||
bool isObserving() const override { return observing; }
|
||||
void observed(std::vector<BleObservation>& out) override {
|
||||
const BleObservation device = {"aa:bb", "tag", -60, "payload", 1234};
|
||||
out.push_back(device);
|
||||
bool nextObservation(const std::string* after, BleObservation& out) override {
|
||||
if (!after) {
|
||||
out = {"aa:bb", "tag", -60, "payload", 1234};
|
||||
return true;
|
||||
}
|
||||
if (*after == "aa:bb") {
|
||||
out = {"cc:dd", "tag 2", -70, "payload 2", 2345};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Status connect(const std::string&) override { return Status::success(); }
|
||||
void disconnect() override {}
|
||||
|
||||
@@ -121,10 +121,28 @@ int main() {
|
||||
run(state, "assert(not ble.isInitialized())\n"
|
||||
"assert(ble.init())\n"
|
||||
"assert(ble.isInitialized())\n"
|
||||
"assert(ble.observe({ services = { '181A' } }))\n"
|
||||
"observations = assert(ble.observe({ services = { '181A' } }))\n"
|
||||
"assert(ble.isObserving())\n"
|
||||
"assert(ble.observed()[1].address == 'aa:bb')\n"
|
||||
"assert(ble.observed()[1].payload == 'payload')\n"
|
||||
"local function checkObservations()\n"
|
||||
" local count = 0\n"
|
||||
" for address, name, rssi, payload, lastSeenMs in observations do\n"
|
||||
" count = count + 1\n"
|
||||
" if address == 'aa:bb' then\n"
|
||||
" assert(name == 'tag' and rssi == -60)\n"
|
||||
" assert(payload == 'payload' and lastSeenMs == 1234)\n"
|
||||
" else\n"
|
||||
" assert(address == 'cc:dd' and name == 'tag 2' and rssi == -70)\n"
|
||||
" assert(payload == 'payload 2' and lastSeenMs == 2345)\n"
|
||||
" end\n"
|
||||
" end\n"
|
||||
" assert(count == 2)\n"
|
||||
"end\n"
|
||||
"checkObservations()\n"
|
||||
"checkObservations()\n"
|
||||
"local duplicate, err = ble.observe()\n"
|
||||
"assert(duplicate == nil and err == 'already observing')\n"
|
||||
"ble.unobserve()\n"
|
||||
"assert(not ble.isObserving())\n"
|
||||
"assert(#ble.read('svc', 'chr') == 3)\n"
|
||||
"assert(ble.write('svc', 'chr', 'x\\0y'))");
|
||||
assert(bench.ble.filter.services.size() == 1);
|
||||
@@ -427,6 +445,11 @@ int main() {
|
||||
assert(host.log.message.find("refusing to start") == 0);
|
||||
}
|
||||
|
||||
run(state, "observations = assert(ble.observe())");
|
||||
assert(bench.ble.observing);
|
||||
runtime.close();
|
||||
assert(!bench.ble.observing);
|
||||
|
||||
// Missing core providers leave no Lua state behind.
|
||||
esp32lua::Providers incomplete = bench.providers();
|
||||
incomplete.fs = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user