From 99ba761ca05fce1b47d66bf90ca6c6ac57814e6a Mon Sep 17 00:00:00 2001 From: Guo-Rong <5484552+gkoh@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:02:29 +0930 Subject: [PATCH 1/4] Implement infinite reconnect with cancel. Refactor a lot of things to support cancelling an active connection attempt. Significantly simplify the callback structure for the connection progress bar. Whilst here, fix possible null pointer dereferences in Fujifilm service and characteristic handling. --- include/furble_control.h | 17 +++-- include/furble_ui.h | 2 +- lib/M5ez/src/M5ez.cpp | 6 +- lib/M5ez/src/M5ez.h | 2 +- lib/furble/Camera.cpp | 15 ++--- lib/furble/Camera.h | 15 ++--- lib/furble/CanonEOS.cpp | 23 +++---- lib/furble/CanonEOS.h | 2 +- lib/furble/Fujifilm.cpp | 120 +++++++++++++++++++----------------- lib/furble/Fujifilm.h | 3 +- lib/furble/MobileDevice.cpp | 16 ++--- lib/furble/MobileDevice.h | 2 +- src/furble.cpp | 81 +++++++++++++++++------- src/furble_control.cpp | 103 +++++++++++++++++-------------- src/interval.cpp | 6 +- 15 files changed, 242 insertions(+), 171 deletions(-) diff --git a/include/furble_control.h b/include/furble_control.h index 9ce0ea5..2146a50 100644 --- a/include/furble_control.h +++ b/include/furble_control.h @@ -12,6 +12,7 @@ typedef enum { CONTROL_CMD_FOCUS_PRESS, CONTROL_CMD_FOCUS_RELEASE, CONTROL_CMD_GPS_UPDATE, + CONTROL_CMD_CONNECT, CONTROL_CMD_DISCONNECT, CONTROL_CMD_ERROR } control_cmd_t; @@ -28,11 +29,10 @@ class Control { Camera *getCamera(void); control_cmd_t getCommand(void); void sendCommand(control_cmd_t cmd); - const Camera::gps_t &getGPS(void); - const Camera::timesync_t &getTimesync(void); - void updateGPS(Camera::gps_t &gps, Camera::timesync_t ×ync); + void task(void); + private: QueueHandle_t m_Queue = NULL; Furble::Camera *m_Camera = NULL; @@ -61,13 +61,18 @@ class Control { /** * Are all active cameras still connected? */ - bool isConnected(void); + bool allConnected(void); /** * Get list of connected targets. */ const std::vector> &getTargets(void); + /** + * Connect to the specified camera. + */ + void connect(Camera *, esp_power_level_t power); + /** * Disconnect all connected cameras. */ @@ -81,6 +86,10 @@ class Control { private: QueueHandle_t m_Queue = NULL; std::vector> m_Targets; + + // Camera connects are serialised, the following track the last attempt + Camera *m_ConnectCamera = nullptr; + esp_power_level_t m_Power; }; }; // namespace Furble diff --git a/include/furble_ui.h b/include/furble_ui.h index 7d78927..46be1e2 100644 --- a/include/furble_ui.h +++ b/include/furble_ui.h @@ -5,7 +5,7 @@ struct FurbleCtx { Furble::Control *control; - bool reconnected; + bool cancelled; }; void vUITask(void *param); diff --git a/lib/M5ez/src/M5ez.cpp b/lib/M5ez/src/M5ez.cpp index 46a699c..2ba42d0 100644 --- a/lib/M5ez/src/M5ez.cpp +++ b/lib/M5ez/src/M5ez.cpp @@ -1058,10 +1058,14 @@ void M5ez::begin() { ez.settings.begin(); } -void M5ez::yield() { +void M5ez::yield(bool events) { vTaskDelay(1); // allow lower priority tasks to run ::yield(); // execute the Arduino yield in the root namespace M5.update(); + if (!events) { + return; + } + for (uint8_t n = 0; n < _events.size(); n++) { if (millis() > _events[n].when) { uint16_t r = (_events[n].function)(_events[n].context); diff --git a/lib/M5ez/src/M5ez.h b/lib/M5ez/src/M5ez.h index 35b7d0a..fb7244d 100644 --- a/lib/M5ez/src/M5ez.h +++ b/lib/M5ez/src/M5ez.h @@ -503,7 +503,7 @@ class M5ez { static void begin(); - static void yield(); + static void yield(bool events = true); static void addEvent(uint16_t (*function)(void *), void *context = nullptr, uint32_t when = 1); static void removeEvent(uint16_t (*function)(void *context)); diff --git a/lib/furble/Camera.cpp b/lib/furble/Camera.cpp index dd0cbbe..4c1992f 100644 --- a/lib/furble/Camera.cpp +++ b/lib/furble/Camera.cpp @@ -12,13 +12,16 @@ Camera::~Camera() { NimBLEDevice::deleteClient(m_Client); } -bool Camera::connect(esp_power_level_t power, progressFunc pFunc, void *pCtx) { +bool Camera::connect(esp_power_level_t power) { // try extending range by adjusting connection parameters - bool connected = this->connect(pFunc, pCtx); + bool connected = this->connect(); if (connected) { // Set BLE transmit power after connection is established. NimBLEDevice::setPower(power); m_Client->updateConnParams(m_MinInterval, m_MaxInterval, m_Latency, m_Timeout); + m_Connected = true; + } else { + m_Connected = false; } return connected; @@ -44,14 +47,12 @@ const NimBLEAddress &Camera::getAddress(void) const { return m_Address; } -void Camera::updateProgress(progressFunc pFunc, void *ctx, float value) { - if (pFunc != nullptr) { - (pFunc)(ctx, value); - } +float Camera::getConnectProgress(void) const { + return m_Progress.load(); } bool Camera::isConnected(void) const { - return m_Client->isConnected(); + return m_Connected && m_Client->isConnected(); } } // namespace Furble diff --git a/lib/furble/Camera.h b/lib/furble/Camera.h index 6006c31..bdc6c82 100644 --- a/lib/furble/Camera.h +++ b/lib/furble/Camera.h @@ -1,6 +1,8 @@ #ifndef CAMERA_H #define CAMERA_H +#include + #include #include #include @@ -9,9 +11,6 @@ #define MAX_NAME (64) -// Progress update function -typedef void(progressFunc(void *, float)); - namespace Furble { /** @@ -55,7 +54,7 @@ class Camera { /** * Wrapper for protected pure virtual Camera::connect(). */ - bool connect(esp_power_level_t power, progressFunc pFunc = nullptr, void *pCtx = nullptr); + bool connect(esp_power_level_t power); /** * Disconnect from the target. @@ -111,8 +110,11 @@ class Camera { const NimBLEAddress &getAddress(void) const; + float getConnectProgress(void) const; + protected: Camera(Type type); + std::atomic m_Progress; /** * Connect to the target camera such that it is ready for shutter control. @@ -122,13 +124,12 @@ class Camera { * * @return true if the client is now ready for shutter control */ - virtual bool connect(progressFunc pFunc = nullptr, void *pCtx = nullptr) = 0; + virtual bool connect(void) = 0; NimBLEAddress m_Address = NimBLEAddress{}; NimBLEClient *m_Client; std::string m_Name; - - void updateProgress(progressFunc pFunc, void *ctx, float value); + bool m_Connected = false; private: const uint16_t m_MinInterval = BLE_GAP_INITIAL_CONN_ITVL_MIN; diff --git a/lib/furble/CanonEOS.cpp b/lib/furble/CanonEOS.cpp index 45e634d..94d61b5 100644 --- a/lib/furble/CanonEOS.cpp +++ b/lib/furble/CanonEOS.cpp @@ -75,7 +75,7 @@ bool CanonEOS::write_prefix(NimBLEClient *pClient, * The EOS uses the 'just works' BLE bonding to pair, all bond management is * handled by the underlying NimBLE and ESP32 libraries. */ -bool CanonEOS::connect(progressFunc pFunc, void *pCtx) { +bool CanonEOS::connect(void) { if (NimBLEDevice::isBonded(m_Address)) { // Already bonded? Assume pair acceptance! m_PairResult = CANON_EOS_PAIR_ACCEPT; @@ -90,14 +90,14 @@ bool CanonEOS::connect(progressFunc pFunc, void *pCtx) { } ESP_LOGI(LOG_TAG, "Connected"); - updateProgress(pFunc, pCtx, 10.0f); + m_Progress = 10.0f; ESP_LOGI(LOG_TAG, "Securing"); if (!m_Client->secureConnection()) { return false; } ESP_LOGI(LOG_TAG, "Secured!"); - updateProgress(pFunc, pCtx, 20.0f); + m_Progress = 20.0f; NimBLERemoteService *pSvc = m_Client->getService(CANON_EOS_SVC_IDEN_UUID); if (pSvc) { @@ -116,21 +116,21 @@ bool CanonEOS::connect(progressFunc pFunc, void *pCtx) { (uint8_t *)name, strlen(name))) return false; - updateProgress(pFunc, pCtx, 30.0f); + m_Progress = 30.0f; ESP_LOGI(LOG_TAG, "Identifying 2!"); if (!write_prefix(m_Client, CANON_EOS_SVC_IDEN_UUID, CANON_EOS_CHR_IDEN_UUID, 0x03, m_Uuid.uint8, UUID128_LEN)) return false; - updateProgress(pFunc, pCtx, 40.0f); + m_Progress = 40.0f; ESP_LOGI(LOG_TAG, "Identifying 3!"); if (!write_prefix(m_Client, CANON_EOS_SVC_IDEN_UUID, CANON_EOS_CHR_IDEN_UUID, 0x04, (uint8_t *)name, strlen(name))) return false; - updateProgress(pFunc, pCtx, 50.0f); + m_Progress = 50.0f; ESP_LOGI(LOG_TAG, "Identifying 4!"); @@ -138,15 +138,14 @@ bool CanonEOS::connect(progressFunc pFunc, void *pCtx) { if (!write_prefix(m_Client, CANON_EOS_SVC_IDEN_UUID, CANON_EOS_CHR_IDEN_UUID, 0x05, &x, 1)) return false; - updateProgress(pFunc, pCtx, 60.0f); + m_Progress = 60.0f; ESP_LOGI(LOG_TAG, "Identifying 5!"); // Give the user 60s to confirm/deny pairing ESP_LOGI(LOG_TAG, "Waiting for user to confirm/deny pairing."); for (unsigned int i = 0; i < 60; i++) { - float progress = 70.0f + (float(i) / 6.0f); - updateProgress(pFunc, pCtx, progress); + m_Progress = 70.0f + (float(i) / 6.0f); if (m_PairResult != 0x00) { break; } @@ -166,7 +165,7 @@ bool CanonEOS::connect(progressFunc pFunc, void *pCtx) { if (!write_value(m_Client, CANON_EOS_SVC_IDEN_UUID, CANON_EOS_CHR_IDEN_UUID, &x, 1)) return false; - updateProgress(pFunc, pCtx, 80.0f); + m_Progress = 80.0f; ESP_LOGI(LOG_TAG, "Switching mode!"); @@ -176,7 +175,7 @@ bool CanonEOS::connect(progressFunc pFunc, void *pCtx) { return false; ESP_LOGI(LOG_TAG, "Done!"); - updateProgress(pFunc, pCtx, 100.0f); + m_Progress = 100.0f; return true; } @@ -207,6 +206,8 @@ void CanonEOS::updateGeoData(const gps_t &gps, const timesync_t ×ync) { } void CanonEOS::disconnect(void) { + m_Progress = 0.0f; + m_Connected = false; m_Client->disconnect(); } diff --git a/lib/furble/CanonEOS.h b/lib/furble/CanonEOS.h index 8bfd6d9..3b7a734 100644 --- a/lib/furble/CanonEOS.h +++ b/lib/furble/CanonEOS.h @@ -55,7 +55,7 @@ class CanonEOS: public Camera { uint8_t *data, size_t length); - bool connect(progressFunc pFunc = nullptr, void *pCtx = nullptr) override; + bool connect(void) override; void shutterPress(void) override; void shutterRelease(void) override; void focusPress(void) override; diff --git a/lib/furble/Fujifilm.cpp b/lib/furble/Fujifilm.cpp index 336bae5..c7796ed 100644 --- a/lib/furble/Fujifilm.cpp +++ b/lib/furble/Fujifilm.cpp @@ -82,6 +82,25 @@ void Fujifilm::notify(BLERemoteCharacteristic *pChr, uint8_t *pData, size_t leng } } +bool Fujifilm::subscribe(const NimBLEUUID &svc, const NimBLEUUID &chr, bool notifications) { + auto pSvc = m_Client->getService(svc); + if (pSvc == nullptr) { + return false; + } + + auto pChr = pSvc->getCharacteristic(chr); + if (pChr == nullptr) { + return false; + } + + return pChr->subscribe( + notifications, + [this](BLERemoteCharacteristic *pChr, uint8_t *pData, size_t length, bool isNotify) { + this->notify(pChr, pData, length, isNotify); + }, + true); +} + Fujifilm::Fujifilm(const void *data, size_t len) : Camera(Type::FUJIFILM) { if (len != sizeof(fujifilm_t)) abort(); @@ -133,10 +152,8 @@ bool Fujifilm::matches(const NimBLEAdvertisedDevice *pDevice) { * is what we use to identify ourselves upfront and during subsequent * re-pairing. */ -bool Fujifilm::connect(progressFunc pFunc, void *pCtx) { - using namespace std::placeholders; - - updateProgress(pFunc, pCtx, 10.0f); +bool Fujifilm::connect(void) { + m_Progress = 10.0f; NimBLERemoteService *pSvc = nullptr; NimBLERemoteCharacteristic *pChr = nullptr; @@ -146,7 +163,7 @@ bool Fujifilm::connect(progressFunc pFunc, void *pCtx) { return false; ESP_LOGI(LOG_TAG, "Connected"); - updateProgress(pFunc, pCtx, 20.0f); + m_Progress = 20.0f; pSvc = m_Client->getService(FUJIFILM_SVC_PAIR_UUID); if (pSvc == nullptr) return false; @@ -162,7 +179,7 @@ bool Fujifilm::connect(progressFunc pFunc, void *pCtx) { if (!pChr->writeValue(m_Token.data(), sizeof(m_Token), true)) return false; ESP_LOGI(LOG_TAG, "Paired!"); - updateProgress(pFunc, pCtx, 30.0f); + m_Progress = 30.0f; ESP_LOGI(LOG_TAG, "Identifying"); pChr = pSvc->getCharacteristic(FUJIFILM_CHR_IDEN_UUID); @@ -171,67 +188,50 @@ bool Fujifilm::connect(progressFunc pFunc, void *pCtx) { if (!pChr->writeValue(Device::getStringID(), true)) return false; ESP_LOGI(LOG_TAG, "Identified!"); - updateProgress(pFunc, pCtx, 40.0f); + m_Progress = 40.0f; - ESP_LOGI(LOG_TAG, "Configuring"); - pSvc = m_Client->getService(FUJIFILM_SVC_CONF_UUID); // indications - pSvc->getCharacteristic(FUJIFILM_CHR_IND1_UUID) - ->subscribe( - false, - [this](BLERemoteCharacteristic *pChr, uint8_t *pData, size_t length, bool isNotify) { - this->notify(pChr, pData, length, isNotify); - }, - true); - updateProgress(pFunc, pCtx, 50.0f); - - pSvc->getCharacteristic(FUJIFILM_CHR_IND2_UUID) - ->subscribe( - false, - [this](BLERemoteCharacteristic *pChr, uint8_t *pData, size_t length, bool isNotify) { - this->notify(pChr, pData, length, isNotify); - }, - true); + ESP_LOGI(LOG_TAG, "Configuring"); + if (!this->subscribe(FUJIFILM_SVC_CONF_UUID, FUJIFILM_CHR_IND1_UUID, false)) { + return false; + } + + if (!this->subscribe(FUJIFILM_SVC_CONF_UUID, FUJIFILM_CHR_IND2_UUID, false)) { + return false; + } + m_Progress = 50.0f; // wait for up to 5000ms callback for (unsigned int i = 0; i < 5000; i += 100) { if (m_Configured) { break; } - updateProgress(pFunc, pCtx, 50.0f + (((float)i / 5000.0f) * 10.0f)); + // 5000 / 100 = 50 + // 10 / 50 = 0.2 + float progress = m_Progress.load() + 0.2f; + m_Progress = progress; delay(100); } - updateProgress(pFunc, pCtx, 60.0f); + m_Progress = 60.0f; // notifications - pSvc->getCharacteristic(FUJIFILM_CHR_NOT1_UUID) - ->subscribe( - true, - [this](BLERemoteCharacteristic *pChr, uint8_t *pData, size_t length, bool isNotify) { - this->notify(pChr, pData, length, isNotify); - }, - true); - - updateProgress(pFunc, pCtx, 70.0f); - pSvc->getCharacteristic(FUJIFILM_CHR_NOT2_UUID) - ->subscribe( - true, - [this](BLERemoteCharacteristic *pChr, uint8_t *pData, size_t length, bool isNotify) { - this->notify(pChr, pData, length, isNotify); - }, - true); - - updateProgress(pFunc, pCtx, 80.0f); - pSvc->getCharacteristic(FUJIFILM_CHR_IND3_UUID) - ->subscribe( - false, - [this](BLERemoteCharacteristic *pChr, uint8_t *pData, size_t length, bool isNotify) { - this->notify(pChr, pData, length, isNotify); - }, - true); - ESP_LOGI(LOG_TAG, "Configured"); + if (!this->subscribe(FUJIFILM_SVC_CONF_UUID, FUJIFILM_CHR_NOT1_UUID, true)) { + return false; + } + m_Progress = 70.0f; - updateProgress(pFunc, pCtx, 100.0f); + if (!this->subscribe(FUJIFILM_SVC_CONF_UUID, FUJIFILM_CHR_NOT2_UUID, true)) { + return false; + } + m_Progress = 80.0f; + + if (!this->subscribe(FUJIFILM_SVC_CONF_UUID, FUJIFILM_CHR_IND3_UUID, false)) { + return false; + } + m_Progress = 100.0f; + + ESP_LOGI(LOG_TAG, "Configured"); + ESP_LOGI(LOG_TAG, "Connected"); return true; } @@ -240,9 +240,13 @@ template void Fujifilm::sendShutterCommand(const std::array &cmd, const std::array ¶m) { NimBLERemoteService *pSvc = m_Client->getService(FUJIFILM_SVC_SHUTTER_UUID); - NimBLERemoteCharacteristic *pChr = pSvc->getCharacteristic(FUJIFILM_CHR_SHUTTER_UUID); - pChr->writeValue(cmd.data(), sizeof(cmd), true); - pChr->writeValue(param.data(), sizeof(cmd), true); + if (pSvc) { + NimBLERemoteCharacteristic *pChr = pSvc->getCharacteristic(FUJIFILM_CHR_SHUTTER_UUID); + if ((pChr != nullptr) && pChr->canWrite()) { + pChr->writeValue(cmd.data(), sizeof(cmd), true); + pChr->writeValue(param.data(), sizeof(cmd), true); + } + } } void Fujifilm::shutterPress(void) { @@ -312,7 +316,9 @@ void Fujifilm::print(void) { } void Fujifilm::disconnect(void) { + m_Progress = 0.0f; m_Client->disconnect(); + m_Connected = false; } size_t Fujifilm::getSerialisedBytes(void) const { diff --git a/lib/furble/Fujifilm.h b/lib/furble/Fujifilm.h index 3aaa577..e55b9c6 100644 --- a/lib/furble/Fujifilm.h +++ b/lib/furble/Fujifilm.h @@ -22,7 +22,7 @@ class Fujifilm: public Camera { */ static bool matches(const NimBLEAdvertisedDevice *pDevice); - bool connect(progressFunc pFunc = nullptr, void *pCtx = nullptr) override; + bool connect(void) override; void shutterPress(void) override; void shutterRelease(void) override; void focusPress(void) override; @@ -58,6 +58,7 @@ class Fujifilm: public Camera { void print(void); void notify(NimBLERemoteCharacteristic *, uint8_t *, size_t, bool); + bool subscribe(const NimBLEUUID &svc, const NimBLEUUID &chr, bool notifications); void sendGeoData(const gps_t &gps, const timesync_t ×ync); template diff --git a/lib/furble/MobileDevice.cpp b/lib/furble/MobileDevice.cpp index b6fd246..669cd63 100644 --- a/lib/furble/MobileDevice.cpp +++ b/lib/furble/MobileDevice.cpp @@ -43,17 +43,17 @@ bool MobileDevice::matches(NimBLEAdvertisedDevice *pDevice) { * connection. * All this logic is encapsulated in the HIDServer class. */ -bool MobileDevice::connect(progressFunc pFunc, void *pCtx) { +bool MobileDevice::connect(void) { unsigned int timeout_secs = 60; - float progress = 0.0f; - updateProgress(pFunc, pCtx, progress); + + m_Progress = 0.0f; m_HIDServer->start(&m_Address); ESP_LOGI(LOG_TAG, "Waiting for %us for connection from %s", timeout_secs, m_Name.c_str()); while (--timeout_secs && !isConnected()) { - progress += 1.0f; - updateProgress(pFunc, pCtx, progress); + float progress = m_Progress.load() + 1.0f; + m_Progress = progress; delay(1000); }; @@ -63,9 +63,9 @@ bool MobileDevice::connect(progressFunc pFunc, void *pCtx) { } ESP_LOGI(LOG_TAG, "Connected to %s.", m_Name.c_str()); - progress = 100.0f; - updateProgress(pFunc, pCtx, progress); + m_Progress = 100.0f; m_HIDServer->stop(); + return true; } @@ -95,6 +95,8 @@ void MobileDevice::updateGeoData(const gps_t &gps, const timesync_t ×ync) { } void MobileDevice::disconnect(void) { + m_Progress = 0.0f; + m_Connected = false; m_HIDServer->disconnect(m_Address); } diff --git a/lib/furble/MobileDevice.h b/lib/furble/MobileDevice.h index 17d9c44..cb97e34 100644 --- a/lib/furble/MobileDevice.h +++ b/lib/furble/MobileDevice.h @@ -18,7 +18,7 @@ class MobileDevice: public Camera { static bool matches(NimBLEAdvertisedDevice *pDevice); - bool connect(progressFunc pFunc = nullptr, void *pCtx = nullptr) override; + bool connect(void) override; void shutterPress(void) override; void shutterRelease(void) override; void focusPress(void) override; diff --git a/src/furble.cpp b/src/furble.cpp index 43a566b..b8114bf 100644 --- a/src/furble.cpp +++ b/src/furble.cpp @@ -19,6 +19,13 @@ typedef struct { #define CONNECT_STAR "Connect *" +/** + * Get seconds since boot. + */ +static uint64_t get_time_secs(void) { + return (esp_timer_get_time() / 1000000LL); +} + /** * Progress bar update function. */ @@ -91,10 +98,7 @@ static void remote_control(FurbleCtx *fctx) { do { ez.yield(); - if (fctx->reconnected) { - show_shutter_control(shutter_lock, shutter_lock_start_ms); - fctx->reconnected = false; - } + // show_shutter_control(shutter_lock, shutter_lock_start_ms); if (M5.BtnPWR.wasClicked() || M5.BtnC.wasPressed()) { if (shutter_lock) { @@ -143,7 +147,35 @@ static void remote_control(FurbleCtx *fctx) { continue; } } - } while (control->isConnected()); + } while (!fctx->cancelled && control->allConnected()); +} + +/** + * Attempt a connection with a progress bar. + * + * @return false if the attempt was cancelled + */ +static bool connect_with_progress(Furble::Control *control, Furble::Camera *camera) { + ezProgressBar progress_bar(FURBLE_STR, {std::string("Connecting to"), camera->getName()}, + {"", "Cancel"}); + control->connect(camera, settings_load_esp_tx_power()); + + // Wait up to 30 seconds + uint64_t timeout = get_time_secs() + 30; + while (get_time_secs() <= timeout) { + progress_bar.value(camera->getConnectProgress()); + ez.yield(false); + if (M5.BtnB.wasClicked()) { + // issue a cancel to the backend stack + ble_gap_conn_cancel(); + return false; + } + if (camera->isConnected()) { + return true; + } + } + + return true; } /** @@ -155,28 +187,30 @@ static uint16_t statusRefresh(void *context) { FurbleCtx *fctx = static_cast(context); auto *control = fctx->control; - if (control->isConnected()) { + if (control->allConnected()) { furble_gps_update(control); return 500; } + if (fctx->cancelled) { + return 500; + } + auto buttons = ez.buttons.get(); std::string header = ez.header.title(); for (const auto &target : control->getTargets()) { auto camera = target->getCamera(); if (!camera->isConnected()) { - ezProgressBar progress_bar(FURBLE_STR, {"Reconnecting ..."}, {""}); - if (camera->connect(settings_load_esp_tx_power(), &update_progress_bar, &progress_bar)) { - ez.screen.clear(); - ez.header.show(header); - ez.buttons.show(buttons); - - fctx->reconnected = true; - - ez.redraw(); - return 500; + if (!connect_with_progress(control, camera)) { + fctx->cancelled = true; } + ez.screen.clear(); + ez.header.show(header); + ez.buttons.show(buttons); + ez.redraw(); + + return 500; } } @@ -198,6 +232,10 @@ static void menu_remote(FurbleCtx *fctx) { ez.addEvent(statusRefresh, fctx, 500); do { + if (fctx->cancelled) { + break; + } + submenu.runOnce(); if (submenu.pickName() == "Shutter") { @@ -224,9 +262,8 @@ static bool do_connect(ezMenu *menu, void *context) { for (int n = 0; n < Furble::CameraList::size(); n++) { auto *camera = Furble::CameraList::get(n); if (camera->isActive()) { - ezProgressBar progress_bar(FURBLE_STR, {std::string("Connecting to ") + camera->getName()}, - {""}); - if (camera->connect(settings_load_esp_tx_power(), &update_progress_bar, &progress_bar)) { + connect_with_progress(ctx->control, camera); + if (camera->isConnected()) { ctx->control->addActive(camera); } else { // Fail all if any connect fails @@ -236,15 +273,15 @@ static bool do_connect(ezMenu *menu, void *context) { } } else { auto *camera = Furble::CameraList::get(menu->pick() - 1); + connect_with_progress(ctx->control, camera); - ezProgressBar progress_bar(FURBLE_STR, {std::string("Connecting to ") + camera->getName()}, - {""}); - if (camera->connect(settings_load_esp_tx_power(), &update_progress_bar, &progress_bar)) { + if (camera->isConnected()) { if (ctx->scan) { Furble::CameraList::save(camera); } ctx->control->addActive(camera); } else { + camera->disconnect(); return false; } } diff --git a/src/furble_control.cpp b/src/furble_control.cpp index a769b4c..f5cc9e7 100644 --- a/src/furble_control.cpp +++ b/src/furble_control.cpp @@ -4,44 +4,7 @@ static void target_task(void *param) { auto *target = static_cast(param); - - Furble::Camera *camera = target->getCamera(); - const char *name = camera->getName().c_str(); - - while (true) { - control_cmd_t cmd = target->getCommand(); - switch (cmd) { - case CONTROL_CMD_SHUTTER_PRESS: - ESP_LOGI(LOG_TAG, "shutterPress(%s)", name); - camera->shutterPress(); - break; - case CONTROL_CMD_SHUTTER_RELEASE: - ESP_LOGI(LOG_TAG, "shutterRelease(%s)", name); - camera->shutterRelease(); - break; - case CONTROL_CMD_FOCUS_PRESS: - ESP_LOGI(LOG_TAG, "focusPress(%s)", name); - camera->focusPress(); - break; - case CONTROL_CMD_FOCUS_RELEASE: - ESP_LOGI(LOG_TAG, "focusRelease(%s)", name); - camera->focusRelease(); - break; - case CONTROL_CMD_GPS_UPDATE: - ESP_LOGI(LOG_TAG, "updateGeoData(%s)", name); - camera->updateGeoData(target->getGPS(), target->getTimesync()); - break; - case CONTROL_CMD_ERROR: - // ignore continue - break; - case CONTROL_CMD_DISCONNECT: - goto task_exit; - default: - ESP_LOGE(LOG_TAG, "Invalid control command %d.", cmd); - } - } -task_exit: - vTaskDelete(NULL); + target->task(); } namespace Furble { @@ -77,19 +40,50 @@ control_cmd_t Control::Target::getCommand(void) { return cmd; } -const Camera::gps_t &Control::Target::getGPS(void) { - return m_GPS; -} - -const Camera::timesync_t &Control::Target::getTimesync(void) { - return m_Timesync; -} - void Control::Target::updateGPS(Camera::gps_t &gps, Camera::timesync_t ×ync) { m_GPS = gps; m_Timesync = timesync; } +void Control::Target::task(void) { + const char *name = m_Camera->getName().c_str(); + + while (true) { + control_cmd_t cmd = this->getCommand(); + switch (cmd) { + case CONTROL_CMD_SHUTTER_PRESS: + ESP_LOGI(LOG_TAG, "shutterPress(%s)", name); + m_Camera->shutterPress(); + break; + case CONTROL_CMD_SHUTTER_RELEASE: + ESP_LOGI(LOG_TAG, "shutterRelease(%s)", name); + m_Camera->shutterRelease(); + break; + case CONTROL_CMD_FOCUS_PRESS: + ESP_LOGI(LOG_TAG, "focusPress(%s)", name); + m_Camera->focusPress(); + break; + case CONTROL_CMD_FOCUS_RELEASE: + ESP_LOGI(LOG_TAG, "focusRelease(%s)", name); + m_Camera->focusRelease(); + break; + case CONTROL_CMD_GPS_UPDATE: + ESP_LOGI(LOG_TAG, "updateGeoData(%s)", name); + m_Camera->updateGeoData(m_GPS, m_Timesync); + break; + case CONTROL_CMD_DISCONNECT: + goto task_exit; + case CONTROL_CMD_ERROR: + // ignore continue + break; + default: + ESP_LOGE(LOG_TAG, "Invalid control command %d.", cmd); + } + } +task_exit: + vTaskDelete(NULL); +} + Control::Control(void) { m_Queue = xQueueCreate(CONTROL_CMD_QUEUE_LEN, sizeof(control_cmd_t)); if (m_Queue == NULL) { @@ -108,6 +102,15 @@ void Control::task(void) { control_cmd_t cmd; BaseType_t ret = xQueueReceive(m_Queue, &cmd, pdMS_TO_TICKS(50)); if (ret == pdTRUE) { + if (cmd == CONTROL_CMD_CONNECT) { + ESP_LOGI(LOG_TAG, "connect(%s)", m_ConnectCamera->getName().c_str()); + if (m_ConnectCamera == nullptr) { + abort(); + } + m_ConnectCamera->connect(m_Power); + continue; + } + for (const auto &target : m_Targets) { switch (cmd) { case CONTROL_CMD_SHUTTER_PRESS: @@ -139,7 +142,7 @@ BaseType_t Control::updateGPS(Camera::gps_t &gps, Camera::timesync_t ×ync) return xQueueSend(m_Queue, &cmd, 0); } -bool Control::isConnected(void) { +bool Control::allConnected(void) { for (const auto &target : m_Targets) { if (!target->getCamera()->isConnected()) { return false; @@ -153,6 +156,12 @@ const std::vector> &Control::getTargets(void) { return m_Targets; } +void Control::connect(Camera *camera, esp_power_level_t power) { + m_ConnectCamera = camera; + m_Power = power; + this->sendCommand(CONTROL_CMD_CONNECT); +} + void Control::disconnect(void) { for (const auto &target : m_Targets) { target->sendCommand(CONTROL_CMD_DISCONNECT); diff --git a/src/interval.cpp b/src/interval.cpp index 04af6c7..4a1aed7 100644 --- a/src/interval.cpp +++ b/src/interval.cpp @@ -88,8 +88,8 @@ static void do_interval(FurbleCtx *fctx, const interval_t &interval) { ez.yield(); now = millis(); - if (fctx->reconnected) { - fctx->reconnected = false; + if (fctx->cancelled) { + break; } switch (state) { @@ -135,7 +135,7 @@ static void do_interval(FurbleCtx *fctx, const interval_t &interval) { } display_interval_msg(state, icount, interval.count, now, next); - } while (active && control->isConnected()); + } while (active && control->allConnected()); ez.backlight.inactivity(USER_SET); } From 8fb37a840d444ab1aae88c783d59cd0c2d60dac3 Mon Sep 17 00:00:00 2001 From: Hijae Song Date: Thu, 3 Oct 2024 11:31:27 +0900 Subject: [PATCH 2/4] (#129) Reconnect without timeout --- README.md | 13 +++++++++++++ include/settings.h | 3 +++ src/furble.cpp | 18 ++++++++++++++++++ src/settings.cpp | 19 +++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/README.md b/README.md index 740cbef..fe2efbd 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,19 @@ WARNING: * mobile device connections are extremely finnicky * multi-connect involving mobile devices is not well tested and can easily crash +### Infinite-ReConnect + +This is useful for using furble as a passive, always on GPS data source. +With this, the camera will attempt to reconnect indefinitely. +You don't need to turn on this setting if you are actively using furble. + +To use: +* Enable `Settings->Infinite-ReConnect` + +WARNING: +* this will not be kind to battery life +* You must use the power button to shut down + ## Motivation I found current smartphone apps for basic wireless remote shutter control to be diff --git a/include/settings.h b/include/settings.h index ebf05cb..a494748 100644 --- a/include/settings.h +++ b/include/settings.h @@ -9,6 +9,9 @@ void settings_menu_tx_power(void); esp_power_level_t settings_load_esp_tx_power(void); +bool settings_load_reconnect(void); +void settings_save_reconnect(bool reconnect); + bool settings_load_gps(void); void settings_menu_gps(void); diff --git a/src/furble.cpp b/src/furble.cpp index b8114bf..7630f9b 100644 --- a/src/furble.cpp +++ b/src/furble.cpp @@ -420,10 +420,25 @@ static bool multiconnect_toggle(ezMenu *menu, void *context) { return true; } +/** + * Toggle Infinite-ReConnect menu setting. + */ +static bool reconnect_toggle(ezMenu *menu, void *context) { + bool *reconnect = static_cast(context); + *reconnect = !*reconnect; + menu->setCaption("reconnectonoff", + std::string("Infinite-ReConnect\t") + (*reconnect ? "ON" : "OFF")); + + settings_save_reconnect(*reconnect); + + return true; +} + static void menu_settings(void) { ezMenu submenu(FURBLE_STR " - Settings"); bool multiconnect = settings_load_multiconnect(); + bool reconnect = settings_load_reconnect(); submenu.buttons({"OK", "down"}); submenu.addItem("Backlight", "", ez.backlight.menu); @@ -432,6 +447,9 @@ static void menu_settings(void) { submenu.addItem("multiconnectonoff", std::string("Multi-Connect\t") + (multiconnect ? "ON" : "OFF"), nullptr, &multiconnect, multiconnect_toggle); + submenu.addItem("reconnectonoff", + std::string("Infinite-ReConnect\t") + (reconnect ? "ON" : "OFF"), nullptr, + &reconnect, reconnect_toggle); submenu.addItem("Theme", "", ez.theme->menu); submenu.addItem("Transmit Power", "", settings_menu_tx_power); submenu.addItem("About", "", about); diff --git a/src/settings.cpp b/src/settings.cpp index ecb0a00..65e27bc 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -14,6 +14,7 @@ const char *PREFS_TX_POWER = "txpower"; const char *PREFS_GPS = "gps"; const char *PREFS_INTERVAL = "interval"; const char *PREFS_MULTICONNECT = "multiconnect"; +const char *PREFS_RECONNECT = "reconnect"; /** * Save BLE transmit power to preferences. @@ -318,3 +319,21 @@ void settings_save_multiconnect(bool multiconnect) { prefs.putBool(PREFS_MULTICONNECT, multiconnect); prefs.end(); } + +bool settings_load_reconnect(void) { + Preferences prefs; + + prefs.begin(FURBLE_STR, true); + bool reconnect = prefs.getBool(PREFS_RECONNECT, false); + prefs.end(); + + return reconnect; +} + +void settings_save_reconnect(bool reconnect) { + Preferences prefs; + + prefs.begin(FURBLE_STR, false); + prefs.putBool(PREFS_RECONNECT, reconnect); + prefs.end(); +} \ No newline at end of file From c629a10d7d39592f15d2659c589250913e3bc353 Mon Sep 17 00:00:00 2001 From: Hijae Song Date: Thu, 3 Oct 2024 11:42:28 +0900 Subject: [PATCH 3/4] (#129) Enhance Warning --- README.md | 2 +- src/furble.cpp | 31 +++++++++++++++---------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index fe2efbd..a809986 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ To use: WARNING: * this will not be kind to battery life -* You must use the power button to shut down +* You must Forceful power off by holding the power button ## Motivation diff --git a/src/furble.cpp b/src/furble.cpp index 7630f9b..28bf819 100644 --- a/src/furble.cpp +++ b/src/furble.cpp @@ -26,14 +26,6 @@ static uint64_t get_time_secs(void) { return (esp_timer_get_time() / 1000000LL); } -/** - * Progress bar update function. - */ -void update_progress_bar(void *ctx, float value) { - ezProgressBar *progress_bar = static_cast(ctx); - progress_bar->value(value); -} - /** * Display the version. */ @@ -98,8 +90,6 @@ static void remote_control(FurbleCtx *fctx) { do { ez.yield(); - // show_shutter_control(shutter_lock, shutter_lock_start_ms); - if (M5.BtnPWR.wasClicked() || M5.BtnC.wasPressed()) { if (shutter_lock) { // ensure shutter is released on exit @@ -187,7 +177,10 @@ static uint16_t statusRefresh(void *context) { FurbleCtx *fctx = static_cast(context); auto *control = fctx->control; + static uint8_t reconnect_count = 0; + if (control->allConnected()) { + reconnect_count = 0; furble_gps_update(control); return 500; } @@ -196,13 +189,19 @@ static uint16_t statusRefresh(void *context) { return 500; } + bool reconnect = settings_load_reconnect(); auto buttons = ez.buttons.get(); std::string header = ez.header.title(); for (const auto &target : control->getTargets()) { auto camera = target->getCamera(); if (!camera->isConnected()) { - if (!connect_with_progress(control, camera)) { + reconnect_count++; + if ((reconnect_count > 1) && !reconnect) { + fctx->cancelled = true; + } + + if (!fctx->cancelled && !connect_with_progress(control, camera)) { fctx->cancelled = true; } ez.screen.clear(); @@ -232,12 +231,12 @@ static void menu_remote(FurbleCtx *fctx) { ez.addEvent(statusRefresh, fctx, 500); do { + submenu.runOnce(); + if (fctx->cancelled) { break; } - submenu.runOnce(); - if (submenu.pickName() == "Shutter") { remote_control(fctx); } @@ -443,13 +442,13 @@ static void menu_settings(void) { submenu.buttons({"OK", "down"}); submenu.addItem("Backlight", "", ez.backlight.menu); submenu.addItem("GPS", "", settings_menu_gps); + submenu.addItem("reconnectonoff", + std::string("Infinite-ReConnect\t") + (reconnect ? "ON" : "OFF"), nullptr, + &reconnect, reconnect_toggle); submenu.addItem("Intervalometer", "", settings_menu_interval); submenu.addItem("multiconnectonoff", std::string("Multi-Connect\t") + (multiconnect ? "ON" : "OFF"), nullptr, &multiconnect, multiconnect_toggle); - submenu.addItem("reconnectonoff", - std::string("Infinite-ReConnect\t") + (reconnect ? "ON" : "OFF"), nullptr, - &reconnect, reconnect_toggle); submenu.addItem("Theme", "", ez.theme->menu); submenu.addItem("Transmit Power", "", settings_menu_tx_power); submenu.addItem("About", "", about); From 29691a38a24e5398c33c74a522fe392f36b7074f Mon Sep 17 00:00:00 2001 From: Guo-Rong <5484552+gkoh@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:02:35 +0930 Subject: [PATCH 4/4] Remove stale warning in README. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a809986..f53233b 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,6 @@ To use: WARNING: * this will not be kind to battery life -* You must Forceful power off by holding the power button ## Motivation