#include "WidgetConfig.hpp" #include #include // WidgetConfig implementation void WidgetConfig::serialize(uint8_t* buf) const { memset(buf, 0, SERIALIZED_SIZE); size_t pos = 0; buf[pos++] = id; buf[pos++] = static_cast(type); buf[pos++] = x & 0xFF; buf[pos++] = (x >> 8) & 0xFF; buf[pos++] = y & 0xFF; buf[pos++] = (y >> 8) & 0xFF; buf[pos++] = width & 0xFF; buf[pos++] = (width >> 8) & 0xFF; buf[pos++] = height & 0xFF; buf[pos++] = (height >> 8) & 0xFF; buf[pos++] = visible ? 1 : 0; buf[pos++] = static_cast(textSource); memcpy(&buf[pos], text, MAX_TEXT_LEN); pos += MAX_TEXT_LEN; buf[pos++] = knxAddress & 0xFF; buf[pos++] = (knxAddress >> 8) & 0xFF; buf[pos++] = fontSize; buf[pos++] = textAlign; buf[pos++] = isContainer ? 1 : 0; buf[pos++] = textColor.r; buf[pos++] = textColor.g; buf[pos++] = textColor.b; buf[pos++] = bgColor.r; buf[pos++] = bgColor.g; buf[pos++] = bgColor.b; buf[pos++] = bgOpacity; buf[pos++] = borderRadius; buf[pos++] = borderWidth; buf[pos++] = borderColor.r; buf[pos++] = borderColor.g; buf[pos++] = borderColor.b; buf[pos++] = borderOpacity; buf[pos++] = shadow.offsetX; buf[pos++] = shadow.offsetY; buf[pos++] = shadow.blur; buf[pos++] = shadow.spread; buf[pos++] = shadow.color.r; buf[pos++] = shadow.color.g; buf[pos++] = shadow.color.b; buf[pos++] = shadow.enabled ? 1 : 0; buf[pos++] = isToggle ? 1 : 0; buf[pos++] = knxAddressWrite & 0xFF; buf[pos++] = (knxAddressWrite >> 8) & 0xFF; buf[pos++] = static_cast(action); buf[pos++] = targetScreen; // Icon properties buf[pos++] = iconCodepoint & 0xFF; buf[pos++] = (iconCodepoint >> 8) & 0xFF; buf[pos++] = (iconCodepoint >> 16) & 0xFF; buf[pos++] = (iconCodepoint >> 24) & 0xFF; buf[pos++] = iconPosition; buf[pos++] = iconSize; buf[pos++] = static_cast(iconGap); buf[pos++] = iconPositionX & 0xFF; buf[pos++] = (iconPositionX >> 8) & 0xFF; buf[pos++] = iconPositionY & 0xFF; buf[pos++] = (iconPositionY >> 8) & 0xFF; // Hierarchy buf[pos++] = static_cast(parentId); // Chart properties buf[pos++] = chartPeriod; buf[pos++] = chartSeriesCount; for (size_t i = 0; i < CHART_MAX_SERIES; ++i) { buf[pos++] = chartKnxAddress[i] & 0xFF; buf[pos++] = (chartKnxAddress[i] >> 8) & 0xFF; } for (size_t i = 0; i < CHART_MAX_SERIES; ++i) { buf[pos++] = static_cast(chartTextSource[i]); } for (size_t i = 0; i < CHART_MAX_SERIES; ++i) { buf[pos++] = chartSeriesColor[i].r; buf[pos++] = chartSeriesColor[i].g; buf[pos++] = chartSeriesColor[i].b; } // Chart flags (1 byte) buf[pos++] = (chartShowXLine ? 0x01 : 0) | (chartShowBg ? 0x02 : 0) | (chartShowGrid ? 0x04 : 0) | (chartShowYLine ? 0x08 : 0) | (chartShowXLabels ? 0x10 : 0) | (chartShowYLabels ? 0x20 : 0); buf[pos++] = chartLineWidth; buf[pos++] = chartPointCount; buf[pos++] = chartShowPoints ? 1 : 0; buf[pos++] = chartPointSize; // Secondary KNX address (left value) buf[pos++] = knxAddress2 & 0xFF; buf[pos++] = (knxAddress2 >> 8) & 0xFF; buf[pos++] = static_cast(textSource2); memcpy(&buf[pos], text2, MAX_FORMAT_LEN); pos += MAX_FORMAT_LEN; // Tertiary KNX address (right value) buf[pos++] = knxAddress3 & 0xFF; buf[pos++] = (knxAddress3 >> 8) & 0xFF; buf[pos++] = static_cast(textSource3); memcpy(&buf[pos], text3, MAX_FORMAT_LEN); pos += MAX_FORMAT_LEN; // Conditions buf[pos++] = conditionCount; for (size_t i = 0; i < MAX_CONDITIONS; ++i) { const StyleCondition& cond = conditions[i]; // threshold (4 bytes as float) memcpy(&buf[pos], &cond.threshold, sizeof(float)); pos += sizeof(float); buf[pos++] = static_cast(cond.op); buf[pos++] = static_cast(cond.source); buf[pos++] = cond.priority; // ConditionStyle buf[pos++] = cond.style.iconCodepoint & 0xFF; buf[pos++] = (cond.style.iconCodepoint >> 8) & 0xFF; buf[pos++] = (cond.style.iconCodepoint >> 16) & 0xFF; buf[pos++] = (cond.style.iconCodepoint >> 24) & 0xFF; buf[pos++] = cond.style.textColor.r; buf[pos++] = cond.style.textColor.g; buf[pos++] = cond.style.textColor.b; buf[pos++] = cond.style.bgColor.r; buf[pos++] = cond.style.bgColor.g; buf[pos++] = cond.style.bgColor.b; buf[pos++] = cond.style.bgOpacity; buf[pos++] = cond.style.flags; buf[pos++] = cond.enabled ? 1 : 0; } // RoomCard sub-buttons buf[pos++] = subButtonCount; buf[pos++] = subButtonSize; buf[pos++] = subButtonDistance; buf[pos++] = subButtonOpacity; buf[pos++] = cardStyle; for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) { const SubButtonConfig& sb = subButtons[i]; buf[pos++] = sb.iconCodepointOff & 0xFF; buf[pos++] = (sb.iconCodepointOff >> 8) & 0xFF; buf[pos++] = (sb.iconCodepointOff >> 16) & 0xFF; buf[pos++] = (sb.iconCodepointOff >> 24) & 0xFF; buf[pos++] = sb.iconCodepointOn & 0xFF; buf[pos++] = (sb.iconCodepointOn >> 8) & 0xFF; buf[pos++] = (sb.iconCodepointOn >> 16) & 0xFF; buf[pos++] = (sb.iconCodepointOn >> 24) & 0xFF; buf[pos++] = sb.knxAddrRead & 0xFF; buf[pos++] = (sb.knxAddrRead >> 8) & 0xFF; buf[pos++] = sb.knxAddrWrite & 0xFF; buf[pos++] = (sb.knxAddrWrite >> 8) & 0xFF; buf[pos++] = sb.bgColorOn.r; buf[pos++] = sb.bgColorOn.g; buf[pos++] = sb.bgColorOn.b; buf[pos++] = sb.bgColorOff.r; buf[pos++] = sb.bgColorOff.g; buf[pos++] = sb.bgColorOff.b; buf[pos++] = sb.iconColorOn.r; buf[pos++] = sb.iconColorOn.g; buf[pos++] = sb.iconColorOn.b; buf[pos++] = sb.iconColorOff.r; buf[pos++] = sb.iconColorOff.g; buf[pos++] = sb.iconColorOff.b; buf[pos++] = static_cast(sb.position); buf[pos++] = static_cast(sb.action); buf[pos++] = sb.targetScreen; buf[pos++] = sb.enabled ? 1 : 0; buf[pos++] = 0; // padding buf[pos++] = 0; // padding } // Arc properties buf[pos++] = arcMin & 0xFF; buf[pos++] = (arcMin >> 8) & 0xFF; buf[pos++] = arcMax & 0xFF; buf[pos++] = (arcMax >> 8) & 0xFF; buf[pos++] = arcUnit; buf[pos++] = arcShowValue ? 1 : 0; buf[pos++] = static_cast(arcScaleOffset); buf[pos++] = arcScaleColor.r; buf[pos++] = arcScaleColor.g; buf[pos++] = arcScaleColor.b; buf[pos++] = arcValueColor.r; buf[pos++] = arcValueColor.g; buf[pos++] = arcValueColor.b; buf[pos++] = arcValueFontSize; // Theme buf[pos++] = themeFixed ? 1 : 0; } void WidgetConfig::deserialize(const uint8_t* buf) { size_t pos = 0; id = buf[pos++]; type = static_cast(buf[pos++]); x = buf[pos] | (buf[pos+1] << 8); pos += 2; y = buf[pos] | (buf[pos+1] << 8); pos += 2; width = buf[pos] | (buf[pos+1] << 8); pos += 2; height = buf[pos] | (buf[pos+1] << 8); pos += 2; visible = buf[pos++] != 0; textSource = static_cast(buf[pos++]); memcpy(text, &buf[pos], MAX_TEXT_LEN); pos += MAX_TEXT_LEN; text[MAX_TEXT_LEN - 1] = '\0'; knxAddress = buf[pos] | (buf[pos+1] << 8); pos += 2; fontSize = buf[pos++]; textAlign = buf[pos++]; isContainer = buf[pos++] != 0; textColor.r = buf[pos++]; textColor.g = buf[pos++]; textColor.b = buf[pos++]; bgColor.r = buf[pos++]; bgColor.g = buf[pos++]; bgColor.b = buf[pos++]; bgOpacity = buf[pos++]; borderRadius = buf[pos++]; borderWidth = buf[pos++]; borderColor.r = buf[pos++]; borderColor.g = buf[pos++]; borderColor.b = buf[pos++]; borderOpacity = buf[pos++]; shadow.offsetX = static_cast(buf[pos++]); shadow.offsetY = static_cast(buf[pos++]); shadow.blur = buf[pos++]; shadow.spread = buf[pos++]; shadow.color.r = buf[pos++]; shadow.color.g = buf[pos++]; shadow.color.b = buf[pos++]; shadow.enabled = buf[pos++] != 0; isToggle = buf[pos++] != 0; knxAddressWrite = buf[pos] | (buf[pos+1] << 8); pos += 2; action = static_cast(buf[pos++]); targetScreen = buf[pos++]; // Icon properties iconCodepoint = buf[pos] | (buf[pos+1] << 8) | (buf[pos+2] << 16) | (buf[pos+3] << 24); pos += 4; iconPosition = buf[pos++]; iconSize = buf[pos++]; iconGap = static_cast(buf[pos++]); iconPositionX = static_cast(buf[pos] | (buf[pos+1] << 8)); pos += 2; iconPositionY = static_cast(buf[pos] | (buf[pos+1] << 8)); pos += 2; // Hierarchy parentId = static_cast(buf[pos++]); // Chart properties chartPeriod = buf[pos++]; chartSeriesCount = buf[pos++]; for (size_t i = 0; i < CHART_MAX_SERIES; ++i) { chartKnxAddress[i] = buf[pos] | (buf[pos + 1] << 8); pos += 2; } for (size_t i = 0; i < CHART_MAX_SERIES; ++i) { chartTextSource[i] = static_cast(buf[pos++]); } for (size_t i = 0; i < CHART_MAX_SERIES; ++i) { chartSeriesColor[i].r = buf[pos++]; chartSeriesColor[i].g = buf[pos++]; chartSeriesColor[i].b = buf[pos++]; } // Chart flags if (pos + 1 <= SERIALIZED_SIZE) { uint8_t chartFlags = buf[pos++]; chartShowXLine = (chartFlags & 0x01) != 0; chartShowBg = (chartFlags & 0x02) != 0; chartShowGrid = (chartFlags & 0x04) != 0; chartShowYLine = (chartFlags & 0x08) != 0; chartShowXLabels = (chartFlags & 0x10) != 0; chartShowYLabels = (chartFlags & 0x20) != 0; } else { chartShowXLine = true; chartShowYLine = true; chartShowXLabels = true; chartShowYLabels = true; chartShowBg = true; chartShowGrid = true; } if (pos + 4 <= SERIALIZED_SIZE) { chartLineWidth = buf[pos++]; if (chartLineWidth == 0) chartLineWidth = 2; chartPointCount = buf[pos++]; if (chartPointCount == 0) chartPointCount = 120; chartShowPoints = buf[pos++] != 0; chartPointSize = buf[pos++]; if (chartPointSize == 0) chartPointSize = 4; } else { chartLineWidth = 2; chartPointCount = 120; chartShowPoints = false; chartPointSize = 4; } // Secondary KNX address (left value) - check bounds for backward compatibility if (pos + 19 <= SERIALIZED_SIZE) { knxAddress2 = buf[pos] | (buf[pos + 1] << 8); pos += 2; textSource2 = static_cast(buf[pos++]); memcpy(text2, &buf[pos], MAX_FORMAT_LEN); pos += MAX_FORMAT_LEN; text2[MAX_FORMAT_LEN - 1] = '\0'; } else { knxAddress2 = 0; textSource2 = TextSource::STATIC; text2[0] = '\0'; } // Tertiary KNX address (right value) if (pos + 19 <= SERIALIZED_SIZE) { knxAddress3 = buf[pos] | (buf[pos + 1] << 8); pos += 2; textSource3 = static_cast(buf[pos++]); memcpy(text3, &buf[pos], MAX_FORMAT_LEN); pos += MAX_FORMAT_LEN; text3[MAX_FORMAT_LEN - 1] = '\0'; } else { knxAddress3 = 0; textSource3 = TextSource::STATIC; text3[0] = '\0'; } // Conditions if (pos + 1 <= SERIALIZED_SIZE) { conditionCount = buf[pos++]; if (conditionCount > MAX_CONDITIONS) conditionCount = MAX_CONDITIONS; } else { conditionCount = 0; } for (size_t i = 0; i < MAX_CONDITIONS; ++i) { StyleCondition& cond = conditions[i]; if (pos + 20 <= SERIALIZED_SIZE) { memcpy(&cond.threshold, &buf[pos], sizeof(float)); pos += sizeof(float); cond.op = static_cast(buf[pos++]); cond.source = static_cast(buf[pos++]); cond.priority = buf[pos++]; cond.style.iconCodepoint = buf[pos] | (buf[pos + 1] << 8) | (buf[pos + 2] << 16) | (buf[pos + 3] << 24); pos += 4; cond.style.textColor.r = buf[pos++]; cond.style.textColor.g = buf[pos++]; cond.style.textColor.b = buf[pos++]; cond.style.bgColor.r = buf[pos++]; cond.style.bgColor.g = buf[pos++]; cond.style.bgColor.b = buf[pos++]; cond.style.bgOpacity = buf[pos++]; cond.style.flags = buf[pos++]; cond.enabled = buf[pos++] != 0; } else { cond = StyleCondition{}; } } // RoomCard sub-buttons if (pos + 5 <= SERIALIZED_SIZE) { subButtonCount = buf[pos++]; if (subButtonCount > MAX_SUBBUTTONS) subButtonCount = MAX_SUBBUTTONS; subButtonSize = buf[pos++]; if (subButtonSize == 0) subButtonSize = 40; // Default subButtonDistance = buf[pos++]; if (subButtonDistance == 0) subButtonDistance = 80; // Default 80px subButtonOpacity = buf[pos++]; if (subButtonOpacity == 0) subButtonOpacity = 255; // Default fully opaque cardStyle = buf[pos++]; // 0=Bubble, 1=Tile } else { subButtonCount = 0; subButtonSize = 40; subButtonDistance = 80; subButtonOpacity = 255; cardStyle = 0; // Default to Bubble style } for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) { SubButtonConfig& sb = subButtons[i]; if (pos + 30 <= SERIALIZED_SIZE) { sb.iconCodepointOff = buf[pos] | (buf[pos + 1] << 8) | (buf[pos + 2] << 16) | (buf[pos + 3] << 24); pos += 4; sb.iconCodepointOn = buf[pos] | (buf[pos + 1] << 8) | (buf[pos + 2] << 16) | (buf[pos + 3] << 24); pos += 4; sb.knxAddrRead = buf[pos] | (buf[pos + 1] << 8); pos += 2; sb.knxAddrWrite = buf[pos] | (buf[pos + 1] << 8); pos += 2; sb.bgColorOn.r = buf[pos++]; sb.bgColorOn.g = buf[pos++]; sb.bgColorOn.b = buf[pos++]; sb.bgColorOff.r = buf[pos++]; sb.bgColorOff.g = buf[pos++]; sb.bgColorOff.b = buf[pos++]; sb.iconColorOn.r = buf[pos++]; sb.iconColorOn.g = buf[pos++]; sb.iconColorOn.b = buf[pos++]; sb.iconColorOff.r = buf[pos++]; sb.iconColorOff.g = buf[pos++]; sb.iconColorOff.b = buf[pos++]; sb.position = static_cast(buf[pos++]); sb.action = static_cast(buf[pos++]); sb.targetScreen = buf[pos++]; sb.enabled = buf[pos++] != 0; pos += 2; // padding } else { sb = SubButtonConfig{}; } } // Arc properties if (pos + 6 <= SERIALIZED_SIZE) { arcMin = static_cast(buf[pos] | (buf[pos + 1] << 8)); pos += 2; arcMax = static_cast(buf[pos] | (buf[pos + 1] << 8)); pos += 2; arcUnit = buf[pos++]; arcShowValue = buf[pos++] != 0; } else { arcMin = 0; arcMax = 100; arcUnit = 0; arcShowValue = true; } if (pos + 4 <= SERIALIZED_SIZE) { arcScaleOffset = static_cast(buf[pos++]); arcScaleColor.r = buf[pos++]; arcScaleColor.g = buf[pos++]; arcScaleColor.b = buf[pos++]; } else { arcScaleOffset = 0; arcScaleColor = textColor; } if (pos + 4 <= SERIALIZED_SIZE) { arcValueColor.r = buf[pos++]; arcValueColor.g = buf[pos++]; arcValueColor.b = buf[pos++]; arcValueFontSize = buf[pos++]; } else { arcValueColor = textColor; arcValueFontSize = fontSize; } // Theme if (pos + 1 <= SERIALIZED_SIZE) { themeFixed = buf[pos++] != 0; } else { themeFixed = false; } } WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const char* labelText) { WidgetConfig cfg = {}; cfg.id = id; cfg.parentId = -1; // Root cfg.type = WidgetType::LABEL; cfg.x = x; cfg.y = y; cfg.width = 150; cfg.height = 40; cfg.visible = true; cfg.textSource = TextSource::STATIC; strncpy(cfg.text, labelText, MAX_TEXT_LEN - 1); cfg.fontSize = 1; // 18pt cfg.textAlign = static_cast(TextAlign::LEFT); cfg.isContainer = false; cfg.textColor = {255, 255, 255}; cfg.bgColor = {0, 0, 0}; cfg.bgOpacity = 0; cfg.borderRadius = 0; cfg.borderWidth = 0; cfg.borderColor = {255, 255, 255}; cfg.borderOpacity = 0; cfg.arcMin = 0; cfg.arcMax = 100; cfg.arcUnit = 0; cfg.arcShowValue = true; cfg.arcScaleOffset = 0; cfg.arcScaleColor = cfg.textColor; cfg.arcValueColor = cfg.textColor; cfg.arcValueFontSize = cfg.fontSize; cfg.themeFixed = false; cfg.shadow.enabled = false; // Icon defaults cfg.iconCodepoint = 0; cfg.iconPosition = 0; cfg.iconSize = 1; cfg.iconGap = 8; // Secondary/tertiary address defaults cfg.knxAddress2 = 0; cfg.textSource2 = TextSource::STATIC; cfg.text2[0] = '\0'; cfg.knxAddress3 = 0; cfg.textSource3 = TextSource::STATIC; cfg.text3[0] = '\0'; // Conditions cfg.conditionCount = 0; // Sub-buttons cfg.subButtonCount = 0; cfg.subButtonSize = 40; cfg.subButtonDistance = 80; return cfg; } WidgetConfig WidgetConfig::createKnxLabel(uint8_t id, int16_t x, int16_t y, TextSource source, uint16_t knxAddr, const char* format) { WidgetConfig cfg = createLabel(id, x, y, format); cfg.textSource = source; cfg.knxAddress = knxAddr; return cfg; } WidgetConfig WidgetConfig::createButton(uint8_t id, int16_t x, int16_t y, const char* labelText, uint16_t knxAddrWrite, bool toggle) { WidgetConfig cfg = {}; cfg.id = id; cfg.parentId = -1; // Root cfg.type = WidgetType::BUTTON; cfg.x = x; cfg.y = y; cfg.width = 120; cfg.height = 50; cfg.visible = true; cfg.textSource = TextSource::STATIC; strncpy(cfg.text, labelText, MAX_TEXT_LEN - 1); cfg.fontSize = 1; cfg.textAlign = static_cast(TextAlign::CENTER); cfg.isContainer = false; cfg.textColor = {255, 255, 255}; cfg.bgColor = {33, 150, 243}; // Blue cfg.bgOpacity = 255; cfg.borderRadius = 8; cfg.borderWidth = 0; cfg.borderColor = {255, 255, 255}; cfg.borderOpacity = 0; cfg.arcMin = 0; cfg.arcMax = 100; cfg.arcUnit = 0; cfg.arcShowValue = true; cfg.arcScaleOffset = 0; cfg.arcScaleColor = cfg.textColor; cfg.arcValueColor = cfg.textColor; cfg.arcValueFontSize = cfg.fontSize; cfg.themeFixed = false; cfg.shadow.enabled = true; cfg.shadow.offsetX = 2; cfg.shadow.offsetY = 2; cfg.shadow.blur = 8; cfg.shadow.spread = 0; cfg.shadow.color = {0, 0, 0}; cfg.isToggle = toggle; cfg.knxAddressWrite = knxAddrWrite; cfg.action = ButtonAction::KNX; cfg.targetScreen = 0; // Icon defaults cfg.iconCodepoint = 0; cfg.iconPosition = 0; cfg.iconSize = 1; cfg.iconGap = 8; cfg.iconPositionX = 0; cfg.iconPositionY = 0; // Secondary/tertiary address defaults cfg.knxAddress2 = 0; cfg.textSource2 = TextSource::STATIC; cfg.text2[0] = '\0'; cfg.knxAddress3 = 0; cfg.textSource3 = TextSource::STATIC; cfg.text3[0] = '\0'; // Conditions cfg.conditionCount = 0; // Sub-buttons cfg.subButtonCount = 0; cfg.subButtonSize = 40; cfg.subButtonDistance = 80; return cfg; } // ScreenConfig implementation void ScreenConfig::clear(uint8_t newId, const char* newName) { id = newId; mode = ScreenMode::FULLSCREEN; backgroundColor = {26, 26, 46}; // Dark blue background nightBackgroundColor = {14, 18, 23}; // Darker night background bgImagePath[0] = '\0'; // No background image bgImageMode = BgImageMode::STRETCH; widgetCount = 0; memset(widgets, 0, sizeof(widgets)); memset(name, 0, sizeof(name)); if (newName && newName[0] != '\0') { strncpy(name, newName, sizeof(name) - 1); } // Modal defaults modalX = 0; // 0 = centered modalY = 0; // 0 = centered modalWidth = 0; // 0 = auto modalHeight = 0; // 0 = auto modalBorderRadius = 12; modalDimBackground = true; } int ScreenConfig::addWidget(const WidgetConfig& widget) { if (widgetCount >= MAX_WIDGETS) return -1; // Find next free ID uint8_t newId = 0; for (uint8_t i = 0; i < widgetCount; i++) { if (widgets[i].id >= newId) newId = widgets[i].id + 1; } widgets[widgetCount] = widget; widgets[widgetCount].id = newId; widgetCount++; return newId; } bool ScreenConfig::removeWidget(uint8_t id) { for (uint8_t i = 0; i < widgetCount; i++) { if (widgets[i].id == id) { // Shift remaining widgets for (uint8_t j = i; j < widgetCount - 1; j++) { widgets[j] = widgets[j + 1]; } widgetCount--; return true; } } return false; } WidgetConfig* ScreenConfig::findWidget(uint8_t id) { for (uint8_t i = 0; i < widgetCount; i++) { if (widgets[i].id == id) return &widgets[i]; } return nullptr; } const WidgetConfig* ScreenConfig::findWidget(uint8_t id) const { for (uint8_t i = 0; i < widgetCount; i++) { if (widgets[i].id == id) return &widgets[i]; } return nullptr; } // GuiConfig implementation void GuiConfig::clear() { screenCount = 0; startScreenId = 0; standbyEnabled = false; standbyScreenId = 0xFF; standbyMinutes = 0; knxTimeAddress = 0; knxDateAddress = 0; knxDateTimeAddress = 0; knxNightModeAddress = 0; screenAnimType = ScreenAnimType::FADE; screenAnimDuration = 300; // Default 300ms for (size_t i = 0; i < MAX_SCREENS; i++) { screens[i].clear(static_cast(i), nullptr); } } ScreenConfig* GuiConfig::findScreen(uint8_t id) { for (uint8_t i = 0; i < screenCount; i++) { if (screens[i].id == id) return &screens[i]; } return nullptr; } const ScreenConfig* GuiConfig::findScreen(uint8_t id) const { for (uint8_t i = 0; i < screenCount; i++) { if (screens[i].id == id) return &screens[i]; } return nullptr; }