Feature: Added ability to change the brightness of the LEDs
Based on the idea of @moritzlerch with several modifications like pwmTable and structure
This commit is contained in:
parent
3b6e9343d4
commit
9ae791edd4
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "PinMapping.h"
|
||||
#include <cstdint>
|
||||
|
||||
#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];
|
||||
};
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -100,3 +100,5 @@
|
||||
#define DISPLAY_LANGUAGE 0U
|
||||
|
||||
#define REACHABLE_THRESHOLD 2U
|
||||
|
||||
#define LED_BRIGHTNESS 100U
|
||||
@ -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<JsonObject>();
|
||||
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<JsonObject>();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<uint8_t>();
|
||||
config.Display.Language = root["display"]["language"].as<uint8_t>();
|
||||
|
||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||
Serial.println(root["led"][i]["brightness"].as<uint8_t>());
|
||||
config.Led_Single[i].Brightness = root["led"][i]["brightness"].as<uint8_t>();
|
||||
config.Led_Single[i].Brightness = min<uint8_t>(100, config.Led_Single[i].Brightness);
|
||||
}
|
||||
|
||||
Display.setOrientation(config.Display.Rotation);
|
||||
Display.enablePowerSafe = config.Display.PowerSafe;
|
||||
Display.enableScreensaver = config.Display.ScreenSaver;
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -580,6 +580,9 @@
|
||||
"en": "English",
|
||||
"de": "German",
|
||||
"fr": "French",
|
||||
"Leds": "LEDs",
|
||||
"EqualBrightness": "Equal brightness:",
|
||||
"LedBrightness": "LED {led} brightness ({brightness}):",
|
||||
"Save": "@:dtuadmin.Save"
|
||||
},
|
||||
"pininfo": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -8,7 +8,12 @@ export interface Display {
|
||||
language: number;
|
||||
}
|
||||
|
||||
export interface Led {
|
||||
brightness: number;
|
||||
}
|
||||
|
||||
export interface DeviceConfig {
|
||||
curPin: Device;
|
||||
display: Display;
|
||||
led: Array<Led>;
|
||||
}
|
||||
@ -13,6 +13,8 @@
|
||||
}}</button>
|
||||
<button class="nav-link" id="nav-display-tab" data-bs-toggle="tab" data-bs-target="#nav-display"
|
||||
type="button" role="tab" aria-controls="nav-display">{{ $t('deviceadmin.Display') }}</button>
|
||||
<button class="nav-link" id="nav-leds-tab" data-bs-toggle="tab" data-bs-target="#nav-leds"
|
||||
type="button" role="tab" aria-controls="nav-leds">{{ $t('deviceadmin.Leds') }}</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
@ -96,6 +98,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade show" id="nav-leds" role="tabpanel" aria-labelledby="nav-leds-tab" tabindex="0">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<InputElement :label="$t('deviceadmin.EqualBrightness')"
|
||||
v-model="equalBrightnessCheckVal" type="checkbox" />
|
||||
|
||||
<div class="row mb-3" v-for="(ledSetting, index) in deviceConfigList.led">
|
||||
<label :for="getLedIdFromNumber(index)" class="col-sm-2 col-form-label">{{
|
||||
$t('deviceadmin.LedBrightness', {
|
||||
led: index,
|
||||
brightness: $n(ledSetting.brightness / 100,
|
||||
'percent')
|
||||
})
|
||||
}}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="range" class="form-range" min="0" max="100" :id="getLedIdFromNumber(index)"
|
||||
v-model="ledSetting.brightness" @change="syncSliders" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mb-3">{{ $t('deviceadmin.Save') }}</button>
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user