diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 46e5a4e9b..811341e0d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,7 @@ jobs: - examples/analog_input.cpp - examples/hysteresis.cpp - examples/lambda_transform.cpp + - examples/manual_networking.cpp - examples/minimal_app.cpp - examples/relay_control.cpp - examples/rpm_counter.cpp diff --git a/examples/manual_networking.cpp b/examples/manual_networking.cpp new file mode 100644 index 000000000..950c87d23 --- /dev/null +++ b/examples/manual_networking.cpp @@ -0,0 +1,68 @@ +#include + +#include "sensesp/net/http_server.h" +#include "sensesp/net/networking.h" +#include "sensesp/sensors/digital_input.h" +#include "sensesp/system/lambda_consumer.h" +#include "sensesp/transforms/linear.h" +#include "sensesp/transforms/typecast.h" +#include "sensesp_minimal_app_builder.h" +#include "sensesp/signalk/signalk_output.h" + +using namespace sensesp; + +const unsigned int read_delay = 500; + +const uint8_t input_pin1 = 0; + +// This is a sample program to demonstrate how to instantiate a +// SensESPMinimalApp application and setup networking manually. +// +// The program reacts to changes on GPIO pin 0 and prints the value to the +// serial console. + +ReactESP app; + +void setup() { + SetupSerialDebug(115200); + + SensESPMinimalAppBuilder builder; + auto sensesp_app = builder.set_hostname("counter-test")->get_app(); + + // manually create Networking and HTTPServer objects to enable + // the HTTP configuration interface + + WiFi.mode(WIFI_STA); + WiFi.begin("Hat Labs Sensors", "kanneluuri2406"); + + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + + debugD("Connected to WiFi. IP address: %s", WiFi.localIP().toString().c_str()); + + WiFi.setHostname(SensESPBaseApp::get_hostname().c_str()); + + auto* http_server = new HTTPServer(); + + auto* digin = new DigitalInputChange(input_pin1, INPUT, CHANGE); + + digin->connect_to(new LambdaConsumer([](bool input) { + Serial.printf("millis: %d\n", millis()); + Serial.printf("Digin: %d\n", input); + })); + + digin->connect_to(new SKOutputBool("electrical.switches.0.state", "/digin/state")); + + sensesp_app->start(); +} + +// The loop function is called in an endless loop during program execution. +// It simply calls `app.tick()` which will then execute all reactions as needed. +void loop() { app.tick(); } diff --git a/src/sensesp/net/networking.cpp b/src/sensesp/net/networking.cpp index 3666a5a5b..552a6369e 100644 --- a/src/sensesp/net/networking.cpp +++ b/src/sensesp/net/networking.cpp @@ -27,7 +27,11 @@ Networking::Networking(String config_path, String ssid, String password, wifi_manager_password_{wifi_manager_password}, Startable(80), Resettable(0) { - this->output = WiFiState::kWifiNoAP; + + // 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); })); preset_ssid = ssid; preset_password = password; @@ -81,35 +85,10 @@ void Networking::activate_wifi_manager() { } } -void Networking::setup_wifi_callbacks() { - - - 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); - -} - /** * @brief Start WiFi using preset SSID and password. */ void Networking::setup_saved_ssid() { - this->emit(WiFiState::kWifiDisconnected); - setup_wifi_callbacks(); - String hostname = SensESPBaseApp::get_hostname(); WiFi.setHostname(hostname.c_str()); @@ -136,37 +115,6 @@ void Networking::setup_saved_ssid() { } } -/** - * This method gets called when WiFi is connected to the AP and has - * received an IP address. - */ -void Networking::wifi_station_connected() { - debugI("Connected to wifi, SSID: %s (signal: %d)", WiFi.SSID().c_str(), - WiFi.RSSI()); - debugI("IP address of Device: %s", WiFi.localIP().toString().c_str()); - debugI("Default route: %s", WiFi.gatewayIP().toString().c_str()); - debugI("DNS server: %s", WiFi.dnsIP().toString().c_str()); - this->emit(WiFiState::kWifiConnectedToAP); -} - -void Networking::wifi_ap_enabled() { - debugI("WiFi Access Point enabled, SSID: %s", WiFi.softAPSSID().c_str()); - debugI("IP address of Device: %s", WiFi.softAPIP().toString().c_str()); - - // Setting the AP mode happens immediately, - // so this callback is likely called already before all startables have been - // initiated. Delay the WiFi state update until the start of the event loop. - ReactESP::app->onDelay(0, [this]() {this->emit(WiFiState::kWifiAPModeActivated);}); -} - -/** - * This method gets called when WiFi is disconnected from the AP. - */ -void Networking::wifi_disconnected() { - debugI("Disconnected from wifi."); - this->emit(WiFiState::kWifiDisconnected); -} - /** * @brief Start WiFi using WiFi Manager. * @@ -179,8 +127,6 @@ void Networking::setup_wifi_manager() { String hostname = SensESPBaseApp::get_hostname(); - setup_wifi_callbacks(); - // set config save notify callback wifi_manager->setBreakAfterConfig(true); @@ -212,6 +158,7 @@ void Networking::setup_wifi_manager() { } const char* pconfig_ssid = config_ssid.c_str(); + // this is the only WiFi state we actively still emit this->emit(WiFiState::kWifiManagerActivated); WiFi.setHostname(SensESPBaseApp::get_hostname().c_str()); @@ -330,4 +277,13 @@ void Networking::reset() { WiFi.begin("0", "0"); } +WiFiStateProducer* WiFiStateProducer::instance_ = nullptr; + +WiFiStateProducer* WiFiStateProducer::get_singleton() { + if (instance_ == nullptr) { + instance_ = new WiFiStateProducer(); + } + return instance_; +} + } // namespace sensesp diff --git a/src/sensesp/net/networking.h b/src/sensesp/net/networking.h index fb36078a0..ba41a2d17 100644 --- a/src/sensesp/net/networking.h +++ b/src/sensesp/net/networking.h @@ -16,6 +16,86 @@ namespace sensesp { +/** + * @brief Provide information about the current WiFi state. + * + * WiFiStateProducer reads the current network state using + * Arduino Core callbacks. It is a replacement for the Networking class + * ValueProducer output and effectively decouples the Networkig class + * from the rest of the system. This allows for replacing the Networking + * class with a different implementation. + */ +class WiFiStateProducer : public ValueProducer, public Startable { + 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(); + + virtual void start() override { + setup_wifi_callbacks(); + // Emit the current state immediately + this->emit(this->output); + } + + protected: + WiFiStateProducer() : Startable(81) { this->output = WiFiState::kWifiNoAP; } + + void setup_wifi_callbacks() { + 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); + } + + void wifi_station_connected() { + debugI("Connected to wifi, SSID: %s (signal: %d)", WiFi.SSID().c_str(), + WiFi.RSSI()); + debugI("IP address of Device: %s", WiFi.localIP().toString().c_str()); + debugI("Default route: %s", WiFi.gatewayIP().toString().c_str()); + debugI("DNS server: %s", WiFi.dnsIP().toString().c_str()); + this->emit(WiFiState::kWifiConnectedToAP); + } + + void wifi_ap_enabled() { + debugI("WiFi Access Point enabled, SSID: %s", WiFi.softAPSSID().c_str()); + debugI("IP address of Device: %s", WiFi.softAPIP().toString().c_str()); + + // Setting the AP mode happens immediately, + // so this callback is likely called already before all startables have been + // initiated. Delay the WiFi state update until the start of the event loop. + ReactESP::app->onDelay( + 0, [this]() { this->emit(WiFiState::kWifiAPModeActivated); }); + } + + void wifi_disconnected() { + debugI("Disconnected from wifi."); + this->emit(WiFiState::kWifiDisconnected); + } + + static WiFiStateProducer* instance_; +}; + /** * @brief Manages the ESP's connection to the Wifi network. */ @@ -43,7 +123,6 @@ class Networking : public Configurable, protected: void setup_saved_ssid(); - void setup_wifi_callbacks(); void setup_wifi_manager(); // callbacks @@ -81,6 +160,8 @@ class Networking : public Configurable, // in the hardcoded value String default_hostname = ""; + WiFiStateProducer* wifi_state_producer; + const char* wifi_manager_password_; }; diff --git a/src/sensesp_app.cpp b/src/sensesp_app.cpp index 8260c3a2f..752439efb 100644 --- a/src/sensesp_app.cpp +++ b/src/sensesp_app.cpp @@ -47,7 +47,7 @@ void SensESPApp::setup() { sk_server_address_, sk_server_port_); // connect the system status controller - this->networking_->connect_to(&system_status_controller_); + WiFiStateProducer::get_singleton()->connect_to(&system_status_controller_); this->ws_client_->connect_to(&system_status_controller_); // create the MDNS discovery object