diff --git a/README.md b/README.md index 984109c55..867a795b5 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,11 @@ `ConfigItem`. Web configuration cards are now only rendered for objects that have been wrapped in a `ConfigItem` call. +- [Smart pointers](https://medium.com/@lucky_rydar/guide-over-smart-pointers-in-c-46ed8b04448c) + are used internally throughout the project. This reduces the risk of memory + leaks or crashes due to dangling pointers. It is also possible, but not + mandatory, to use smart pointers in your own code. + ### Migrating Existing Projects - Update your project's `platformio.ini` file to use the new version of SensESP: diff --git a/examples/freertos_tasks.cpp b/examples/freertos_tasks.cpp index 7c9eb6836..6183dbb42 100644 --- a/examples/freertos_tasks.cpp +++ b/examples/freertos_tasks.cpp @@ -36,32 +36,37 @@ void setup() { SetupLogging(); SensESPMinimalAppBuilder builder; - SensESPMinimalApp *sensesp_app = builder.set_hostname("async")->get_app(); + auto sensesp_app = builder.set_hostname("async")->get_app(); - auto *networking = new Networking("/system/networking", "", ""); - auto *http_server = new HTTPServer(); + auto networking = std::make_shared("/system/networking", "", ""); + auto http_server = std::make_shared(); // create the SK delta object - auto sk_delta_queue_ = new SKDeltaQueue(); + auto sk_delta_queue_ = std::make_shared(); // create the websocket client - auto ws_client_ = new SKWSClient("/system/sk", sk_delta_queue_, "", 0); + auto ws_client_ = + std::make_shared("/system/sk", sk_delta_queue_, "", 0); - ws_client_->connect_to( - new LambdaConsumer([](SKWSConnectionState input) { + auto output_consumer = std::make_shared>( + [](SKWSConnectionState input) { ESP_LOGD("Example", "SKWSConnectionState: %d", input); - })); + }); + + ws_client_->connect_to(output_consumer); // create the MDNS discovery object - auto mdns_discovery_ = new MDNSDiscovery(); + auto mdns_discovery_ = std::make_shared(); // create a system status controller and a led blinker - auto *system_status_controller = new SystemStatusController(); - auto *system_status_led = new SystemStatusLed(LED_BUILTIN); + auto system_status_controller = std::make_shared(); + auto system_status_led = std::make_shared(LED_BUILTIN); - system_status_controller->connect_to(system_status_led); - ws_client_->get_delta_tx_count_producer().connect_to(system_status_led); + system_status_controller->connect_to( + system_status_led->get_system_status_consumer()); + ws_client_->get_delta_tx_count_producer().connect_to( + system_status_led->get_delta_tx_count_consumer()); // create a new task for toggling the output pin diff --git a/examples/join_and_zip.cpp b/examples/join_and_zip.cpp index 29a1ada64..937ae5dff 100644 --- a/examples/join_and_zip.cpp +++ b/examples/join_and_zip.cpp @@ -24,7 +24,7 @@ using namespace sensesp; -SensESPMinimalApp* sensesp_app; +std::shared_ptr sensesp_app; void setup() { SetupLogging(); diff --git a/examples/minimal_app.cpp b/examples/minimal_app.cpp index d492734f3..2f614619a 100644 --- a/examples/minimal_app.cpp +++ b/examples/minimal_app.cpp @@ -35,27 +35,32 @@ void setup() { // manually create Networking and HTTPServer objects to enable // the HTTP configuration interface - auto* networking = new Networking("/system/networking", "", ""); - auto* http_server = new HTTPServer(); + auto networking = std::make_shared("/system/networking", "", ""); + auto http_server = std::make_shared(); - auto* digin1 = new DigitalInputCounter(input_pin1, INPUT, RISING, read_delay); - auto* digin2 = new DigitalInputCounter(input_pin2, INPUT, CHANGE, read_delay); + auto digin1 = std::make_shared(input_pin1, INPUT, RISING, + read_delay); + auto digin2 = std::make_shared(input_pin2, INPUT, CHANGE, + read_delay); - auto* scaled1 = new Linear(2, 1, "/digin1/scale"); - auto* scaled2 = new Linear(4, -1, "/digin2/scale"); + auto scaled1 = std::make_shared(2, 1, "/digin1/scale"); + auto scaled2 = std::make_shared(4, -1, "/digin2/scale"); digin1->connect_to(scaled1); - scaled1->connect_to(new LambdaTransform([](int input) { - Serial.printf("millis: %d\n", millis()); - Serial.printf("Counter 1: %d\n", input); - return input; - })); - - digin2->connect_to(scaled2)->connect_to( - new LambdaTransform([](int input) { + auto lambda_transform1 = + std::make_shared>([](int input) { + Serial.printf("millis: %d\n", millis()); + Serial.printf("Counter 1: %d\n", input); + return input; + }); + scaled1->connect_to(lambda_transform1); + auto lambda_transform2 = + std::make_shared>([](int input) { Serial.printf("Counter 2: %d\n", input); return input; - })); + }); + + digin2->connect_to(scaled2)->connect_to(lambda_transform2); pinMode(output_pin1, OUTPUT); event_loop()->onRepeat( diff --git a/examples/raw_json.cpp b/examples/raw_json.cpp index f5d384c61..bc0110d2c 100644 --- a/examples/raw_json.cpp +++ b/examples/raw_json.cpp @@ -15,9 +15,9 @@ void setup() { SetupLogging(); SensESPAppBuilder builder; - SensESPApp *sensesp_app = builder.set_hostname("json_demo") - ->set_wifi_client("Hat Labs Sensors", "kanneluuri2406") - ->get_app(); + auto sensesp_app = builder.set_hostname("json_demo") + ->set_wifi_client("Hat Labs Sensors", "kanneluuri2406") + ->get_app(); event_loop()->onRepeat(1000, []() { toggler.set(!toggler.get()); }); diff --git a/examples/repeat_transform.cpp b/examples/repeat_transform.cpp index f4081fcc6..0cd328a16 100644 --- a/examples/repeat_transform.cpp +++ b/examples/repeat_transform.cpp @@ -24,7 +24,7 @@ using namespace sensesp; -SensESPMinimalApp* sensesp_app; +std::shared_ptr sensesp_app; void setup() { SetupLogging(); diff --git a/examples/smart_switch/remote_switch_example.cpp b/examples/smart_switch/remote_switch_example.cpp index bcee09512..ca2b9b15f 100644 --- a/examples/smart_switch/remote_switch_example.cpp +++ b/examples/smart_switch/remote_switch_example.cpp @@ -78,7 +78,7 @@ void setup() { controller->connect_to(new BoolSKPutRequest(sk_path)); // Also connect the controller to an onboard LED... - controller->connect_to(led); + controller->connect_to(led->on_off_consumer_); // Connect a physical button that will feed manual click types into the // controller... @@ -92,7 +92,7 @@ void setup() { ->set_title("Click Type") ->set_sort_order(1000); - pr->connect_to(click_type)->connect_to(controller); + pr->connect_to(click_type)->connect_to(controller->click_consumer_); // In addition to the manual button "click types", a // SmartSwitchController accepts explicit state settings via @@ -105,7 +105,7 @@ void setup() { // sent across the Signal K network when the controlling device // confirms it has made the change in state. auto* sk_listener = new SKValueListener(sk_path); - sk_listener->connect_to(controller); + sk_listener->connect_to(controller->swich_consumer_); } void loop() { event_loop()->tick(); } diff --git a/examples/smart_switch/smart_switch_example.cpp b/examples/smart_switch/smart_switch_example.cpp index 2ecc59c6d..ebb37020e 100644 --- a/examples/smart_switch/smart_switch_example.cpp +++ b/examples/smart_switch/smart_switch_example.cpp @@ -47,7 +47,7 @@ void setup() { // Create the global SensESPApp() object. sensesp_app = builder.set_hostname("sk-engine-lights") ->set_sk_server("192.168.10.3", 3000) - ->set_wifi_client(client("YOUR_WIFI_SSID", "YOUR_WIFI_PASSWORD") + ->set_wifi_client("YOUR_WIFI_SSID", "YOUR_WIFI_PASSWORD") ->get_app(); // Define the SK Path that represents the load this device controls. @@ -73,9 +73,10 @@ void setup() { // electric light. Also connect this pin's state to an LED to get // a visual indicator of load's state. auto* load_switch = new DigitalOutput(PIN_RELAY); - load_switch->connect_to(new RgbLed(PIN_LED_R, PIN_LED_G, PIN_LED_B, - config_path_status_light, LED_ON_COLOR, - LED_OFF_COLOR)); + load_switch->connect_to( + (new RgbLed(PIN_LED_R, PIN_LED_G, PIN_LED_B, config_path_status_light, + LED_ON_COLOR, LED_OFF_COLOR)) + ->on_off_consumer_); // Create a switch controller to handle the user press logic and // connect it to the load switch... @@ -87,7 +88,8 @@ void setup() { DigitalInputState* btn = new DigitalInputState(PIN_BUTTON, INPUT, 100); PressRepeater* pr = new PressRepeater(); btn->connect_to(pr); - pr->connect_to(new ClickType(config_path_button_c))->connect_to(controller); + pr->connect_to(new ClickType(config_path_button_c)) + ->connect_to(controller->click_consumer_); // In addition to the manual button "click types", a // SmartSwitchController accepts explicit state settings via @@ -98,7 +100,7 @@ void setup() { // This allows any device on the SignalK network that can make // such a request to also control the state of our switch. auto* sk_listener = new StringSKPutRequestListener(sk_path); - sk_listener->connect_to(controller); + sk_listener->connect_to(controller->truthy_string_consumer_); // Finally, connect the load switch to an SKOutput so it reports its state // to the Signal K server. Since the load switch only reports its state diff --git a/platformio.ini b/platformio.ini index fac59fec7..dfb6217a9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,25 +30,25 @@ upload_speed = 2000000 monitor_speed = 115200 lib_deps = - mairas/ReactESP @ ^3.0.0 - bblanchon/ArduinoJson @ ^7.0.0 - pfeerick/elapsedMillis @ ^1.0.6 - bxparks/AceButton @ ^1.10.1 + mairas/ReactESP @ ^3.0.0 + bblanchon/ArduinoJson @ ^7.0.0 + pfeerick/elapsedMillis @ ^1.0.6 + bxparks/AceButton @ ^1.10.1 build_unflags = -Werror=reorder board_build.partitions = min_spiffs.csv monitor_filters = esp32_exception_decoder extra_scripts = - pre:extra_script.py + pre:extra_script.py check_skip_packages = true build_flags = - -D LED_BUILTIN=2 - ; Max (and default) debugging level in Arduino ESP32 Core - -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE - ; Arduino Core bug workaround: define the log tag for the Arduino - ; logging macros. - -D TAG='"Arduino"' - ; Use the ESP-IDF logging library - required by SensESP. - -D USE_ESP_IDF_LOG + -D LED_BUILTIN=2 + ; Max (and default) debugging level in Arduino ESP32 Core + -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE + ; Arduino Core bug workaround: define the log tag for the Arduino + ; logging macros. + -D TAG='"Arduino"' + ; Use the ESP-IDF logging library - required by SensESP. + -D USE_ESP_IDF_LOG ; Uncomment the following to use the OTA interface for flashing. ; "mydevice" must correspond to the device hostname. diff --git a/src/sensesp/controllers/smart_switch_controller.cpp b/src/sensesp/controllers/smart_switch_controller.cpp index 338b44487..09652e1e8 100644 --- a/src/sensesp/controllers/smart_switch_controller.cpp +++ b/src/sensesp/controllers/smart_switch_controller.cpp @@ -8,7 +8,7 @@ namespace sensesp { SmartSwitchController::SmartSwitchController(bool auto_initialize, String config_path, const char* sk_sync_paths[]) - : BooleanTransform(config_path), auto_initialize_{auto_initialize} { + : FileSystemSaveable(config_path), auto_initialize_{auto_initialize} { if (sk_sync_paths != NULL) { sync_paths_.clear(); int i = 0; @@ -27,48 +27,6 @@ SmartSwitchController::SmartSwitchController(bool auto_initialize, } } -void SmartSwitchController::set(const bool& new_value) { - is_on_ = new_value; - this->emit(is_on_); -} - -void SmartSwitchController::set(const ClickTypes& new_value) { - if (!ClickType::is_click(new_value)) { - // Ignore button presses (we only want interpreted clicks) - return; - } - - if (new_value == ClickTypes::UltraLongSingleClick) { - // Long clicks reboot the system... - ESP.restart(); - return; - } - - // All other click types toggle the current state... - is_on_ = !is_on_; - this->emit(is_on_); - - if (new_value == ClickTypes::DoubleClick) { - // Sync any specified sync paths... - for (auto& path : sync_paths_) { - ESP_LOGD(__FILENAME__, "Sync status to %s", path.sk_sync_path_.c_str()); - path.put_request_->set(is_on_); - } - } -} - -void SmartSwitchController::set(const String& new_value) { - if (TextToTruth::is_valid_true(new_value)) { - is_on_ = true; - } else if (TextToTruth::is_valid_false(new_value)) { - is_on_ = false; - } else { - // All other values simply toggle... - is_on_ = !is_on_; - } - this->emit(is_on_); -} - bool SmartSwitchController::to_json(JsonObject& root) { JsonArray jPaths = root["sync_paths"].to(); for (auto& path : sync_paths_) { diff --git a/src/sensesp/controllers/smart_switch_controller.h b/src/sensesp/controllers/smart_switch_controller.h index 919c7c070..185c2c2c6 100644 --- a/src/sensesp/controllers/smart_switch_controller.h +++ b/src/sensesp/controllers/smart_switch_controller.h @@ -2,8 +2,10 @@ #define _smart_switch_controller_h #include "sensesp/signalk/signalk_put_request.h" +#include "sensesp/system/lambda_consumer.h" #include "sensesp/system/valueconsumer.h" #include "sensesp/transforms/click_type.h" +#include "sensesp/transforms/truth_text.h" #include "sensesp/ui/config_item.h" namespace sensesp { @@ -41,9 +43,7 @@ namespace sensesp { * @see TextToTruth * @see ClickType */ -class SmartSwitchController : public BooleanTransform, - public ValueConsumer, - public ValueConsumer { +class SmartSwitchController : public ValueProducer, FileSystemSaveable { public: /** * The constructor @@ -62,15 +62,54 @@ class SmartSwitchController : public BooleanTransform, */ SmartSwitchController(bool auto_initialize = true, String config_path = "", const char* sk_sync_paths[] = NULL); - void set(const bool& new_value) override; - void set(const String& new_value) override; - void set(const ClickTypes& new_value) override; // For reading and writing the configuration of this transformation virtual bool to_json(JsonObject& doc) override; virtual bool from_json(const JsonObject& config) override; public: + LambdaConsumer click_consumer_{[this](ClickTypes new_value) { + if (!ClickType::is_click(new_value)) { + // Ignore button presses (we only want interpreted clicks) + return; + } + + if (new_value == ClickTypes::UltraLongSingleClick) { + // Long clicks reboot the system... + ESP.restart(); + return; + } + + // All other click types toggle the current state... + this->is_on_ = !this->is_on_; + this->emit(this->is_on_); + + if (new_value == ClickTypes::DoubleClick) { + // Sync any specified sync paths... + for (auto& path : sync_paths_) { + ESP_LOGD(__FILENAME__, "Sync status to %s", path.sk_sync_path_.c_str()); + path.put_request_->set(this->is_on_); + } + } + }}; + + LambdaConsumer truthy_string_consumer_{[this](String new_value) { + if (TextToTruth::is_valid_true(new_value)) { + this->is_on_ = true; + } else if (TextToTruth::is_valid_false(new_value)) { + this->is_on_ = false; + } else { + // All other values simply toggle... + this->is_on_ = !this->is_on_; + } + this->emit(this->is_on_); + }}; + + LambdaConsumer swich_consumer_{[this](bool value) { + this->is_on_ = value; + this->emit(is_on_); + }}; + /// Used to store configuration internally. class SyncPath { public: diff --git a/src/sensesp/controllers/system_status_controller.cpp b/src/sensesp/controllers/system_status_controller.cpp deleted file mode 100644 index ac1141f11..000000000 --- a/src/sensesp/controllers/system_status_controller.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "system_status_controller.h" - -namespace sensesp { - -void SystemStatusController::set(const WiFiState& new_value) { - // FIXME: If pointers to member functions would be held in an array, - // this would be a simple array dereferencing - switch (new_value) { - case WiFiState::kWifiNoAP: - this->update_state(SystemStatus::kWifiNoAP); - break; - case WiFiState::kWifiDisconnected: - this->update_state(SystemStatus::kWifiDisconnected); - break; - case WiFiState::kWifiConnectedToAP: - case WiFiState::kWifiAPModeActivated: - this->update_state(SystemStatus::kSKWSDisconnected); - break; - case WiFiState::kWifiManagerActivated: - this->update_state(SystemStatus::kWifiManagerActivated); - break; - } -} - -void SystemStatusController::set(const SKWSConnectionState& new_value) { - switch (new_value) { - case SKWSConnectionState::kSKWSDisconnected: - if (current_state_ != SystemStatus::kWifiDisconnected && - current_state_ != SystemStatus::kWifiNoAP && - current_state_ != SystemStatus::kWifiManagerActivated) { - // Wifi disconnection states override the higher level protocol state - this->update_state(SystemStatus::kSKWSDisconnected); - } - break; - case SKWSConnectionState::kSKWSConnecting: - this->update_state(SystemStatus::kSKWSConnecting); - break; - case SKWSConnectionState::kSKWSAuthorizing: - this->update_state(SystemStatus::kSKWSAuthorizing); - break; - case SKWSConnectionState::kSKWSConnected: - this->update_state(SystemStatus::kSKWSConnected); - break; - } -} - -} // namespace sensesp diff --git a/src/sensesp/controllers/system_status_controller.h b/src/sensesp/controllers/system_status_controller.h index 2dbf409f9..14b58a586 100644 --- a/src/sensesp/controllers/system_status_controller.h +++ b/src/sensesp/controllers/system_status_controller.h @@ -3,6 +3,7 @@ #include "sensesp/net/networking.h" #include "sensesp/signalk/signalk_ws_client.h" +#include "sensesp/system/lambda_consumer.h" #include "sensesp/system/valueproducer.h" namespace sensesp { @@ -24,20 +25,66 @@ enum class SystemStatus { * set_wifi_* and set_ws_* methods to take the relevant action when such * an event occurs. */ -class SystemStatusController : public ValueConsumer, - public ValueConsumer, - public ValueProducer { +class SystemStatusController : public ValueProducer { public: SystemStatusController() {} - /// ValueConsumer interface for ValueConsumer (Networking object - /// state updates) - virtual void set(const WiFiState& new_value) override; - /// ValueConsumer interface for ValueConsumer - /// (SKWSClient object state updates) - virtual void set(const SKWSConnectionState& new_value) override; + ValueConsumer& get_wifi_state_consumer() { + return wifi_state_consumer_; + } + + ValueConsumer& get_ws_connection_state_consumer() { + return ws_connection_state_consumer_; + } protected: + void set_wifi_state(const WiFiState& new_value) { + switch (new_value) { + case WiFiState::kWifiNoAP: + this->update_state(SystemStatus::kWifiNoAP); + break; + case WiFiState::kWifiDisconnected: + this->update_state(SystemStatus::kWifiDisconnected); + break; + case WiFiState::kWifiConnectedToAP: + case WiFiState::kWifiAPModeActivated: + this->update_state(SystemStatus::kSKWSDisconnected); + break; + case WiFiState::kWifiManagerActivated: + this->update_state(SystemStatus::kWifiManagerActivated); + break; + } + } + + void set_sk_ws_connection_state(const SKWSConnectionState& new_value) { + switch (new_value) { + case SKWSConnectionState::kSKWSDisconnected: + if (current_state_ != SystemStatus::kWifiDisconnected && + current_state_ != SystemStatus::kWifiNoAP && + current_state_ != SystemStatus::kWifiManagerActivated) { + // Wifi disconnection states override the higher level protocol state + this->update_state(SystemStatus::kSKWSDisconnected); + } + break; + case SKWSConnectionState::kSKWSConnecting: + this->update_state(SystemStatus::kSKWSConnecting); + break; + case SKWSConnectionState::kSKWSAuthorizing: + this->update_state(SystemStatus::kSKWSAuthorizing); + break; + case SKWSConnectionState::kSKWSConnected: + this->update_state(SystemStatus::kSKWSConnected); + break; + } + } + + LambdaConsumer wifi_state_consumer_{ + [this](const WiFiState& new_value) { this->set_wifi_state(new_value); }}; + LambdaConsumer ws_connection_state_consumer_{ + [this](const SKWSConnectionState& new_value) { + this->set_sk_ws_connection_state(new_value); + }}; + void update_state(const SystemStatus new_state) { current_state_ = new_state; this->emit(new_state); diff --git a/src/sensesp/net/http_server.cpp b/src/sensesp/net/http_server.cpp index 01e5f6686..7f5274dc2 100644 --- a/src/sensesp/net/http_server.cpp +++ b/src/sensesp/net/http_server.cpp @@ -37,8 +37,6 @@ void urldecode2(char* dst, const char* src) { *dst++ = '\0'; } -HTTPServer* HTTPServer::singleton_ = nullptr; - esp_err_t HTTPServer::dispatch_request(httpd_req_t* req) { ESP_LOGI(__FILENAME__, "Handling request: %s", req->uri); @@ -178,9 +176,7 @@ bool HTTPServer::authenticate_request( } esp_err_t call_request_dispatcher(httpd_req_t* req) { - HTTPServer* server = HTTPServer::get_server(); - bool continue_; - + HTTPServer* server = (HTTPServer*)req->user_ctx; return server->dispatch_request(req); } diff --git a/src/sensesp/net/http_server.h b/src/sensesp/net/http_server.h index 47ec45681..99c22ea76 100644 --- a/src/sensesp/net/http_server.h +++ b/src/sensesp/net/http_server.h @@ -58,7 +58,7 @@ class HTTPRequestHandler { * @brief HTTP server class wrapping the esp-idf http server. * */ -class HTTPServer : virtual public FileSystemSaveable { +class HTTPServer : public FileSystemSaveable { public: HTTPServer(int port = HTTP_DEFAULT_PORT, const String& config_path = "/system/httpserver") @@ -73,13 +73,6 @@ class HTTPServer : virtual public FileSystemSaveable { authenticator_ = std::unique_ptr( new HTTPDigestAuthenticator(username_, password_, auth_realm_)); } - if (singleton_ == nullptr) { - singleton_ = this; - ESP_LOGD(__FILENAME__, "HTTPServer instance created"); - } else { - ESP_LOGE(__FILENAME__, "Only one HTTPServer instance is allowed"); - return; - } event_loop()->onDelay(0, [this]() { esp_err_t error = httpd_start(&server_, &config_); if (error != ESP_OK) { @@ -111,11 +104,6 @@ class HTTPServer : virtual public FileSystemSaveable { MDNS.addService("http", "tcp", 80); }); }; - ~HTTPServer() { - if (singleton_ == this) { - singleton_ = nullptr; - } - } void stop() { httpd_stop(server_); } @@ -151,17 +139,11 @@ class HTTPServer : virtual public FileSystemSaveable { return true; } - void add_handler(HTTPRequestHandler* handler) { - handlers_.push_back(std::make_shared(*handler)); - } - - void add_handler(std::shared_ptr handler) { + void add_handler(std::shared_ptr& handler) { handlers_.push_back(handler); } protected: - static HTTPServer* get_server() { return singleton_; } - static HTTPServer* singleton_; bool captive_portal_ = false; httpd_handle_t server_ = nullptr; httpd_config_t config_; diff --git a/src/sensesp/net/networking.cpp b/src/sensesp/net/networking.cpp index 20a2cbb8c..b44663552 100644 --- a/src/sensesp/net/networking.cpp +++ b/src/sensesp/net/networking.cpp @@ -4,6 +4,7 @@ #include +#include "sensesp/system/lambda_consumer.h" #include "sensesp/system/led_blinker.h" #include "sensesp_app.h" @@ -29,9 +30,10 @@ Networking::Networking(const String& config_path, const String& client_ssid, const String& ap_password) : FileSystemSaveable{config_path}, Resettable(0) { // Get the WiFi state producer singleton and make it update this object output - wifi_state_producer = WiFiStateProducer::get_singleton(); - wifi_state_producer->connect_to(new LambdaConsumer( - [this](WiFiState state) { this->emit(state); })); + wifi_state_emitter_ = std::make_shared>( + [this](WiFiState state) { this->emit(state); }); + + wifi_state_producer_->connect_to(wifi_state_emitter_); bool config_loaded = load(); @@ -97,7 +99,7 @@ Networking::Networking(const String& config_path, const String& client_ssid, if (this->ap_settings_.enabled_ && this->ap_settings_.captive_portal_enabled_) { - dns_server_ = new DNSServer(); + dns_server_ = std::unique_ptr(new DNSServer()); dns_server_->setErrorReplyCode(DNSReplyCode::NoError); dns_server_->start(53, "*", WiFi.softAPIP()); @@ -109,7 +111,6 @@ Networking::Networking(const String& config_path, const String& client_ssid, Networking::~Networking() { if (dns_server_) { dns_server_->stop(); - delete dns_server_; } // Stop WiFi @@ -292,18 +293,13 @@ bool Networking::from_json(const JsonObject& config) { void Networking::reset() { ESP_LOGI(__FILENAME__, "Resetting WiFi SSID settings"); - remove(); + clear(); WiFi.disconnect(true); // On ESP32, disconnect does not erase previous credentials. Let's connect // to a bogus network instead WiFi.begin("0", "0", 0, nullptr, false); } -WiFiStateProducer* WiFiStateProducer::get_singleton() { - static WiFiStateProducer instance; - return &instance; -} - void Networking::start_wifi_scan() { // Scan fails if WiFi is connecting. Disconnect to allow scanning. if (WiFi.status() != WL_CONNECTED) { diff --git a/src/sensesp/net/networking.h b/src/sensesp/net/networking.h index 466eb9dd8..8c92a0d7a 100644 --- a/src/sensesp/net/networking.h +++ b/src/sensesp/net/networking.h @@ -6,6 +6,7 @@ #include #include "sensesp/net/wifi_state.h" +#include "sensesp/system/lambda_consumer.h" #include "sensesp/system/observablevalue.h" #include "sensesp/system/resettable.h" #include "sensesp/system/serializable.h" @@ -27,22 +28,6 @@ constexpr int kMaxNumClientConfigs = 3; */ class WiFiStateProducer : public ValueProducer { public: - /** - * Singletons should not be cloneable - */ - WiFiStateProducer(WiFiStateProducer& other) = delete; - - /** - * Singletons should not be assignable - */ - void operator=(const WiFiStateProducer&) = delete; - - /** - * @brief Get the singleton instance of the WiFiStateProducer - */ - static WiFiStateProducer* get_singleton(); - - protected: WiFiStateProducer() { this->output_ = WiFiState::kWifiNoAP; @@ -52,21 +37,44 @@ class WiFiStateProducer : public ValueProducer { event_loop()->onDelay(0, [this]() { this->emit(this->output_); }); } + ~WiFiStateProducer() { remove_wifi_callbacks(); } + + WiFiStateProducer(WiFiStateProducer& other) = delete; + void operator=(const WiFiStateProducer&) = delete; + + protected: + wifi_event_id_t wifi_sta_got_ip_event_id_; + wifi_event_id_t wifi_ap_start_event_id_; + wifi_event_id_t wifi_sta_disconnected_event_id_; + wifi_event_id_t wifi_ap_stop_event_id_; + void setup_wifi_callbacks() { - WiFi.onEvent( + wifi_sta_got_ip_event_id_ = WiFi.onEvent( [this](WiFiEvent_t event, WiFiEventInfo_t info) { this->wifi_station_connected(); }, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); - WiFi.onEvent([this](WiFiEvent_t event, - WiFiEventInfo_t info) { this->wifi_ap_enabled(); }, - WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_START); - WiFi.onEvent([this](WiFiEvent_t event, - WiFiEventInfo_t info) { this->wifi_disconnected(); }, - WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); - WiFi.onEvent([this](WiFiEvent_t event, - WiFiEventInfo_t info) { this->wifi_disconnected(); }, - WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_STOP); + wifi_ap_start_event_id_ = + WiFi.onEvent([this](WiFiEvent_t event, + WiFiEventInfo_t info) { this->wifi_ap_enabled(); }, + WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_START); + wifi_sta_disconnected_event_id_ = WiFi.onEvent( + [this](WiFiEvent_t event, WiFiEventInfo_t info) { + this->wifi_disconnected(); + }, + WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + wifi_ap_stop_event_id_ = WiFi.onEvent( + [this](WiFiEvent_t event, WiFiEventInfo_t info) { + this->wifi_disconnected(); + }, + WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_STOP); + } + + void remove_wifi_callbacks() { + WiFi.removeEvent(wifi_sta_got_ip_event_id_); + WiFi.removeEvent(wifi_ap_start_event_id_); + WiFi.removeEvent(wifi_sta_disconnected_event_id_); + WiFi.removeEvent(wifi_ap_stop_event_id_); } void wifi_station_connected() { @@ -233,7 +241,7 @@ class WiFiNetworkInfo { /** * @brief Manages the ESP's connection to the Wifi network. */ -class Networking : virtual public FileSystemSaveable, +class Networking : public FileSystemSaveable, public Resettable, public ValueProducer { public: @@ -254,25 +262,25 @@ class Networking : virtual public FileSystemSaveable, return ap_settings_.captive_portal_enabled_; } + const std::shared_ptr& get_wifi_state_producer() { + return wifi_state_producer_; + } + protected: void start_access_point(); void start_client_autoconnect(); - // callbacks - - void wifi_station_connected(); - void wifi_ap_enabled(); - void wifi_disconnected(); - - protected: AccessPointSettings ap_settings_; bool client_enabled_ = false; std::vector client_settings_; - DNSServer* dns_server_ = nullptr; + std::unique_ptr dns_server_; + + std::shared_ptr wifi_state_producer_ = + std::make_shared(); - WiFiStateProducer* wifi_state_producer; + std::shared_ptr> wifi_state_emitter_; }; inline bool ConfigRequiresRestart(const Networking& obj) { return true; } diff --git a/src/sensesp/net/web/app_command_handler.cpp b/src/sensesp/net/web/app_command_handler.cpp index 936addd2f..bb2aea332 100644 --- a/src/sensesp/net/web/app_command_handler.cpp +++ b/src/sensesp/net/web/app_command_handler.cpp @@ -1,12 +1,13 @@ #include "app_command_handler.h" +#include #include -#include +#include "sensesp_app.h" namespace sensesp { -void add_scan_wifi_networks_handlers(HTTPServer* server) { +void add_scan_wifi_networks_handlers(std::shared_ptr& server) { auto scan_wifi_networks_handler = std::make_shared( 1 << HTTP_POST, "/api/wifi/scan", [](httpd_req_t* req) { auto networking = SensESPApp::get()->get_networking(); @@ -52,7 +53,7 @@ void add_scan_wifi_networks_handlers(HTTPServer* server) { server->add_handler(scan_results_handler); } -void add_app_http_command_handlers(HTTPServer* server) { +void add_app_http_command_handlers(std::shared_ptr& server) { add_scan_wifi_networks_handlers(server); } diff --git a/src/sensesp/net/web/app_command_handler.h b/src/sensesp/net/web/app_command_handler.h index f68f30ce3..c700c8f64 100644 --- a/src/sensesp/net/web/app_command_handler.h +++ b/src/sensesp/net/web/app_command_handler.h @@ -1,15 +1,15 @@ #ifndef SENSESP_NET_HTTP_APP_COMMAND_HANDLER_H_ #define SENSESP_NET_HTTP_APP_COMMAND_HANDLER_H_ +#include #include #include "ArduinoJson.h" #include "sensesp/net/http_server.h" -#include "sensesp_app.h" namespace sensesp { -void add_app_http_command_handlers(HTTPServer* server); +void add_app_http_command_handlers(std::shared_ptr& server); } // namespace sensesp diff --git a/src/sensesp/net/web/base_command_handler.cpp b/src/sensesp/net/web/base_command_handler.cpp index c3b7ad16d..327f5b321 100644 --- a/src/sensesp/net/web/base_command_handler.cpp +++ b/src/sensesp/net/web/base_command_handler.cpp @@ -5,8 +5,8 @@ namespace sensesp { -void add_http_reset_handler(HTTPServer* server) { - HTTPRequestHandler* reset_handler = new HTTPRequestHandler( +void add_http_reset_handler(std::shared_ptr& server) { + auto reset_handler = std::make_shared( 1 << HTTP_POST, "/api/device/reset", [](httpd_req_t* req) { httpd_resp_send(req, "Resetting device back to factory defaults. " @@ -18,8 +18,8 @@ void add_http_reset_handler(HTTPServer* server) { server->add_handler(reset_handler); } -void add_http_restart_handler(HTTPServer* server) { - HTTPRequestHandler* restart_handler = new HTTPRequestHandler( +void add_http_restart_handler(std::shared_ptr& server) { + auto restart_handler = std::make_shared( 1 << HTTP_POST, "/api/device/restart", [](httpd_req_t* req) { httpd_resp_send(req, "Restarting device", 0); event_loop()->onDelay(500, []() { ESP.restart(); }); @@ -28,9 +28,9 @@ void add_http_restart_handler(HTTPServer* server) { server->add_handler(restart_handler); } -void add_http_info_handler(HTTPServer* server) { - HTTPRequestHandler* info_handler = - new HTTPRequestHandler(1 << HTTP_GET, "/api/info", [](httpd_req_t* req) { +void add_http_info_handler(std::shared_ptr& server) { + auto info_handler = std::make_shared( + 1 << HTTP_GET, "/api/info", [](httpd_req_t* req) { auto status_page_items = StatusPageItemBase::get_status_page_items(); JsonDocument json_doc; @@ -50,7 +50,7 @@ void add_http_info_handler(HTTPServer* server) { server->add_handler(info_handler); } -void add_routes_handlers(HTTPServer* server) { +void add_routes_handlers(std::shared_ptr& server) { std::vector routes; routes.push_back(RouteDefinition("Status", "/status", "StatusPage")); @@ -74,7 +74,7 @@ void add_routes_handlers(HTTPServer* server) { serializeJson(routes_json, response); - HTTPRequestHandler* routes_handler = new HTTPRequestHandler( + auto routes_handler = std::make_shared( 1 << HTTP_GET, "/api/routes", [response](httpd_req_t* req) { httpd_resp_set_type(req, "application/json"); httpd_resp_sendstr(req, response.c_str()); @@ -100,7 +100,7 @@ void add_routes_handlers(HTTPServer* server) { for (auto it = routes.begin(); it != routes.end(); ++it) { String path = it->get_path(); - HTTPRequestHandler* route_handler = new HTTPRequestHandler( + auto route_handler = std::make_shared( 1 << HTTP_GET, path.c_str(), [root_page](httpd_req_t* req) { httpd_resp_set_type(req, root_page->content_type); if (root_page->content_encoding != nullptr) { @@ -114,7 +114,7 @@ void add_routes_handlers(HTTPServer* server) { } } -void add_base_app_http_command_handlers(HTTPServer* server) { +void add_base_app_http_command_handlers(std::shared_ptr& server) { add_http_reset_handler(server); add_http_restart_handler(server); add_http_info_handler(server); diff --git a/src/sensesp/net/web/base_command_handler.h b/src/sensesp/net/web/base_command_handler.h index 721c35a9e..d3198628e 100644 --- a/src/sensesp/net/web/base_command_handler.h +++ b/src/sensesp/net/web/base_command_handler.h @@ -34,7 +34,7 @@ class RouteDefinition { String component_name_; }; -void add_base_app_http_command_handlers(HTTPServer* server); +void add_base_app_http_command_handlers(std::shared_ptr& server); } // namespace sensesp diff --git a/src/sensesp/net/web/config_handler.cpp b/src/sensesp/net/web/config_handler.cpp index 85601d9ef..42231d794 100644 --- a/src/sensesp/net/web/config_handler.cpp +++ b/src/sensesp/net/web/config_handler.cpp @@ -2,11 +2,14 @@ #include "config_handler.h" +#include + #include "sensesp/ui/config_item.h" namespace sensesp { -bool get_item_data(JsonDocument& doc, ConfigItemBase* item) { +bool get_item_data(JsonDocument& doc, + const std::shared_ptr& item) { JsonObject obj = doc.to(); String str; @@ -76,13 +79,14 @@ esp_err_t handle_config_item_list(httpd_req_t* req) { return ESP_OK; } -void add_config_list_handler(HTTPServer* server) { - server->add_handler(new HTTPRequestHandler(1 << HTTP_GET, "/api/config", - handle_config_item_list)); +void add_config_list_handler(std::shared_ptr& server) { + auto handler = std::make_shared( + 1 << HTTP_GET, "/api/config", handle_config_item_list); + server->add_handler(handler); } -void add_config_get_handler(HTTPServer* server) { - server->add_handler(new HTTPRequestHandler( +void add_config_get_handler(std::shared_ptr& server) { + auto handler = std::make_shared( 1 << HTTP_GET, "/api/config/*", [](httpd_req_t* req) { ESP_LOGD("ConfigHandler", "GET request to URL %s", req->uri); String url_tail = String(req->uri).substring(11); @@ -104,7 +108,7 @@ void add_config_get_handler(HTTPServer* server) { } // find the config item object with the matching config_path - ConfigItemBase* config_item = ConfigItemBase::get_config_item(url_tail); + auto config_item = ConfigItemBase::get_config_item(url_tail); if (config_item == nullptr) { httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "No ConfigItem found with that path"); @@ -120,11 +124,12 @@ void add_config_get_handler(HTTPServer* server) { httpd_resp_set_type(req, "application/json"); httpd_resp_sendstr(req, response.c_str()); return ESP_OK; - })); + }); + server->add_handler(handler); } -void add_config_put_handler(HTTPServer* server) { - server->add_handler(new HTTPRequestHandler( +void add_config_put_handler(std::shared_ptr& server) { + auto handler = std::make_shared( 1 << HTTP_PUT, "/api/config/*", [](httpd_req_t* req) { // check that the content type is JSON ESP_LOGI(__FILENAME__, "PUT request to URL %s", req->uri); @@ -148,7 +153,7 @@ void add_config_put_handler(HTTPServer* server) { url_tail = String(url_tail_cstr); // find the ConfigItemT object with the matching config_path - ConfigItemBase* config_item = ConfigItemBase::get_config_item(url_tail); + auto config_item = ConfigItemBase::get_config_item(url_tail); if (config_item == nullptr) { httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "No Configurable found with that path"); @@ -195,10 +200,11 @@ void add_config_put_handler(HTTPServer* server) { httpd_resp_sendstr(req, response.c_str()); return ESP_OK; - })); + }); + server->add_handler(handler); } -void add_config_handlers(HTTPServer* server) { +void add_config_handlers(std::shared_ptr& server) { add_config_list_handler(server); add_config_get_handler(server); add_config_put_handler(server); diff --git a/src/sensesp/net/web/config_handler.h b/src/sensesp/net/web/config_handler.h index 490c8c900..43ac38e7e 100644 --- a/src/sensesp/net/web/config_handler.h +++ b/src/sensesp/net/web/config_handler.h @@ -15,7 +15,7 @@ namespace sensesp { * to provide a RESTful API for configuring Configurable objects. * */ -void add_config_handlers(HTTPServer* server); +void add_config_handlers(std::shared_ptr& server); } // namespace sensesp diff --git a/src/sensesp/net/web/static_file_handler.cpp b/src/sensesp/net/web/static_file_handler.cpp index 264e5973b..2cf8f432a 100644 --- a/src/sensesp/net/web/static_file_handler.cpp +++ b/src/sensesp/net/web/static_file_handler.cpp @@ -4,10 +4,10 @@ namespace sensesp { -void add_static_file_handlers(HTTPServer* server) { +void add_static_file_handlers(std::shared_ptr server) { for (int i = 0; kFrontendFiles[i].url != nullptr; i++) { const StaticFileData& data = kFrontendFiles[i]; - HTTPRequestHandler* handler = new HTTPRequestHandler( + auto handler = std::make_shared( 1 << HTTP_GET, kFrontendFiles[i].url, [data](httpd_req_t* req) { httpd_resp_set_type(req, data.content_type); if (data.content_encoding != nullptr) { diff --git a/src/sensesp/net/web/static_file_handler.h b/src/sensesp/net/web/static_file_handler.h index 685549df3..6e11d2370 100644 --- a/src/sensesp/net/web/static_file_handler.h +++ b/src/sensesp/net/web/static_file_handler.h @@ -13,7 +13,7 @@ namespace sensesp { * @brief Provide handlers for static web content. * */ -void add_static_file_handlers(HTTPServer* server); +void add_static_file_handlers(std::shared_ptr server); } // namespace sensesp diff --git a/src/sensesp/sensors/system_info.h b/src/sensesp/sensors/system_info.h index 0f11528e1..7ead39148 100644 --- a/src/sensesp/sensors/system_info.h +++ b/src/sensesp/sensors/system_info.h @@ -1,9 +1,9 @@ #ifndef SENSESP_SENSORS_SYSTEM_INFO_H_ #define SENSESP_SENSORS_SYSTEM_INFO_H_ -#include #include #include +#include #include "sensesp/signalk/signalk_output.h" #include "sensesp_base_app.h" @@ -15,7 +15,8 @@ namespace sensesp { * @brief Connect a system information sensor to SKOutput **/ template -void connect_system_info_sensor(ValueProducer* sensor, String prefix, String name) { +void connect_system_info_sensor(std::shared_ptr>& sensor, + const String& prefix, const String& name) { auto hostname_obs = SensESPBaseApp::get()->get_hostname_observable(); String hostname = hostname_obs->get(); String path = prefix + hostname + "." + name; @@ -49,8 +50,7 @@ class SystemHz : public ValueProducer { elapsed_millis_ = 0; event_loop()->onTick([this]() { this->tick(); }); - event_loop()->onRepeat(1000, - [this]() { this->update(); }); + event_loop()->onRepeat(1000, [this]() { this->update(); }); } String get_value_name() { return "systemhz"; } @@ -71,8 +71,7 @@ class SystemHz : public ValueProducer { class FreeMem : public ValueProducer { public: FreeMem() { - event_loop()->onRepeat(1000, - [this]() { this->update(); }); + event_loop()->onRepeat(1000, [this]() { this->update(); }); } String get_value_name() { return "freemem"; } @@ -91,8 +90,7 @@ class FreeMem : public ValueProducer { class Uptime : public ValueProducer { public: Uptime() { - event_loop()->onRepeat(1000, - [this]() { this->update(); }); + event_loop()->onRepeat(1000, [this]() { this->update(); }); } String get_value_name() { return "uptime"; } @@ -111,8 +109,7 @@ class Uptime : public ValueProducer { class IPAddrDev : public ValueProducer { public: IPAddrDev() { - event_loop()->onRepeat(10000, - [this]() { this->update(); }); + event_loop()->onRepeat(10000, [this]() { this->update(); }); } String get_value_name() { return "ipaddr"; } @@ -131,8 +128,7 @@ class IPAddrDev : public ValueProducer { class WiFiSignal : public ValueProducer { public: WiFiSignal() { - event_loop()->onRepeat(3000, - [this]() { this->update(); }); + event_loop()->onRepeat(3000, [this]() { this->update(); }); } String get_value_name() { return "wifisignal"; } diff --git a/src/sensesp/signalk/signalk_ws_client.cpp b/src/sensesp/signalk/signalk_ws_client.cpp index 95c4a8931..cc59a808a 100644 --- a/src/sensesp/signalk/signalk_ws_client.cpp +++ b/src/sensesp/signalk/signalk_ws_client.cpp @@ -74,7 +74,7 @@ static void websocket_event_handler(void* handler_args, esp_event_base_t base, } } -SKWSClient::SKWSClient(const String& config_path, SKDeltaQueue* sk_delta_queue, +SKWSClient::SKWSClient(const String& config_path, std::shared_ptr sk_delta_queue, const String& server_address, uint16_t server_port, bool use_mdns) : FileSystemSaveable{config_path}, @@ -90,8 +90,7 @@ SKWSClient::SKWSClient(const String& config_path, SKDeltaQueue* sk_delta_queue, [this]() { this->emit(this->connection_state_.get()); }); // process any received updates in the main task - event_loop()->onRepeat( - 1, [this]() { this->process_received_updates(); }); + event_loop()->onRepeat(1, [this]() { this->process_received_updates(); }); // set the singleton object pointer ws_client = this; diff --git a/src/sensesp/signalk/signalk_ws_client.h b/src/sensesp/signalk/signalk_ws_client.h index 99b598199..c357ea194 100644 --- a/src/sensesp/signalk/signalk_ws_client.h +++ b/src/sensesp/signalk/signalk_ws_client.h @@ -6,8 +6,8 @@ #include #include #include -#include #include +#include #include "sensesp/signalk/signalk_delta_queue.h" #include "sensesp/system/observablevalue.h" @@ -31,13 +31,13 @@ enum class SKWSConnectionState { * @brief The websocket connection to the Signal K server. * @see SensESPApp */ -class SKWSClient : virtual public FileSystemSaveable, +class SKWSClient : public FileSystemSaveable, virtual public ValueProducer { public: ///////////////////////////////////////////////////////// // main task methods - SKWSClient(const String& config_path, SKDeltaQueue* sk_delta_queue, + SKWSClient(const String& config_path, std::shared_ptr sk_delta_queue, const String& server_address, uint16_t server_port, bool use_mdns = true); @@ -103,10 +103,8 @@ class SKWSClient : virtual public FileSystemSaveable, bool server_detected_ = false; bool token_test_success_ = false; - TaskQueueProducer connection_state_ = - TaskQueueProducer( - SKWSConnectionState::kSKWSDisconnected, - event_loop()); + TaskQueueProducer connection_state_{ + SKWSConnectionState::kSKWSDisconnected, event_loop()}; /// task_connection_state is used to track the internal task state which might /// be out of sync with the published connection state. @@ -115,10 +113,9 @@ class SKWSClient : virtual public FileSystemSaveable, WiFiClient wifi_client_{}; esp_websocket_client_handle_t client_{}; - SKDeltaQueue* sk_delta_queue_; + std::shared_ptr sk_delta_queue_; /// @brief Emits the number of deltas sent since last report - TaskQueueProducer delta_tx_tick_producer_ = - TaskQueueProducer(0, event_loop(), 990); + TaskQueueProducer delta_tx_tick_producer_{0, event_loop(), 990}; Integrator delta_tx_count_producer_{1, 0, ""}; Integrator delta_rx_count_producer_{1, 0, ""}; @@ -165,9 +162,7 @@ class SKWSClient : virtual public FileSystemSaveable, SKWSConnectionState get_connection_state() { return task_connection_state_; } }; -inline bool ConfigRequiresRestart(const SKWSClient& obj) { - return true; -} +inline bool ConfigRequiresRestart(const SKWSClient& obj) { return true; } } // namespace sensesp diff --git a/src/sensesp/system/filesystem.cpp b/src/sensesp/system/filesystem.cpp index ee9bd8aa3..939e365f0 100644 --- a/src/sensesp/system/filesystem.cpp +++ b/src/sensesp/system/filesystem.cpp @@ -11,6 +11,7 @@ Filesystem::Filesystem() : Resettable(-100) { ESP_LOGE(__FILENAME__, "FATAL: Filesystem initialization failed."); ESP.restart(); } + ESP_LOGI(__FILENAME__, "Filesystem initialized"); } Filesystem::~Filesystem() { diff --git a/src/sensesp/system/observable.cpp b/src/sensesp/system/observable.cpp deleted file mode 100644 index d54861f9a..000000000 --- a/src/sensesp/system/observable.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "observable.h" - -namespace sensesp { - -Observable::Observable(Observable&& other) : observers_{other.observers_} {} - -void Observable::notify() { - for (auto o : observers_) { - o(); - } -} - -void Observable::attach(std::function observer) { - // First iterate to the last element - auto before_end = observers_.before_begin(); - for (auto& _ : observers_) { - ++before_end; - } - // Then insert the new observer - observers_.insert_after(before_end, observer); -} - -} // namespace sensesp diff --git a/src/sensesp/system/observable.h b/src/sensesp/system/observable.h index 8cb260c3a..246a71ba2 100644 --- a/src/sensesp/system/observable.h +++ b/src/sensesp/system/observable.h @@ -18,10 +18,23 @@ class Observable { Observable() {} /// Move constructor - Observable(Observable&& other); - - void notify(); - void attach(std::function observer); + Observable(Observable&& other) : observers_{other.observers_} {} + + void notify() { + for (auto o : observers_) { + o(); + } + } + + void attach(std::function observer) { + // First iterate to the last element + auto before_end = observers_.before_begin(); + for (auto& _ : observers_) { + ++before_end; + } + // Then insert the new observer + observers_.insert_after(before_end, observer); + } private: std::forward_list > observers_; diff --git a/src/sensesp/system/observablevalue.h b/src/sensesp/system/observablevalue.h index 969724186..3573e4a70 100644 --- a/src/sensesp/system/observablevalue.h +++ b/src/sensesp/system/observablevalue.h @@ -28,11 +28,15 @@ bool operator!=(ObservableValue const& lhs, T const& rhs) { template class ObservableValue : public ValueConsumer, public ValueProducer { public: - ObservableValue() : ValueConsumer(), ValueProducer() {} + ObservableValue() = default; ObservableValue(const T& value) : ValueConsumer(), ValueProducer(value) {} + // Delete copy constructor and assignment operator + ObservableValue(const ObservableValue&) = delete; + ObservableValue& operator=(const ObservableValue&) = delete; + void set(const T& value) override { this->ValueProducer::emit(value); } const T& operator=(const T& value) { @@ -89,7 +93,7 @@ class ObservableValue : public ValueConsumer, public ValueProducer { */ template class PersistingObservableValue : public ObservableValue, - virtual public FileSystemSaveable { + public FileSystemSaveable { public: PersistingObservableValue(const T& value, String config_path = "") : ObservableValue(value), FileSystemSaveable(config_path) { diff --git a/src/sensesp/system/rgb_led.cpp b/src/sensesp/system/rgb_led.cpp index 86a6347c6..71f4ad11c 100644 --- a/src/sensesp/system/rgb_led.cpp +++ b/src/sensesp/system/rgb_led.cpp @@ -33,31 +33,6 @@ static float get_pwm(int64_t rgb, int shift_right, bool common_anode) { return color_pct; } -void RgbLed::set(const long& new_value) { - if (led_r_channel_ >= 0) { - float r = get_pwm(new_value, 16, common_anode_); - PWMOutput::set_pwm(led_r_channel_, r); - } - - if (led_g_channel_ >= 0) { - float g = get_pwm(new_value, 8, common_anode_); - PWMOutput::set_pwm(led_g_channel_, g); - } - - if (led_b_channel_ >= 0) { - float b = get_pwm(new_value, 0, common_anode_); - PWMOutput::set_pwm(led_b_channel_, b); - } -} - -void RgbLed::set(const bool& new_value) { - if (new_value) { - set(led_on_rgb_); - } else { - set(led_off_rgb_); - } -} - bool RgbLed::to_json(JsonObject& root) { root["led_on_rgb"] = led_on_rgb_; root["led_off_rgb"] = led_off_rgb_; @@ -76,6 +51,23 @@ bool RgbLed::from_json(const JsonObject& config) { return true; } +void RgbLed::set_color(long new_value) { + if (led_r_channel_ >= 0) { + float r = get_pwm(new_value, 16, common_anode_); + PWMOutput::set_pwm(led_r_channel_, r); + } + + if (led_g_channel_ >= 0) { + float g = get_pwm(new_value, 8, common_anode_); + PWMOutput::set_pwm(led_g_channel_, g); + } + + if (led_b_channel_ >= 0) { + float b = get_pwm(new_value, 0, common_anode_); + PWMOutput::set_pwm(led_b_channel_, b); + } +} + const String ConfigSchema(const RgbLed& obj) { return R"({"type":"object","properties":{"led_on_rgb":{"title":"RGB color for led ON","type":"integer"},"led_off_rgb":{"title":"RGB color for led OFF","type":"integer"}}})"; } diff --git a/src/sensesp/system/rgb_led.h b/src/sensesp/system/rgb_led.h index 0da9191dc..56c13b33a 100644 --- a/src/sensesp/system/rgb_led.h +++ b/src/sensesp/system/rgb_led.h @@ -1,6 +1,7 @@ #ifndef SENSESP_SYSTEM_RGB_LED_H #define SENSESP_SYSTEM_RGB_LED_H +#include "sensesp/system/lambda_consumer.h" #include "sensesp/ui/config_item.h" #include "valueconsumer.h" @@ -24,9 +25,7 @@ namespace sensesp { * the 24 bit color definition however. * @see PWMOutput */ -class RgbLed : public FileSystemSaveable, - public ValueConsumer, - public ValueConsumer { +class RgbLed : public FileSystemSaveable { public: /** * The constructor @@ -55,14 +54,25 @@ class RgbLed : public FileSystemSaveable, * Used to set the current display state of the LED. * @param new_value The RGB color to display. */ - virtual void set(const long& new_value) override; + + LambdaConsumer rgb_consumer_ = + LambdaConsumer([this](long new_value) { + set_color(new_value); + }); /** * Used to set the current display state of the LED with a simple on/off * boolean value. Using TRUE for new_value sets the color to the ON color. * Using FALSE uses the OFF color. */ - virtual void set(const bool& new_value) override; + LambdaConsumer on_off_consumer_ = + LambdaConsumer([this](bool new_value) { + if (new_value) { + set_color(led_on_rgb_); + } else { + set_color(led_off_rgb_); + } + }); virtual bool to_json(JsonObject& root) override; virtual bool from_json(const JsonObject& config) override; @@ -74,6 +84,9 @@ class RgbLed : public FileSystemSaveable, long led_on_rgb_; long led_off_rgb_; bool common_anode_; + + void set_color(long new_value); + }; const String ConfigSchema(const RgbLed& obj); diff --git a/src/sensesp/system/saveable.cpp b/src/sensesp/system/saveable.cpp index 90867fea5..9bc647b1c 100644 --- a/src/sensesp/system/saveable.cpp +++ b/src/sensesp/system/saveable.cpp @@ -69,7 +69,7 @@ bool FileSystemSaveable::save() { return true; } -bool FileSystemSaveable::remove() { +bool FileSystemSaveable::clear() { if (config_path_ == "") { return false; } diff --git a/src/sensesp/system/saveable.h b/src/sensesp/system/saveable.h index 2e79f8412..39736a2bb 100644 --- a/src/sensesp/system/saveable.h +++ b/src/sensesp/system/saveable.h @@ -45,12 +45,12 @@ class Saveable { */ virtual bool save() { return false; } /** - * @brief Remove the object from a persistent storage. + * @brief Delete the data from a persistent storage. * * @return true * @return false */ - virtual bool remove() { return false; } + virtual bool clear() { return false; } const String& get_config_path() const { return config_path_; } @@ -64,7 +64,7 @@ class FileSystemSaveable : public Saveable, virtual public Serializable { virtual bool load() override; virtual bool save() override; - virtual bool remove() override; + virtual bool clear() override; bool find_config_file(const String& config_path, String& filename); }; diff --git a/src/sensesp/system/system_status_led.cpp b/src/sensesp/system/system_status_led.cpp index 6d061bf00..297e58750 100644 --- a/src/sensesp/system/system_status_led.cpp +++ b/src/sensesp/system/system_status_led.cpp @@ -56,32 +56,4 @@ void SystemStatusLed::set_ws_connected() { blinker_->set_pattern(ws_connected_pattern); } -void SystemStatusLed::set(const SystemStatus& new_value) { - switch (new_value) { - case SystemStatus::kWifiNoAP: - this->set_wifi_no_ap(); - break; - case SystemStatus::kWifiDisconnected: - this->set_wifi_disconnected(); - break; - case SystemStatus::kWifiManagerActivated: - this->set_wifimanager_activated(); - break; - case SystemStatus::kSKWSDisconnected: - this->set_ws_disconnected(); - break; - case SystemStatus::kSKWSConnecting: - this->set_ws_connecting(); - break; - case SystemStatus::kSKWSAuthorizing: - this->set_ws_authorizing(); - break; - case SystemStatus::kSKWSConnected: - this->set_ws_connected(); - break; - } -} - -void SystemStatusLed::set(const int& /*new_value*/) { blinker_->blip(); } - } // namespace sensesp diff --git a/src/sensesp/system/system_status_led.h b/src/sensesp/system/system_status_led.h index 766dec23d..14ae9c596 100644 --- a/src/sensesp/system/system_status_led.h +++ b/src/sensesp/system/system_status_led.h @@ -14,8 +14,7 @@ namespace sensesp { * and updates the device LED accordingly. Inherit this class and override * the methods to customize the behavior. */ -class SystemStatusLed : public ValueConsumer, - public ValueConsumer { +class SystemStatusLed { protected: std::unique_ptr blinker_; @@ -28,11 +27,45 @@ class SystemStatusLed : public ValueConsumer, virtual void set_ws_connecting(); virtual void set_ws_connected(); + LambdaConsumer system_status_consumer_{ + [this](SystemStatus status) { + switch (status) { + case SystemStatus::kWifiNoAP: + this->set_wifi_no_ap(); + break; + case SystemStatus::kWifiDisconnected: + this->set_wifi_disconnected(); + break; + case SystemStatus::kWifiManagerActivated: + this->set_wifimanager_activated(); + break; + case SystemStatus::kSKWSDisconnected: + this->set_ws_disconnected(); + break; + case SystemStatus::kSKWSConnecting: + this->set_ws_connecting(); + break; + case SystemStatus::kSKWSAuthorizing: + this->set_ws_authorizing(); + break; + case SystemStatus::kSKWSConnected: + this->set_ws_connected(); + break; + } + }}; + + LambdaConsumer delta_tx_count_consumer_{ + [this](int) { blinker_->blip(); }}; + public: SystemStatusLed(int pin); - virtual void set(const SystemStatus& new_value) override; - virtual void set(const int& new_value) override; + ValueConsumer& get_system_status_consumer() { + return system_status_consumer_; + } + ValueConsumer& get_delta_tx_count_consumer() { + return delta_tx_count_consumer_; + } }; } // namespace sensesp diff --git a/src/sensesp/system/valueconsumer.h b/src/sensesp/system/valueconsumer.h index bb67f05b9..e11b4ac20 100644 --- a/src/sensesp/system/valueconsumer.h +++ b/src/sensesp/system/valueconsumer.h @@ -21,27 +21,24 @@ class ValueProducer; template class ValueConsumer { public: + using input_type = T; /** - * Used to set an input of this consumer. It is usually called - * automatically by a ValueProducer. + * Used to set an input of this consumer. It is called + * automatically by a ValueProducer but can also be called + * manually. + * * @param new_value the value of the input */ virtual void set(const T& new_value) {} + [[deprecated("Use set() instead")]] virtual void set_input(const T& new_value) { - static bool warned = false; - if (!warned) { - warned = true; - ESP_LOGW(__FILENAME__, "set_input() is deprecated. Use set() instead."); - } set(new_value); } - /** - * Registers this consumer with the specified producer, letting it - * know that this consumer would like to receive notifications whenever - * its value changes. - */ + // Pointer safety cannot be guaranteed because we don't know whether + // "this" is a shared pointer. + [[deprecated("Use ValueProducer::connect_to instead")]] void connect_from(ValueProducer* producer) { producer->attach([producer, this]() { this->set(producer->get()); }); } diff --git a/src/sensesp/system/valueproducer.h b/src/sensesp/system/valueproducer.h index a435c3801..a5e7965c1 100644 --- a/src/sensesp/system/valueproducer.h +++ b/src/sensesp/system/valueproducer.h @@ -32,103 +32,57 @@ class ValueProducer : virtual public Observable { */ virtual const T& get() const { return output_; } - /** - * Connects this producer to the specified consumer, registering that - * consumer for notifications to when this producer's value changes. - */ - void connect_to(ValueConsumer* consumer) { - this->attach([this, consumer]() { consumer->set(this->get()); }); - } - void connect_to(std::shared_ptr> consumer) { - this->attach([this, consumer]() { consumer->set(this->get()); }); - } - void connect_to(ValueConsumer& consumer) { - this->attach([this, consumer]() { consumer.set(this->get()); }); - } - - /** - * @brief Connect a producer to a consumer of a different type. - * - * This allows you to connect a producer to a consumer of a different type. - * Automatic type conversion is performed. - * - * @tparam CT Consumer type - * @param consumer Consumer object to connect to - */ - template - void connect_to(ValueConsumer* consumer) { - this->attach([this, consumer]() { consumer->set(CT(this->get())); }); - } - template - void connect_to(std::shared_ptr> consumer) const { - this->attach([this, consumer]() { consumer->set(CT(this->get())); }); - } - - template - void connect_to(ValueConsumer& consumer) { - this->attach([this, consumer]() { consumer.set(CT(this->get())); }); - } - - /** - * If the consumer this producer is connecting to is ALSO a producer - * of values of the same type, connect_to() calls can be chained - * together, as this specialized version returns the producer/consumer - * being conencted to so connect_to() can be called on THAT object. - */ - template - Transform* connect_to(Transform* consumer_producer) { - this->attach([this, consumer_producer]() { - consumer_producer->set(T(this->get())); - }); - return consumer_producer; - } - template - Transform* connect_to( - std::shared_ptr> consumer_producer) { - this->attach([this, consumer_producer]() { - consumer_producer->set(T(this->get())); - }); - return consumer_producer.get(); - } - template - Transform* connect_to(Transform& consumer_producer) { - this->attach( - [this, consumer_producer]() { consumer_producer.set(T(this->get())); }); - return &consumer_producer; - } - /** * @brief Connect a producer to a transform with a different input type * * This allows you to connect a producer to a transform with a different * input type. Automatic type conversion is performed. * - * @tparam TT Transform input type - * @tparam T2 Transform output type + * @tparam VConsumer ValueConsumer type + * @tparam CInput Consumer input type * @param consumer_producer Transform object to connect to - * @return Transform* + * @return std::shared_ptr The connected consumer */ - template - Transform* connect_to(Transform* consumer_producer) { - this->attach([this, consumer_producer]() { - consumer_producer->set(TT(this->get())); + template + typename std::enable_if< + std::is_base_of, VConsumer>::value && + std::is_convertible::value, + std::shared_ptr + >::type + connect_to(std::shared_ptr consumer) { + using CInput = typename VConsumer::input_type; + // Capture consumer_producer as weak_ptr to avoid strong reference cycles + std::weak_ptr weak_consumer = consumer; + this->attach([this, weak_consumer]() { + if (auto consumer = weak_consumer.lock()) { + consumer->set(static_cast(this->get())); + } }); - return consumer_producer; + return consumer; } - template - Transform* connect_to( - std::shared_ptr> consumer_producer) { - this->attach([this, consumer_producer]() { - consumer_producer->set(TT(this->get())); + + template + typename std::enable_if< + std::is_base_of, VConsumer>::value && + std::is_convertible::value, + VConsumer* + >::type + connect_to(VConsumer* consumer) { + using CInput = typename VConsumer::input_type; + this->attach([this, consumer]() { + consumer->set(static_cast(this->get())); }); - return consumer_producer.get(); + return consumer; } - template - Transform* connect_to(Transform& consumer_producer) { - this->attach([this, consumer_producer]() { - consumer_producer->set(TT(this->get())); - }); - return &consumer_producer; + + template + typename std::enable_if< + std::is_base_of, VConsumer>::value && + std::is_convertible::value, + VConsumer* + >::type + connect_to(VConsumer& consumer) { + return connect_to(&consumer); } /* @@ -154,4 +108,4 @@ typedef ValueProducer StringProducer; } // namespace sensesp -#endif +#endif // SENSESP_SYSTEM_VALUE_PRODUCER_H_ diff --git a/src/sensesp/transforms/press_repeater.cpp b/src/sensesp/transforms/press_repeater.cpp index 52695995c..09c75f7a3 100644 --- a/src/sensesp/transforms/press_repeater.cpp +++ b/src/sensesp/transforms/press_repeater.cpp @@ -32,10 +32,6 @@ PressRepeater::PressRepeater(const String& config_path, int integer_false, }); } -void PressRepeater::set(const int& new_value) { - this->set(new_value != integer_false_); -} - void PressRepeater::set(const bool& new_value) { if (new_value != pushed_) { pushed_ = new_value; diff --git a/src/sensesp/transforms/press_repeater.h b/src/sensesp/transforms/press_repeater.h index c31e3f70b..8b7d81b2f 100644 --- a/src/sensesp/transforms/press_repeater.h +++ b/src/sensesp/transforms/press_repeater.h @@ -40,13 +40,12 @@ namespace sensesp { * @param repeat_interval How often to repeat the repeated output, in ms. * The default is 250. */ -class PressRepeater : public BooleanTransform, public IntConsumer { +class PressRepeater : public BooleanTransform { public: PressRepeater(const String& config_path = "", int integer_false = 0, int repeat_start_interval = 1500, int repeat_interval = 250); virtual void set(const bool& new_value) override; - virtual void set(const int& new_value) override; virtual bool to_json(JsonObject& root) override; virtual bool from_json(const JsonObject& config) override; diff --git a/src/sensesp/ui/config_item.cpp b/src/sensesp/ui/config_item.cpp index b4ee362fd..404c5739f 100644 --- a/src/sensesp/ui/config_item.cpp +++ b/src/sensesp/ui/config_item.cpp @@ -2,7 +2,7 @@ namespace sensesp { -std::map ConfigItemBase::config_items_; +std::map> ConfigItemBase::config_items_; template <> const char* get_schema_type_string(const int /*dummy*/) { diff --git a/src/sensesp/ui/config_item.h b/src/sensesp/ui/config_item.h index 81b95b864..028c530c8 100644 --- a/src/sensesp/ui/config_item.h +++ b/src/sensesp/ui/config_item.h @@ -45,12 +45,32 @@ const String ConfigSchema(const T& obj) { return "null"; } +template +const String ConfigSchema(const std::shared_ptr& obj) { + return ConfigSchema(*obj); +} + template bool ConfigRequiresRestart(const T& obj) { return false; } -class ConfigItemBase { +template +bool ConfigRequiresRestart(const std::shared_ptr& obj) { + return ConfigRequiresRestart(*obj); +} + +// Forward declarations + +template +class ConfigItemT; + +template +std::shared_ptr> ConfigItem(std::shared_ptr); + + +class ConfigItemBase + : virtual public std::enable_shared_from_this { public: const String& get_title() const { return title_; } ConfigItemBase* set_title(const String& title) { @@ -114,7 +134,7 @@ class ConfigItemBase { * * Return nullptr if not found. */ - static ConfigItemBase* get_config_item(const String key) { + static std::shared_ptr get_config_item(const String key) { auto it = ConfigItemBase::config_items_.find(key); if (it != ConfigItemBase::config_items_.end()) { return it->second; @@ -126,15 +146,17 @@ class ConfigItemBase { * @brief Get all config items as a vector. * */ - static std::unique_ptr> get_config_items() { - std::unique_ptr> sorted_config_items( - new std::vector()); + static std::unique_ptr>> + get_config_items() { + std::unique_ptr>> + sorted_config_items(new std::vector>()); for (auto& it : ConfigItemBase::config_items_) { sorted_config_items->push_back(it.second); } std::sort(sorted_config_items->begin(), sorted_config_items->end(), - [](ConfigItemBase* a, ConfigItemBase* b) { + [](std::shared_ptr a, + std::shared_ptr b) { return a->get_sort_order() < b->get_sort_order(); }); @@ -149,7 +171,7 @@ class ConfigItemBase { virtual const String& get_config_path() const = 0; protected: - static std::map config_items_; + static std::map> config_items_; /// The path of the ConfigItemT. This is used to identify the ConfigItemT. String config_path_ = ""; @@ -167,6 +189,9 @@ class ConfigItemBase { bool requires_restart_ = false; virtual const String get_default_config_schema() const = 0; + + template + friend std::shared_ptr> ConfigItem(std::shared_ptr); }; /** @@ -183,10 +208,8 @@ class ConfigItemT : public ConfigItemBase { "T must inherit from Saveable"); public: - ConfigItemT(T& config_object) - : ConfigItemBase(), config_object_{&config_object} { - ConfigItemT::config_items_[config_object.get_config_path()] = this; - } + ConfigItemT(std::shared_ptr config_object) + : ConfigItemBase(), config_object_{config_object} {} virtual bool to_json(JsonObject& config) const override { return config_object_->to_json(config); @@ -237,7 +260,8 @@ class ConfigItemT : public ConfigItemBase { T* get_config_object() { return config_object_; } protected: - T* config_object_; + // The object that this ConfigItemT is managing. + std::shared_ptr config_object_; /** * @brief Get the default configuration schema. * @@ -247,6 +271,7 @@ class ConfigItemT : public ConfigItemBase { String schema = ConfigSchema(*config_object_); return schema; } + }; /** @@ -264,14 +289,22 @@ class ConfigItemT : public ConfigItemBase { * @return T* */ template -ConfigItemT* ConfigItem(T* config_object) { - // Constructor will add the ConfigItemT to the config_items_ map +std::shared_ptr> ConfigItem(std::shared_ptr config_object) { ESP_LOGD(__FILENAME__, "Registering ConfigItemT with path %s", config_object->get_config_path().c_str()); - auto config_item = new ConfigItemT(*config_object); + auto config_item = std::make_shared>(config_object); + auto base_sptr = std::static_pointer_cast(config_item); + ConfigItemBase::config_items_[config_object->get_config_path()] = base_sptr; return config_item; } +// Unsafe: We don't know whether other shared_ptrs to the same object exist! +template +std::shared_ptr> ConfigItem(T* config_object) { + auto config_object_sptr = std::shared_ptr(config_object); + return ConfigItem(config_object_sptr); +} + } // namespace sensesp #endif // SENSESP_SRC_SENSESP_NET_WEB_CONFIG_ITEM_H_ diff --git a/src/sensesp_app.cpp b/src/sensesp_app.cpp index 21c2fefe1..64c7db37f 100644 --- a/src/sensesp_app.cpp +++ b/src/sensesp_app.cpp @@ -1,133 +1,9 @@ #include "sensesp_app.h" -#include "sensesp/net/discovery.h" -#include "sensesp/net/networking.h" -#include "sensesp/net/ota.h" -#include "sensesp/net/web/autogen/frontend_files.h" -#include "sensesp/system/button.h" -#include "sensesp/system/system_status_led.h" -#include "sensesp/transforms/debounce.h" -#include "sensesp/ui/config_item.h" +#include namespace sensesp { -SensESPApp::~SensESPApp() { - delete networking_; - delete ota_; - delete ws_client_; - delete mdns_discovery_; - delete http_server_; - delete sk_delta_queue_; - delete system_status_led_; - delete button_handler_; -} - -SensESPApp* SensESPApp::get() { - if (instance_ == nullptr) { - instance_ = new SensESPApp(); - } - return static_cast(instance_); -} - -/** - * @brief Perform initialization of SensESPApp once builder configuration is - * done. - * - * This should be only called from the builder! - * - */ -void SensESPApp::setup() { - // call the parent setup() - SensESPBaseApp::setup(); - - // create the networking object - networking_ = new Networking("/System/WiFi Settings", ssid_, - wifi_client_password_, ap_ssid_, ap_password_); - - ConfigItem(networking_); - - if (ota_password_ != nullptr) { - // create the OTA object - ota_ = new OTA(ota_password_); - } - - bool captive_portal_enabled = networking_->is_captive_portal_enabled(); - - // create the HTTP server - this->http_server_ = new HTTPServer(); - this->http_server_->set_captive_portal(captive_portal_enabled); - - // Add the default HTTP server response handlers - add_static_file_handlers(this->http_server_); - add_base_app_http_command_handlers(this->http_server_); - add_app_http_command_handlers(this->http_server_); - add_config_handlers(this->http_server_); - - ConfigItem(this->http_server_); - - // create the SK delta object - sk_delta_queue_ = new SKDeltaQueue(); - - // create the websocket client - bool const use_mdns = sk_server_address_ == ""; - this->ws_client_ = - new SKWSClient("/System/Signal K Settings", sk_delta_queue_, - sk_server_address_, sk_server_port_, use_mdns); - - ConfigItem(this->ws_client_); - - // connect the system status controller - WiFiStateProducer::get_singleton()->connect_to(&system_status_controller_); - this->ws_client_->connect_to(&system_status_controller_); - - // create the MDNS discovery object - mdns_discovery_ = new MDNSDiscovery(); - - // create a system status led and connect it - - if (system_status_led_ == NULL) { - system_status_led_ = new SystemStatusLed(LED_PIN); - } - this->system_status_controller_.connect_to(system_status_led_); - this->ws_client_->get_delta_tx_count_producer().connect_to( - system_status_led_); - - // create the button handler - if (button_gpio_pin_ != -1) { - button_handler_ = new ButtonHandler(button_gpio_pin_); - } - - // connect status page items - connect_status_page_items(); -} - -void SensESPApp::connect_status_page_items() { - this->hostname_->connect_to(&this->hostname_ui_output_); - this->event_loop_.onRepeat( - 4999, [this]() { wifi_ssid_ui_output_.set(WiFi.SSID()); }); - this->event_loop_.onRepeat( - 4998, [this]() { free_memory_ui_output_.set(ESP.getFreeHeap()); }); - this->event_loop_.onRepeat( - 4997, [this]() { wifi_rssi_ui_output_.set(WiFi.RSSI()); }); - this->event_loop_.onRepeat(4996, [this]() { - sk_server_address_ui_output_.set(ws_client_->get_server_address()); - }); - this->event_loop_.onRepeat(4995, [this]() { - sk_server_port_ui_output_.set(ws_client_->get_server_port()); - }); - this->event_loop_.onRepeat(4994, [this]() { - sk_server_connection_ui_output_.set(ws_client_->get_connection_status()); - }); - ws_client_->get_delta_tx_count_producer().connect_to( - &delta_tx_count_ui_output_); - ws_client_->get_delta_rx_count_producer().connect_to( - &delta_rx_count_ui_output_); -} - -ObservableValue* SensESPApp::get_hostname_observable() { - return hostname_; -} - -SensESPApp* sensesp_app; +std::shared_ptr sensesp_app; } // namespace sensesp diff --git a/src/sensesp_app.h b/src/sensesp_app.h index 929d0c2cb..77d2b3e7d 100644 --- a/src/sensesp_app.h +++ b/src/sensesp_app.h @@ -29,6 +29,11 @@ namespace sensesp { +class SensESPApp; +// I'd rather not have this global variable here but many legacy examples +// access it. Use SensESPApp::get() instead. +extern std::shared_ptr sensesp_app; + /** * The default SensESP application object with networking and Signal K * communication. @@ -49,17 +54,34 @@ class SensESPApp : public SensESPBaseApp { /** * @brief Get the singleton instance of the SensESPApp */ - static SensESPApp* get(); + static std::shared_ptr get() { + if (instance_ == nullptr) { + instance_ = std::shared_ptr(new SensESPApp()); + } + return std::static_pointer_cast(instance_); + } - ObservableValue* get_hostname_observable(); + virtual bool destroy() override { + bool outside_users = instance_.use_count() > 2; + if (outside_users) { + ESP_LOGW( + __FILENAME__, + "SensESPApp instance has active references and won't be properly " + "destroyed."); + } + instance_ = nullptr; + // Also destroy the global pointer + sensesp_app = nullptr; + return !outside_users; + } // getters for internal members - SKDeltaQueue* get_sk_delta() { return this->sk_delta_queue_; } - SystemStatusController* get_system_status_controller() { - return &(this->system_status_controller_); + std::shared_ptr get_sk_delta() { return this->sk_delta_queue_; } + std::shared_ptr get_system_status_controller() { + return this->system_status_controller_; } - Networking* get_networking() { return this->networking_; } - SKWSClient* get_ws_client() { return this->ws_client_; } + std::shared_ptr& get_networking() { return this->networking_; } + std::shared_ptr get_ws_client() { return this->ws_client_; } protected: /** @@ -71,8 +93,6 @@ class SensESPApp : public SensESPBaseApp { */ SensESPApp() : SensESPBaseApp() {} - ~SensESPApp(); - // setters for all constructor arguments const SensESPApp* set_hostname(String hostname) { @@ -103,7 +123,8 @@ class SensESPApp : public SensESPBaseApp { this->sk_server_port_ = sk_server_port; return this; } - const SensESPApp* set_system_status_led(SystemStatusLed* system_status_led) { + const SensESPApp* set_system_status_led( + std::shared_ptr& system_status_led) { this->system_status_led_ = system_status_led; return this; } @@ -120,30 +141,129 @@ class SensESPApp : public SensESPBaseApp { return this; } - void setup(); - void connect_status_page_items(); + /** + * @brief Perform initialization of SensESPApp once builder configuration is + * done. + * + * This should be only called from the builder! + * + */ + void setup() { + // call the parent setup() + SensESPBaseApp::setup(); + + ap_ssid_ = SensESPBaseApp::get_hostname(); + + // create the networking object + networking_ = std::make_shared("/System/WiFi Settings", ssid_, + wifi_client_password_, ap_ssid_, + ap_password_); + + ConfigItem(networking_); + + if (ota_password_ != nullptr) { + // create the OTA object + ota_ = std::make_shared(ota_password_); + } + + bool captive_portal_enabled = networking_->is_captive_portal_enabled(); + + // create the HTTP server + this->http_server_ = std::make_shared(); + this->http_server_->set_captive_portal(captive_portal_enabled); + + // Add the default HTTP server response handlers + add_static_file_handlers(this->http_server_); + add_base_app_http_command_handlers(this->http_server_); + add_app_http_command_handlers(this->http_server_); + add_config_handlers(this->http_server_); + + ConfigItem(this->http_server_); + + // create the SK delta object + sk_delta_queue_ = std::make_shared(); + + // create the websocket client + bool const use_mdns = sk_server_address_ == ""; + this->ws_client_ = std::make_shared( + "/System/Signal K Settings", sk_delta_queue_, sk_server_address_, + sk_server_port_, use_mdns); + + ConfigItem(this->ws_client_); + + // connect the system status controller + this->networking_->get_wifi_state_producer()->connect_to( + &system_status_controller_->get_wifi_state_consumer()); + this->ws_client_->connect_to( + &system_status_controller_->get_ws_connection_state_consumer()); + + // create the MDNS discovery object + mdns_discovery_ = std::make_shared(); + + // create a system status led and connect it + + if (system_status_led_ == nullptr) { + system_status_led_ = std::make_shared(LED_PIN); + } + this->system_status_controller_->connect_to( + system_status_led_->get_system_status_consumer()); + this->ws_client_->get_delta_tx_count_producer().connect_to( + system_status_led_->get_delta_tx_count_consumer()); + + // create the button handler + if (button_gpio_pin_ != -1) { + button_handler_ = std::make_shared(button_gpio_pin_); + } + + // connect status page items + connect_status_page_items(); + } + + void connect_status_page_items() { + this->hostname_->connect_to(&this->hostname_ui_output_); + this->event_loop_.onRepeat( + 4999, [this]() { wifi_ssid_ui_output_.set(WiFi.SSID()); }); + this->event_loop_.onRepeat( + 4998, [this]() { free_memory_ui_output_.set(ESP.getFreeHeap()); }); + this->event_loop_.onRepeat( + 4997, [this]() { wifi_rssi_ui_output_.set(WiFi.RSSI()); }); + this->event_loop_.onRepeat(4996, [this]() { + sk_server_address_ui_output_.set(ws_client_->get_server_address()); + }); + this->event_loop_.onRepeat(4995, [this]() { + sk_server_port_ui_output_.set(ws_client_->get_server_port()); + }); + this->event_loop_.onRepeat(4994, [this]() { + sk_server_connection_ui_output_.set(ws_client_->get_connection_status()); + }); + ws_client_->get_delta_tx_count_producer().connect_to( + &delta_tx_count_ui_output_); + ws_client_->get_delta_rx_count_producer().connect_to( + &delta_rx_count_ui_output_); + } String ssid_ = ""; String wifi_client_password_ = ""; String sk_server_address_ = ""; uint16_t sk_server_port_ = 0; - String ap_ssid_ = SensESPBaseApp::get_hostname(); + String ap_ssid_ = ""; String ap_password_ = "thisisfine"; const char* ota_password_ = nullptr; - MDNSDiscovery* mdns_discovery_; - HTTPServer* http_server_; + std::shared_ptr mdns_discovery_; + std::shared_ptr http_server_; - SystemStatusLed* system_status_led_ = NULL; - SystemStatusController system_status_controller_; + std::shared_ptr system_status_led_; + std::shared_ptr system_status_controller_ = + std::make_shared(); int button_gpio_pin_ = SENSESP_BUTTON_PIN; - ButtonHandler* button_handler_ = nullptr; + std::shared_ptr button_handler_; - Networking* networking_ = NULL; + std::shared_ptr networking_; - OTA* ota_; - SKDeltaQueue* sk_delta_queue_; - SKWSClient* ws_client_; + std::shared_ptr ota_; + std::shared_ptr sk_delta_queue_; + std::shared_ptr ws_client_; StatusPageItem sensesp_version_ui_output_{ "SenseESP version", kSensESPVersion, "Software", 1900}; @@ -168,12 +288,17 @@ class SensESPApp : public SensESPBaseApp { StatusPageItem delta_rx_count_ui_output_{"SK Delta RX count", 0, "Signal K", 1800}; +// Placeholders for system status sensors in case they are created +std::shared_ptr> system_hz_sensor_; +std::shared_ptr> free_mem_sensor_; +std::shared_ptr> uptime_sensor_; +std::shared_ptr> ip_address_sensor_; +std::shared_ptr> wifi_signal_sensor_; + friend class WebServer; friend class SensESPAppBuilder; }; -extern SensESPApp* sensesp_app; - } // namespace sensesp #endif diff --git a/src/sensesp_app_builder.h b/src/sensesp_app_builder.h index 29d2bfbdf..4f0dd5607 100644 --- a/src/sensesp_app_builder.h +++ b/src/sensesp_app_builder.h @@ -3,7 +3,9 @@ #include +#include "Arduino.h" #include "sensesp/sensors/system_info.h" +#include "sensesp/system/valueproducer.h" #include "sensesp/transforms/debounce.h" #include "sensesp_app.h" #include "sensesp_base_app_builder.h" @@ -25,7 +27,7 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { uint16_t sk_server_port_ = 0; protected: - SensESPApp* app_; + std::shared_ptr app_; public: /** @@ -34,7 +36,10 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { * SensESPAppBuilder is used to instantiate a SensESPApp * object with non-trivial configuration. */ - SensESPAppBuilder() { app_ = SensESPApp::get(); } + SensESPAppBuilder() { + app_ = std::shared_ptr(new SensESPApp()); + app_->set_instance(app_); + } /** * @brief Set the Wi-Fi network SSID and password. * @@ -65,7 +70,8 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { * @param password * @return SensESPAppBuilder* */ - SensESPAppBuilder* set_wifi_access_point(const String& ssid, const String& password) { + SensESPAppBuilder* set_wifi_access_point(const String& ssid, + const String& password) { app_->set_ap_ssid(ssid); app_->set_ap_password(password); return this; @@ -115,7 +121,8 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { * @param system_status_led * @return SensESPAppBuilder* */ - SensESPAppBuilder* set_system_status_led(SystemStatusLed* system_status_led) { + SensESPAppBuilder* set_system_status_led( + std::shared_ptr system_status_led) { app_->set_system_status_led(system_status_led); return this; } @@ -130,7 +137,12 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { */ SensESPAppBuilder* enable_system_hz_sensor( String prefix = kDefaultSystemInfoSensorPrefix) { - connect_system_info_sensor(new SystemHz(), prefix, "systemHz"); + auto sensor = std::make_shared(); + auto vproducer = std::static_pointer_cast>(sensor); + // We need to store the sensor in the app so it doesn't get garbage + // collected. + app_->system_hz_sensor_ = vproducer; + connect_system_info_sensor(vproducer, prefix, "systemHz"); return this; } /** @@ -141,7 +153,12 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { */ SensESPAppBuilder* enable_free_mem_sensor( String prefix = kDefaultSystemInfoSensorPrefix) { - connect_system_info_sensor(new FreeMem(), prefix, "freeMemory"); + auto sensor = std::make_shared(); + auto vproducer = std::static_pointer_cast>(sensor); + // We need to store the sensor in the app so it doesn't get garbage + // collected. + app_->free_mem_sensor_ = vproducer; + connect_system_info_sensor(vproducer, prefix, "freeMemory"); return this; } /** @@ -152,7 +169,12 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { */ SensESPAppBuilder* enable_uptime_sensor( String prefix = kDefaultSystemInfoSensorPrefix) { - connect_system_info_sensor(new Uptime(), prefix, "uptime"); + auto sensor = std::make_shared(); + auto vproducer = std::static_pointer_cast>(sensor); + // We need to store the sensor in the app so it doesn't get garbage + // collected. + app_->uptime_sensor_ = vproducer; + connect_system_info_sensor(vproducer, prefix, "uptime"); return this; } /** @@ -163,7 +185,12 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { */ SensESPAppBuilder* enable_ip_address_sensor( String prefix = kDefaultSystemInfoSensorPrefix) { - connect_system_info_sensor(new IPAddrDev(), prefix, "ipAddress"); + auto sensor = std::make_shared(); + auto vproducer = std::static_pointer_cast>(sensor); + // We need to store the sensor in the app so it doesn't get garbage + // collected. + app_->ip_address_sensor_ = vproducer; + connect_system_info_sensor(vproducer, prefix, "ipAddress"); return this; } /** @@ -174,7 +201,12 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { */ SensESPAppBuilder* enable_wifi_signal_sensor( String prefix = kDefaultSystemInfoSensorPrefix) { - connect_system_info_sensor(new WiFiSignal(), prefix, "wifiSignalLevel"); + auto sensor = std::make_shared(); + auto vproducer = std::static_pointer_cast>(sensor); + // We need to store the sensor in the app so it doesn't get garbage + // collected. + app_->wifi_signal_sensor_ = vproducer; + connect_system_info_sensor(vproducer, prefix, "wifiSignalLevel"); return this; } @@ -234,7 +266,7 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { const SensESPAppBuilder* enable_wifi_watchdog() { // create the wifi disconnect watchdog app_->system_status_controller_ - .connect_to(new Debounce( + ->connect_to(new Debounce( 3 * 60 * 1000 // 180 s = 180000 ms = 3 minutes )) ->connect_to(new LambdaConsumer([](SystemStatus input) { @@ -257,7 +289,8 @@ class SensESPAppBuilder : public SensESPBaseAppBuilder { * * @return SensESPApp* */ - SensESPApp* get_app() override final { + std::shared_ptr get_app() { + app_ = SensESPApp::get(); app_->setup(); return app_; } diff --git a/src/sensesp_base_app.cpp b/src/sensesp_base_app.cpp index dcc0d9fe3..8a9712c90 100644 --- a/src/sensesp_base_app.cpp +++ b/src/sensesp_base_app.cpp @@ -1,88 +1,7 @@ #include "sensesp_base_app.h" -#include "sensesp/ui/config_item.h" - namespace sensesp { -void SetupSerialDebug(uint32_t baudrate) { - SetupLogging(); - - if (baudrate != 115200) { - ESP_LOGW(__FILENAME__, "SetupSerialDebug baudrate parameter is ignored."); - } -} - -void SetupLogging(esp_log_level_t default_level) { - esp_log_level_set("*", default_level); -} - -SensESPBaseApp* SensESPBaseApp::instance_ = nullptr; - -SensESPBaseApp::SensESPBaseApp() : filesystem_(new Filesystem()) { - instance_ = this; - hostname_ = new PersistingObservableValue(kDefaultHostname, - "/system/hostname"); - ConfigItem(hostname_); // Make hostname configurable -} - -SensESPBaseApp::~SensESPBaseApp() { - delete filesystem_; - delete hostname_; - - instance_ = nullptr; -} - -/** - * Get the singleton SensESPBaseApp singleton instance. - * The instance must be set by the builder. - */ -SensESPBaseApp* SensESPBaseApp::get() { return instance_; } - -/** - * @brief Get the event loop object from the singleton SensESPBaseApp instance. - * - */ -reactesp::EventLoop* SensESPBaseApp::get_event_loop() { - return &(SensESPBaseApp::get()->event_loop_); -} - -/** - * @brief Perform initialization of SensESPBaseApp once builder configuration is - * done. - * - * This should be only called from the builder! - * - */ -void SensESPBaseApp::setup() {} - -void SensESPBaseApp::start() { - // start all individual startable components - - ESP_LOGW(__FILENAME__, "start() call is deprecated and can be removed."); -} - -void SensESPBaseApp::reset() { - ESP_LOGW(__FILENAME__, - "Resetting the device configuration to system defaults."); - Resettable::reset_all(); - - this->event_loop_.onDelay(1000, []() { - ESP.restart(); - delay(1000); - }); -} - -ObservableValue* SensESPBaseApp::get_hostname_observable() { - return hostname_; -} - -/** - * @brief Get the current hostname. - * - * @return String - */ -String SensESPBaseApp::get_hostname() { - return SensESPBaseApp::get()->get_hostname_observable()->get(); -} +std::shared_ptr SensESPBaseApp::instance_ = nullptr; } // namespace sensesp diff --git a/src/sensesp_base_app.h b/src/sensesp_base_app.h index 4f2a63545..2442a7fd5 100644 --- a/src/sensesp_base_app.h +++ b/src/sensesp_base_app.h @@ -8,6 +8,8 @@ #include "sensesp.h" +#include + #include "esp_log.h" #include "sensesp/system/filesystem.h" #include "sensesp/system/observablevalue.h" @@ -16,8 +18,17 @@ namespace sensesp { constexpr auto kDefaultHostname = "SensESP"; -void SetupSerialDebug(uint32_t baudrate); -void SetupLogging(esp_log_level_t default_level = ESP_LOG_VERBOSE); +inline void SetupLogging(esp_log_level_t default_level = ESP_LOG_VERBOSE) { + esp_log_level_set("*", default_level); +} + +inline void SetupSerialDebug(uint32_t baudrate) { + SetupLogging(); + + if (baudrate != 115200) { + ESP_LOGW(__FILENAME__, "SetupSerialDebug baudrate parameter is ignored."); + } +} /** * @brief The base class for SensESP applications. @@ -36,30 +47,72 @@ class SensESPBaseApp { /** * @brief Get the singleton instance of the SensESPBaseApp */ - static SensESPBaseApp* get(); + static const std::shared_ptr& get() { return instance_; } + + /** + * @brief Destroy the SensESPBaseApp instance + */ + virtual bool destroy() { + bool outside_users = instance_.use_count() > 1; + + if (outside_users) { + ESP_LOGW(__FILENAME__, + "SensESPBaseApp instance has active references and won't be " + "properly destroyed."); + } + instance_ = nullptr; + return !outside_users; + } /** * @brief Start the app (activate all the subcomponents) * */ - virtual void start(); + virtual void start() { + ESP_LOGW(__FILENAME__, "start() call is deprecated and can be removed."); + } /** * @brief Reset the device to factory defaults * */ - virtual void reset(); + virtual void reset() { + ESP_LOGW(__FILENAME__, + "Resetting the device configuration to system defaults."); + Resettable::reset_all(); + + this->event_loop_.onDelay(1000, []() { + ESP.restart(); + delay(1000); + }); + } /** * @brief Get the hostname observable object * * @return ObservableValue* */ - ObservableValue* get_hostname_observable(); + std::shared_ptr> get_hostname_observable() { + return std::static_pointer_cast>(hostname_); + } - static String get_hostname(); + /** + * @brief Get the current hostname. + * + * @return String + */ + static String get_hostname() { + return SensESPBaseApp::get()->get_hostname_observable().get()->get(); + } - static reactesp::EventLoop* get_event_loop(); + /** + * @brief Get the event loop object from the singleton SensESPBaseApp + * instance. + * + */ + static reactesp::EventLoop* get_event_loop() { + return &(SensESPBaseApp::get()->event_loop_); + } protected: /** @@ -69,20 +122,45 @@ class SensESPBaseApp { * be called only once. For compatibility reasons, the class hasn't been * refactored into a singleton. */ - SensESPBaseApp(); - ~SensESPBaseApp(); + SensESPBaseApp() : filesystem_{std::make_shared()} { + // Instance is now set by the builder + } + + ~SensESPBaseApp() { instance_ = nullptr; } + + void init_hostname() { + hostname_ = std::make_shared>( + kDefaultHostname, "/system/hostname"); + ConfigItem(hostname_); // Make hostname configurable + } - virtual void setup(); + /** + * @brief Perform initialization of SensESPBaseApp once builder configuration + * is done. + * + * This should be only called from the builder! + * + */ + virtual void setup() { + if (!hostname_) { + init_hostname(); + } + } - static SensESPBaseApp* instance_; + static std::shared_ptr instance_; - void set_instance(SensESPBaseApp* instance) { instance_ = instance; } + void set_instance(const std::shared_ptr& instance) { + instance_ = instance; + } - PersistingObservableValue* hostname_; + std::shared_ptr> hostname_; - Filesystem* filesystem_; + std::shared_ptr filesystem_; const SensESPBaseApp* set_hostname(String hostname) { + if (!hostname_) { + init_hostname(); + } hostname_->set(hostname); return this; } diff --git a/src/sensesp_base_app_builder.h b/src/sensesp_base_app_builder.h index 31af4294a..3286a030c 100644 --- a/src/sensesp_base_app_builder.h +++ b/src/sensesp_base_app_builder.h @@ -14,7 +14,6 @@ class SensESPBaseAppBuilder { public: virtual SensESPBaseAppBuilder* set_hostname(String hostname) = 0; - virtual SensESPBaseApp* get_app() = 0; }; } // namespace sensesp diff --git a/src/sensesp_minimal_app.cpp b/src/sensesp_minimal_app.cpp deleted file mode 100644 index cdba24e52..000000000 --- a/src/sensesp_minimal_app.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "sensesp_minimal_app.h" - -namespace sensesp { - -SensESPMinimalApp* SensESPMinimalApp::get() { - if (instance_ == nullptr) { - instance_ = new SensESPMinimalApp(); - } - return static_cast(instance_); -} - -} // namespace sensesp diff --git a/src/sensesp_minimal_app.h b/src/sensesp_minimal_app.h index 372dc198d..3e983b116 100644 --- a/src/sensesp_minimal_app.h +++ b/src/sensesp_minimal_app.h @@ -1,6 +1,8 @@ #ifndef SENSESP_MINIMAL_APP_H_ #define SENSESP_MINIMAL_APP_H_ +#include + #include "sensesp_base_app.h" namespace sensesp { @@ -13,7 +15,12 @@ class SensESPMinimalApp : public SensESPBaseApp { /** * @brief Get the singleton instance of the SensESPMinimalApp */ - static SensESPMinimalApp *get(); + static const std::shared_ptr get() { + if (instance_ == nullptr) { + instance_ = std::shared_ptr(new SensESPMinimalApp()); + } + return std::static_pointer_cast(instance_); + } protected: SensESPMinimalApp() : SensESPBaseApp() {} diff --git a/src/sensesp_minimal_app_builder.h b/src/sensesp_minimal_app_builder.h index 239cffe6b..b7e43b413 100644 --- a/src/sensesp_minimal_app_builder.h +++ b/src/sensesp_minimal_app_builder.h @@ -8,7 +8,7 @@ namespace sensesp { class SensESPMinimalAppBuilder : public SensESPBaseAppBuilder { protected: - SensESPMinimalApp* app_; + std::shared_ptr app_; public: SensESPMinimalAppBuilder() { app_ = SensESPMinimalApp::get(); } @@ -16,7 +16,7 @@ class SensESPMinimalAppBuilder : public SensESPBaseAppBuilder { app_->set_hostname(hostname); return this; } - SensESPMinimalApp* get_app() override final { + std::shared_ptr get_app() { app_->setup(); return app_; } diff --git a/test/system/test_event_loop/minimal_app.cpp b/test/system/test_event_loop/minimal_app.cpp index 863886d6a..764abd732 100644 --- a/test/system/test_event_loop/minimal_app.cpp +++ b/test/system/test_event_loop/minimal_app.cpp @@ -2,8 +2,8 @@ #include "elapsedMillis.h" #include "sensesp/sensors/sensor.h" -#include "sensesp/system/observablevalue.h" #include "sensesp/system/lambda_consumer.h" +#include "sensesp/system/observablevalue.h" #include "sensesp_minimal_app_builder.h" #include "unity.h" @@ -11,7 +11,7 @@ using namespace sensesp; void test_sensesp() { SensESPMinimalAppBuilder builder; - SensESPMinimalApp* sensesp_app = builder.get_app(); + auto sensesp_app = builder.get_app(); TEST_ASSERT_NOT_NULL(sensesp_app); auto sensor = RepeatSensor(10, []() { diff --git a/test/system/test_offline/serializable.cpp b/test/system/test_offline/basics.cpp similarity index 58% rename from test/system/test_offline/serializable.cpp rename to test/system/test_offline/basics.cpp index c59533bd8..893a8ceed 100644 --- a/test/system/test_offline/serializable.cpp +++ b/test/system/test_offline/basics.cpp @@ -1,10 +1,13 @@ #include "sensesp.h" -#include "sensesp/system/serializable.h" +#include #include "sensesp/net/networking.h" +#include "sensesp/system/serializable.h" #include "sensesp/system/valueproducer.h" #include "sensesp/transforms/angle_correction.h" +#include "sensesp/transforms/linear.h" +#include "sensesp/transforms/typecast.h" #include "sensesp_base_app.h" #include "sensesp_minimal_app_builder.h" #include "unity.h" @@ -135,11 +138,15 @@ void test_angle_correction_from_json() { // PersistingObservableValue saves itself on change void test_persisting_observable_value_schema() { SensESPMinimalAppBuilder builder; - SensESPMinimalApp* sensesp_app = builder.get_app(); + auto sensesp_app = builder.get_app(); TEST_ASSERT_NOT_NULL(sensesp_app); + ESP_LOGD("test_persisting_observable_value_schema", "about to create object"); + PersistingObservableValue value{2, "/test"}; + ESP_LOGD("test_persisting_observable_value_schema", "object created"); + auto config_item = ConfigItem(&value); String ref_schema = @@ -205,31 +212,32 @@ class Networking_ : public Networking { Networking_(const String& config_path, const String& client_ssid, const String& client_password, const String& ap_ssid, const String& ap_password) - : FileSystemSaveable{config_path}, - Networking(config_path, client_ssid, client_password, ap_ssid, + : Networking(config_path, client_ssid, client_password, ap_ssid, ap_password) {} protected: - friend void test_networking(); + friend void test_save_networking(); }; -void test_networking() { +void test_save_networking() { SensESPMinimalAppBuilder builder; - SensESPMinimalApp* sensesp_app = builder.get_app(); + auto sensesp_app = std::shared_ptr(builder.get_app()); TEST_ASSERT_NOT_NULL(sensesp_app); String wifi_ssid = "Hat Labs Sensors"; - String wifi_password = "kanneluuri2406"; + String wifi_password = "fooba"; String ap_ssid = "SensESP AP!"; String ap_password = "thisisexcellent"; - Networking_* networking = new Networking_( - "/System/WiFi Settings", wifi_ssid, wifi_password, ap_ssid, ap_password); - networking->remove(); - delete networking; + auto networking_ptr = new Networking_("/System/WiFi Settings", wifi_ssid, + wifi_password, ap_ssid, ap_password); - networking = new Networking_("/System/WiFi Settings", wifi_ssid, - wifi_password, ap_ssid, ap_password); + networking_ptr->clear(); + + delete networking_ptr; + + auto networking = std::make_shared( + "/System/WiFi Settings", wifi_ssid, wifi_password, ap_ssid, ap_password); networking->save(); @@ -245,6 +253,137 @@ void test_networking() { networking->ap_settings_.password_.c_str()); } +void test_connect_to() { + SensESPMinimalAppBuilder builder; + auto sensesp_app = std::shared_ptr(builder.get_app()); + TEST_ASSERT_NOT_NULL(sensesp_app); + + ObservableValue value{2}; + ObservableValue value2; + ObservableValue float_value; + + // Same types + + value.connect_to(value2); + value.set(3); + TEST_ASSERT_EQUAL(3, value2.get()); + + // Same types, pointers (using smart pointers for automatic cleanup) + + auto value3 = std::make_shared>(4); + auto value4 = std::make_shared>(); + value3.get()->connect_to(value4.get()); + value3.get()->set(5); + TEST_ASSERT_EQUAL(5, value4.get()->get()); + + // Same types, smart pointers + + auto value5 = std::make_shared>(6); + auto value6 = std::make_shared>(); + value5->connect_to(value6); + value5->set(7); + TEST_ASSERT_EQUAL(7, value6->get()); + + // Automatic type conversion + + value.connect_to(float_value); + value.set(4); + TEST_ASSERT_EQUAL(4, value2.get()); + TEST_ASSERT_EQUAL(4, float_value.get()); + + // Automatic type conversion, pointers + + auto value7 = std::make_shared>(8); + auto value8 = std::make_shared>(); + value7.get()->connect_to(value8.get()); + value7.get()->set(9); + TEST_ASSERT_EQUAL(9, value8.get()->get()); + + // Automatic type conversion, smart pointers + + auto value9 = std::make_shared>(10); + auto value10 = std::make_shared>(); + value9->connect_to(value10); + value9->set(11); + TEST_ASSERT_EQUAL(11, value10->get()); + + // Chaining of same types + + ObservableValue value11{1}; + Linear linear12{2.0, 3.0}; + ObservableValue value13; + value11.connect_to(linear12)->connect_to(value13); + value11.set(2); + TEST_ASSERT_EQUAL(7.0, value13.get()); + + // Chaining of same types, pointers + + auto value14 = std::make_shared>(1); + auto linear15 = std::make_shared(2.0, 3.0); + auto value16 = std::make_shared>(); + value14.get()->connect_to(linear15.get())->connect_to(value16.get()); + value14.get()->set(2); + TEST_ASSERT_EQUAL(7.0, value16.get()->get()); + + // Chaining of same types, smart pointers + + auto value17 = std::make_shared>(1); + auto linear18 = std::make_shared(2.0, 3.0); + auto value19 = std::make_shared>(); + value17->connect_to(linear18)->connect_to(value19); + value17->set(2); + TEST_ASSERT_EQUAL(7.0, value19->get()); + + // Chaining with a transform that changes the type + + ObservableValue value20{1.0}; + RoundToInt round21; + ObservableValue value22; + value20.connect_to(round21)->connect_to(value22); + value20.set(2.3); + TEST_ASSERT_EQUAL(2, value22.get()); + + // Chaining with a transform that changes the type, pointers + + auto value23 = std::make_shared>(1.0); + auto round24 = std::make_shared(); + auto value25 = std::make_shared>(); + value23.get()->connect_to(round24.get())->connect_to(value25.get()); + value23.get()->set(2.3); + TEST_ASSERT_EQUAL(2, value25.get()->get()); + + // Chaining with a transform that changes the type, smart pointers + + auto value26 = std::make_shared>(1.0); + auto round27 = std::make_shared(); + auto value28 = std::make_shared>(); + value26->connect_to(round27)->connect_to(value28); + value26->set(2.3); + TEST_ASSERT_EQUAL(2, value28->get()); +} + +// Now that connections can be made with weak_ptrs, we should be able to +// safely delete both the producer and the consumer. +void test_weak_ptr_connect_to() { + SensESPMinimalAppBuilder builder; + auto sensesp_app = builder.get_app(); + TEST_ASSERT_NOT_NULL(sensesp_app); + + auto value = std::make_shared>(2); + auto value2 = std::make_shared>(); + + value->connect_to(value2); + + value->set(3); + + TEST_ASSERT_EQUAL(3, value2->get()); + + // This will crash if the weak_ptr is not handled correctly + value->set(4); + + TEST_ASSERT_EQUAL(4, value->get()); +} + void setup() { esp_log_level_set("*", ESP_LOG_VERBOSE); @@ -257,7 +396,10 @@ void setup() { RUN_TEST(test_persisting_observable_value_schema); - RUN_TEST(test_networking); + RUN_TEST(test_save_networking); + + RUN_TEST(test_connect_to); + RUN_TEST(test_weak_ptr_connect_to); UNITY_END(); }