From 9ae791edd423e0b9e379ed60de04f24b34ee215c Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 7 Dec 2023 12:46:38 +0100 Subject: [PATCH] Feature: Added ability to change the brightness of the LEDs Based on the idea of @moritzlerch with several modifications like pwmTable and structure --- include/Configuration.h | 5 ++ include/Led_Single.h | 7 ++- include/defaults.h | 2 + src/Configuration.cpp | 12 ++++ src/Led_Single.cpp | 87 ++++++++++++++++++++-------- src/WebApi_device.cpp | 12 ++++ webapp/src/locales/de.json | 7 ++- webapp/src/locales/en.json | 3 + webapp/src/locales/fr.json | 3 + webapp/src/types/DeviceConfig.ts | 5 ++ webapp/src/views/DeviceAdminView.vue | 58 ++++++++++++++++++- 11 files changed, 171 insertions(+), 30 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 5e74c3a..c0df771 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "PinMapping.h" #include #define CONFIG_FILENAME "/config.json" @@ -144,6 +145,10 @@ struct CONFIG_T { uint8_t Language; } Display; + struct { + uint8_t Brightness; + } Led_Single[PINMAPPING_LED_COUNT]; + INVERTER_CONFIG_T Inverter[INV_MAX_COUNT]; char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1]; }; diff --git a/include/Led_Single.h b/include/Led_Single.h index 9622600..b4cabde 100644 --- a/include/Led_Single.h +++ b/include/Led_Single.h @@ -19,6 +19,8 @@ private: void setLoop(); void outputLoop(); + void setLed(uint8_t ledNo, bool ledState); + Task _setTask; Task _outputTask; @@ -28,8 +30,9 @@ private: Blink, }; - LedState_t _ledState[PINMAPPING_LED_COUNT]; - LedState_t _allState; + LedState_t _ledMode[PINMAPPING_LED_COUNT]; + LedState_t _allMode; + bool _ledStateCurrent[PINMAPPING_LED_COUNT]; TimeoutHelper _blinkTimeout; }; diff --git a/include/defaults.h b/include/defaults.h index 264e666..36f2cfb 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -100,3 +100,5 @@ #define DISPLAY_LANGUAGE 0U #define REACHABLE_THRESHOLD 2U + +#define LED_BRIGHTNESS 100U \ No newline at end of file diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 8874a57..8e7fe8f 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -104,6 +104,12 @@ bool ConfigurationClass::write() display["contrast"] = config.Display.Contrast; display["language"] = config.Display.Language; + JsonArray leds = device.createNestedArray("led"); + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + JsonObject led = leds.createNestedObject(); + led["brightness"] = config.Led_Single[i].Brightness; + } + JsonArray inverters = doc.createNestedArray("inverters"); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { JsonObject inv = inverters.createNestedObject(); @@ -259,6 +265,12 @@ bool ConfigurationClass::read() config.Display.Contrast = display["contrast"] | DISPLAY_CONTRAST; config.Display.Language = display["language"] | DISPLAY_LANGUAGE; + JsonArray leds = device["led"]; + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + JsonObject led = leds[i].as(); + config.Led_Single[i].Brightness = led["brightness"] | LED_BRIGHTNESS; + } + JsonArray inverters = doc["inverters"]; for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { JsonObject inv = inverters[i].as(); diff --git a/src/Led_Single.cpp b/src/Led_Single.cpp index 40d4857..21eb3bf 100644 --- a/src/Led_Single.cpp +++ b/src/Led_Single.cpp @@ -12,6 +12,31 @@ LedSingleClass LedSingle; +/* + The table is calculated using the following formula + (See https://www.mikrocontroller.net/articles/LED-Fading) + a = Step count: 101 --> 0 - 100 + b = PWM resolution: 256: 0 - 255 + y = Calculated value of index x: + y = 0 if x = 0 + y = pow(2, log2(b-1) * (x+1) / a) if x > 0 +*/ +const uint8_t pwmTable[] = { + 0, + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 7, 7, 8, 8, 8, 9, 9, + 10, 11, 11, 12, 12, 13, 14, 15, 16, 16, + 17, 18, 19, 20, 22, 23, 24, 25, 27, 28, + 30, 32, 33, 35, 37, 39, 42, 44, 47, 49, + 52, 55, 58, 61, 65, 68, 72, 76, 81, 85, + 90, 95, 100, 106, 112, 118, 125, 132, 139, 147, + 156, 164, 174, 183, 194, 205, 216, 228, 241, 255 +}; + +#define LED_OFF 0 + LedSingleClass::LedSingleClass() { } @@ -28,11 +53,11 @@ void LedSingleClass::init(Scheduler* scheduler) if (pin.led[i] >= 0) { pinMode(pin.led[i], OUTPUT); - digitalWrite(pin.led[i], LOW); + setLed(i, false); ledActive = true; } - _ledState[i] = LedState_t::Off; + _ledMode[i] = LedState_t::Off; } if (ledActive) { @@ -51,58 +76,52 @@ void LedSingleClass::init(Scheduler* scheduler) void LedSingleClass::setLoop() { - if (_allState == LedState_t::On) { + if (_allMode == LedState_t::On) { const CONFIG_T& config = Configuration.get(); // Update network status - _ledState[0] = LedState_t::Off; + _ledMode[0] = LedState_t::Off; if (NetworkSettings.isConnected()) { - _ledState[0] = LedState_t::Blink; + _ledMode[0] = LedState_t::Blink; } struct tm timeinfo; if (getLocalTime(&timeinfo, 5) && (!config.Mqtt.Enabled || (config.Mqtt.Enabled && MqttSettings.getConnected()))) { - _ledState[0] = LedState_t::On; + _ledMode[0] = LedState_t::On; } // Update inverter status - _ledState[1] = LedState_t::Off; + _ledMode[1] = LedState_t::Off; if (Hoymiles.getNumInverters() && Datastore.getIsAtLeastOnePollEnabled()) { // set LED status if (Datastore.getIsAllEnabledReachable() && Datastore.getIsAllEnabledProducing()) { - _ledState[1] = LedState_t::On; + _ledMode[1] = LedState_t::On; } if (Datastore.getIsAllEnabledReachable() && !Datastore.getIsAllEnabledProducing()) { - _ledState[1] = LedState_t::Blink; + _ledMode[1] = LedState_t::Blink; } } - } else if (_allState == LedState_t::Off) { - _ledState[0] = LedState_t::Off; - _ledState[1] = LedState_t::Off; + } else if (_allMode == LedState_t::Off) { + _ledMode[0] = LedState_t::Off; + _ledMode[1] = LedState_t::Off; } } void LedSingleClass::outputLoop() { - auto& pin = PinMapping.get(); for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { - - if (pin.led[i] < 0) { - continue; - } - - switch (_ledState[i]) { + switch (_ledMode[i]) { case LedState_t::Off: - digitalWrite(pin.led[i], LOW); + setLed(i, false); break; case LedState_t::On: - digitalWrite(pin.led[i], HIGH); + setLed(i, true); break; case LedState_t::Blink: if (_blinkTimeout.occured()) { - digitalWrite(pin.led[i], !digitalRead(pin.led[i])); + setLed(i, !_ledStateCurrent[i]); _blinkTimeout.reset(); } break; @@ -110,12 +129,32 @@ void LedSingleClass::outputLoop() } } +void LedSingleClass::setLed(uint8_t ledNo, bool ledState) +{ + const auto& pin = PinMapping.get(); + const auto& config = Configuration.get(); + + if (pin.led[ledNo] < 0) { + return; + } + + const uint32_t currentPWM = ledcRead(analogGetChannel(pin.led[ledNo])); + const uint32_t targetPWM = ledState ? pwmTable[config.Led_Single[ledNo].Brightness] : LED_OFF; + + if (currentPWM == targetPWM) { + return; + } + + analogWrite(pin.led[ledNo], targetPWM); + _ledStateCurrent[ledNo] = ledState; +} + void LedSingleClass::turnAllOff() { - _allState = LedState_t::Off; + _allMode = LedState_t::Off; } void LedSingleClass::turnAllOn() { - _allState = LedState_t::On; + _allMode = LedState_t::On; } diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index af5f7d3..9008f94 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -84,6 +84,12 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) display["contrast"] = config.Display.Contrast; display["language"] = config.Display.Language; + JsonArray leds = root.createNestedArray("led"); + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + JsonObject led = leds.createNestedObject(); + led["brightness"] = config.Led_Single[i].Brightness; + } + response->setLength(); request->send(response); } @@ -155,6 +161,12 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) config.Display.Contrast = root["display"]["contrast"].as(); config.Display.Language = root["display"]["language"].as(); + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + Serial.println(root["led"][i]["brightness"].as()); + config.Led_Single[i].Brightness = root["led"][i]["brightness"].as(); + config.Led_Single[i].Brightness = min(100, config.Led_Single[i].Brightness); + } + Display.setOrientation(config.Display.Rotation); Display.enablePowerSafe = config.Display.PowerSafe; Display.enableScreensaver = config.Display.ScreenSaver; diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 81ac5b4..fe94668 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -574,12 +574,15 @@ "Rotation": "Rotation:", "rot0": "Keine Rotation", "rot90": "90 Grad Drehung", + "rot180": "180 Grad Drehung", + "rot270": "270 Grad Drehung", "DisplayLanguage": "Displaysprache:", "en": "Englisch", "de": "Deutsch", "fr": "Französisch", - "rot180": "180 Grad Drehung", - "rot270": "270 Grad Drehung", + "Leds": "LEDs", + "EqualBrightness": "Gleiche Helligkeit:", + "LedBrightness": "LED {led} Helligkeit ({brightness}):", "Save": "@:dtuadmin.Save" }, "pininfo": { diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 67faba0..ec284f7 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -580,6 +580,9 @@ "en": "English", "de": "German", "fr": "French", + "Leds": "LEDs", + "EqualBrightness": "Equal brightness:", + "LedBrightness": "LED {led} brightness ({brightness}):", "Save": "@:dtuadmin.Save" }, "pininfo": { diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 006ab01..8f96264 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -580,6 +580,9 @@ "en": "Anglais", "de": "Allemand", "fr": "Français", + "Leds": "LEDs", + "EqualBrightness": "Même luminosité:", + "LedBrightness": "LED {led} luminosité ({brightness}):", "Save": "@:dtuadmin.Save" }, "pininfo": { diff --git a/webapp/src/types/DeviceConfig.ts b/webapp/src/types/DeviceConfig.ts index 8b77c5b..0711526 100644 --- a/webapp/src/types/DeviceConfig.ts +++ b/webapp/src/types/DeviceConfig.ts @@ -8,7 +8,12 @@ export interface Display { language: number; } +export interface Led { + brightness: number; +} + export interface DeviceConfig { curPin: Device; display: Display; + led: Array; } \ No newline at end of file diff --git a/webapp/src/views/DeviceAdminView.vue b/webapp/src/views/DeviceAdminView.vue index 2af0e50..bcf3d40 100644 --- a/webapp/src/views/DeviceAdminView.vue +++ b/webapp/src/views/DeviceAdminView.vue @@ -13,6 +13,8 @@ }} + + + @@ -109,7 +134,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import InputElement from '@/components/InputElement.vue'; import PinInfo from '@/components/PinInfo.vue'; -import type { DeviceConfig } from "@/types/DeviceConfig"; +import type { DeviceConfig, Led } from "@/types/DeviceConfig"; import type { PinMapping, Device } from "@/types/PinMapping"; import { authHeader, handleResponse } from '@/utils/authentication'; import { defineComponent } from 'vue'; @@ -130,6 +155,7 @@ export default defineComponent({ alertMessage: "", alertType: "info", showAlert: false, + equalBrightnessCheckVal: false, displayRotationList: [ { key: 0, value: 'rot0' }, { key: 1, value: 'rot90' }, @@ -147,6 +173,14 @@ export default defineComponent({ this.getDeviceConfig(); this.getPinMappingList(); }, + watch: { + equalBrightnessCheckVal: function(val) { + if (!val) { + return; + } + this.deviceConfigList.led.every(v => v.brightness = this.deviceConfigList.led[0].brightness); + } + }, methods: { getPinMappingList() { this.pinMappingLoading = true; @@ -180,7 +214,10 @@ export default defineComponent({ this.deviceConfigList = data; this.dataLoading = false; } - ); + ) + .then(() => { + this.equalBrightnessCheckVal = this.isEqualBrightness(); + }); }, savePinConfig(e: Event) { e.preventDefault(); @@ -202,6 +239,23 @@ export default defineComponent({ } ); }, + getLedIdFromNumber(ledNo: number) : string { + return 'inputLED' + ledNo + 'Brightness'; + }, + getNumberFromLedId(id: string): number { + return parseInt(id.replace("inputLED", "").replace("Brightness", "")); + }, + isEqualBrightness(): boolean { + const allEqual = (arr : Led[]) => arr.every(v => v.brightness === arr[0].brightness); + return allEqual(this.deviceConfigList.led); + }, + syncSliders(event: Event) { + if (!this.equalBrightnessCheckVal) { + return; + } + const srcId = this.getNumberFromLedId((event.target as Element).id); + this.deviceConfigList.led.every(v => v.brightness = this.deviceConfigList.led[srcId].brightness); + } }, }); \ No newline at end of file