feat(energy-pie): finish configurable colors and Symcon 8 migration
Tests / test (push) Waiting to run
Tests / test (push) Waiting to run
This commit is contained in:
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user