feat(energy-pie): finish configurable colors and Symcon 8 migration
Tests / test (push) Waiting to run

This commit is contained in:
dh
2026-10-07 17:16:00 +00:00
parent 5b6125d086
commit 2ce95e77f8
7 changed files with 376 additions and 37 deletions
+26 -2
View File
@@ -66,18 +66,42 @@
"items": [
{
"type": "SelectColor",
"name": "FarbeEigenverbrauch",
"name": "FarbeEigenverbrauchRGB",
"caption": "Eigenverbrauchsquote",
"allowTransparent": false
},
{
"type": "SelectColor",
"name": "FarbeAutarkie",
"name": "FarbeAutarkieRGB",
"caption": "Autarkiegrad",
"allowTransparent": false
}
]
},
{
"type": "ExpansionPanel",
"caption": "Hintergrundfarben",
"items": [
{
"type": "SelectColor",
"name": "FarbeHintergrundRGB",
"caption": "Grundfarbe",
"allowTransparent": false
},
{
"type": "SelectColor",
"name": "FarbeHintergrundAkzent1RGB",
"caption": "Verlaufsfarbe 1 (links und unten)",
"allowTransparent": false
},
{
"type": "SelectColor",
"name": "FarbeHintergrundAkzent2RGB",
"caption": "Verlaufsfarbe 2 (rechts)",
"allowTransparent": false
}
]
},
{
"type": "CheckBox",
"name": "Debug",
+30 -20
View File
@@ -73,6 +73,8 @@
#energy-app {
--card-color: #121216;
--content-color: #f5f5f7;
--background-accent-1-rgb: 99, 179, 255;
--background-accent-2-rgb: 168, 85, 247;
position: relative;
isolation: isolate;
color-scheme: dark;
@@ -86,16 +88,16 @@
background-color: var(--card-color, transparent);
}
/* Keep the original Energy Pie colors behind the scrollable controls. */
/* Keep the configurable background behind the scrollable controls. */
#energy-app::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background:
radial-gradient(520px 420px at 18% 18%, rgba(99, 179, 255, 0.42), transparent 60%),
radial-gradient(560px 460px at 82% 28%, rgba(168, 85, 247, 0.38), transparent 62%),
radial-gradient(620px 520px at 55% 85%, rgba(99, 179, 255, 0.26), transparent 64%);
radial-gradient(520px 420px at 18% 18%, rgba(var(--background-accent-1-rgb), 0.42), transparent 60%),
radial-gradient(560px 460px at 82% 28%, rgba(var(--background-accent-2-rgb), 0.38), transparent 62%),
radial-gradient(620px 520px at 55% 85%, rgba(var(--background-accent-1-rgb), 0.26), transparent 64%);
filter: blur(18px);
pointer-events: none;
}
@@ -309,21 +311,29 @@
const selfConsumptionChart = document.getElementById('self-consumption-chart');
const autarkyChart = document.getElementById('autarky-chart');
function syncColorScheme() {
const cardColor = getComputedStyle(document.documentElement)
.getPropertyValue('--card-color')
.trim();
const match = cardColor.match(/^#?([0-9a-f]{6})$/i);
if (!match) {
return;
}
function colorRgb(color) {
const value = Number.parseInt(color.slice(1), 16);
return [(value >> 16) & 255, (value >> 8) & 255, value & 255];
}
const value = Number.parseInt(match[1], 16);
const red = (value >> 16) & 255;
const green = (value >> 8) & 255;
const blue = value & 255;
const luminance = ((0.2126 * red) + (0.7152 * green) + (0.0722 * blue)) / 255;
document.documentElement.style.colorScheme = luminance < 0.5 ? 'dark' : 'light';
function syncBackground(colors) {
const safeColor = (value, fallback) => typeof value === 'string'
&& /^#[0-9a-f]{6}$/i.test(value) ? value : fallback;
const background = safeColor(colors.background, '#121216');
const accent1 = safeColor(colors.backgroundAccent1, '#63B3FF');
const accent2 = safeColor(colors.backgroundAccent2, '#A855F7');
app.style.setProperty('--card-color', background);
app.style.setProperty('--background-accent-1-rgb', colorRgb(accent1).join(', '));
app.style.setProperty('--background-accent-2-rgb', colorRgb(accent2).join(', '));
const linear = colorRgb(background).map(channel => {
const value = channel / 255;
return value <= 0.04045 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
});
const luminance = 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
const dark = luminance < 0.179;
app.style.setProperty('--content-color', dark ? '#f5f5f7' : '#121216');
app.style.colorScheme = dark ? 'dark' : 'light';
}
function send(action, value) {
@@ -425,7 +435,8 @@
return;
}
syncColorScheme();
const colors = data.colors || {};
syncBackground(colors);
range.value = data.range || 'day';
date.value = data.date || '';
customStart.value = data.customStart || '';
@@ -447,7 +458,6 @@
metric('Einspeisung', values.Einspeisung)
].join('');
const colors = data.colors || {};
donut(
selfConsumptionChart,
data.sharesAvailable === false ? null : values.Eigenverbrauchsquote,
+88 -9
View File
@@ -15,6 +15,16 @@ class Energiediagramm extends IPSModule
private const ATTR_CUSTOM_END = 'CustomEnd';
private const COLOR_SELF_CONSUMPTION = '#2F80ED';
private const COLOR_AUTARKY = '#22A06B';
private const COLOR_BACKGROUND = '#121216';
private const COLOR_BACKGROUND_ACCENT_1 = '#63B3FF';
private const COLOR_BACKGROUND_ACCENT_2 = '#A855F7';
private const COLOR_PROPERTIES = [
'FarbeEigenverbrauch' => self::COLOR_SELF_CONSUMPTION,
'FarbeAutarkie' => self::COLOR_AUTARKY,
'FarbeHintergrund' => self::COLOR_BACKGROUND,
'FarbeHintergrundAkzent1' => self::COLOR_BACKGROUND_ACCENT_1,
'FarbeHintergrundAkzent2' => self::COLOR_BACKGROUND_ACCENT_2,
];
public function Create(): void
{
@@ -30,6 +40,13 @@ class Energiediagramm extends IPSModule
$this->RegisterPropertyInteger('Aktualisierungsintervall', 60);
$this->RegisterPropertyString('FarbeEigenverbrauch', self::COLOR_SELF_CONSUMPTION);
$this->RegisterPropertyString('FarbeAutarkie', self::COLOR_AUTARKY);
$this->RegisterPropertyString('FarbeHintergrund', self::COLOR_BACKGROUND);
$this->RegisterPropertyString('FarbeHintergrundAkzent1', self::COLOR_BACKGROUND_ACCENT_1);
$this->RegisterPropertyString('FarbeHintergrundAkzent2', self::COLOR_BACKGROUND_ACCENT_2);
// Keep legacy string properties: changing their type discards saved colors in Symcon 8.
foreach (self::COLOR_PROPERTIES as $property => $default) {
$this->RegisterPropertyInteger($property . 'RGB', (int) hexdec(substr($default, 1)));
}
$this->RegisterPropertyBoolean('Debug', false);
$today = date('Y-m-d');
@@ -37,6 +54,7 @@ class Energiediagramm extends IPSModule
$this->RegisterAttributeString(self::ATTR_DATE, $today);
$this->RegisterAttributeString(self::ATTR_CUSTOM_START, $today);
$this->RegisterAttributeString(self::ATTR_CUSTOM_END, $today);
$this->RegisterAttributeString('ColorConfiguration', '{}');
$visualizationType = version_compare(IPS_GetKernelVersion(), '9.0', '>=')
? 2
@@ -53,6 +71,10 @@ class Energiediagramm extends IPSModule
{
parent::ApplyChanges();
if ($this->synchronizeColorProperties()) {
return;
}
$today = date('Y-m-d');
$defaultRange = $this->ReadPropertyString('Standardzeitraum');
if (!in_array($defaultRange, ['day', 'week', 'month', 'year', 'total'], true)) {
@@ -84,6 +106,51 @@ class Energiediagramm extends IPSModule
$this->recalculateAndPush();
}
private function synchronizeColorProperties(): bool
{
$previous = json_decode($this->ReadAttributeString('ColorConfiguration'), true);
$previous = is_array($previous) ? $previous : [];
$state = [];
$updates = [];
foreach (self::COLOR_PROPERTIES as $property => $default) {
$legacy = $this->ReadPropertyString($property);
$rgb = $this->ReadPropertyInteger($property . 'RGB');
$old = $previous[$property] ?? null;
$rgbChanged = $rgb !== ($old['rgb'] ?? (int) hexdec(substr($default, 1)));
$legacyChanged = $legacy !== ($old['legacy'] ?? null);
// The native picker wins simultaneous edits; old scripts can still set hex strings.
if ($rgbChanged && $rgb >= 0 && $rgb <= 0xFFFFFF) {
$legacy = sprintf('#%06X', $rgb);
} elseif (!$rgbChanged && $legacyChanged) {
$normalized = EnergyChartCalculator::normalizeColor($legacy, '');
if ($normalized !== '') {
$rgb = (int) hexdec(substr($normalized, 1));
}
}
if ($legacy !== $this->ReadPropertyString($property)) {
$updates[$property] = $legacy;
}
if ($rgb !== $this->ReadPropertyInteger($property . 'RGB')) {
$updates[$property . 'RGB'] = $rgb;
}
$state[$property] = ['legacy' => $legacy, 'rgb' => $rgb];
}
foreach ($updates as $property => $value) {
IPS_SetProperty($this->InstanceID, $property, $value);
}
$this->WriteAttributeString('ColorConfiguration', json_encode($state, JSON_THROW_ON_ERROR));
if ($updates === []) {
return false;
}
IPS_ApplyChanges($this->InstanceID);
return true;
}
public function GetVisualizationTile(): string
{
$path = __DIR__ . '/module.html';
@@ -277,6 +344,18 @@ class Energiediagramm extends IPSModule
$this->ReadPropertyString('FarbeAutarkie'),
self::COLOR_AUTARKY
),
'background' => EnergyChartCalculator::normalizeColor(
$this->ReadPropertyString('FarbeHintergrund'),
self::COLOR_BACKGROUND
),
'backgroundAccent1' => EnergyChartCalculator::normalizeColor(
$this->ReadPropertyString('FarbeHintergrundAkzent1'),
self::COLOR_BACKGROUND_ACCENT_1
),
'backgroundAccent2' => EnergyChartCalculator::normalizeColor(
$this->ReadPropertyString('FarbeHintergrundAkzent2'),
self::COLOR_BACKGROUND_ACCENT_2
),
],
'values' => EnergyChartCalculator::metrics(0.0, 0.0, 0.0, null, $sharesAvailable),
];
@@ -435,6 +514,14 @@ class Energiediagramm extends IPSModule
return false;
}
foreach (self::COLOR_PROPERTIES as $property => $default) {
$rgb = $this->ReadPropertyInteger($property . 'RGB');
if ($rgb < 0 || $rgb > 0xFFFFFF
|| EnergyChartCalculator::normalizeColor($this->ReadPropertyString($property), '') === '') {
return false;
}
}
return $this->ReadPropertyFloat('Zaehlerfaktor') > 0.0
&& $this->ReadPropertyInteger('Aktualisierungsintervall') >= 10
&& $this->ReadPropertyInteger('Aktualisierungsintervall') <= 3600
@@ -442,15 +529,7 @@ class Energiediagramm extends IPSModule
$this->ReadPropertyString('Standardzeitraum'),
['day', 'week', 'month', 'year', 'total'],
true
)
&& EnergyChartCalculator::normalizeColor(
$this->ReadPropertyString('FarbeEigenverbrauch'),
''
) !== ''
&& EnergyChartCalculator::normalizeColor(
$this->ReadPropertyString('FarbeAutarkie'),
''
) !== '';
);
}
private function requiredSourceIdsAreEmpty(): bool