diff --git a/docs/DeviceProfiles.md b/docs/DeviceProfiles.md index 083b9ec..bad916e 100644 --- a/docs/DeviceProfiles.md +++ b/docs/DeviceProfiles.md @@ -102,4 +102,6 @@ The json file can contain multiple profiles. Each profile requires a name and di | display.data | number | Data Pin (e.g. SDA for i2c displays) required for all displays. Use 255 for not assigned pins. | | display.clk | number | Clock Pin (e.g. SCL for i2c displays) required for SSD1306 and SH1106. Use 255 for not assigned pins. | | display.cs | number | Chip Select Pin required for PCD8544. Use 255 for not assigned pins. | -| display.reset | number | Reset Pin required for PCD8544, optional for all other displays. Use 255 for not assigned pins. | \ No newline at end of file +| display.reset | number | Reset Pin required for PCD8544, optional for all other displays. Use 255 for not assigned pins. | +| led.led0 | number | LED pin for network indication. Blinking = WLAN connected but NTP & MQTT (if enabled) disconnected. On = WLAN, NTP, MQTT connected. Off = Network not connected | +| led.led1 | number | LED pin for inverter indication. On = All inverters reachable & producing. Blinking = All inverters reachable but not producing. Off = At least one inverter is not reachable. Only inverters with polling enabled are considered. | \ No newline at end of file diff --git a/include/Led_Single.h b/include/Led_Single.h new file mode 100644 index 0000000..22fc87a --- /dev/null +++ b/include/Led_Single.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "PinMapping.h" +#include + +#define LEDSINGLE_UPDATE_INTERVAL 2000 + +enum eLedFunction { + CONNECTED_NETWORK, + CONNECTED_MQTT, + INV_REACHABLE, + INV_PRODUCING, +}; + +class LedSingleClass { +public: + LedSingleClass(); + void init(); + void loop(); + +private: + enum class LedState_t { + On, + Off, + Blink, + }; + + LedState_t _ledState[PINMAPPING_LED_COUNT]; + TimeoutHelper _updateTimeout; + TimeoutHelper _blinkTimeout; + uint8_t _ledActive = 0; +}; + +extern LedSingleClass LedSingle; \ No newline at end of file diff --git a/include/PinMapping.h b/include/PinMapping.h index 4862b06..b80b3ef 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -6,6 +6,7 @@ #include #define PINMAPPING_FILENAME "/pin_mapping.json" +#define PINMAPPING_LED_COUNT 2 #define MAPPING_NAME_STRLEN 31 @@ -29,6 +30,7 @@ struct PinMapping_t { uint8_t display_clk; uint8_t display_cs; uint8_t display_reset; + int8_t led[PINMAPPING_LED_COUNT]; }; class PinMappingClass { diff --git a/src/Led_Single.cpp b/src/Led_Single.cpp new file mode 100644 index 0000000..5b6f569 --- /dev/null +++ b/src/Led_Single.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Thomas Basler and others + */ +#include "Led_Single.h" +#include "Configuration.h" +#include "MqttSettings.h" +#include "NetworkSettings.h" +#include "PinMapping.h" +#include + +LedSingleClass LedSingle; + +LedSingleClass::LedSingleClass() +{ +} + +void LedSingleClass::init() +{ + _blinkTimeout.set(500); + _updateTimeout.set(LEDSINGLE_UPDATE_INTERVAL); + + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + auto& pin = PinMapping.get(); + + if (pin.led[i] >= 0) { + pinMode(pin.led[i], OUTPUT); + digitalWrite(pin.led[i], LOW); + _ledActive++; + } + + _ledState[i] = LedState_t::Off; + } +} + +void LedSingleClass::loop() +{ + if (_ledActive == 0) { + return; + } + + if (_updateTimeout.occured()) { + const CONFIG_T& config = Configuration.get(); + + // Update network status + _ledState[0] = LedState_t::Off; + + if (NetworkSettings.isConnected()) { + _ledState[0] = LedState_t::Blink; + } + + struct tm timeinfo; + if (getLocalTime(&timeinfo, 5) && (!config.Mqtt_Enabled || (config.Mqtt_Enabled && MqttSettings.getConnected()))) { + _ledState[0] = LedState_t::On; + } + + // Update inverter status + _ledState[1] = LedState_t::Off; + if (Hoymiles.getNumInverters()) { + bool allReachable = true; + bool allProducing = true; + for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { + auto inv = Hoymiles.getInverterByPos(i); + if (inv == nullptr) { + continue; + } + if (inv->getEnablePolling()) { + if (!inv->isReachable()) { + allReachable = false; + } + if (!inv->isProducing()) { + allProducing = false; + } + } + } + // set LED status + if (allReachable && allProducing) { + _ledState[1] = LedState_t::On; + } + if (allReachable && !allProducing) { + _ledState[1] = LedState_t::Blink; + } + } + + _updateTimeout.reset(); + } + + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + auto& pin = PinMapping.get(); + + if (pin.led[i] < 0) { + continue; + } + + switch (_ledState[i]) { + case LedState_t::Off: + digitalWrite(pin.led[i], LOW); + break; + case LedState_t::On: + digitalWrite(pin.led[i], HIGH); + break; + case LedState_t::Blink: + if (_blinkTimeout.occured()) { + digitalWrite(pin.led[i], !digitalRead(pin.led[i])); + _blinkTimeout.reset(); + } + break; + } + } +} diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index 45111a3..a8e0bc4 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -30,6 +30,14 @@ #define DISPLAY_RESET 255 #endif +#ifndef LED0 +#define LED0 -1 +#endif + +#ifndef LED1 +#define LED1 -1 +#endif + PinMappingClass PinMapping; PinMappingClass::PinMappingClass() @@ -61,6 +69,8 @@ PinMappingClass::PinMappingClass() _pinMapping.display_cs = DISPLAY_CS; _pinMapping.display_reset = DISPLAY_RESET; + _pinMapping.led[0] = LED0; + _pinMapping.led[1] = LED1; } PinMapping_t& PinMappingClass::get() @@ -113,6 +123,9 @@ bool PinMappingClass::init(const String& deviceMapping) _pinMapping.display_cs = doc[i]["display"]["cs"] | DISPLAY_CS; _pinMapping.display_reset = doc[i]["display"]["reset"] | DISPLAY_RESET; + _pinMapping.led[0] = doc[i]["led"]["led0"] | LED0; + _pinMapping.led[1] = doc[i]["led"]["led1"] | LED1; + return true; } } diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 7d706b1..53f79d3 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -63,6 +63,10 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) displayPinObj["cs"] = pin.display_cs; displayPinObj["reset"] = pin.display_reset; + JsonObject ledPinObj = curPin.createNestedObject("led"); + ledPinObj["led0"] = pin.led[0]; + ledPinObj["led1"] = pin.led[1]; + JsonObject display = root.createNestedObject("display"); display["rotation"] = config.Display_Rotation; display["power_safe"] = config.Display_PowerSafe; diff --git a/src/main.cpp b/src/main.cpp index 2e7c843..76cb1f3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "Configuration.h" #include "Display_Graphic.h" #include "InverterSettings.h" +#include "Led_Single.h" #include "MessageOutput.h" #include "MqttHandleDtu.h" #include "MqttHandleHass.h" @@ -114,6 +115,11 @@ void setup() Display.setStartupDisplay(); MessageOutput.println("done"); + // Initialize Single LEDs + MessageOutput.print("Initialize LEDs... "); + LedSingle.init(); + MessageOutput.println("done"); + // Check for default DTU serial MessageOutput.print("Check for default DTU serial... "); if (config.Dtu_Serial == DTU_SERIAL) { @@ -150,4 +156,6 @@ void loop() yield(); MessageOutput.loop(); yield(); + LedSingle.loop(); + yield(); } \ No newline at end of file