Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61545f54d3 | ||
|
|
4214dcaf98 | ||
|
|
def0fc070b | ||
|
|
99d65f674d | ||
|
|
02f799cf9c | ||
|
|
45f3caffd8 | ||
|
|
eeb69589f1 | ||
|
|
6fbfef6358 | ||
|
|
207a6087b8 | ||
|
|
575cffb395 | ||
|
|
fe13ea68e8 | ||
|
|
79bae082ed | ||
|
|
687b7cd097 | ||
|
|
0ebfbcccef | ||
|
|
4c309faf1e | ||
|
|
421b9f0585 | ||
|
|
73798387bb | ||
|
|
5551666865 | ||
|
|
20e1a9e921 | ||
|
|
a9b9adb1d9 | ||
|
|
b809670b61 | ||
|
|
474ce234a6 | ||
|
|
8b3847441e | ||
|
|
03c28c347c | ||
|
|
e333307a96 | ||
|
|
7c0e3894e1 | ||
|
|
c5b545f1db | ||
|
|
2f413fea03 | ||
|
|
1160f568d5 | ||
|
|
888582e9a3 | ||
|
|
54be3a041f | ||
|
|
142d47ad08 | ||
|
|
f12e9c196b | ||
|
|
38926b2d35 | ||
|
|
d943328ad3 | ||
|
|
b1c879d305 |
@@ -59,25 +59,24 @@ static void draw_cb(Canvas* c, void* ctx) {
|
|||||||
char buf[64];
|
char buf[64];
|
||||||
canvas_clear(c);
|
canvas_clear(c);
|
||||||
canvas_set_font(c, FontPrimary);
|
canvas_set_font(c, FontPrimary);
|
||||||
snprintf(buf, sizeof(buf), "Battery %u%%", m->pct);
|
canvas_draw_str(c, 2, 9, "Battery Health");
|
||||||
canvas_draw_str(c, 2, 9, buf);
|
const char* st = m->charged ? "full" : m->charging ? "chging" : "dischg";
|
||||||
const char* st = m->charged ? "full" : m->charging ? "charging" : "discharging";
|
|
||||||
canvas_set_font(c, FontSecondary);
|
canvas_set_font(c, FontSecondary);
|
||||||
canvas_draw_str_aligned(c, 126, 9, AlignRight, AlignBottom, st);
|
|
||||||
canvas_draw_line(c, 0, 11, 127, 11);
|
canvas_draw_line(c, 0, 11, 127, 11);
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "%.3f V %+.0f mA %.1f C", (double)m->v, (double)(m->i * 1000), (double)m->temp);
|
// Compact rows, each kept within the 128 px width.
|
||||||
canvas_draw_str(c, 2, 21, buf);
|
snprintf(buf, sizeof(buf), "%u%% %.2fV %s", m->pct, (double)m->v, st);
|
||||||
snprintf(
|
canvas_draw_str(c, 2, 20, buf);
|
||||||
buf, sizeof(buf), "%lu/%lu mAh (design %lu)", (unsigned long)m->rem_mah,
|
snprintf(buf, sizeof(buf), "%dmA %.0fC VBUS%.1f", (int)(m->i * 1000), (double)m->temp, (double)m->vbus);
|
||||||
(unsigned long)m->full_mah, (unsigned long)m->design_mah);
|
canvas_draw_str(c, 2, 30, buf);
|
||||||
canvas_draw_str(c, 2, 31, buf);
|
|
||||||
int wear = m->design_mah ? 100 - (int)(m->full_mah * 100 / m->design_mah) : 0;
|
int wear = m->design_mah ? 100 - (int)(m->full_mah * 100 / m->design_mah) : 0;
|
||||||
snprintf(buf, sizeof(buf), "Health %u%% Wear %d%% VBUS %.2fV", m->health, wear, (double)m->vbus);
|
snprintf(
|
||||||
canvas_draw_str(c, 2, 41, buf);
|
buf, sizeof(buf), "%lu/%lumAh H%u%% W%d%%", (unsigned long)m->rem_mah,
|
||||||
|
(unsigned long)m->full_mah, m->health, wear);
|
||||||
|
canvas_draw_str(c, 2, 40, buf);
|
||||||
|
|
||||||
// Voltage graph, auto-scaled to 3.3-4.2 V window.
|
// Voltage graph, auto-scaled to 3.3-4.2 V window.
|
||||||
int gx = 2, gy = 45, gw = GRAPH_N, gh = 17;
|
int gx = 2, gy = 44, gw = GRAPH_N, gh = 17;
|
||||||
canvas_draw_frame(c, gx, gy, gw + 2, gh + 2);
|
canvas_draw_frame(c, gx, gy, gw + 2, gh + 2);
|
||||||
for(int k = 0; k < m->graph_count; k++) {
|
for(int k = 0; k < m->graph_count; k++) {
|
||||||
float v = m->graph[k];
|
float v = m->graph[k];
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 91 B |
@@ -65,20 +65,20 @@ static void draw_cb(Canvas* c, void* ctx) {
|
|||||||
canvas_set_font(c, FontPrimary);
|
canvas_set_font(c, FontPrimary);
|
||||||
canvas_draw_str(c, 2, 9, "GATT Server");
|
canvas_draw_str(c, 2, 9, "GATT Server");
|
||||||
canvas_set_font(c, FontSecondary);
|
canvas_set_font(c, FontSecondary);
|
||||||
|
canvas_draw_line(c, 0, 11, 127, 11);
|
||||||
const char* st = app->status == BtStatusConnected ? "connected" :
|
const char* st = app->status == BtStatusConnected ? "connected" :
|
||||||
app->status == BtStatusAdvertising ? "advertising" :
|
app->status == BtStatusAdvertising ? "advertising" :
|
||||||
app->status == BtStatusOff ? "off" :
|
app->status == BtStatusOff ? "off" :
|
||||||
"unavailable";
|
"unavailable";
|
||||||
canvas_draw_str_aligned(c, 126, 9, AlignRight, AlignBottom, st);
|
snprintf(buf, sizeof(buf), "Serial: %s", st);
|
||||||
canvas_draw_line(c, 0, 11, 127, 11);
|
canvas_draw_str(c, 2, 22, buf);
|
||||||
canvas_draw_str(c, 2, 22, "Serial GATT service (RX/TX)");
|
|
||||||
snprintf(buf, sizeof(buf), "RX %lu B TX %lu B", (unsigned long)app->rx_bytes, (unsigned long)app->tx_bytes);
|
snprintf(buf, sizeof(buf), "RX %lu B TX %lu B", (unsigned long)app->rx_bytes, (unsigned long)app->tx_bytes);
|
||||||
canvas_draw_str(c, 2, 34, buf);
|
canvas_draw_str(c, 2, 33, buf);
|
||||||
snprintf(buf, sizeof(buf), "Echo: %s (OK toggles)", app->echo ? "on" : "off");
|
snprintf(buf, sizeof(buf), "Echo %s OK=toggle", app->echo ? "on" : "off");
|
||||||
canvas_draw_str(c, 2, 45, buf);
|
canvas_draw_str(c, 2, 44, buf);
|
||||||
if(app->last[0]) {
|
if(app->last[0]) {
|
||||||
snprintf(buf, sizeof(buf), "last: %s", app->last);
|
snprintf(buf, sizeof(buf), "last: %.14s", app->last);
|
||||||
canvas_draw_str(c, 2, 57, buf);
|
canvas_draw_str(c, 2, 58, buf);
|
||||||
}
|
}
|
||||||
furi_mutex_release(app->mutex);
|
furi_mutex_release(app->mutex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ typedef struct {
|
|||||||
Sub subs[SUBS_MAX];
|
Sub subs[SUBS_MAX];
|
||||||
int sub_count;
|
int sub_count;
|
||||||
bool connected_ok;
|
bool connected_ok;
|
||||||
|
volatile bool cancel;
|
||||||
char setup_msg[48];
|
char setup_msg[48];
|
||||||
} App;
|
} App;
|
||||||
|
|
||||||
@@ -95,6 +96,8 @@ typedef struct {
|
|||||||
|
|
||||||
// ---- logging ----------------------------------------------------------------
|
// ---- logging ----------------------------------------------------------------
|
||||||
|
|
||||||
|
static App* s_app; // popup Back callback (popup view ctx isn't the App)
|
||||||
|
|
||||||
static void app_log(App* app, const char* fmt, ...) {
|
static void app_log(App* app, const char* fmt, ...) {
|
||||||
if(!app->log) return;
|
if(!app->log) return;
|
||||||
DateTime dt;
|
DateTime dt;
|
||||||
@@ -240,7 +243,13 @@ static int32_t worker_thread(void* ctx) {
|
|||||||
while(furi_message_queue_get(app->jobs, &job, FuriWaitForever) == FuriStatusOk) {
|
while(furi_message_queue_get(app->jobs, &job, FuriWaitForever) == FuriStatusOk) {
|
||||||
if(job == JobQuit) break;
|
if(job == JobQuit) break;
|
||||||
if(job == JobConnect) {
|
if(job == JobConnect) {
|
||||||
app->connected_ok = bc_connect(app->bc, &app->dev, 8000) && hid_setup(app);
|
bool ok = bc_connect(app->bc, &app->dev, 8000) && hid_setup(app);
|
||||||
|
if(app->cancel) {
|
||||||
|
if(bc_is_connected(app->bc)) bc_disconnect(app->bc);
|
||||||
|
bc_scan_start(app->bc);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
app->connected_ok = ok;
|
||||||
} else if(job == JobDisconnect) {
|
} else if(job == JobDisconnect) {
|
||||||
bc_disconnect(app->bc);
|
bc_disconnect(app->bc);
|
||||||
bc_scan_start(app->bc);
|
bc_scan_start(app->bc);
|
||||||
@@ -442,9 +451,11 @@ static uint32_t nav_mon_back(void* ctx) {
|
|||||||
return ViewScan;
|
return ViewScan;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t nav_stay_popup(void* ctx) {
|
static uint32_t nav_connecting_back(void* ctx) {
|
||||||
UNUSED(ctx);
|
UNUSED(ctx);
|
||||||
return ViewPopup;
|
s_app->cancel = true;
|
||||||
|
s_app->cur_view = ViewScan;
|
||||||
|
return ViewScan;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- events -----------------------------------------------------------------
|
// ---- events -----------------------------------------------------------------
|
||||||
@@ -508,12 +519,17 @@ static bool custom_event_cb(void* ctx, uint32_t event) {
|
|||||||
false);
|
false);
|
||||||
if(have) {
|
if(have) {
|
||||||
app_log(app, "CONNECTING %s", dev_label(&app->dev));
|
app_log(app, "CONNECTING %s", dev_label(&app->dev));
|
||||||
|
app->cancel = false;
|
||||||
show_popup(app, "Connecting...", dev_label(&app->dev), false);
|
show_popup(app, "Connecting...", dev_label(&app->dev), false);
|
||||||
furi_message_queue_put(app->jobs, &(JobType){JobConnect}, 0);
|
furi_message_queue_put(app->jobs, &(JobType){JobConnect}, 0);
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case EvtNotify: on_notify(app); break;
|
case EvtNotify: on_notify(app); break;
|
||||||
case EvtJobDone:
|
case EvtJobDone:
|
||||||
|
if(app->cancel) {
|
||||||
|
app->cancel = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if(app->connected_ok) {
|
if(app->connected_ok) {
|
||||||
char hdr[40];
|
char hdr[40];
|
||||||
snprintf(hdr, sizeof(hdr), "%s", dev_label(&app->dev));
|
snprintf(hdr, sizeof(hdr), "%s", dev_label(&app->dev));
|
||||||
@@ -554,6 +570,7 @@ int32_t ble_hid_host_app(void* p) {
|
|||||||
UNUSED(p);
|
UNUSED(p);
|
||||||
App* app = malloc(sizeof(App));
|
App* app = malloc(sizeof(App));
|
||||||
memset(app, 0, sizeof(App));
|
memset(app, 0, sizeof(App));
|
||||||
|
s_app = app;
|
||||||
app->gui = furi_record_open(RECORD_GUI);
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
app->storage = furi_record_open(RECORD_STORAGE);
|
app->storage = furi_record_open(RECORD_STORAGE);
|
||||||
app->jobs = furi_message_queue_alloc(8, sizeof(JobType));
|
app->jobs = furi_message_queue_alloc(8, sizeof(JobType));
|
||||||
@@ -582,7 +599,7 @@ int32_t ble_hid_host_app(void* p) {
|
|||||||
view_dispatcher_add_view(app->vd, ViewMonitor, app->mon_view);
|
view_dispatcher_add_view(app->vd, ViewMonitor, app->mon_view);
|
||||||
|
|
||||||
app->popup = popup_alloc();
|
app->popup = popup_alloc();
|
||||||
view_set_previous_callback(popup_get_view(app->popup), nav_stay_popup);
|
view_set_previous_callback(popup_get_view(app->popup), nav_connecting_back);
|
||||||
view_dispatcher_add_view(app->vd, ViewPopup, popup_get_view(app->popup));
|
view_dispatcher_add_view(app->vd, ViewPopup, popup_get_view(app->popup));
|
||||||
|
|
||||||
app->timer = furi_timer_alloc(timer_cb, FuriTimerTypePeriodic, app);
|
app->timer = furi_timer_alloc(timer_cb, FuriTimerTypePeriodic, app);
|
||||||
|
|||||||
+23
-6
@@ -70,9 +70,12 @@ typedef struct {
|
|||||||
BcService services[BC_MAX_SERVICES];
|
BcService services[BC_MAX_SERVICES];
|
||||||
BcChar chars[BC_MAX_CHARS];
|
BcChar chars[BC_MAX_CHARS];
|
||||||
bool connected_ok;
|
bool connected_ok;
|
||||||
|
volatile bool cancel;
|
||||||
char setup_msg[40];
|
char setup_msg[40];
|
||||||
} App;
|
} App;
|
||||||
|
|
||||||
|
static App* s_app; // for the popup Back callback (popup view ctx isn't the App)
|
||||||
|
|
||||||
static const char* dev_label(const BcDevice* d) {
|
static const char* dev_label(const BcDevice* d) {
|
||||||
return d->name[0] ? d->name : "(no name)";
|
return d->name[0] ? d->name : "(no name)";
|
||||||
}
|
}
|
||||||
@@ -139,9 +142,15 @@ static int32_t worker_thread(void* ctx) {
|
|||||||
JobType job;
|
JobType job;
|
||||||
while(furi_message_queue_get(app->jobs, &job, FuriWaitForever) == FuriStatusOk) {
|
while(furi_message_queue_get(app->jobs, &job, FuriWaitForever) == FuriStatusOk) {
|
||||||
if(job == JobQuit) break;
|
if(job == JobQuit) break;
|
||||||
if(job == JobConnect)
|
if(job == JobConnect) {
|
||||||
app->connected_ok = bc_connect(app->bc, &app->dev, 8000) && sensor_setup(app);
|
bool ok = bc_connect(app->bc, &app->dev, 8000) && sensor_setup(app);
|
||||||
else if(job == JobDisconnect) {
|
if(app->cancel) { // user pressed Back while we were busy
|
||||||
|
if(bc_is_connected(app->bc)) bc_disconnect(app->bc);
|
||||||
|
bc_scan_start(app->bc);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
app->connected_ok = ok;
|
||||||
|
} else if(job == JobDisconnect) {
|
||||||
bc_disconnect(app->bc);
|
bc_disconnect(app->bc);
|
||||||
bc_scan_start(app->bc);
|
bc_scan_start(app->bc);
|
||||||
}
|
}
|
||||||
@@ -288,9 +297,11 @@ static uint32_t scan_exit(void* ctx) {
|
|||||||
UNUSED(ctx);
|
UNUSED(ctx);
|
||||||
return VIEW_NONE;
|
return VIEW_NONE;
|
||||||
}
|
}
|
||||||
static uint32_t stay_popup(void* ctx) {
|
static uint32_t connecting_back(void* ctx) {
|
||||||
UNUSED(ctx);
|
UNUSED(ctx);
|
||||||
return ViewPopup;
|
s_app->cancel = true; // worker will drop the half-made connection
|
||||||
|
s_app->cur = ViewScan;
|
||||||
|
return ViewScan;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void popup_cb(void* ctx) {
|
static void popup_cb(void* ctx) {
|
||||||
@@ -343,6 +354,7 @@ static bool custom_cb(void* ctx, uint32_t event) {
|
|||||||
popup_set_context(app->popup, app);
|
popup_set_context(app->popup, app);
|
||||||
popup_set_header(app->popup, "Connecting...", 64, 26, AlignCenter, AlignCenter);
|
popup_set_header(app->popup, "Connecting...", 64, 26, AlignCenter, AlignCenter);
|
||||||
popup_set_text(app->popup, dev_label(&app->dev), 64, 40, AlignCenter, AlignCenter);
|
popup_set_text(app->popup, dev_label(&app->dev), 64, 40, AlignCenter, AlignCenter);
|
||||||
|
app->cancel = false;
|
||||||
app->cur = ViewPopup;
|
app->cur = ViewPopup;
|
||||||
view_dispatcher_switch_to_view(app->vd, ViewPopup);
|
view_dispatcher_switch_to_view(app->vd, ViewPopup);
|
||||||
furi_message_queue_put(app->jobs, &(JobType){JobConnect}, 0);
|
furi_message_queue_put(app->jobs, &(JobType){JobConnect}, 0);
|
||||||
@@ -350,6 +362,10 @@ static bool custom_cb(void* ctx, uint32_t event) {
|
|||||||
} break;
|
} break;
|
||||||
case EvtNotify: on_notify(app); break;
|
case EvtNotify: on_notify(app); break;
|
||||||
case EvtJobDone:
|
case EvtJobDone:
|
||||||
|
if(app->cancel) { // user already backed out to the scan list
|
||||||
|
app->cancel = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if(app->connected_ok) {
|
if(app->connected_ok) {
|
||||||
with_view_model(
|
with_view_model(
|
||||||
app->readings_view, ReadingsModel * m,
|
app->readings_view, ReadingsModel * m,
|
||||||
@@ -383,6 +399,7 @@ int32_t ble_sensor_app(void* p) {
|
|||||||
UNUSED(p);
|
UNUSED(p);
|
||||||
App* app = malloc(sizeof(App));
|
App* app = malloc(sizeof(App));
|
||||||
memset(app, 0, sizeof(App));
|
memset(app, 0, sizeof(App));
|
||||||
|
s_app = app;
|
||||||
app->gui = furi_record_open(RECORD_GUI);
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
app->jobs = furi_message_queue_alloc(8, sizeof(JobType));
|
app->jobs = furi_message_queue_alloc(8, sizeof(JobType));
|
||||||
app->notify_q = furi_message_queue_alloc(16, sizeof(NotifyMsg));
|
app->notify_q = furi_message_queue_alloc(16, sizeof(NotifyMsg));
|
||||||
@@ -410,7 +427,7 @@ int32_t ble_sensor_app(void* p) {
|
|||||||
view_dispatcher_add_view(app->vd, ViewReadings, app->readings_view);
|
view_dispatcher_add_view(app->vd, ViewReadings, app->readings_view);
|
||||||
|
|
||||||
app->popup = popup_alloc();
|
app->popup = popup_alloc();
|
||||||
view_set_previous_callback(popup_get_view(app->popup), stay_popup);
|
view_set_previous_callback(popup_get_view(app->popup), connecting_back);
|
||||||
view_dispatcher_add_view(app->vd, ViewPopup, popup_get_view(app->popup));
|
view_dispatcher_add_view(app->vd, ViewPopup, popup_get_view(app->popup));
|
||||||
|
|
||||||
app->timer = furi_timer_alloc(timer_cb, FuriTimerTypePeriodic, app);
|
app->timer = furi_timer_alloc(timer_cb, FuriTimerTypePeriodic, app);
|
||||||
|
|||||||
+11
-11
@@ -89,6 +89,7 @@ typedef struct {
|
|||||||
BleCentral* bc;
|
BleCentral* bc;
|
||||||
Tracker db[BC_MAX_DEVICES];
|
Tracker db[BC_MAX_DEVICES];
|
||||||
size_t db_count;
|
size_t db_count;
|
||||||
|
BcDevice snap[BC_MAX_DEVICES]; // snapshot scratch (kept off the stack)
|
||||||
} App;
|
} App;
|
||||||
|
|
||||||
static void app_log(App* app, const char* fmt, ...) {
|
static void app_log(App* app, const char* fmt, ...) {
|
||||||
@@ -107,7 +108,7 @@ static void app_log(App* app, const char* fmt, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void tick(App* app) {
|
static void tick(App* app) {
|
||||||
BcDevice snap[BC_MAX_DEVICES];
|
BcDevice* snap = app->snap;
|
||||||
size_t n = bc_snapshot(app->bc, snap, BC_MAX_DEVICES);
|
size_t n = bc_snapshot(app->bc, snap, BC_MAX_DEVICES);
|
||||||
uint32_t now = furi_get_tick();
|
uint32_t now = furi_get_tick();
|
||||||
for(size_t i = 0; i < n; i++) {
|
for(size_t i = 0; i < n; i++) {
|
||||||
@@ -156,15 +157,6 @@ static void tick(App* app) {
|
|||||||
m->items[i].hits = app->db[i].hits;
|
m->items[i].hits = app->db[i].hits;
|
||||||
m->items[i].warned = app->db[i].warned;
|
m->items[i].warned = app->db[i].warned;
|
||||||
}
|
}
|
||||||
for(size_t i = 1; i < m->count; i++) { // insertion sort by rssi
|
|
||||||
Tracker t = m->items[i];
|
|
||||||
size_t j = i;
|
|
||||||
while(j > 0 && m->items[j - 1].rssi < t.rssi) {
|
|
||||||
m->items[j] = m->items[j - 1];
|
|
||||||
j--;
|
|
||||||
}
|
|
||||||
m->items[j] = t;
|
|
||||||
}
|
|
||||||
if(m->sel >= m->count) m->sel = m->count ? m->count - 1 : 0;
|
if(m->sel >= m->count) m->sel = m->count ? m->count - 1 : 0;
|
||||||
if(m->sel < m->top) m->top = m->sel;
|
if(m->sel < m->top) m->top = m->sel;
|
||||||
if(m->sel >= m->top + ROWS) m->top = m->sel - ROWS + 1;
|
if(m->sel >= m->top + ROWS) m->top = m->sel - ROWS + 1;
|
||||||
@@ -227,8 +219,14 @@ static uint32_t exit_cb(void* ctx) {
|
|||||||
return VIEW_NONE;
|
return VIEW_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool tracker_custom(void* ctx, uint32_t event) {
|
||||||
|
if(event == 1) tick((App*)ctx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void timer_cb(void* ctx) {
|
static void timer_cb(void* ctx) {
|
||||||
tick((App*)ctx);
|
App* app = ctx;
|
||||||
|
view_dispatcher_send_custom_event(app->vd, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t ble_tracker_app(void* p) {
|
int32_t ble_tracker_app(void* p) {
|
||||||
@@ -239,6 +237,8 @@ int32_t ble_tracker_app(void* p) {
|
|||||||
app->storage = furi_record_open(RECORD_STORAGE);
|
app->storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
|
||||||
app->vd = view_dispatcher_alloc();
|
app->vd = view_dispatcher_alloc();
|
||||||
|
view_dispatcher_set_event_callback_context(app->vd, app);
|
||||||
|
view_dispatcher_set_custom_event_callback(app->vd, tracker_custom);
|
||||||
view_dispatcher_attach_to_gui(app->vd, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->vd, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
app->view = view_alloc();
|
app->view = view_alloc();
|
||||||
view_allocate_model(app->view, ViewModelTypeLocking, sizeof(ListModel));
|
view_allocate_model(app->view, ViewModelTypeLocking, sizeof(ListModel));
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ typedef struct {
|
|||||||
BcDevice devs[BC_MAX_DEVICES];
|
BcDevice devs[BC_MAX_DEVICES];
|
||||||
size_t count, sel, top;
|
size_t count, sel, top;
|
||||||
bool scanning;
|
bool scanning;
|
||||||
|
uint8_t scan_err;
|
||||||
} ScanModel;
|
} ScanModel;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -128,6 +129,7 @@ typedef struct {
|
|||||||
|
|
||||||
JobType job;
|
JobType job;
|
||||||
bool job_ok;
|
bool job_ok;
|
||||||
|
volatile bool cancel;
|
||||||
FuriString* status;
|
FuriString* status;
|
||||||
FuriString* info_text;
|
FuriString* info_text;
|
||||||
} App;
|
} App;
|
||||||
@@ -328,7 +330,10 @@ static void scan_draw(Canvas* c, void* m_) {
|
|||||||
canvas_set_font(c, FontPrimary);
|
canvas_set_font(c, FontPrimary);
|
||||||
canvas_draw_str(c, 2, 9, "BT Inspector");
|
canvas_draw_str(c, 2, 9, "BT Inspector");
|
||||||
canvas_set_font(c, FontSecondary);
|
canvas_set_font(c, FontSecondary);
|
||||||
furi_string_printf(s, m->scanning ? "%u found" : "%u (stopped)", (unsigned)m->count);
|
if(m->scanning)
|
||||||
|
furi_string_printf(s, "%u found", (unsigned)m->count);
|
||||||
|
else
|
||||||
|
furi_string_printf(s, "err 0x%02X", m->scan_err);
|
||||||
canvas_draw_str_aligned(c, 126, 9, AlignRight, AlignBottom, furi_string_get_cstr(s));
|
canvas_draw_str_aligned(c, 126, 9, AlignRight, AlignBottom, furi_string_get_cstr(s));
|
||||||
canvas_draw_line(c, 0, 11, 127, 11);
|
canvas_draw_line(c, 0, 11, 127, 11);
|
||||||
if(!m->count) canvas_draw_str_aligned(c, 64, 40, AlignCenter, AlignCenter, "Scanning...");
|
if(!m->count) canvas_draw_str_aligned(c, 64, 40, AlignCenter, AlignCenter, "Scanning...");
|
||||||
@@ -389,7 +394,8 @@ static void scan_enter(void* ctx) {
|
|||||||
app->cur_view = ViewScan;
|
app->cur_view = ViewScan;
|
||||||
bool ok = bc_scan_start(app->bc);
|
bool ok = bc_scan_start(app->bc);
|
||||||
if(!ok) app_log(app, "SCAN START FAILED 0x%02X", bc_last_error(app->bc));
|
if(!ok) app_log(app, "SCAN START FAILED 0x%02X", bc_last_error(app->bc));
|
||||||
with_view_model(app->scan_view, ScanModel * m, { m->scanning = ok; }, true);
|
uint8_t err = ok ? 0 : bc_last_error(app->bc);
|
||||||
|
with_view_model(app->scan_view, ScanModel * m, { m->scanning = ok; m->scan_err = err; }, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- page view (scrollable text with optional buttons) -----------------------
|
// ---- page view (scrollable text with optional buttons) -----------------------
|
||||||
@@ -591,6 +597,32 @@ static uint16_t char_end_handle(App* app) {
|
|||||||
return app->services[app->svc_sel].end;
|
return app->services[app->svc_sel].end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the device advertised no name, read the GAP Device Name (0x2A00) over the
|
||||||
|
// connection so the device page and service list show its real name, not just
|
||||||
|
// the vendor. Runs on the worker thread right after service discovery.
|
||||||
|
static void resolve_name(App* app) {
|
||||||
|
if(app->dev.name[0]) return; // already have an advertised name
|
||||||
|
for(int i = 0; i < app->svc_count; i++) {
|
||||||
|
BcService* s = &app->services[i];
|
||||||
|
if(!(s->uuid_len == 2 && (s->uuid[0] | (s->uuid[1] << 8)) == 0x1800)) continue; // GAP
|
||||||
|
BcChar tmp[8];
|
||||||
|
int n = bc_discover_chars(app->bc, s, tmp, COUNT_OF(tmp));
|
||||||
|
for(int k = 0; k < n; k++) {
|
||||||
|
uint16_t u = tmp[k].uuid_len == 2 ? (tmp[k].uuid[0] | (tmp[k].uuid[1] << 8)) : 0;
|
||||||
|
if(u != 0x2A00 || !(tmp[k].props & CHAR_PROP_READ)) continue;
|
||||||
|
uint8_t val[BC_NAME_MAX + 1];
|
||||||
|
int len = bc_read(app->bc, tmp[k].value_handle, val, BC_NAME_MAX);
|
||||||
|
if(len > 0) {
|
||||||
|
memcpy(app->dev.name, val, len);
|
||||||
|
app->dev.name[len] = 0;
|
||||||
|
app_log(app, "NAME resolved: %s", app->dev.name);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return; // found GAP but no readable name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void do_info(App* app) {
|
static void do_info(App* app) {
|
||||||
furi_string_printf(app->info_text, "%s\nMTU %u\n", dev_label(&app->dev), bc_mtu(app->bc));
|
furi_string_printf(app->info_text, "%s\nMTU %u\n", dev_label(&app->dev), bc_mtu(app->bc));
|
||||||
FuriString* label = furi_string_alloc();
|
FuriString* label = furi_string_alloc();
|
||||||
@@ -639,6 +671,7 @@ static int32_t worker_thread(void* ctx) {
|
|||||||
int n = bc_discover_services(app->bc, app->services, BC_MAX_SERVICES);
|
int n = bc_discover_services(app->bc, app->services, BC_MAX_SERVICES);
|
||||||
app->svc_count = n < 0 ? 0 : n;
|
app->svc_count = n < 0 ? 0 : n;
|
||||||
ok = n >= 0;
|
ok = n >= 0;
|
||||||
|
if(ok) resolve_name(app);
|
||||||
} break;
|
} break;
|
||||||
case JobChars: {
|
case JobChars: {
|
||||||
int n = bc_discover_chars(app->bc, &app->services[app->svc_sel], app->chars, BC_MAX_CHARS);
|
int n = bc_discover_chars(app->bc, &app->services[app->svc_sel], app->chars, BC_MAX_CHARS);
|
||||||
@@ -848,7 +881,10 @@ static uint32_t nav_exit(void* ctx) {
|
|||||||
|
|
||||||
static uint32_t nav_stay_popup(void* ctx) {
|
static uint32_t nav_stay_popup(void* ctx) {
|
||||||
UNUSED(ctx);
|
UNUSED(ctx);
|
||||||
return ViewPopup;
|
// Back on a connect/discover popup: abort and return to the scan list.
|
||||||
|
nav_app->cancel = true;
|
||||||
|
nav_app->cur_view = ViewScan;
|
||||||
|
return ViewScan;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t nav_page_back(void* ctx) {
|
static uint32_t nav_page_back(void* ctx) {
|
||||||
@@ -911,6 +947,16 @@ static void on_tick(App* app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void on_job_done(App* app) {
|
static void on_job_done(App* app) {
|
||||||
|
if(app->cancel) { // user backed out of the connect/discover popup
|
||||||
|
app->cancel = false;
|
||||||
|
if(app->job != JobDisconnect) {
|
||||||
|
if(bc_is_connected(app->bc))
|
||||||
|
post_job(app, JobDisconnect);
|
||||||
|
else
|
||||||
|
bc_scan_start(app->bc);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch(app->job) {
|
switch(app->job) {
|
||||||
case JobConnect:
|
case JobConnect:
|
||||||
if(app->job_ok) {
|
if(app->job_ok) {
|
||||||
@@ -1033,6 +1079,7 @@ static bool custom_event_cb(void* ctx, uint32_t event) {
|
|||||||
case EvtPageCenter:
|
case EvtPageCenter:
|
||||||
if(app->page_kind == PageDetails) {
|
if(app->page_kind == PageDetails) {
|
||||||
app_log(app, "CONNECTING %s", dev_label(&app->dev));
|
app_log(app, "CONNECTING %s", dev_label(&app->dev));
|
||||||
|
app->cancel = false;
|
||||||
show_popup(app, "Connecting...", dev_label(&app->dev), false);
|
show_popup(app, "Connecting...", dev_label(&app->dev), false);
|
||||||
post_job(app, JobConnect);
|
post_job(app, JobConnect);
|
||||||
} else if(app->page_kind == PageChar) {
|
} else if(app->page_kind == PageChar) {
|
||||||
@@ -1054,6 +1101,12 @@ static bool custom_event_cb(void* ctx, uint32_t event) {
|
|||||||
case EvtJobDone: on_job_done(app); break;
|
case EvtJobDone: on_job_done(app); break;
|
||||||
case EvtNotify: on_notify(app); break;
|
case EvtNotify: on_notify(app); break;
|
||||||
case EvtPopupDone:
|
case EvtPopupDone:
|
||||||
|
if(app->cancel) {
|
||||||
|
app->cancel = false;
|
||||||
|
bc_scan_start(app->bc);
|
||||||
|
show_view(app, ViewScan);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if(app->after_popup == AfterPopupServices) {
|
if(app->after_popup == AfterPopupServices) {
|
||||||
show_view(app, ViewServices);
|
show_view(app, ViewServices);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -116,6 +116,33 @@ if changed:
|
|||||||
PY
|
PY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Don't validate the CPU2-controlled secure-region option bytes against the
|
||||||
|
# stock reference. The updater's OB validation stage compares device option
|
||||||
|
# bytes to scripts/ob.data; that reference is tuned for the LIGHT stack (SFSA
|
||||||
|
# 0x080D7000, SBRV 0x0D7000>>2). Installing the FULL stack makes CPU2 move the
|
||||||
|
# secure boundary (SFSA -> 0xCE, SBRV -> 0x0CE000>>2, plus the SRAM2 secure
|
||||||
|
# boundaries), so the compare fails with "Uncorr. value mismatch #[8-93]" right
|
||||||
|
# after the radio installs. These bytes are read-only (only CPU2 sets them) and
|
||||||
|
# are correct-by-construction once the radio install has succeeded, so exclude
|
||||||
|
# them from the compare. The user-controllable OBs (RDP, BOR, boot config,
|
||||||
|
# watchdogs, PCROP, WRP) are still validated. obdata.py skips '#'-commented lines.
|
||||||
|
python3 - scripts/ob.data <<'PY'
|
||||||
|
import sys
|
||||||
|
p = sys.argv[1]
|
||||||
|
secure = {"SFSA", "FSD", "DDS", "C2OPT", "NBRSD", "SNBRSA", "BRSD", "SBRSA", "SBRV"}
|
||||||
|
out, changed = [], False
|
||||||
|
for line in open(p).read().splitlines():
|
||||||
|
name = line.split(":", 1)[0].strip()
|
||||||
|
if name in secure and not line.startswith("#"):
|
||||||
|
out.append("# " + line + " # Blue++: CPU2-set for full stack, not validated")
|
||||||
|
changed = True
|
||||||
|
else:
|
||||||
|
out.append(line)
|
||||||
|
if changed:
|
||||||
|
open(p, "w").write("\n".join(out) + "\n")
|
||||||
|
print(">> patched " + p + ": exclude CPU2 secure-region OBs from validation")
|
||||||
|
PY
|
||||||
|
|
||||||
# Stage each app into applications_user, with the shared libble sources copied
|
# Stage each app into applications_user, with the shared libble sources copied
|
||||||
# alongside so the fap glob (*.c) picks them up.
|
# alongside so the fap glob (*.c) picks them up.
|
||||||
mkdir -p applications_user
|
mkdir -p applications_user
|
||||||
|
|||||||
+16
-9
@@ -9,6 +9,7 @@
|
|||||||
#include <ble/core/auto/ble_vs_codes.h>
|
#include <ble/core/auto/ble_vs_codes.h>
|
||||||
#include <ble/core/auto/ble_hci_le.h>
|
#include <ble/core/auto/ble_hci_le.h>
|
||||||
#include <ble/core/auto/ble_gatt_aci.h>
|
#include <ble/core/auto/ble_gatt_aci.h>
|
||||||
|
#include <ble/core/auto/ble_gap_aci.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
@@ -314,9 +315,11 @@ void bc_free(BleCentral* bc) {
|
|||||||
|
|
||||||
bool bc_scan_start(BleCentral* bc) {
|
bool bc_scan_start(BleCentral* bc) {
|
||||||
if(bc->scanning) return true;
|
if(bc->scanning) return true;
|
||||||
// active scan, 50 ms interval / 30 ms window; no duplicate filtering so RSSI stays live
|
// Active scan, 50 ms interval / 30 ms window, public address, no duplicate
|
||||||
tBleStatus st = hci_le_set_scan_parameters(0x01, 0x50, 0x30, OWN_ADDR_TYPE, 0x00);
|
// filtering (so RSSI stays live), accept-all filter policy. The firmware's
|
||||||
if(st == BLE_STATUS_SUCCESS) st = hci_le_set_scan_enable(0x01, 0x00);
|
// GAP is initialised, so we must use the GAP observation procedure; raw
|
||||||
|
// hci_le_set_scan_* returns "command disallowed" in that state.
|
||||||
|
tBleStatus st = aci_gap_start_observation_proc(0x0050, 0x0030, 0x01, OWN_ADDR_TYPE, 0x00, 0x00);
|
||||||
if(st != BLE_STATUS_SUCCESS) {
|
if(st != BLE_STATUS_SUCCESS) {
|
||||||
FURI_LOG_E(TAG, "scan start failed: 0x%02X", st);
|
FURI_LOG_E(TAG, "scan start failed: 0x%02X", st);
|
||||||
bc->last_error = st;
|
bc->last_error = st;
|
||||||
@@ -328,7 +331,7 @@ bool bc_scan_start(BleCentral* bc) {
|
|||||||
|
|
||||||
void bc_scan_stop(BleCentral* bc) {
|
void bc_scan_stop(BleCentral* bc) {
|
||||||
if(!bc->scanning) return;
|
if(!bc->scanning) return;
|
||||||
hci_le_set_scan_enable(0x00, 0x00);
|
aci_gap_terminate_gap_proc(GAP_OBSERVATION_PROC);
|
||||||
bc->scanning = false;
|
bc->scanning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,10 +351,12 @@ size_t bc_snapshot(BleCentral* bc, BcDevice* out, size_t max) {
|
|||||||
d->logged = true;
|
d->logged = true;
|
||||||
}
|
}
|
||||||
furi_mutex_release(bc->mutex);
|
furi_mutex_release(bc->mutex);
|
||||||
for(size_t i = 1; i < n; i++) { // insertion sort by RSSI, strongest first (n <= 32)
|
// Order by discovery time (first seen) so the list is stable and doesn't
|
||||||
|
// jump around as RSSI changes. Insertion sort, n <= 32.
|
||||||
|
for(size_t i = 1; i < n; i++) {
|
||||||
BcDevice d = out[i];
|
BcDevice d = out[i];
|
||||||
size_t j = i;
|
size_t j = i;
|
||||||
while(j > 0 && out[j - 1].rssi < d.rssi) {
|
while(j > 0 && out[j - 1].first_seen > d.first_seen) {
|
||||||
out[j] = out[j - 1];
|
out[j] = out[j - 1];
|
||||||
j--;
|
j--;
|
||||||
}
|
}
|
||||||
@@ -407,9 +412,11 @@ bool bc_connect(BleCentral* bc, const BcDevice* dev, uint32_t timeout_ms) {
|
|||||||
furi_event_flag_clear(bc->flags, FlagConnected | FlagConnFailed | FlagDisconnected | FlagDone);
|
furi_event_flag_clear(bc->flags, FlagConnected | FlagConnFailed | FlagDisconnected | FlagDone);
|
||||||
bc->connecting = true;
|
bc->connecting = true;
|
||||||
bc->mtu = 23;
|
bc->mtu = 23;
|
||||||
|
// Observation must fully stop before starting a connection procedure.
|
||||||
|
furi_delay_ms(100);
|
||||||
// scan 60/30 ms, conn interval 30-50 ms, latency 0, supervision 5 s
|
// scan 60/30 ms, conn interval 30-50 ms, latency 0, supervision 5 s
|
||||||
tBleStatus st = hci_le_create_connection(
|
tBleStatus st = aci_gap_create_connection(
|
||||||
0x60, 0x30, 0x00, dev->addr_type, dev->addr, OWN_ADDR_TYPE, 0x18, 0x28, 0, 0x01F4, 0, 0);
|
0x60, 0x30, dev->addr_type, dev->addr, OWN_ADDR_TYPE, 0x18, 0x28, 0, 0x01F4, 0, 0);
|
||||||
if(st != BLE_STATUS_SUCCESS) {
|
if(st != BLE_STATUS_SUCCESS) {
|
||||||
FURI_LOG_E(TAG, "create_connection failed: 0x%02X", st);
|
FURI_LOG_E(TAG, "create_connection failed: 0x%02X", st);
|
||||||
bc->connecting = false;
|
bc->connecting = false;
|
||||||
@@ -418,7 +425,7 @@ bool bc_connect(BleCentral* bc, const BcDevice* dev, uint32_t timeout_ms) {
|
|||||||
}
|
}
|
||||||
uint32_t f = furi_event_flag_wait(bc->flags, FlagConnected | FlagConnFailed, FuriFlagWaitAny, timeout_ms);
|
uint32_t f = furi_event_flag_wait(bc->flags, FlagConnected | FlagConnFailed, FuriFlagWaitAny, timeout_ms);
|
||||||
if(f & FuriFlagError) {
|
if(f & FuriFlagError) {
|
||||||
hci_le_create_connection_cancel();
|
aci_gap_terminate_gap_proc(GAP_DIRECT_CONNECTION_ESTABLISHMENT_PROC);
|
||||||
f = furi_event_flag_wait(bc->flags, FlagConnected | FlagConnFailed, FuriFlagWaitAny, 2000);
|
f = furi_event_flag_wait(bc->flags, FlagConnected | FlagConnFailed, FuriFlagWaitAny, 2000);
|
||||||
if(!(f & FlagConnected)) {
|
if(!(f & FlagConnected)) {
|
||||||
bc->connecting = false;
|
bc->connecting = false;
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 93 B |
@@ -146,7 +146,7 @@ static void draw_cb(Canvas* c, void* ctx) {
|
|||||||
(unsigned long)(m->range_end / 1000 % 1000));
|
(unsigned long)(m->range_end / 1000 % 1000));
|
||||||
canvas_draw_str(c, 2, 20, buf);
|
canvas_draw_str(c, 2, 20, buf);
|
||||||
snprintf(
|
snprintf(
|
||||||
buf, sizeof(buf), "peak %lu.%03lu MHz %d dBm", (unsigned long)(m->peak_freq / 1000000),
|
buf, sizeof(buf), "pk %lu.%03luM %ddBm", (unsigned long)(m->peak_freq / 1000000),
|
||||||
(unsigned long)(m->peak_freq / 1000 % 1000), (int)m->peak_rssi);
|
(unsigned long)(m->peak_freq / 1000 % 1000), (int)m->peak_rssi);
|
||||||
canvas_draw_str(c, 2, 29, buf);
|
canvas_draw_str(c, 2, 29, buf);
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ static void draw_cb(Canvas* c, void* ctx) {
|
|||||||
int h = (int)((r + 110) / 70.0f * gh);
|
int h = (int)((r + 110) / 70.0f * gh);
|
||||||
canvas_draw_line(c, i, gy + gh - h, i, gy + gh);
|
canvas_draw_line(c, i, gy + gh - h, i, gy + gh);
|
||||||
}
|
}
|
||||||
canvas_draw_str(c, 2, 63, "OK pause <>band Back exit");
|
canvas_draw_str(c, 2, 63, "OK:pause <>:band");
|
||||||
furi_mutex_release(app->mutex);
|
furi_mutex_release(app->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 95 B After Width: | Height: | Size: 93 B |
@@ -95,12 +95,10 @@ static void draw_cb(Canvas* c, void* ctx) {
|
|||||||
canvas_clear(c);
|
canvas_clear(c);
|
||||||
canvas_set_font(c, FontSecondary);
|
canvas_set_font(c, FontSecondary);
|
||||||
snprintf(
|
snprintf(
|
||||||
buf, sizeof(buf), "%lu.%03lu MHz +-%lu kHz", (unsigned long)(m->center / 1000000),
|
buf, sizeof(buf), "%lu.%03lu +-%luk", (unsigned long)(m->center / 1000000),
|
||||||
(unsigned long)(m->center / 1000 % 1000), (unsigned long)(m->span / 2000));
|
(unsigned long)(m->center / 1000 % 1000), (unsigned long)(m->span / 2000));
|
||||||
canvas_draw_str(c, 1, 8, buf);
|
canvas_draw_str(c, 1, 8, buf);
|
||||||
snprintf(
|
snprintf(buf, sizeof(buf), "%ddBm", (int)m->peak_rssi);
|
||||||
buf, sizeof(buf), "pk %lu.%03lu %d", (unsigned long)(m->peak_freq / 1000000),
|
|
||||||
(unsigned long)(m->peak_freq / 1000 % 1000), (int)m->peak_rssi);
|
|
||||||
canvas_draw_str_aligned(c, 127, 8, AlignRight, AlignBottom, buf);
|
canvas_draw_str_aligned(c, 127, 8, AlignRight, AlignBottom, buf);
|
||||||
|
|
||||||
// spectrum, top band 10..30
|
// spectrum, top band 10..30
|
||||||
|
|||||||
Reference in New Issue
Block a user