diff --git a/usermods/usermod_v2_busstatus/README.md b/usermods/usermod_v2_busstatus/README.md new file mode 100644 index 0000000000..5fc7666b49 --- /dev/null +++ b/usermods/usermod_v2_busstatus/README.md @@ -0,0 +1,41 @@ +# Bus Status Usermod + +v2 usermod to display bus/port status on a configured LED segment +Use instead of the builtin STATUSLED + +WLED uses the term `bus` to refer to a logical port. This may be a physical collection of gpio pins or a virtual +network bus. + +This usermod finds the type of each specified bus, plus the device status, and displays the result on configured LEDs. +The bus status colours are configurable and listed at the end. + +The device status colours are: +* Red device element = Error value +* AP mode = Blue +* Connected to Wifi = Green +* MQTT Connected = Cyan + +The status is also shown on the UI Info page. + +## Define Your Options + +* `USERMOD_BUSSTATUS` - have this usermod included +* `BUSSTATUS_PINS` - (optional) Array of GPIO pin numbers to display the status of, in order. e.g. `-D BUSSTATUS_PINS='{1,2,-1}'` + - `-1` displays the device status. Default: { -1, 1, 2} + +## Configure + +* Set up your pixel strips in `LED Preferences`. For a racitup.com v1 board the settings for the status bus are: + - Type = WS281x + - LED order = GRB + - Length = 3 + - GPIO = 12 + - Skip = 0 + - `Make a segment for each output`: ticked + - Ensure the automatic brightness limiter is set up appropriately for your number of LEDs, or disable + +* Configure the associated segment. The bus type colours below are configurable: + - Effect = Bus Status + - Colour 1 = DMX (Blue) + - Colour 2 = Digital (Green) + - Colour 3 = PWM (Magenta) diff --git a/usermods/usermod_v2_busstatus/usermod_v2_busstatus.h b/usermods/usermod_v2_busstatus/usermod_v2_busstatus.h new file mode 100644 index 0000000000..248d5b0300 --- /dev/null +++ b/usermods/usermod_v2_busstatus/usermod_v2_busstatus.h @@ -0,0 +1,179 @@ +#pragma once +#include "wled.h" + +#define PSBUFSIZ 20 +#ifndef BUSSTATUS_PINS +#define BUSSTATUS_PINS { -1, 1, 2} +#endif +#define BUSSTATUS_SIZ (sizeof((int8_t[])BUSSTATUS_PINS)/sizeof(int8_t)) +#ifndef FX_MODE_BUSSTATUS +#define FX_MODE_BUSSTATUS 255 // Temporary Effect ID +#endif + +class BusStatusUsermod : public Usermod { + + private: + char buf[PSBUFSIZ]; + + // strings to reduce flash memory usage (used more than twice) + static const char _FX_MODE_BUSSTATUS[]; + static const int8_t status_gpios[BUSSTATUS_SIZ]; + + static uint8_t get_bustype(uint8_t gpio) { + Bus* bus; + uint8_t numPins; + uint8_t pinArr[8]; + for (uint8_t busNr = 0; busNr < busses.getNumBusses(); busNr++) { + bus = busses.getBus(busNr); + if (bus) { + numPins = bus->getPins(pinArr); + for (uint8_t i = 0; i < numPins; i++) { + if (pinArr[i] == gpio) { + return bus->getType(); + } + } + } + } + return TYPE_NONE; + } + + // errorFlag -> Red + static uint32_t device_status_colour() { + // assumes max error flag is 36 + uint8_t red_val = errorFlag * 7; + if (WLED_CONNECTED) return RGBW32(red_val,255,0,0); // Green + else if (WLED_MQTT_CONNECTED) return RGBW32(red_val,128,128,0); // Cyan + else if (apActive) return RGBW32(red_val,0,255,0); // Blue + else return RGBW32(red_val,0,0,0); // Red + } + + static const char* device_status_string() { + if (WLED_CONNECTED) return "Wifi"; + else if (WLED_MQTT_CONNECTED) return "MQTT"; + else if (apActive) return "HotSpot"; + else return "Unknown"; + } + + char* device_error_string() { + snprintf(buf, PSBUFSIZ, "0x%X", errorFlag); + return buf; + } + + // configurable status colours for the first 3 only + static uint32_t bus_status_colour(int8_t gpio) { + uint8_t busType = get_bustype(gpio); + if (IS_DMX(busType)) return SEGCOLOR(0); // Blue + else if (IS_DIGITAL(busType)) return SEGCOLOR(1); // Green + else if (IS_PWM(busType)) return SEGCOLOR(2); // Magenta + else return 0UL; // Black + } + + const char* bus_status_string(int8_t gpio) { + // bus types in const.h + uint8_t busType = get_bustype(gpio); + if (busType == TYPE_NONE) return "Unconfigured"; + else if (IS_DMX(busType)) return "DMX"; + else if (IS_DIGITAL(busType)) return "Digital LED"; + else if (IS_PWM(busType)) return "PWM LED"; + else snprintf(buf, PSBUFSIZ, "Type %d", busType); + return buf; + } + + // modification of static/blink + static uint16_t mode_busstatus(void) { + uint32_t status_colours[BUSSTATUS_SIZ]; + for (uint16_t i = 0; i < BUSSTATUS_SIZ; i++) { + if (status_gpios[i] < 0) { + status_colours[i] = device_status_colour(); + } else { + status_colours[i] = bus_status_colour(status_gpios[i]); + } + } + uint16_t i; + for (uint16_t s = 0; s < SEGLEN; s++) { + i = s % BUSSTATUS_SIZ; + SEGMENT.setPixelColor(s, status_colours[i]); + } + return strip.isOffRefreshRequired() ? FRAMETIME : 350; + } + + public: + // gets called once at boot. Do all initialization that doesn't depend on network here + // parameters are already read by readFromConfig, busses & segments have been created by beginStrip() + void setup() { + strip.addEffect(FX_MODE_BUSSTATUS, &mode_busstatus, _FX_MODE_BUSSTATUS); + } + + // gets called every time WiFi is (re-)connected. Initialize own network + // interfaces here + void connected() {} + + // main loop + void loop() {} + + /* + * addToJsonInfo() adds info to the main UI Info section + */ + void addToJsonInfo(JsonObject& root) { + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + for (uint8_t i = 0; i < BUSSTATUS_SIZ; i++) { + int8_t gpio = status_gpios[i]; + if (gpio < 0) { + user.createNestedArray("Device Status").add(device_status_string()); + user.createNestedArray("Error Status").add(device_error_string()); + } else { + snprintf_P(buf, PSBUFSIZ, "GPIO %d Status", gpio); + JsonArray arr = user.createNestedArray(buf); + arr.add(bus_status_string(gpio)); + } + } + } + + /* + * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void addToJsonState(JsonObject& root) {} + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void readFromJsonState(JsonObject& root) {} + + /* + * 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) + * Use serializeconfig 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. + */ + void addToConfig(JsonObject& root) {} + + /* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * Called before beginStrip() on startup so busses and segments not created yet. + * Also called on settings page save + * + * The function should return true if configuration was successfully loaded or false if there was no configuration. + */ + bool readFromConfig(JsonObject& root) { + 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_BUSSTATUS; + } +}; + +const int8_t BusStatusUsermod::status_gpios[BUSSTATUS_SIZ] = BUSSTATUS_PINS; +// config strings to reduce flash memory usage (used more than twice) +// (None);(3 Colours);(None);(1Dim); +const char BusStatusUsermod::_FX_MODE_BUSSTATUS[] PROGMEM = "Bus Status@;1,2,3;;1;"; diff --git a/wled00/const.h b/wled00/const.h index 3f4ef5cfc1..73f71b7cf9 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -151,6 +151,7 @@ #define USERMOD_ID_WIREGUARD 41 //Usermod "wireguard.h" #define USERMOD_ID_INTERNAL_TEMPERATURE 42 //Usermod "usermod_internal_temperature.h" #define USERMOD_ID_LDR_DUSK_DAWN 43 //Usermod "usermod_LDR_Dusk_Dawn_v2.h" +#define USERMOD_ID_BUSSTATUS 44 //Usermod "usermod_v2_busstatus.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -217,7 +218,7 @@ // - 0b001 (dec. 16-31) digital (data pin only) // - 0b010 (dec. 32-47) analog (PWM) // - 0b011 (dec. 48-63) digital (data + clock / SPI) -// - 0b100 (dec. 64-79) unused/reserved +// - 0b100 (dec. 64-79) other digital // - 0b101 (dec. 80-95) virtual network busses // - 0b110 (dec. 96-111) unused/reserved // - 0b111 (dec. 112-127) unused/reserved @@ -251,6 +252,9 @@ #define TYPE_LPD8806 52 #define TYPE_P9813 53 #define TYPE_LPD6803 54 +//Other digital (IS_DIGITAL is false, requires custom Bus class) (64-79) +#define TYPE_DMX_OUT 64 //DMX requires a hardware UART. TX and Direction pins +#define TYPE_DMX_IN 65 //RX and Direction pins //Network types (master broadcast) (80-95) #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) #define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus, unused) @@ -258,6 +262,7 @@ #define TYPE_NET_DDP_RGBW 88 //network DDP RGBW bus (master broadcast bus) #define IS_DIGITAL(t) ((t) & 0x10) //digital are 16-31 and 48-63 +#define IS_DMX(t) ((t) >= TYPE_DMX_OUT && (t) <= TYPE_DMX_IN) #define IS_PWM(t) ((t) > 40 && (t) < 46) #define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only #define IS_2PIN(t) ((t) > 47) diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 024e2bdc9c..0d23d5e541 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -201,6 +201,10 @@ #include "../usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h" #endif +#ifdef USERMOD_BUSSTATUS + #include "../usermods/usermod_v2_busstatus/usermod_v2_busstatus.h" +#endif + void registerUsermods() { /* @@ -380,4 +384,8 @@ void registerUsermods() #ifdef USERMOD_LDR_DUSK_DAWN usermods.add(new LDR_Dusk_Dawn_v2()); #endif + + #ifdef USERMOD_BUSSTATUS + usermods.add(new BusStatusUsermod()); + #endif }