diff --git a/usermods/WifiStatus/readme.md b/usermods/WifiStatus/readme.md new file mode 100644 index 0000000000..7ad4ce3f13 --- /dev/null +++ b/usermods/WifiStatus/readme.md @@ -0,0 +1,35 @@ +# Wifi Status on LED +v2 Usermod to indicate Wifi connection state on LED connected to a GPIO. + +Three states are supported: + +1. Wifi **disconnected** - indicated by fast blinks of LED +2. Wifi **connected** - LED on with very short off after 2 seconds +3. Wifi in **Access Point mode** - LED blink twice time and pause for 1 second + +## Installation + +Copy and update the example `platformio_override.ini.sample` +from the Rotary Encoder UI usermode folder to the root directory of your particular build. +This file should be placed in the same directory as `platformio.ini`. + +### Define Your Options + +* `USERMOD_WIFI_STATUS` - define this to have this usermod included wled00\usermods_list.cpp + +Example to add in platformio_override: + -D USERMOD_WIFI_STATUS + +You can also configure plugin parameters (like led pin and enablation) using Usermods settings page. + +### PlatformIO requirements + +No special requirements. + +## Authors +Piotr Jóźwiak [@petrusvr](https://github.com/petrusvr) + +## Change Log + +2023-11 +* First public release diff --git a/usermods/WifiStatus/wifi_status.h b/usermods/WifiStatus/wifi_status.h new file mode 100644 index 0000000000..4d604d591a --- /dev/null +++ b/usermods/WifiStatus/wifi_status.h @@ -0,0 +1,210 @@ +#pragma once + +#include "wled.h" +#include + +#ifndef USERMOD_WIFI_STATUS_LED_PIN + #ifdef ARDUINO_ARCH_ESP32 + #define USERMOD_WIFI_STATUS_LED_PIN 2 + #else //ESP8266 boards + #define USERMOD_WIFI_STATUS_LED_PIN 16 + #endif +#endif + +class WifiStatusUsermod : public Usermod { + + private: + bool initDone = false; + bool enabled = true; + int8_t ledPin = USERMOD_WIFI_STATUS_LED_PIN; // LED PIN + std::deque> blinkSequence {}; + + int ledState = LOW; + unsigned long previousMillis = 0; + unsigned int interval = 1000; + + // string that are used multiple time (this will save some flash memory) + static const char _name[]; + static const char _enabled[]; + + void enable(bool enable) { + enabled = enable; + } + + void reconfigure(int8_t oldPin, bool oldEnabled){ + if(oldEnabled != enabled || oldPin != ledPin){ + DEBUG_PRINT(FPSTR(_name)); + DEBUG_PRINTLN(F(" enabled or ledPin changed")); + if(isAllocatedPin(oldPin)){ + deallocatePin(oldPin); + digitalWrite(oldPin, LOW); + } + } + configure(); + } + + void configure(){ + if(enabled && pinManager.isPinOk(ledPin) && ledPin >= 0){ + if(!isAllocatedPin(ledPin)){ + if(allocatePin(ledPin)){ + pinMode(ledPin, OUTPUT); + }else{ + DEBUG_PRINT(FPSTR(_name)); + DEBUG_PRINTLN(F(" Can't allocate ledPin")); + enabled = false; + return; + } + } else pinMode(ledPin, OUTPUT); + } + } + + bool isAllocatedPin(int8_t pin){ + return pinManager.getPinOwner(pin) == PinOwner::UM_WIFI_STATUS; + } + + void deallocatePin(int8_t pin){ + pinManager.deallocatePin(pin, PinOwner::UM_WIFI_STATUS); + } + + bool allocatePin(int8_t pin){ + return pinManager.allocatePin(pin, true, PinOwner::UM_WIFI_STATUS); + } + + void handleBlink(){ + unsigned long currentMillis = millis(); + + if(blinkSequence.size() < 1) //nothing to blink + return; + + if (currentMillis - previousMillis >= interval){ + auto event = blinkSequence.front(); + DEBUG_PRINTF("handleBlink() STATE=%d INTERVAL=%d \n", event.first, event.second); + blinkSequence.pop_front(); + digitalWrite(ledPin, event.first); + interval = event.second; + previousMillis = millis(); + } + } + + void addSequenceConnected(){ + blinkSequence.push_back({HIGH, 2000}); + blinkSequence.push_back({LOW, 10}); + } + + void addSequenceDisconnected(){ + blinkSequence.push_back({HIGH, 200}); + blinkSequence.push_back({LOW, 100}); + } + + void addSequenceAPmode(){ + // blinkSequence.push_back({LOW, 200}); + blinkSequence.push_back({HIGH, 200}); + blinkSequence.push_back({LOW, 200}); + blinkSequence.push_back({HIGH, 200}); + blinkSequence.push_back({LOW, 1000}); + } + + public: + + // gets called once at boot. Do all initialization that doesn't depend on network here + void setup() { + DEBUG_PRINTLN(F("---Wifi Status Usermod: setup() start")); + initDone = true; + configure(); + DEBUG_PRINTLN(F("---Wifi Status Usermod: setup() end")); + } + + // gets called every time WiFi is (re-)connected. Initialize own network interfaces here + void connected() { + DEBUG_PRINTLN(F("---Wifi Status Usermod: connected()")); + } + + /* + * Main Wifi Status loop() + */ + void loop() { + if (!enabled || strip.isUpdating()) + return; + + if(blinkSequence.size() <= 1){ + if(WiFi.status() == WL_CONNECTED){ + addSequenceConnected(); + }else if(apActive){ + addSequenceAPmode(); + }else { + addSequenceDisconnected(); + } + } + + handleBlink(); + } + + /* + * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will also not yet add your setting to one of the settings pages automatically. + * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ + void addToConfig(JsonObject& root) { + // we add JSON object: {"WifiStatus": {"enabled": true, "pin": 2}} + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + top[FPSTR(_enabled)] = enabled; + top["pin"] = ledPin; + + DEBUG_PRINTLN(F("Autosave config saved.")); + } + + /* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + * + * The function should return true if configuration was successfully loaded or false if there was no configuration. + */ + bool readFromConfig(JsonObject& root) { + // Looking for JSON object: {"WifiStatus": {"enabled": true, "pin": 2}} + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINT(FPSTR(_name)); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + bool oldEnabled = enabled; + int8_t oldLedPin = ledPin; + + enabled = top[FPSTR(_enabled)] | enabled; + ledPin = top[FPSTR("pin")] | ledPin; + + reconfigure(oldLedPin, oldEnabled); + + DEBUG_PRINT(FPSTR(_name)); + DEBUG_PRINTLN(F(" config (re)loaded.")); + + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return true; + } + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() { + return USERMOD_ID_WIFI_STATUS; + } +}; + +// strings to reduce flash memory usage (used more than twice) +const char WifiStatusUsermod::_name[] PROGMEM = "WifiStatus"; +const char WifiStatusUsermod::_enabled[] PROGMEM = "enabled"; diff --git a/wled00/const.h b/wled00/const.h index 6ee8345182..54d40dd6b3 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -150,6 +150,7 @@ #define USERMOD_ID_KLIPPER 40 //Usermod Klipper percentage #define USERMOD_ID_WIREGUARD 41 //Usermod "wireguard.h" #define USERMOD_ID_INTERNAL_TEMPERATURE 42 //Usermod "usermod_internal_temperature.h" +#define USERMOD_ID_WIFI_STATUS 43 //Usermod "wifi_status.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 6248a60668..ca3f006b8e 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -60,7 +60,8 @@ enum struct PinOwner : uint8_t { UM_BME280 = USERMOD_ID_BME280, // 0x1E // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE, // 0x20 // Usermod "audio_reactive.h" UM_SdCard = USERMOD_ID_SD_CARD, // 0x25 // Usermod "usermod_sd_card.h" - UM_PWM_OUTPUTS = USERMOD_ID_PWM_OUTPUTS // 0x26 // Usermod "usermod_pwm_outputs.h" + UM_PWM_OUTPUTS = USERMOD_ID_PWM_OUTPUTS, // 0x26 // Usermod "usermod_pwm_outputs.h" + UM_WIFI_STATUS = USERMOD_ID_WIFI_STATUS // 0x2B // Usermod "wifi_status.h" }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 6184571acc..0b4c2712cb 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -77,6 +77,10 @@ #include "../usermods/usermod_v2_auto_save/usermod_v2_auto_save.h" #endif +#ifdef USERMOD_WIFI_STATUS + #include "../usermods/WifiStatus/wifi_status.h" +#endif + #ifdef USERMOD_DHT #include "../usermods/DHT/usermod_dht.h" #endif @@ -258,6 +262,10 @@ void registerUsermods() usermods.add(new AutoSaveUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY #endif + #ifdef USERMOD_WIFI_STATUS + usermods.add(new WifiStatusUsermod()); + #endif + #ifdef USERMOD_DHT usermods.add(new UsermodDHT()); #endif