From 792be219a4faee0d889d59487a502a4b137c0b0e Mon Sep 17 00:00:00 2001 From: matthudsonau <78771181+matthudsonau@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:59:15 +1100 Subject: [PATCH] Add bluetooth connection disconnect handling (#74) * Added basic disconnect handling * More advanced reconnect function * Add context argument to addEvent function signature. This allows the event function to have context on call and avoid requiring global variables to maintain state. * Run clang-format. * Appease the clang-format bot. * Further refactor to remove global reconnected boolean. * Actually record the private data. * Restart on failed reconnect. * Add incremental progress during notification wait. * Appease the clang-format bot. --------- Co-authored-by: Guo-Rong <5484552+gkoh@users.noreply.github.com> --- lib/M5ez/src/M5ez.cpp | 25 ++++++++---- lib/M5ez/src/M5ez.h | 20 ++++++---- lib/furble/Device.cpp | 4 ++ lib/furble/Device.h | 5 +++ lib/furble/Fujifilm.cpp | 2 + src/furble.ino | 86 ++++++++++++++++++++++++++++++++--------- 6 files changed, 108 insertions(+), 34 deletions(-) diff --git a/lib/M5ez/src/M5ez.cpp b/lib/M5ez/src/M5ez.cpp index aa47bd0..c0252d5 100644 --- a/lib/M5ez/src/M5ez.cpp +++ b/lib/M5ez/src/M5ez.cpp @@ -419,7 +419,7 @@ size_t ezCanvas::write(const uint8_t *buffer, size_t size) { return size; } -uint16_t ezCanvas::loop() { +uint16_t ezCanvas::loop(void *private_data) { if (_next_scroll && millis() >= _next_scroll) { ez.setFont(_font); uint8_t h = ez.fontHeight(); @@ -554,6 +554,10 @@ void ezButtons::show(String buttons) { } } +String ezButtons::get() { + return (_btn_a_s + "#" + _btn_b_s + "#" + _btn_c_s); +} + void ezButtons::clear(bool wipe /* = true */) { if (wipe && (_lower_button_row || _upper_button_row)) { M5.Lcd.fillRect(0, ez.canvas.bottom() + 1, TFT_H - ez.canvas.bottom() - 1, TFT_W, @@ -887,7 +891,7 @@ void ezBacklight::activity() { _last_activity = millis(); } -uint16_t ezBacklight::loop() { +uint16_t ezBacklight::loop(void *private_data) { if (!_backlight_off && _inactivity) { if (millis() > _last_activity + 30000 * _inactivity) { _backlight_off = true; @@ -1008,7 +1012,7 @@ void ezClock::menu() { } } -uint16_t ezClock::loop() { +uint16_t ezClock::loop(void *private_data) { ezt::events(); if (_starting && timeStatus() != timeNotSet) { _starting = false; @@ -1562,7 +1566,7 @@ void ezWifi::_askAdd() { } } -uint16_t ezWifi::loop() { +uint16_t ezWifi::loop(void *private_data) { if (millis() > _widget_time + ez.theme->signal_interval) { ez.header.draw("wifi"); _widget_time = millis(); @@ -2109,7 +2113,7 @@ void ezBattery::menu() { } } -uint16_t ezBattery::loop() { +uint16_t ezBattery::loop(void *private_data) { if (!_on) return 0; ez.header.draw("battery"); @@ -2232,7 +2236,7 @@ void M5ez::yield() { M5.update(); for (uint8_t n = 0; n < _events.size(); n++) { if (millis() > _events[n].when) { - uint16_t r = (_events[n].function)(); + uint16_t r = (_events[n].function)(_events[n].private_data); if (r) { _events[n].when = millis() + r - 1; } else { @@ -2246,14 +2250,17 @@ void M5ez::yield() { #endif } -void M5ez::addEvent(uint16_t (*function)(), uint32_t when /* = 1 */) { +void M5ez::addEvent(uint16_t (*function)(void *private_data), + void *private_data, + uint32_t when /* = 1 */) { event_t n; n.function = function; + n.private_data = private_data; n.when = millis() + when - 1; _events.push_back(n); } -void M5ez::removeEvent(uint16_t (*function)()) { +void M5ez::removeEvent(uint16_t (*function)(void *private_data)) { uint8_t n = 0; while (n < _events.size()) { if (_events[n].function == function) { @@ -3467,6 +3474,8 @@ ezProgressBar::ezProgressBar(String header /* = "" */, bar_color = ez.theme->progressbar_color; _bar_color = bar_color; ez.screen.clear(); + M5.Lcd.fillRect(0, 0, TFT_W, TFT_H, ez.screen.background()); + if (header != "") ez.header.show(header); ez.buttons.show(buttons); diff --git a/lib/M5ez/src/M5ez.h b/lib/M5ez/src/M5ez.h index a1b8b82..f52ed94 100644 --- a/lib/M5ez/src/M5ez.h +++ b/lib/M5ez/src/M5ez.h @@ -293,7 +293,7 @@ class ezCanvas: public Print { uint8_t c); // These three are used to inherint print and println from Print class virtual size_t write(const char *str); virtual size_t write(const uint8_t *buffer, size_t size); - static uint16_t loop(); + static uint16_t loop(void *private_data); private: static std::vector _printed; @@ -321,6 +321,7 @@ class ezButtons { public: static void begin(); static void show(String buttons); + static String get(); static void clear(bool wipe = true); static void releaseWait(); static String poll(); @@ -501,7 +502,7 @@ class ezBacklight { static void menu(); static void inactivity(uint8_t half_minutes); static void activity(); - static uint16_t loop(); + static uint16_t loop(void *private_data); private: static uint8_t _brightness; @@ -526,7 +527,7 @@ class ezClock { static void begin(); static void restart(); static void menu(); - static uint16_t loop(); + static uint16_t loop(void *private_data); static void clear(); static void draw(uint16_t x, uint16_t w); static bool waitForSync(const uint16_t timeout = 0); @@ -599,7 +600,7 @@ class ezWifi { static void readFlash(); static void writeFlash(); static void menu(); - static uint16_t loop(); + static uint16_t loop(void *private_data); static bool update(String url, const char *root_cert, ezProgressBar *pb = NULL); static String updateError(); @@ -678,7 +679,7 @@ class ezBattery { static void readFlash(); static void writeFlash(); static void menu(); - static uint16_t loop(); + static uint16_t loop(void *private_data); static uint8_t getTransformedBatteryLevel(); static uint16_t getBatteryBarColor(uint8_t batteryLevel); @@ -698,7 +699,8 @@ class ezBattery { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct event_t { - uint16_t (*function)(); + uint16_t (*function)(void *); + void *private_data; uint32_t when; }; class M5ez { @@ -742,8 +744,10 @@ class M5ez { static void yield(); - static void addEvent(uint16_t (*function)(), uint32_t when = 1); - static void removeEvent(uint16_t (*function)()); + static void addEvent(uint16_t (*function)(void *), + void *private_data = nullptr, + uint32_t when = 1); + static void removeEvent(uint16_t (*function)(void *private_data)); static void redraw(); // ez.msgBox diff --git a/lib/furble/Device.cpp b/lib/furble/Device.cpp index c0bceab..624ea4a 100644 --- a/lib/furble/Device.cpp +++ b/lib/furble/Device.cpp @@ -180,4 +180,8 @@ void Device::getUUID128(uuid128_t *uuid) { } } +bool Device::isConnected(void) { + return m_Client->isConnected(); +} + } // namespace Furble diff --git a/lib/furble/Device.h b/lib/furble/Device.h index 1132043..91b12bc 100644 --- a/lib/furble/Device.h +++ b/lib/furble/Device.h @@ -83,6 +83,11 @@ class Device { */ virtual void updateGeoData(gps_t &gps, timesync_t ×ync) = 0; + /** + * Checks if the client is still connected. + */ + bool isConnected(void); + const char *getName(void); void save(void); void remove(void); diff --git a/lib/furble/Fujifilm.cpp b/lib/furble/Fujifilm.cpp index 7ad12f0..0d2e25a 100644 --- a/lib/furble/Fujifilm.cpp +++ b/lib/furble/Fujifilm.cpp @@ -200,6 +200,7 @@ bool Fujifilm::connect(NimBLEClient *pClient, ezProgressBar &progress_bar) { if (m_Configured) { break; } + progress_bar.value(50.0f + (((float)i / 5000.0f) * 10.0f)); delay(100); } @@ -224,6 +225,7 @@ bool Fujifilm::connect(NimBLEClient *pClient, ezProgressBar &progress_bar) { m_GeoRequested = false; break; } + progress_bar.value(90.0f + (((float)i / 5000.0f) * 10.0f)); delay(100); } diff --git a/src/furble.ino b/src/furble.ino index 0e56e88..38f84b7 100644 --- a/src/furble.ino +++ b/src/furble.ino @@ -25,6 +25,11 @@ static const uint8_t GPS_HEADER_POSITION = CURRENT_POSITION + 1; static bool gps_enable = false; static bool gps_has_fix = false; +struct FurbleCtx { + Furble::Device *device; + bool reconnected; +}; + /** * BLE Advertisement callback. */ @@ -38,7 +43,7 @@ class AdvertisedCallback: public NimBLEAdvertisedDeviceCallbacks { /** * GPS serial event service handler. */ -static uint16_t service_grove_gps(void) { +static uint16_t service_grove_gps(void *private_data) { if (!gps_enable) { return GPS_SERVICE_MS; } @@ -112,7 +117,7 @@ static void current_draw_widget(uint16_t x, uint16_t y) { M5.Lcd.drawString(s, x + ez.theme->header_hmargin, ez.theme->header_tmargin + 2); } -static uint16_t current_service(void) { +static uint16_t current_service(void *private_data) { ez.header.draw("current"); return 1000; } @@ -139,16 +144,22 @@ static void trigger(Furble::Device *device, int counter) { device->shutterRelease(); } -static void remote_interval(Furble::Device *device) { +static void remote_interval(FurbleCtx *fctx) { + Furble::Device *device = fctx->device; int i = 0; int j = 1; ez.msgBox("Interval Release", "", "Stop", false); trigger(device, j); - while (true) { + while (device->isConnected()) { i++; + if (fctx->reconnected) { + ez.msgBox("Interval Release", String(j), "Stop", false); + fctx->reconnected = false; + } + M5.update(); if (M5.BtnB.wasReleased()) { @@ -200,7 +211,8 @@ static void show_shutter_control(bool shutter_locked, unsigned long lock_start_m } } -static void remote_control(Furble::Device *device) { +static void remote_control(FurbleCtx *fctx) { + Furble::Device *device = fctx->device; static unsigned long shutter_lock_start_ms = 0; static bool shutter_lock = false; @@ -213,6 +225,11 @@ static void remote_control(Furble::Device *device) { update_geodata(device); + if (fctx->reconnected) { + show_shutter_control(shutter_lock, shutter_lock_start_ms); + fctx->reconnected = false; + } + if (M5.BtnPWR.wasClicked() || M5.BtnC.wasPressed()) { if (shutter_lock) { // ensure shutter is released on exit @@ -268,7 +285,7 @@ static void remote_control(Furble::Device *device) { ez.yield(); delay(50); - } while (true); + } while (device->isConnected()); } /** @@ -291,7 +308,36 @@ static void do_saved(void) { menu_connect(false); } -static void menu_remote(Furble::Device *device) { +uint16_t disconnectDetect(void *private_data) { + FurbleCtx *fctx = (FurbleCtx *)private_data; + Furble::Device *device = fctx->device; + + if (device->isConnected()) + return 500; + + String buttons = ez.buttons.get(); + String header = ez.header.title(); + + NimBLEClient *pClient = NimBLEDevice::createClient(); + ezProgressBar progress_bar(FURBLE_STR, "Reconnecting ...", ""); + if (device->connect(pClient, progress_bar)) { + ez.screen.clear(); + ez.header.show(header); + ez.buttons.show(buttons); + + fctx->reconnected = true; + + ez.redraw(); + return 500; + } + + ez.screen.clear(); + // no recovery, restart device + esp_restart(); + return 0; +} + +static void menu_remote(FurbleCtx *fctx) { ez.backlight.inactivity(NEVER); ezMenu submenu(FURBLE_STR " - Connected"); submenu.buttons("OK#down"); @@ -300,19 +346,23 @@ static void menu_remote(Furble::Device *device) { submenu.addItem("Disconnect"); submenu.downOnLast("first"); + ez.addEvent(disconnectDetect, fctx, 500); + do { submenu.runOnce(); if (submenu.pickName() == "Shutter") { - remote_control(device); + remote_control(fctx); } if (submenu.pickName() == "Interval") { - remote_interval(device); + remote_interval(fctx); } } while (submenu.pickName() != "Disconnect"); - device->disconnect(); + ez.removeEvent(disconnectDetect); + + fctx->device->disconnect(); ez.backlight.inactivity(USER_SET); } @@ -329,21 +379,21 @@ static void menu_connect(bool save) { if (i == 0) return; - Furble::Device *device = connect_list[i - 1]; + FurbleCtx fctx = {connect_list[i - 1], false}; - update_geodata(device); + update_geodata(fctx.device); NimBLEClient *pClient = NimBLEDevice::createClient(); ezProgressBar progress_bar(FURBLE_STR, "Connecting ...", ""); - if (device->connect(pClient, progress_bar)) { + if (fctx.device->connect(pClient, progress_bar)) { if (save) { - device->save(); + fctx.device->save(); } - menu_remote(device); + menu_remote(&fctx); } } -static void menu_delete() { +static void menu_delete(void) { std::vector devices; ezMenu submenu(FURBLE_STR " - Delete"); submenu.buttons("OK#down"); @@ -394,8 +444,8 @@ void setup() { uint8_t width = 4 * M5.Lcd.textWidth("5") + ez.theme->header_hmargin * 2; ez.header.insert(CURRENT_POSITION, "current", width, current_draw_widget); ez.header.insert(GPS_HEADER_POSITION, "gps", ez.theme->header_height * 0.8, gps_draw_widget); - ez.addEvent(service_grove_gps, millis() + 500); - ez.addEvent(current_service, millis() + 500); + ez.addEvent(service_grove_gps, nullptr, millis() + 500); + ez.addEvent(current_service, nullptr, millis() + 500); NimBLEDevice::init(FURBLE_STR); NimBLEDevice::setSecurityAuth(true, true, true);