From 65a131dfc2d2f1a259bad030254ae83e7cbb3024 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Mon, 13 Apr 2026 00:34:25 +0200 Subject: [PATCH 01/34] chore: Add test/ folder to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65e3ba2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +test/ From 0f427a3a0ba58bce6ce1a925bd1ed1f55a0a961c Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Mon, 13 Apr 2026 00:41:40 +0200 Subject: [PATCH 02/34] feat: Include position limit check in ShadingReadyUser status --- src/ModeShading.cpp | 5 +++++ src/ModeShading.h | 2 ++ src/ShutterControllerChannel.cpp | 16 +++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index 6109a6d..7411e48 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -745,6 +745,11 @@ void ModeShading::processInputKo(GroupObject &ko, PositionController &positionCo _recalcMeasurmentValues = true; } +bool ModeShading::isPositionAllowed(const CallContext& callContext) const +{ + return callContext.positionController->targetPosition() <= ParamSHC_CShading1OnlyIfLessThan; +} + bool ModeShading::isModeShading() const { return true; diff --git a/src/ModeShading.h b/src/ModeShading.h index 0917411..32022d9 100644 --- a/src/ModeShading.h +++ b/src/ModeShading.h @@ -68,4 +68,6 @@ class ModeShading : public ModeBase void stop(const CallContext& callContext, const ModeBase* next, PositionController& positionController) override; void processInputKo(GroupObject &ko, PositionController& positionController) override; bool isModeShading() const override; +public: + bool isPositionAllowed(const CallContext& callContext) const; }; \ No newline at end of file diff --git a/src/ShutterControllerChannel.cpp b/src/ShutterControllerChannel.cpp index cbec2d3..e0c7f87 100755 --- a/src/ShutterControllerChannel.cpp +++ b/src/ShutterControllerChannel.cpp @@ -711,12 +711,26 @@ void ShutterControllerChannel::execute(CallContext &callContext) KoSHC_CShading2LockActive.value(DPT_Switch) || KoSHC_CShading2BreakLockActive.value(DPT_Switch); } + bool shadingPositionAllowed = false; + for (auto mode : _modes) + { + if (mode->isModeShading()) + { + auto modeShading = (ModeShading *)mode; + if (modeShading->isPositionAllowed(callContext)) + { + shadingPositionAllowed = true; + break; + } + } + } bool readinessUser = shadingControlActive() && !_channelLockActive && _currentWindowOpenHandler == nullptr && _windowOpenState == WindowOpenStateClosed && _currentMode != _modeManual && - !shadingModeLockActive; + !shadingModeLockActive && + shadingPositionAllowed; if (KoSHC_CShadingReadyUser.valueNoSendCompare(readinessUser, DPT_Switch)) KoSHC_CShadingReadyUser.objectWritten(); From 545c6b7f6ecf6f1f2315a69cd657d97e9a14712b Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:28:14 +0200 Subject: [PATCH 03/34] feat: rename KO-enable parameters for consistent naming convention Apply naming rules: 'Status' prefix only for pure status KOs (TransmitFlag=Enabled), 'Sperren' prefix at front for lock-related parameters. - %CC%047: 'Sperren Kanal freigeben' - %CC%048: 'Status aktiver Modus freigeben' - %CC%051: 'Status Beschattung aktiv freigeben' - %CC%052: 'Status Beschattung bereit freigeben' - %CC%055: 'Status Fenster offen freigeben' - %CC%056: 'Sperren Fenster offen freigeben' - %CC%057: 'Status Fenster gekippt freigeben' - %CC%058: 'Sperren Fenster gekippt freigeben' Add %CC%050/051/052 to channel-level KoFreigabe block (channel-wide shading KOs) --- src/ShutterControllerModule.templ.xml | 83 ++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/src/ShutterControllerModule.templ.xml b/src/ShutterControllerModule.templ.xml index 50671e4..ec7883c 100755 --- a/src/ShutterControllerModule.templ.xml +++ b/src/ShutterControllerModule.templ.xml @@ -49,6 +49,18 @@ + + + + + + + + + + + + @@ -221,6 +233,18 @@ + + + + + + + + + + + + @@ -572,6 +596,9 @@ + + + @@ -583,16 +610,28 @@ + + + + + + + + + + + + @@ -646,14 +685,29 @@ + + + + + + + + + + + + + + + @@ -762,10 +816,15 @@ + + + + + @@ -780,6 +839,9 @@ + + + @@ -791,6 +853,8 @@ + + @@ -905,6 +969,10 @@ + + + + @@ -1034,7 +1102,18 @@ - + + + + + + + + + + + + @@ -1046,4 +1125,4 @@ - \ No newline at end of file + From 7cfb7301c71f29450209c13051e0e0d08e4a55b9 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:28:31 +0200 Subject: [PATCH 04/34] feat: split PPP+53 into separate lock and active-status enable parameters - PPP+53 renamed to 'Sperren Beschattungsmodus freigeben' (guards Lock + LockActive KOs only) - PPP+54 renamed to 'Status Beschattungsmodus bereit freigeben' - PPP+55 added at Offset=31/BitOffset=7: 'Status Beschattungsmodus freigeben' (guards Modus-Aktiv KO) - Update choose blocks: PPP+53 -> PPP+1+2, PPP+55 -> PPP+0 - Remove %CC%050/051/052 from ModeShading KO-enable section (moved to channel level) --- src/ShutterControllerModule.ModeShading.xml | 41 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/ShutterControllerModule.ModeShading.xml b/src/ShutterControllerModule.ModeShading.xml index ed4460a..a3649b7 100755 --- a/src/ShutterControllerModule.ModeShading.xml +++ b/src/ShutterControllerModule.ModeShading.xml @@ -153,6 +153,9 @@ + + + @@ -267,6 +270,9 @@ + + + @@ -334,14 +340,29 @@ - - - - + + + + + + + + + + + + + + + + + + + @@ -662,7 +683,17 @@ - + + + + + + + + + + + From e65052f896a83fa61e6fae5508778c2764eb1499 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:28:38 +0200 Subject: [PATCH 05/34] feat: gate window KOs behind KO-enable parameters in ETS Wrap window open/tilt status and lock KOs in choose blocks controlled by %CC%055/%CC%056/%CC%057/%CC%058 parameters. Add KO-enable section to window mode parameter tab. --- ...ShutterControllerModule.ModeWindowOpen.xml | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/src/ShutterControllerModule.ModeWindowOpen.xml b/src/ShutterControllerModule.ModeWindowOpen.xml index 9753f4c..230290b 100755 --- a/src/ShutterControllerModule.ModeWindowOpen.xml +++ b/src/ShutterControllerModule.ModeWindowOpen.xml @@ -138,8 +138,13 @@ - - + + + + + + + @@ -151,16 +156,26 @@ - - - - + + + + + + + + + - - + + + + + + + @@ -172,10 +187,15 @@ - - - - + + + + + + + + + @@ -266,6 +286,23 @@ + + + + + + + + + + + + + + + + + From 11ac4d3ee13a3ecb970d275b2f73744612ea6f40 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:28:47 +0200 Subject: [PATCH 06/34] chore: bump version to 0.6.1 --- library.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.json b/library.json index b72b987..776a67c 100755 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "OFM-ShutterControllerModule", - "version": "0.6.0", + "version": "0.6.1", "dependencies": { }, From 5741797e0cc44343233ca625516d8f4662a554c4 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:55:58 +0200 Subject: [PATCH 07/34] feat: implement shadow edge tracking, expert slat tracking and flat roof guard with diagnostic bits --- src/ModeShading.cpp | 392 +++++++++++++++++++++++++++++++++++++++++++- src/ModeShading.h | 4 + 2 files changed, 393 insertions(+), 3 deletions(-) diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index 7411e48..6cfe8ba 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -2,6 +2,7 @@ #include "Timer.h" #include "PositionController.h" #include "MeasurementWatchdog.h" +#include #ifdef SHC_KoCShading2Active // redefine SHC_ParamCalcIndex to add offset for Shading Mode 2 @@ -622,9 +623,70 @@ bool ModeShading::allowedByMeasurmentValues(const CallContext &callContext) #define SHC_KoCShading2Active SHC_KoCShading1Active #endif +// Berechnet den Profilwinkel [°] der Sonne bezogen auf die Fassadenebene. +// Rückgabe: Winkel in Grad [>0], oder -1.0f als Sentinel (Sonne trifft Fassade nicht / nicht berechenbar). +// Argumente: +// elevationDeg - Sonnenhöhe in Grad [0..90] +// azimuthDeg - Sonnenazimut in Grad [0..360, 0=Nord, 90=Ost, 180=Süd, 270=West] +// orientationEnum - WindowOrientation enum: 0=Ost, 1=Südost, 2=Süd, 3=Südwest, 4=West, 5=Dachfläche, 6=Keine +// facadeInclinationDeg - Fassadenneigung in Grad (0=senkrecht/Wand, positiv=nach vorne geneigt) +float ModeShading::calculateProfileAngle(float elevationDeg, float azimuthDeg, uint8_t orientationEnum, float facadeInclinationDeg) const +{ + static const float DEG2RAD = (float)M_PI / 180.0f; + static const float RAD2DEG = 180.0f / (float)M_PI; + static const float COS_EPSILON = 0.0175f; // cos(89°) ≈ 0.0175 + + if (orientationEnum == 6) + return -1.0f; // "Keine Himmelsrichtungsauswertung" → Modi 3-5 inaktiv + + if (orientationEnum == 5) + { + // Dachfläche: Profilwinkel = Höhenwinkel minus Dachneigung (von Horizontal) + float beta_deg = elevationDeg - facadeInclinationDeg; + return (beta_deg <= 0.0f) ? -1.0f : beta_deg; + } + + // Normalfall: Fassade mit Himmelsrichtung + // Mapping: Ost=90°, Südost=135°, Süd=180°, Südwest=225°, West=270° + static const float orientationAzimuth[] = {90.0f, 135.0f, 180.0f, 225.0f, 270.0f}; + float facadeAzimuth = orientationAzimuth[orientationEnum]; + + // Relativwinkel Sonne–Fassade, normiert auf [-180°, 180°] + float delta_phi = fmodf(azimuthDeg - facadeAzimuth + 180.0f, 360.0f) - 180.0f; + + // Guard 1: Sonne hinter der Fassade + if (fabsf(delta_phi) >= 90.0f) + return -1.0f; + + float cos_delta = cosf(delta_phi * DEG2RAD); + + // Guard 2: Sonne nahezu parallel zur Fassade (cos → 0) + if (fabsf(cos_delta) < COS_EPSILON) + return 89.9f; + + float elevation_rad = elevationDeg * DEG2RAD; + float facadeInclination_rad = facadeInclinationDeg * DEG2RAD; + + // Profilwinkel: Standardformel. facadeInclination korrigiert senkrechte Fassade → geneigte Fassade + float beta_rad = atanf(tanf(elevation_rad) / cos_delta) - facadeInclination_rad; + + // Guard 3: Vorneigung der Fassade übersteigt Sonnenhöhe → Sonne unterhalb Fassadenebene + if (beta_rad <= 0.0f) + return -1.0f; + + float beta_deg = beta_rad * RAD2DEG; + + // Guard 4: Cap bei 85° (tan(>85°) numerisch instabil) + if (beta_deg > 85.0f) + beta_deg = 85.0f; + + return beta_deg; +} + void ModeShading::start(const CallContext &callContext, const ModeBase *previous, PositionController &positionController) { _active = true; + _lastSentShadowPos = -1.0f; KoSHC_CShading1Active.value(true, DPT_Switch); positionController.setAutomaticPosition(ParamSHC_CShading1ShadingPosition); @@ -643,10 +705,86 @@ void ModeShading::control(const CallContext &callContext, PositionController &po // // // - if (!positionController.hasSlat()) + + // ── Rollo-Pfad: Schattenkantennachführung für Rollo (kein hasSlat!) ── + if (ParamSHC_CType == 2 && ParamSHC_CShading1RolloPositionMode == 1) + { + static const float DEG2RAD = (float)M_PI / 180.0f; + _notAllowedReason &= ~(ModeShadingNotAllowedReasonProfileAngleSentinel | ModeShadingNotAllowedReasonFlatRoofGuard); + + float gamma_rad; + float sin_alpha; + const uint8_t orientation = ParamSHC_CWindowOrientation; + const float facadeInclination = (float)(int8_t)ParamSHC_CFacadeInclination; + + if (orientation == 5) + { + // Dachfläche: gamma = Höhenwinkel, sin_alpha = sin(Dachneigung) + float elev = (float)callContext.elevation; + if (elev <= 0.0f) + return; + sin_alpha = sinf(facadeInclination * DEG2RAD); + if (sin_alpha < 0.05f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonFlatRoofGuard; + return; // Flachdach — kein sinnvolles Positionstracking + } + gamma_rad = elev * DEG2RAD; + } + else + { + float beta_deg = calculateProfileAngle( + (float)callContext.elevation, + (float)callContext.azimuth, + orientation, + facadeInclination); + if (beta_deg <= 0.0f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; + return; // Sonne trifft Fassade nicht + } + gamma_rad = beta_deg * DEG2RAD; + sin_alpha = 1.0f; // senkrechte Fassade: sin(90°) = 1 + } + + float windowHeight = (float)ParamSHC_CShading1WindowHeight; + float maxPenetration = (float)ParamSHC_CShading1MaxPenetrationDepth; + float windowSillHeight = (float)ParamSHC_CShading1WindowSillHeight; + float minShadowEdgeChange = (float)ParamSHC_CShading1MinShadowEdgeChange; + + float s_crit = (maxPenetration * tanf(gamma_rad) - windowSillHeight) / sin_alpha; + + float targetPos; + if (s_crit <= 0.0f) + targetPos = 100.0f; + else if (s_crit >= windowHeight) + targetPos = (float)ParamSHC_CShading1ShadingPosition; + else + targetPos = (1.0f - s_crit / windowHeight) * 100.0f; + + if (targetPos < 0.0f) targetPos = 0.0f; + if (targetPos > 100.0f) targetPos = 100.0f; + + // Hysterese in cm + if (_lastSentShadowPos >= 0.0f && s_crit < windowHeight && s_crit > 0.0f) + { + float delta_cm = fabsf(targetPos - _lastSentShadowPos) * windowHeight / 100.0f; + if (delta_cm < minShadowEdgeChange) + return; + } + + if (callContext.diagnosticLog) + logInfoP("Rollo shadow tracking: gamma=%.1f° s_crit=%.1f targetPos=%.1f", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos); + + _lastSentShadowPos = targetPos; + positionController.setAutomaticPosition((uint8_t)targetPos); return; + } // Jalousie + if (!positionController.hasSlat()) + return; + switch (ParamSHC_CShading1SlatElevationDepending) { case 0: @@ -680,9 +818,9 @@ void ModeShading::control(const CallContext &callContext, PositionController &po } case 2: { - // Benutzerdefiniert: linear interpolation between posLow (sun low) and posHigh (sun high) + // Lamellennachführung Min/Max: linear interpolation between posLow (sun low) and posHigh (sun high) if (callContext.diagnosticLog) - logInfoP("ModeShading control Benutzerdefiniert"); + logInfoP("ModeShading control Lamellennachführung Min/Max"); double elevMin = (double)ParamSHC_CShading1ElevationMin; double elevMax = (double)ParamSHC_CShading1ElevationMax; double t = (elevMax > elevMin) ? (callContext.elevation - elevMin) / (elevMax - elevMin) : 0.5; @@ -700,6 +838,254 @@ void ModeShading::control(const CallContext &callContext, PositionController &po positionController.setAutomaticSlat(slatPosition); break; } + case 3: + { + // Lamellennachführung Experte: geometrisch berechnet + if (ParamSHC_CType == 2) break; // Schutzguard: Rollo hat kein Slat + static const float DEG2RAD = (float)M_PI / 180.0f; + _notAllowedReason &= ~ModeShadingNotAllowedReasonProfileAngleSentinel; + float beta_deg = calculateProfileAngle( + (float)callContext.elevation, + (float)callContext.azimuth, + ParamSHC_CWindowOrientation, + (float)(int8_t)ParamSHC_CFacadeInclination); + + if (beta_deg <= 0.0f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; + return; // Sonne trifft Fassade nicht + } + + float beta_rad = beta_deg * DEG2RAD; + float a = (float)ParamSHC_CShading1SlatSpacing; // Lamellenabstand [mm] + float b = (float)ParamSHC_CShading1SlatWidth; // Lamellenbreite [mm] + + uint8_t angleAtMin = ParamSHC_CShading1SlatAngleAtMin; + uint8_t angleAtMax = ParamSHC_CShading1SlatAngleAtMax; + if (angleAtMin == angleAtMax) + break; // Guard: Division durch Null + + // Kritischer Kippwinkel zur Horizontalen + float theta_krit_h = atan2f(a * sinf(beta_rad), b - a * cosf(beta_rad)) * (180.0f / (float)M_PI); + // Umrechnung auf Elsner-Winkel (zur Senkrechten) + float theta_krit = 90.0f - theta_krit_h; + + // Guard: Lamelle kann den Strahl nicht sperren (z.B. SlatSpacing > SlatWidth) + if (theta_krit < 0.0f) + { + positionController.setAutomaticSlat(100); + return; + } + + float slatPercent = ((float)theta_krit - (float)angleAtMin) / ((float)angleAtMax - (float)angleAtMin) * 100.0f; + if (slatPercent < 0.0f) slatPercent = 0.0f; + if (slatPercent > 100.0f) slatPercent = 100.0f; + auto slatPosition = (uint8_t)slatPercent; + + if (callContext.diagnosticLog) + logInfoP("Case3 Expert slat: beta=%.1f° theta_krit=%.1f° → slat=%d%%", beta_deg, theta_krit, (int)slatPosition); + + if (!callContext.modeNewStarted && abs((uint8_t)KoSHC_CShutterSlatOutput.value(DPT_Scaling) - slatPosition) < ParamSHC_CShading1MinChangeForSlatAdaption) + return; + + positionController.setAutomaticSlat(slatPosition); + break; + } + case 4: + { + // Schattenkantennachführung (Jalousie): Position + feste Lamellenstellung + if (ParamSHC_CType == 2) break; // Schutzguard + static const float DEG2RAD = (float)M_PI / 180.0f; + _notAllowedReason &= ~(ModeShadingNotAllowedReasonProfileAngleSentinel | ModeShadingNotAllowedReasonFlatRoofGuard); + + float gamma_rad; + float sin_alpha; + const uint8_t orientation = ParamSHC_CWindowOrientation; + const float facadeInclination = (float)(int8_t)ParamSHC_CFacadeInclination; + + if (orientation == 5) + { + // Dachfläche: gamma = Höhenwinkel, sin_alpha = sin(Dachneigung) + float elev = (float)callContext.elevation; + if (elev <= 0.0f) + return; + sin_alpha = sinf(facadeInclination * DEG2RAD); + if (sin_alpha < 0.05f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonFlatRoofGuard; + return; // Flachdach — kein sinnvolles Positionstracking + } + gamma_rad = elev * DEG2RAD; + } + else + { + float beta_deg = calculateProfileAngle( + (float)callContext.elevation, + (float)callContext.azimuth, + orientation, + facadeInclination); + if (beta_deg <= 0.0f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; + return; + } + gamma_rad = beta_deg * DEG2RAD; + sin_alpha = 1.0f; // senkrechte Fassade: sin(90°) = 1 + } + + float windowHeight = (float)ParamSHC_CShading1WindowHeight; + float maxPenetration = (float)ParamSHC_CShading1MaxPenetrationDepth; + float windowSillHeight = (float)ParamSHC_CShading1WindowSillHeight; + float minShadowEdgeChange = (float)ParamSHC_CShading1MinShadowEdgeChange; + + float s_crit = (maxPenetration * tanf(gamma_rad) - windowSillHeight) / sin_alpha; + + float targetPos; + if (s_crit <= 0.0f) + targetPos = 100.0f; + else if (s_crit >= windowHeight) + targetPos = (float)ParamSHC_CShading1ShadingPosition; + else + targetPos = (1.0f - s_crit / windowHeight) * 100.0f; + + if (targetPos < 0.0f) targetPos = 0.0f; + if (targetPos > 100.0f) targetPos = 100.0f; + + // Hysterese in cm + if (_lastSentShadowPos >= 0.0f && s_crit < windowHeight && s_crit > 0.0f) + { + float delta_cm = fabsf(targetPos - _lastSentShadowPos) * windowHeight / 100.0f; + if (delta_cm < minShadowEdgeChange) + { + positionController.setAutomaticSlat(ParamSHC_CShading1SlatShadingPosition); + return; + } + } + + if (callContext.diagnosticLog) + logInfoP("Case4 shadow tracking: gamma=%.1f° s_crit=%.1f targetPos=%.1f", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos); + + _lastSentShadowPos = targetPos; + positionController.setAutomaticPosition((uint8_t)targetPos); + positionController.setAutomaticSlat(ParamSHC_CShading1SlatShadingPosition); + break; + } + case 5: + { + // Schattenkanten- und Lamellennachführung: Position (Case 4) + Lamellenwinkel (Case 3) + if (ParamSHC_CType == 2) break; // Schutzguard + static const float DEG2RAD = (float)M_PI / 180.0f; + _notAllowedReason &= ~(ModeShadingNotAllowedReasonProfileAngleSentinel | ModeShadingNotAllowedReasonFlatRoofGuard); + + float gamma_rad; + float sin_alpha; + bool slatTrackingPossible; + const uint8_t orientation = ParamSHC_CWindowOrientation; + const float facadeInclination = (float)(int8_t)ParamSHC_CFacadeInclination; + + if (orientation == 5) + { + // Dachfläche: gamma = Höhenwinkel, sin_alpha = sin(Dachneigung) + float elev = (float)callContext.elevation; + if (elev <= 0.0f) + return; + sin_alpha = sinf(facadeInclination * DEG2RAD); + if (sin_alpha < 0.05f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonFlatRoofGuard; + return; // Flachdach — kein sinnvolles Positionstracking + } + gamma_rad = elev * DEG2RAD; + // Für Dachfläche: calculateProfileAngle() prüfen ob Lamellennachführung möglich + float beta_slat = (float)callContext.elevation - facadeInclination; + slatTrackingPossible = (beta_slat > 0.0f); + if (!slatTrackingPossible) + _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; + } + else + { + float beta_deg = calculateProfileAngle( + (float)callContext.elevation, + (float)callContext.azimuth, + orientation, + facadeInclination); + if (beta_deg <= 0.0f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; + return; + } + gamma_rad = beta_deg * DEG2RAD; + sin_alpha = 1.0f; // senkrechte Fassade: sin(90°) = 1 + slatTrackingPossible = true; + } + + float windowHeight = (float)ParamSHC_CShading1WindowHeight; + float maxPenetration = (float)ParamSHC_CShading1MaxPenetrationDepth; + float windowSillHeight = (float)ParamSHC_CShading1WindowSillHeight; + float minShadowEdgeChange = (float)ParamSHC_CShading1MinShadowEdgeChange; + + float s_crit = (maxPenetration * tanf(gamma_rad) - windowSillHeight) / sin_alpha; + + float targetPos; + if (s_crit <= 0.0f) + targetPos = 100.0f; + else if (s_crit >= windowHeight) + targetPos = (float)ParamSHC_CShading1ShadingPosition; + else + targetPos = (1.0f - s_crit / windowHeight) * 100.0f; + + if (targetPos < 0.0f) targetPos = 0.0f; + if (targetPos > 100.0f) targetPos = 100.0f; + + bool positionChanged = true; + if (_lastSentShadowPos >= 0.0f && s_crit < windowHeight && s_crit > 0.0f) + { + float delta_cm = fabsf(targetPos - _lastSentShadowPos) * windowHeight / 100.0f; + if (delta_cm < minShadowEdgeChange) + positionChanged = false; + } + + if (positionChanged) + { + _lastSentShadowPos = targetPos; + positionController.setAutomaticPosition((uint8_t)targetPos); + } + + // Lamellenwinkel (Experte-Formel) — nur wenn Profilwinkel berechenbar + if (slatTrackingPossible) + { + float a = (float)ParamSHC_CShading1SlatSpacing; + float b = (float)ParamSHC_CShading1SlatWidth; + uint8_t angleAtMin = ParamSHC_CShading1SlatAngleAtMin; + uint8_t angleAtMax = ParamSHC_CShading1SlatAngleAtMax; + + if (angleAtMin != angleAtMax) + { + float theta_krit_h = atan2f(a * sinf(gamma_rad), b - a * cosf(gamma_rad)) * (180.0f / (float)M_PI); + float theta_krit = 90.0f - theta_krit_h; + + if (theta_krit < 0.0f) + { + positionController.setAutomaticSlat(100); + } + else + { + float slatPercent = ((float)theta_krit - (float)angleAtMin) / ((float)angleAtMax - (float)angleAtMin) * 100.0f; + if (slatPercent < 0.0f) slatPercent = 0.0f; + if (slatPercent > 100.0f) slatPercent = 100.0f; + auto slatPosition = (uint8_t)slatPercent; + + if (callContext.modeNewStarted || abs((uint8_t)KoSHC_CShutterSlatOutput.value(DPT_Scaling) - slatPosition) >= ParamSHC_CShading1MinChangeForSlatAdaption) + positionController.setAutomaticSlat(slatPosition); + } + } + } + + if (callContext.diagnosticLog) + logInfoP("Case5 combined: gamma=%.1f° s_crit=%.1f targetPos=%.1f slatPossible=%d", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos, (int)slatTrackingPossible); + + break; + } } } diff --git a/src/ModeShading.h b/src/ModeShading.h index 32022d9..c43b984 100644 --- a/src/ModeShading.h +++ b/src/ModeShading.h @@ -29,6 +29,8 @@ enum ModeShadingNotAllowedReason : uint32_t ModeShadingNotAllowedReasonTemperatureForecase = 1048576, ModeShadingNotAllowedReasonClouds = 2097152, ModeShadingNotAllowedReasonUVI = 4194304, + ModeShadingNotAllowedReasonProfileAngleSentinel = 8388608, // Bit 23: Profilwinkel nicht berechenbar + ModeShadingNotAllowedReasonFlatRoofGuard = 16777216, // Bit 24: Flachdach-Schutz aktiv }; @@ -50,9 +52,11 @@ class ModeShading : public ModeBase bool _needWaitTime = false; unsigned long _lastHeadingTimeStamp = 0; bool _heatingOff = true; + float _lastSentShadowPos = -1.0f; bool allowedByMeasurmentValues(const CallContext& callContext); bool handleMeasurmentValue(bool& allowed, bool enabled, const MeasurementSource *measurementSource, const CallContext &callContext, bool (*predicate)(const MeasurementSource *, uint8_t _channelIndex, uint8_t _index, bool previousAllowed), ModeShadingNotAllowedReason reasonBit); void updateDiagnosticKos(); + float calculateProfileAngle(float elevationDeg, float azimuthDeg, uint8_t orientationEnum, float facadeInclinationDeg) const; public: ModeShading(uint8_t index); bool allowedBySun(const CallContext& callContext); From 463f75de4d1d5040ccf489d27252c977f4189a0c Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:55:59 +0200 Subject: [PATCH 08/34] feat: add ETS parameters for modes 3-5 (expert slat tracking and shadow edge tracking) --- src/ShutterControllerModule.ModeShading.xml | 96 ++++++++++++++++++++- src/ShutterControllerModule.share.xml | 37 +++++++- src/ShutterControllerModule.templ.xml | 16 ++-- 3 files changed, 140 insertions(+), 9 deletions(-) diff --git a/src/ShutterControllerModule.ModeShading.xml b/src/ShutterControllerModule.ModeShading.xml index a3649b7..7e87f12 100755 --- a/src/ShutterControllerModule.ModeShading.xml +++ b/src/ShutterControllerModule.ModeShading.xml @@ -8,7 +8,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -156,6 +156,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -273,6 +295,25 @@ + + + + + + + + + + + + + + + + + + + @@ -513,6 +554,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ShutterControllerModule.share.xml b/src/ShutterControllerModule.share.xml index d82edb4..748dab0 100755 --- a/src/ShutterControllerModule.share.xml +++ b/src/ShutterControllerModule.share.xml @@ -194,12 +194,45 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ShutterControllerModule.templ.xml b/src/ShutterControllerModule.templ.xml index ec7883c..c6f5e33 100755 --- a/src/ShutterControllerModule.templ.xml +++ b/src/ShutterControllerModule.templ.xml @@ -8,7 +8,7 @@ - + @@ -21,7 +21,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -214,9 +214,11 @@ - - + + + + @@ -264,6 +266,8 @@ + + @@ -674,6 +678,8 @@ + + From 169622004b31e0cd5abcb6eafe9051d26a6d3318 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:56:01 +0200 Subject: [PATCH 09/34] docs: add help context for new shadow edge tracking parameters --- src/Baggages/Help_de/SHC-Bruestungshoehe.md | 6 ++++++ src/Baggages/Help_de/SHC-Fassadenneigung.md | 10 ++++++++++ src/Baggages/Help_de/SHC-Fensterhoehe.md | 6 ++++++ src/Baggages/Help_de/SHC-Lamellenabstand.md | 6 ++++++ src/Baggages/Help_de/SHC-Lamellenbreite.md | 6 ++++++ .../Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-0.md | 6 ++++++ .../Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-100.md | 6 ++++++ src/Baggages/Help_de/SHC-Max-Eindringtiefe.md | 6 ++++++ .../Help_de/SHC-Mindestverschiebung-Schattenkante.md | 6 ++++++ src/Baggages/Help_de/SHC-Positionsnachfuehrung.md | 7 +++++++ 10 files changed, 65 insertions(+) create mode 100644 src/Baggages/Help_de/SHC-Bruestungshoehe.md create mode 100644 src/Baggages/Help_de/SHC-Fassadenneigung.md create mode 100644 src/Baggages/Help_de/SHC-Fensterhoehe.md create mode 100644 src/Baggages/Help_de/SHC-Lamellenabstand.md create mode 100644 src/Baggages/Help_de/SHC-Lamellenbreite.md create mode 100644 src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-0.md create mode 100644 src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-100.md create mode 100644 src/Baggages/Help_de/SHC-Max-Eindringtiefe.md create mode 100644 src/Baggages/Help_de/SHC-Mindestverschiebung-Schattenkante.md create mode 100644 src/Baggages/Help_de/SHC-Positionsnachfuehrung.md diff --git a/src/Baggages/Help_de/SHC-Bruestungshoehe.md b/src/Baggages/Help_de/SHC-Bruestungshoehe.md new file mode 100644 index 0000000..ba07d43 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Bruestungshoehe.md @@ -0,0 +1,6 @@ +### Brüstungshöhe + +Höhe der Fensterbrüstung in Zentimetern, gemessen vom Fußboden bis zur Unterkante der Fensterscheibe. + +Dieser Wert wird zusammen mit der Fensterhöhe verwendet, um die absolute Position der Schattenkante im Raum zu bestimmen. Bei bodentiefen Fenstern (z.B. Terrassentür bis zum Boden) ist dieser Wert 0. + diff --git a/src/Baggages/Help_de/SHC-Fassadenneigung.md b/src/Baggages/Help_de/SHC-Fassadenneigung.md new file mode 100644 index 0000000..5b572f8 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Fassadenneigung.md @@ -0,0 +1,10 @@ +### Fassadenneigung + +Die Neigung der Fassade in Grad, gemessen gegenüber der Senkrechten (0° = senkrechte Wand). + +- **0°**: Senkrechte Fassade (Standardfall) +- **Positiver Wert**: Fassade neigt sich nach außen (z.B. überhängende Dachkante) +- **Negativer Wert**: Fassade neigt sich nach innen (z.B. nach innen geneigte Wand) + +Dieser Wert wird für die Berechnung des Profilwinkels bei der **Lamellennachführung (Experte)** und der **Schattenkantennachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. + diff --git a/src/Baggages/Help_de/SHC-Fensterhoehe.md b/src/Baggages/Help_de/SHC-Fensterhoehe.md new file mode 100644 index 0000000..1769db1 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Fensterhoehe.md @@ -0,0 +1,6 @@ +### Fensterhöhe + +Die lichte Höhe der Fensterscheibe in Zentimetern (Innenkante Blendrahmen oben bis Innenkante Blendrahmen unten bzw. Fensterbank). + +Dieser Wert wird für die Berechnung der optimalen Jalousie- oder Rolladenposition bei der **Schattenkantennachführung** benötigt. Zusammen mit der aktuellen Sonnenposition wird berechnet, wie weit der Behang abgesenkt sein muss, damit die Schattenkante die konfigurierte Eindringtiefe nicht überschreitet. + diff --git a/src/Baggages/Help_de/SHC-Lamellenabstand.md b/src/Baggages/Help_de/SHC-Lamellenabstand.md new file mode 100644 index 0000000..1ffb9b9 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Lamellenabstand.md @@ -0,0 +1,6 @@ +### Lamellenabstand + +Der Abstand zwischen zwei benachbarten Lamellenachsen in Millimetern (Achsmaß). + +Dieser Wert wird zusammen mit der Lamellenbreite für die geometrische Berechnung des kritischen Kippwinkels verwendet, ab dem die Lamellen den Lichtstrahl vollständig sperren. Den Wert finden Sie in der technischen Dokumentation des Jalousie-Herstellers. + diff --git a/src/Baggages/Help_de/SHC-Lamellenbreite.md b/src/Baggages/Help_de/SHC-Lamellenbreite.md new file mode 100644 index 0000000..c8d6706 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Lamellenbreite.md @@ -0,0 +1,6 @@ +### Lamellenbreite + +Die physische Breite einer einzelnen Lamelle in Millimetern (gemessen quer zur Lamellenachse). + +Dieser Wert wird für die geometrische Berechnung des optimalen Kippwinkels bei der **Lamellennachführung (Experte)** benötigt. Den Wert finden Sie in der technischen Dokumentation des Jalousie-Herstellers. + diff --git a/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-0.md b/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-0.md new file mode 100644 index 0000000..e4dc0d3 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-0.md @@ -0,0 +1,6 @@ +### Lamellenwinkel bei Fahrbefehl 0% + +Der physische Winkel der Lamellen in Grad, wenn der Aktor den Stellwert 0% empfängt. + +Dieser Kalibrierungswert wird benötigt, damit die berechnete optimale Lamellenstellung korrekt auf den vom Aktor erwarteten Prozentwert abgebildet wird. Verschiedene Aktoren und Montagevarianten können unterschiedliche Konventionen verwenden. + diff --git a/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-100.md b/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-100.md new file mode 100644 index 0000000..948bd7c --- /dev/null +++ b/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-100.md @@ -0,0 +1,6 @@ +### Lamellenwinkel bei Fahrbefehl 100% + +Der physische Winkel der Lamellen in Grad, wenn der Aktor den Stellwert 100% empfängt. + +**Hinweis:** Ein Wert kleiner als "Lamellenwinkel bei Fahrbefehl 0%" ist ausdrücklich erlaubt und notwendig für Lamellen, die umgekehrt montiert sind. Die Nachführungsformel invertiert das Mapping automatisch. + diff --git a/src/Baggages/Help_de/SHC-Max-Eindringtiefe.md b/src/Baggages/Help_de/SHC-Max-Eindringtiefe.md new file mode 100644 index 0000000..c2ac20f --- /dev/null +++ b/src/Baggages/Help_de/SHC-Max-Eindringtiefe.md @@ -0,0 +1,6 @@ +### Max. Eindringtiefe + +Die maximale Sonneneindringtiefe im Raum in Zentimetern, gemessen ab der Fensterscheibe (Innenmaß). + +Wenn die Sonne tiefer als dieser Wert in den Raum eindringen würde, schließt die Jalousie oder der Rollladen weiter, bis die Schattenkante genau auf der konfigurierten Tiefe liegt. Bei sehr flachem Sonnenstand (Schattenkante wäre jenseits der Fenstertiefe) wird der Behang vollständig geschlossen. + diff --git a/src/Baggages/Help_de/SHC-Mindestverschiebung-Schattenkante.md b/src/Baggages/Help_de/SHC-Mindestverschiebung-Schattenkante.md new file mode 100644 index 0000000..afdf0a9 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Mindestverschiebung-Schattenkante.md @@ -0,0 +1,6 @@ +### Mindestverschiebung Schattenkante + +Mindestverschiebung der berechneten Schattenkante in Zentimetern, bevor ein neuer Positionsbefehl an den Aktor gesendet wird. + +Verhindert zu häufige kleine Positionskorrekturen bei langsam wandernder Sonne. Ein Wert von 5–10 cm ist für die meisten Anwendungen empfehlenswert. + diff --git a/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md b/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md new file mode 100644 index 0000000..81262a0 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md @@ -0,0 +1,7 @@ +### Positionsnachführung + +Aktiviert die dynamische Positionsnachführung für Rollläden basierend auf der aktuellen Sonnenposition. + +- **Nein**: Keine Positionsnachführung. Der Rollladen fährt auf die konfigurierte Beschattungsposition und bleibt dort. +- **Schattenkantennachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. + From 5bf485de99c7e29bb0e49756b20f8f95d0d43622 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:56:03 +0200 Subject: [PATCH 10/34] docs: remove outdated brightness sensor help context files --- src/Baggages/Help_de/SHC-Azimut-Sensor-1.md | 5 ----- src/Baggages/Help_de/SHC-Azimut-Sensor-2.md | 5 ----- src/Baggages/Help_de/SHC-Azimut-Sensor-3.md | 5 ----- src/Baggages/Help_de/SHC-Azimut-Sensor-4.md | 5 ----- src/Baggages/Help_de/SHC-Azimut-Sensor-5.md | 5 ----- src/Baggages/Help_de/SHC-Helligkeit.md | 4 ---- src/Baggages/Help_de/SHC-Weitere-Helligkeitssensoren.md | 5 ----- 7 files changed, 34 deletions(-) delete mode 100644 src/Baggages/Help_de/SHC-Azimut-Sensor-1.md delete mode 100644 src/Baggages/Help_de/SHC-Azimut-Sensor-2.md delete mode 100644 src/Baggages/Help_de/SHC-Azimut-Sensor-3.md delete mode 100644 src/Baggages/Help_de/SHC-Azimut-Sensor-4.md delete mode 100644 src/Baggages/Help_de/SHC-Azimut-Sensor-5.md delete mode 100644 src/Baggages/Help_de/SHC-Helligkeit.md delete mode 100644 src/Baggages/Help_de/SHC-Weitere-Helligkeitssensoren.md diff --git a/src/Baggages/Help_de/SHC-Azimut-Sensor-1.md b/src/Baggages/Help_de/SHC-Azimut-Sensor-1.md deleted file mode 100644 index 7a5bff5..0000000 --- a/src/Baggages/Help_de/SHC-Azimut-Sensor-1.md +++ /dev/null @@ -1,5 +0,0 @@ -### Azimut Sensor 1 - -Azimut-Zuordnung fuer Sensor 1 in 5-Grad-Schritten. -"Keine Zuordnung" deaktiviert die Azimut-Auswertung fuer diesen Sensor. - diff --git a/src/Baggages/Help_de/SHC-Azimut-Sensor-2.md b/src/Baggages/Help_de/SHC-Azimut-Sensor-2.md deleted file mode 100644 index ae32292..0000000 --- a/src/Baggages/Help_de/SHC-Azimut-Sensor-2.md +++ /dev/null @@ -1,5 +0,0 @@ -### Azimut Sensor 2 - -Azimut-Zuordnung fuer Sensor 2 in 5-Grad-Schritten. -"Keine Zuordnung" deaktiviert die Azimut-Auswertung fuer diesen Sensor. - diff --git a/src/Baggages/Help_de/SHC-Azimut-Sensor-3.md b/src/Baggages/Help_de/SHC-Azimut-Sensor-3.md deleted file mode 100644 index 78cf245..0000000 --- a/src/Baggages/Help_de/SHC-Azimut-Sensor-3.md +++ /dev/null @@ -1,5 +0,0 @@ -### Azimut Sensor 3 - -Azimut-Zuordnung fuer Sensor 3 in 5-Grad-Schritten. -"Keine Zuordnung" deaktiviert die Azimut-Auswertung fuer diesen Sensor. - diff --git a/src/Baggages/Help_de/SHC-Azimut-Sensor-4.md b/src/Baggages/Help_de/SHC-Azimut-Sensor-4.md deleted file mode 100644 index 71e5fb0..0000000 --- a/src/Baggages/Help_de/SHC-Azimut-Sensor-4.md +++ /dev/null @@ -1,5 +0,0 @@ -### Azimut Sensor 4 - -Azimut-Zuordnung fuer Sensor 4 in 5-Grad-Schritten. -"Keine Zuordnung" deaktiviert die Azimut-Auswertung fuer diesen Sensor. - diff --git a/src/Baggages/Help_de/SHC-Azimut-Sensor-5.md b/src/Baggages/Help_de/SHC-Azimut-Sensor-5.md deleted file mode 100644 index be968e2..0000000 --- a/src/Baggages/Help_de/SHC-Azimut-Sensor-5.md +++ /dev/null @@ -1,5 +0,0 @@ -### Azimut Sensor 5 - -Azimut-Zuordnung fuer Sensor 5 in 5-Grad-Schritten. -"Keine Zuordnung" deaktiviert die Azimut-Auswertung fuer diesen Sensor. - diff --git a/src/Baggages/Help_de/SHC-Helligkeit.md b/src/Baggages/Help_de/SHC-Helligkeit.md deleted file mode 100644 index a9f60d5..0000000 --- a/src/Baggages/Help_de/SHC-Helligkeit.md +++ /dev/null @@ -1,4 +0,0 @@ -### Helligkeit - -Vorgesehen für den Helligkeitswert einer KNX-Wetterstation. - diff --git a/src/Baggages/Help_de/SHC-Weitere-Helligkeitssensoren.md b/src/Baggages/Help_de/SHC-Weitere-Helligkeitssensoren.md deleted file mode 100644 index c5883d5..0000000 --- a/src/Baggages/Help_de/SHC-Weitere-Helligkeitssensoren.md +++ /dev/null @@ -1,5 +0,0 @@ -### Weitere Helligkeitssensoren - -Legt fest, wie viele zusaetzliche Helligkeitssensoren (2 bis 5) verwendet werden. -Die Anzahl bestimmt, welche weiteren Kommunikationsobjekte sichtbar und zu verknuepfen sind. - From 4f8c96f7d1d9833119ba1f8b768409f49ea46a7f Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:56:06 +0200 Subject: [PATCH 11/34] docs: update application description for new shading modes --- ...likationsbeschreibung-ShutterController.md | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/doc/Applikationsbeschreibung-ShutterController.md b/doc/Applikationsbeschreibung-ShutterController.md index bc8e1c2..5eebe22 100644 --- a/doc/Applikationsbeschreibung-ShutterController.md +++ b/doc/Applikationsbeschreibung-ShutterController.md @@ -225,8 +225,8 @@ Beispiele (vereinfachte Sicht): | 3x Sensor mit Azimut (O/S/W) | Dachflaeche | Max(alle 3 Sensoren) — Fallback, da kein unzugeordneter Sensor | | 4x Sensor mit Azimut + 1x Sensor ohne Azimut | Dachflaeche | Max(nur der Sensor ohne Azimut) | | 4x Sensor mit Azimut + 1x Sensor ohne Azimut | Sued | Azimut-Interpolation nur mit den 4 Azimut-Sensoren | -| 2x Sensor ohne Azimut | Keine Himmelsrichtungsauswertung | Aggregation ueber alle Sensoren ohne Azimut | -| 1x Sensor mit Azimut | Keine Himmelsrichtungsauswertung | Aggregation ueber alle gueltigen Sensoren | +| 2x Sensor ohne Azimut | Keine Himmelsrichtungsauswertung | Aggregation über alle Sensoren ohne Azimut | +| 1x Sensor mit Azimut | Keine Himmelsrichtungsauswertung | Aggregation über alle gueltigen Sensoren | ##### Ausrichtung Sensor 1..5 @@ -800,6 +800,17 @@ Diese Angabe wird verwendet, um den passenden Helligkeitssensor fuer die Beschat - **Dachflaeche**: Bevorzugt Sensoren ohne Azimut-Zuordnung (z.B. Dachsensor). Die eingestellte Aggregation wird ignoriert — es gilt immer der Maximalwert. Falls keine Sensoren ohne Azimut-Zuordnung vorhanden sind, greift ein automatischer Fallback: alle Sensoren werden mit Max-Aggregation ausgewertet. - **Keine Himmelsrichtungsauswertung**: Azimut-basierte Sensorauswahl ist deaktiviert. Alle gueltigen Sensoren werden mit der eingestellten Aggregation (Mittelwert/Maximum) zusammengefasst. + +#### Fassadenneigung + +Die Neigung der Fassade in Grad, gemessen gegenüber der Senkrechten (0° = senkrechte Wand). + +- **0°**: Senkrechte Fassade (Standardfall) +- **Positiver Wert**: Fassade neigt sich nach außen (z.B. überhängende Dachkante) +- **Negativer Wert**: Fassade neigt sich nach innen (z.B. nach innen geneigte Wand) + +Dieser Wert wird für die Berechnung des Profilwinkels bei der **Lamellennachführung (Experte)** und der **Schattenkantennachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. + #### Nur starten wenn aktuelle Position kleiner gleich @@ -862,6 +873,70 @@ Zwischen diesen beiden Werten wird die Lamellenstellung linear interpoliert. Hat die Jalousie sehr breite oder sehr schmale Lamellenblätter oder ist das Fenster nicht senkrecht verbaut, kann es notwendig sein zum errechnete Wert der Jalousiennachführung einen zusätzlichen Kippwinkel-Offset anzugeben. Der Kippwinkel-Offset kann positiv oder negativ sein um mehr oder weniger zu kippen. + +#### Lamellenbreite + +Die physische Breite einer einzelnen Lamelle in Millimetern (gemessen quer zur Lamellenachse). + +Dieser Wert wird für die geometrische Berechnung des optimalen Kippwinkels bei der **Lamellennachführung (Experte)** benötigt. Den Wert finden Sie in der technischen Dokumentation des Jalousie-Herstellers. + + +#### Lamellenabstand + +Der Abstand zwischen zwei benachbarten Lamellenachsen in Millimetern (Achsmaß). + +Dieser Wert wird zusammen mit der Lamellenbreite für die geometrische Berechnung des kritischen Kippwinkels verwendet, ab dem die Lamellen den Lichtstrahl vollständig sperren. Den Wert finden Sie in der technischen Dokumentation des Jalousie-Herstellers. + + +#### Lamellenwinkel bei Fahrbefehl 0% + +Der physische Winkel der Lamellen in Grad, wenn der Aktor den Stellwert 0% empfängt. + +Dieser Kalibrierungswert wird benötigt, damit die berechnete optimale Lamellenstellung korrekt auf den vom Aktor erwarteten Prozentwert abgebildet wird. Verschiedene Aktoren und Montagevarianten können unterschiedliche Konventionen verwenden. + + +#### Lamellenwinkel bei Fahrbefehl 100% + +Der physische Winkel der Lamellen in Grad, wenn der Aktor den Stellwert 100% empfängt. + +**Hinweis:** Ein Wert kleiner als "Lamellenwinkel bei Fahrbefehl 0%" ist ausdrücklich erlaubt und notwendig für Lamellen, die umgekehrt montiert sind. Die Nachführungsformel invertiert das Mapping automatisch. + + +#### Fensterhöhe + +Die lichte Höhe der Fensterscheibe in Zentimetern (Innenkante Blendrahmen oben bis Innenkante Blendrahmen unten bzw. Fensterbank). + +Dieser Wert wird für die Berechnung der optimalen Jalousie- oder Rolladenposition bei der **Schattenkantennachführung** benötigt. Zusammen mit der aktuellen Sonnenposition wird berechnet, wie weit der Behang abgesenkt sein muss, damit die Schattenkante die konfigurierte Eindringtiefe nicht überschreitet. + + +#### Max. Eindringtiefe + +Die maximale Sonneneindringtiefe im Raum in Zentimetern, gemessen ab der Fensterscheibe (Innenmaß). + +Wenn die Sonne tiefer als dieser Wert in den Raum eindringen würde, schließt die Jalousie oder der Rollladen weiter, bis die Schattenkante genau auf der konfigurierten Tiefe liegt. Bei sehr flachem Sonnenstand (Schattenkante wäre jenseits der Fenstertiefe) wird der Behang vollständig geschlossen. + + +#### Mindestverschiebung Schattenkante + +Mindestverschiebung der berechneten Schattenkante in Zentimetern, bevor ein neuer Positionsbefehl an den Aktor gesendet wird. + +Verhindert zu häufige kleine Positionskorrekturen bei langsam wandernder Sonne. Ein Wert von 5–10 cm ist für die meisten Anwendungen empfehlenswert. + + +#### Brüstungshöhe + +Höhe der Fensterbrüstung in Zentimetern, gemessen vom Fußboden bis zur Unterkante der Fensterscheibe. + +Dieser Wert wird zusammen mit der Fensterhöhe verwendet, um die absolute Position der Schattenkante im Raum zu bestimmen. Bei bodentiefen Fenstern (z.B. Terrassentür bis zum Boden) ist dieser Wert 0. + + +#### Positionsnachführung + +Aktiviert die dynamische Positionsnachführung für Rollläden basierend auf der aktuellen Sonnenposition. + +- **Nein**: Keine Positionsnachführung. Der Rollladen fährt auf die konfigurierte Beschattungsposition und bleibt dort. +- **Schattenkantennachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. + ### Temperaturgrenzen From 88dd1a202185daa5f73112f83954fcbea5f7deb1 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:56:08 +0200 Subject: [PATCH 12/34] chore: bump version to 0.6.2 and update release notes --- ReleaseNotes.md | 11 +++++++++++ library.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 3cba9b7..80021c5 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,3 +1,14 @@ +v 0.6.2 +- Feature: Neue Beschattungsmodi 3 „Lamellennachführung Experte", 4 „Schattenkantennachführung", 5 „Schattenkanten- und Lamellennachführung" +- Feature: Schattenkantennachführung für Rollo (neuer Parameter „Positionsnachführung" im Rollo-Kanal) +- Feature: Neuer Kanal-Parameter „Fassadenneigung" für geometrische Berechnung +- Breaking/Rename: Beschattungsmodus-Lamellensteuerung Wert 2 umbenannt: „Benutzerdefiniert" → „Lamellennachführung Min/Max". Der Parameterwert (2) ist unverändert. Bestehende ETS-Projekte laufen ohne Migration weiter; lediglich der angezeigte Text im ETS-Dropdown ändert sich nach einem Update der knxprod. +v 0.6.1 +- Feature: Positionsprüfung im Status „Beschattung Bereit (Benutzer)" – Beschattungsbereitschaft wird nur signalisiert, wenn die aktuelle Zielposition ≤ dem konfigurierten Grenzwert „Nur wenn Position kleiner als" ist +- Feature: KO-Freigabe-Parameter je Kanal – alle optionalen KOs können nun einzeln in der ETS ein-/ausgeblendet werden (Sperren Kanal, Status aktiver Modus, Aktorrückmeldung, Beschattung ein/aus, Beschattung aktiv, Beschattungsbereitschaft, Handbetrieb Schalten, Handbetrieb Position, Fenster offen/gekippt Status und Sperren) +- Feature: KO-Freigabe-Parameter je Beschattungsmodus – Sperren, Status Aktiv und Status Bereitschaft je Beschattungsmodus separat freigebbar +- Feature: KO-Freigabe-Parameter je Fensterkontakt – Status und Sperren für Fenster offen / Fenster gekippt separat freigebbar +- Rename: KO-Freigabe-Parameter für Beschattungsmodus konsistent benannt (PPP+53 = Sperren freigeben, PPP+54 = Bereitschaft freigeben, PPP+55 = Status Aktiv freigeben) v 0.6.0 - Refactor: "Helligkeit" (bool) + "Weitere Helligkeitssensoren" (count) zusammengefasst zu "Helligkeitssensoren" (Enum: Nein/1-5 Sensoren) - Rename: "Helligkeit Sensor 1..5" -> "Ausrichtung Sensor 1..5" diff --git a/library.json b/library.json index 776a67c..b08c0af 100755 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "OFM-ShutterControllerModule", - "version": "0.6.1", + "version": "0.6.2", "dependencies": { }, From 35fb348f77e56bfb748b679ea0ba0a40d4e5fe8f Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:55:28 +0200 Subject: [PATCH 13/34] chore: bump version to 0.6.3, update .gitignore --- .gitignore | 2 ++ library.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 65e3ba2..0877619 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ test/ +Plan-Lamellennachführung-Schattenkantennachführung.md +ToDos.md diff --git a/library.json b/library.json index b08c0af..a3152af 100755 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "OFM-ShutterControllerModule", - "version": "0.6.2", + "version": "0.6.3", "dependencies": { }, From 670123f4c97779ed7d33a120f548e00d2eeb52c0 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:00:27 +0200 Subject: [PATCH 14/34] fix: correct slat angle calculation and facade sin_alpha sign - theta_krit: remove incorrect 90-atan2f() conversion; use atan2f() directly (angle convention from vertical, not horizontal) - Dachflaeche: use fabsf(sinf(...)) for sin_alpha to handle negative inclination values correctly (cases 4, 5) --- src/ModeShading.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index 6cfe8ba..59a232b 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -719,11 +719,11 @@ void ModeShading::control(const CallContext &callContext, PositionController &po if (orientation == 5) { - // Dachfläche: gamma = Höhenwinkel, sin_alpha = sin(Dachneigung) + // Dachfläche: gamma = Höhenwinkel, sin_alpha = |sin(Dachneigung)| float elev = (float)callContext.elevation; if (elev <= 0.0f) return; - sin_alpha = sinf(facadeInclination * DEG2RAD); + sin_alpha = fabsf(sinf(facadeInclination * DEG2RAD)); if (sin_alpha < 0.05f) { _notAllowedReason |= ModeShadingNotAllowedReasonFlatRoofGuard; @@ -865,10 +865,8 @@ void ModeShading::control(const CallContext &callContext, PositionController &po if (angleAtMin == angleAtMax) break; // Guard: Division durch Null - // Kritischer Kippwinkel zur Horizontalen - float theta_krit_h = atan2f(a * sinf(beta_rad), b - a * cosf(beta_rad)) * (180.0f / (float)M_PI); - // Umrechnung auf Elsner-Winkel (zur Senkrechten) - float theta_krit = 90.0f - theta_krit_h; + // Kritischer Kippwinkel (in Nutzerkonvention: von der Senkrechten nach unten) + float theta_krit = atan2f(a * sinf(beta_rad), b - a * cosf(beta_rad)) * (180.0f / (float)M_PI); // Guard: Lamelle kann den Strahl nicht sperren (z.B. SlatSpacing > SlatWidth) if (theta_krit < 0.0f) @@ -905,11 +903,11 @@ void ModeShading::control(const CallContext &callContext, PositionController &po if (orientation == 5) { - // Dachfläche: gamma = Höhenwinkel, sin_alpha = sin(Dachneigung) + // Dachfläche: gamma = Höhenwinkel, sin_alpha = |sin(Dachneigung)| float elev = (float)callContext.elevation; if (elev <= 0.0f) return; - sin_alpha = sinf(facadeInclination * DEG2RAD); + sin_alpha = fabsf(sinf(facadeInclination * DEG2RAD)); if (sin_alpha < 0.05f) { _notAllowedReason |= ModeShadingNotAllowedReasonFlatRoofGuard; @@ -985,11 +983,11 @@ void ModeShading::control(const CallContext &callContext, PositionController &po if (orientation == 5) { - // Dachfläche: gamma = Höhenwinkel, sin_alpha = sin(Dachneigung) + // Dachfläche: gamma = Höhenwinkel, sin_alpha = |sin(Dachneigung)| float elev = (float)callContext.elevation; if (elev <= 0.0f) return; - sin_alpha = sinf(facadeInclination * DEG2RAD); + sin_alpha = fabsf(sinf(facadeInclination * DEG2RAD)); if (sin_alpha < 0.05f) { _notAllowedReason |= ModeShadingNotAllowedReasonFlatRoofGuard; @@ -1061,8 +1059,7 @@ void ModeShading::control(const CallContext &callContext, PositionController &po if (angleAtMin != angleAtMax) { - float theta_krit_h = atan2f(a * sinf(gamma_rad), b - a * cosf(gamma_rad)) * (180.0f / (float)M_PI); - float theta_krit = 90.0f - theta_krit_h; + float theta_krit = atan2f(a * sinf(gamma_rad), b - a * cosf(gamma_rad)) * (180.0f / (float)M_PI); if (theta_krit < 0.0f) { From 57a765651ea6af26ab2b3bec6581117ba2ac9978 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:02:13 +0200 Subject: [PATCH 15/34] fix: apply OffsetSlatPosition and MinChangeForSlatAdaption in shadow-edge modes - Mode 2: add MinChangeForSlatAdaption hysteresis guard before setAutomaticSlat - Mode 5: apply OffsetSlatPosition offset to slatPercent before clamping --- src/ModeShading.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index 59a232b..041d2c7 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -835,6 +835,8 @@ void ModeShading::control(const CallContext &callContext, PositionController &po auto slatPosition = (uint8_t)targetSlatPosition; if (callContext.diagnosticLog) logInfoP("Calculated slat position %d for elevation %lf (t=%.2f, low=%d, high=%d)", (int)slatPosition, callContext.elevation, t, (int)posLow, (int)posHigh); + if (!callContext.modeNewStarted && abs((uint8_t)KoSHC_CShutterSlatOutput.value(DPT_Scaling) - slatPosition) < ParamSHC_CShading1MinChangeForSlatAdaption) + break; positionController.setAutomaticSlat(slatPosition); break; } @@ -1068,6 +1070,7 @@ void ModeShading::control(const CallContext &callContext, PositionController &po else { float slatPercent = ((float)theta_krit - (float)angleAtMin) / ((float)angleAtMax - (float)angleAtMin) * 100.0f; + slatPercent += (float)(int8_t)ParamSHC_CShading1OffsetSlatPosition; if (slatPercent < 0.0f) slatPercent = 0.0f; if (slatPercent > 100.0f) slatPercent = 100.0f; auto slatPosition = (uint8_t)slatPercent; From f8df2efab1dab097e8a3dbf236b8eb96c8c15246 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:03:59 +0200 Subject: [PATCH 16/34] rename: facade parameter label to 'Fassadenneigung/Fensterneigung' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - templ.xml: rename Text attribute, comments, and ParameterRef comment to 'Fassadenneigung/Fensterneigung' to reflect that the inclination applies to both vertical facades and tilted windows (Dachfläche) --- src/ShutterControllerModule.templ.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ShutterControllerModule.templ.xml b/src/ShutterControllerModule.templ.xml index c6f5e33..63acd80 100755 --- a/src/ShutterControllerModule.templ.xml +++ b/src/ShutterControllerModule.templ.xml @@ -214,8 +214,8 @@ - - + + @@ -266,7 +266,7 @@ - + @@ -678,7 +678,7 @@ - + From 0a1c41207edaa26000ac07be8abb7abb7e16431c Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:04:35 +0200 Subject: [PATCH 17/34] =?UTF-8?q?feat:=20add=20shading=20mode=206=20'Geo.?= =?UTF-8?q?=20Positions-=20und=20Lamellennachf=C3=BChrung=20(Min/Max)'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New mode combining shadow-edge tracking with configurable position and slat clip limits (Min/Max) - _lastSentShadowPos stores unclipped value for correct hysteresis - Slat clipping uses min/max of SlatLowSunPosition/SlatHighSunPosition regardless of value order - Position clipping ignored when MinClipPos > MaxClipPos - Mode 4 renamed to 'Geometrische Positionsnachführung' - Mode 5 renamed to 'Geometrische Positions- und Lamellennachführung' - PT-PenetrationDepth range changed: 1-200 to 10-255 - ModeShading parameter block increment changed: 50 to 52 (breaking) --- src/ModeShading.cpp | 132 +++++++++++++++++++- src/ShutterControllerModule.ModeShading.xml | 72 ++++++++--- src/ShutterControllerModule.share.xml | 15 +-- src/ShutterControllerModule.templ.xml | 2 +- 4 files changed, 197 insertions(+), 24 deletions(-) diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index 041d2c7..dca27b4 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -629,7 +629,7 @@ bool ModeShading::allowedByMeasurmentValues(const CallContext &callContext) // elevationDeg - Sonnenhöhe in Grad [0..90] // azimuthDeg - Sonnenazimut in Grad [0..360, 0=Nord, 90=Ost, 180=Süd, 270=West] // orientationEnum - WindowOrientation enum: 0=Ost, 1=Südost, 2=Süd, 3=Südwest, 4=West, 5=Dachfläche, 6=Keine -// facadeInclinationDeg - Fassadenneigung in Grad (0=senkrecht/Wand, positiv=nach vorne geneigt) +// facadeInclinationDeg - Fassadenneigung/Fensterneigung in Grad (0=senkrecht/Wand, positiv=nach vorne geneigt) float ModeShading::calculateProfileAngle(float elevationDeg, float azimuthDeg, uint8_t orientationEnum, float facadeInclinationDeg) const { static const float DEG2RAD = (float)M_PI / 180.0f; @@ -1086,6 +1086,136 @@ void ModeShading::control(const CallContext &callContext, PositionController &po break; } + case 6: + { + // Geo. Positions- und Lamellennachführung mit Begrenzung + // Identisch zu Case 5, jedoch mit konfigurierbarem Clipping für Position und Lamelle + if (ParamSHC_CType == 2) break; + static const float DEG2RAD = (float)M_PI / 180.0f; + _notAllowedReason &= ~(ModeShadingNotAllowedReasonProfileAngleSentinel | ModeShadingNotAllowedReasonFlatRoofGuard); + + float gamma_rad; + float sin_alpha; + bool slatTrackingPossible; + const uint8_t orientation = ParamSHC_CWindowOrientation; + const float facadeInclination = (float)(int8_t)ParamSHC_CFacadeInclination; + + if (orientation == 5) + { + float elev = (float)callContext.elevation; + if (elev <= 0.0f) + return; + sin_alpha = fabsf(sinf(facadeInclination * DEG2RAD)); + if (sin_alpha < 0.05f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonFlatRoofGuard; + return; + } + gamma_rad = elev * DEG2RAD; + float beta_slat = (float)callContext.elevation - facadeInclination; + slatTrackingPossible = (beta_slat > 0.0f); + if (!slatTrackingPossible) + _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; + } + else + { + float beta_deg = calculateProfileAngle( + (float)callContext.elevation, + (float)callContext.azimuth, + orientation, + facadeInclination); + if (beta_deg <= 0.0f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; + return; + } + gamma_rad = beta_deg * DEG2RAD; + sin_alpha = 1.0f; + slatTrackingPossible = true; + } + + float windowHeight = (float)ParamSHC_CShading1WindowHeight; + float maxPenetration = (float)ParamSHC_CShading1MaxPenetrationDepth; + float windowSillHeight = (float)ParamSHC_CShading1WindowSillHeight; + float minShadowEdgeChange = (float)ParamSHC_CShading1MinShadowEdgeChange; + + float s_crit = (maxPenetration * tanf(gamma_rad) - windowSillHeight) / sin_alpha; + + float targetPos; + if (s_crit <= 0.0f) + targetPos = 100.0f; + else if (s_crit >= windowHeight) + targetPos = (float)ParamSHC_CShading1ShadingPosition; + else + targetPos = (1.0f - s_crit / windowHeight) * 100.0f; + + if (targetPos < 0.0f) targetPos = 0.0f; + if (targetPos > 100.0f) targetPos = 100.0f; + + bool positionChanged = true; + if (_lastSentShadowPos >= 0.0f && s_crit < windowHeight && s_crit > 0.0f) + { + float delta_cm = fabsf(targetPos - _lastSentShadowPos) * windowHeight / 100.0f; + if (delta_cm < minShadowEdgeChange) + positionChanged = false; + } + + if (positionChanged) + { + _lastSentShadowPos = targetPos; // Ungeclippten Wert für korrekte Hysterese speichern + float clippedPos = targetPos; + uint8_t minClipPos = ParamSHC_CShading1MinClipPosition; + uint8_t maxClipPos = ParamSHC_CShading1MaxClipPosition; + if (minClipPos <= maxClipPos) + { + if (clippedPos < (float)minClipPos) clippedPos = (float)minClipPos; + if (clippedPos > (float)maxClipPos) clippedPos = (float)maxClipPos; + } + positionController.setAutomaticPosition((uint8_t)clippedPos); + } + + if (slatTrackingPossible) + { + float a = (float)ParamSHC_CShading1SlatSpacing; + float b = (float)ParamSHC_CShading1SlatWidth; + uint8_t angleAtMin = ParamSHC_CShading1SlatAngleAtMin; + uint8_t angleAtMax = ParamSHC_CShading1SlatAngleAtMax; + + if (angleAtMin != angleAtMax) + { + float theta_krit = atan2f(a * sinf(gamma_rad), b - a * cosf(gamma_rad)) * (180.0f / (float)M_PI); + + if (theta_krit < 0.0f) + { + positionController.setAutomaticSlat(100); + } + else + { + float slatPercent = ((float)theta_krit - (float)angleAtMin) / ((float)angleAtMax - (float)angleAtMin) * 100.0f; + slatPercent += (float)(int8_t)ParamSHC_CShading1OffsetSlatPosition; + if (slatPercent < 0.0f) slatPercent = 0.0f; + if (slatPercent > 100.0f) slatPercent = 100.0f; + + // Lamellen-Clipping: min(PPP+51, PPP+52) bis max(PPP+51, PPP+52) + float slatLow = (float)ParamSHC_CShading1SlatLowSunPosition; + float slatHigh = (float)ParamSHC_CShading1SlatHighSunPosition; + float slatClipMin = (slatLow < slatHigh) ? slatLow : slatHigh; + float slatClipMax = (slatLow > slatHigh) ? slatLow : slatHigh; + if (slatPercent < slatClipMin) slatPercent = slatClipMin; + if (slatPercent > slatClipMax) slatPercent = slatClipMax; + + auto slatPosition = (uint8_t)slatPercent; + if (callContext.modeNewStarted || abs((uint8_t)KoSHC_CShutterSlatOutput.value(DPT_Scaling) - slatPosition) >= ParamSHC_CShading1MinChangeForSlatAdaption) + positionController.setAutomaticSlat(slatPosition); + } + } + } + + if (callContext.diagnosticLog) + logInfoP("Case6 clip: gamma=%.1f° s_crit=%.1f targetPos=%.1f slatPossible=%d", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos, (int)slatTrackingPossible); + + break; + } } } diff --git a/src/ShutterControllerModule.ModeShading.xml b/src/ShutterControllerModule.ModeShading.xml index 7e87f12..b5bc847 100755 --- a/src/ShutterControllerModule.ModeShading.xml +++ b/src/ShutterControllerModule.ModeShading.xml @@ -1,4 +1,4 @@ - + @@ -8,7 +8,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -157,29 +157,34 @@ - + - + - + - + - + - + - - - + + + + + + + + @@ -314,6 +319,10 @@ + + + + @@ -522,9 +531,6 @@ - - - @@ -532,6 +538,8 @@ + + @@ -551,6 +559,9 @@ + + + @@ -564,6 +575,8 @@ + + @@ -590,12 +603,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ShutterControllerModule.share.xml b/src/ShutterControllerModule.share.xml index 748dab0..7015dfa 100755 --- a/src/ShutterControllerModule.share.xml +++ b/src/ShutterControllerModule.share.xml @@ -1,4 +1,4 @@ - + @@ -197,16 +197,17 @@ - - - - + + + + + - + @@ -225,7 +226,7 @@ - + diff --git a/src/ShutterControllerModule.templ.xml b/src/ShutterControllerModule.templ.xml index 63acd80..c71d043 100755 --- a/src/ShutterControllerModule.templ.xml +++ b/src/ShutterControllerModule.templ.xml @@ -21,7 +21,7 @@ - + From cb25cb7e19266629aedfecfb5ad93f935044ad46 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:04:48 +0200 Subject: [PATCH 18/34] docs: document v0.6.3 changes, regenerate Help_de files - ReleaseNotes: list all v0.6.3 changes - Applikationsbeschreibung: add mode 6 description and parameter docs - Help_de: regenerate all context help files (new, modified, deleted) to match renamed and new parameters --- ReleaseNotes.md | 11 ++- ToDos.md | 16 ++-- ...likationsbeschreibung-ShutterController.md | 77 +++++++++++++++---- .../Help_de/SHC-Beschattungsposition.md | 2 + src/Baggages/Help_de/SHC-Fassadenneigung.md | 10 --- .../SHC-FassadenneigungFensterneigung.md | 10 +++ ...Behangausrichtung-und-Azimut-Auswertung.md | 4 +- src/Baggages/Help_de/SHC-Fensterhoehe.md | 2 +- ...ellenstellenung-an-Sonnenstand-anpassen.md | 1 + ...tellung-bei-max-Hoehenwinkel-Begrenzung.md | 6 ++ ...tellung-bei-min-Hoehenwinkel-Begrenzung.md | 4 + .../SHC-Lamellenwinkel-bei-Fahrbefehl-100.md | 6 -- ...amellenwinkel-vollstaendig-geoeffnet-0.md} | 2 +- ...llenwinkel-vollstaendig-geschlossen-100.md | 6 ++ .../Help_de/SHC-Max-Position-Begrenzung.md | 8 ++ .../Help_de/SHC-Min-Position-Begrenzung.md | 8 ++ ...HC-Nicht-erlaubt-Bits-Nur-fuer-Experten.md | 2 + .../Help_de/SHC-Nicht-erlaubt-Grund.md | 2 + .../Help_de/SHC-Positionsnachfuehrung.md | 2 +- 19 files changed, 137 insertions(+), 42 deletions(-) delete mode 100644 src/Baggages/Help_de/SHC-Fassadenneigung.md create mode 100644 src/Baggages/Help_de/SHC-FassadenneigungFensterneigung.md create mode 100644 src/Baggages/Help_de/SHC-Lamellenstellung-bei-max-Hoehenwinkel-Begrenzung.md create mode 100644 src/Baggages/Help_de/SHC-Lamellenstellung-bei-min-Hoehenwinkel-Begrenzung.md delete mode 100644 src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-100.md rename src/Baggages/Help_de/{SHC-Lamellenwinkel-bei-Fahrbefehl-0.md => SHC-Lamellenwinkel-vollstaendig-geoeffnet-0.md} (86%) create mode 100644 src/Baggages/Help_de/SHC-Lamellenwinkel-vollstaendig-geschlossen-100.md create mode 100644 src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md create mode 100644 src/Baggages/Help_de/SHC-Min-Position-Begrenzung.md diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 80021c5..5ae9e57 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,7 +1,16 @@ +v 0.6.3 +- Feature: Neuer Beschattungsmodus 6 „Geo. Positions- und Lamellennachführung (Min/Max)" – entspricht Modus 5 mit konfigurierbarer Min./Max.-Begrenzung für Position und Lamellenstellung. Wenn Min. Position > Max. Position konfiguriert ist, wird die Positionsbegrenzung ignoriert. Die Lamellenstellungsbegrenzung verwendet automatisch min/max der beiden Werte (Reihenfolge egal). +- Breaking: Parameterlayout des Beschattungsmodus-Blocks vergrößert (increment 50 → 52). Betrifft nur Kanäle mit zwei oder mehr konfigurierten Beschattungsmodi. Bestehende ETS-Projekte mit nur einem Beschattungsmodus pro Kanal sind nicht betroffen. +- Rename: Beschattungsmodus 4 umbenannt: „Schattenkantennachführung" → „Geometrische Positionsnachführung". Beschattungsmodus 5 umbenannt: „Schattenkanten- und Lamellenführung" → „Geometrische Positions- und Lamellennachführung". Die Parameterwerte (4, 5) sind unverändert. +- Fix: Kritischer Kippwinkel (theta_krit) wurde fälschlicherweise als 90°−atan2(...) berechnet; korrekt ist direkt atan2(...) (Winkelkonvention zur Senkrechten). +- Fix: Bei Dachflächen-Orientierung wurde sin_alpha ohne Betrag berechnet, was bei negativen Neigungswinkeln zu falschen Positionen führte (jetzt fabsf). +- Fix: OffsetSlatPosition wurde in Modus 5 nicht auf die berechnete Lamellenstellung angewendet. +- Fix: Hysterese (MinChangeForSlatAdaption) fehlte in Modus 2 (Standard-Lamellennachführung) vor dem Setzen der Lamellenstellung. +- Change: Wertebereich „Max. Eindringtiefe" angepasst: 1–200 → 10–255 cm. v 0.6.2 - Feature: Neue Beschattungsmodi 3 „Lamellennachführung Experte", 4 „Schattenkantennachführung", 5 „Schattenkanten- und Lamellennachführung" - Feature: Schattenkantennachführung für Rollo (neuer Parameter „Positionsnachführung" im Rollo-Kanal) -- Feature: Neuer Kanal-Parameter „Fassadenneigung" für geometrische Berechnung +- Feature: Neuer Kanal-Parameter „Fassadenneigung/Fensterneigung" für geometrische Berechnung - Breaking/Rename: Beschattungsmodus-Lamellensteuerung Wert 2 umbenannt: „Benutzerdefiniert" → „Lamellennachführung Min/Max". Der Parameterwert (2) ist unverändert. Bestehende ETS-Projekte laufen ohne Migration weiter; lediglich der angezeigte Text im ETS-Dropdown ändert sich nach einem Update der knxprod. v 0.6.1 - Feature: Positionsprüfung im Status „Beschattung Bereit (Benutzer)" – Beschattungsbereitschaft wird nur signalisiert, wenn die aktuelle Zielposition ≤ dem konfigurierten Grenzwert „Nur wenn Position kleiner als" ist diff --git a/ToDos.md b/ToDos.md index 2ec68fb..fd63d7a 100644 --- a/ToDos.md +++ b/ToDos.md @@ -1,11 +1,15 @@ To Do's -- Lamellenstellung Beschattung Grundposition wählbar mit maximaler Abweichung von der Grundeinstellung um nicht den Raum komplett zu verdunkeln. -- Optionale Status KO´s & Sperren ausblendbar um die Anzahl der sichtbaren KO´s zu reduzieren (falls sie nicht benötigt werden). + +- Optionale Status KO´s & Sperren ausblendbar um die Anzahl der sichtbaren KO´s zu reduzieren (falls sie nicht benötigt werden). -> erledigt und implementiert + +- Neigung der Fassade & Schattenkantennachführung + https://www.youtube.com/watch?v=WLM9XcgRE_Q + https://www.youtube.com/watch?v=-t9FiaM6x4Y + +- Vertikale Lamellen in "Lamellennachführung Experte": Berechnung auf Azimut-Basis + (statt Profilwinkel) für vertikal montierte Lamellen. Long Term To Do'S - Szenen -- Hysterese bei Helligkeit -- Neigung der Fassade -- Schattenkantennachführung - +- Hysterese bei Helligkeit \ No newline at end of file diff --git a/doc/Applikationsbeschreibung-ShutterController.md b/doc/Applikationsbeschreibung-ShutterController.md index 5eebe22..3151863 100644 --- a/doc/Applikationsbeschreibung-ShutterController.md +++ b/doc/Applikationsbeschreibung-ShutterController.md @@ -800,16 +800,16 @@ Diese Angabe wird verwendet, um den passenden Helligkeitssensor fuer die Beschat - **Dachflaeche**: Bevorzugt Sensoren ohne Azimut-Zuordnung (z.B. Dachsensor). Die eingestellte Aggregation wird ignoriert — es gilt immer der Maximalwert. Falls keine Sensoren ohne Azimut-Zuordnung vorhanden sind, greift ein automatischer Fallback: alle Sensoren werden mit Max-Aggregation ausgewertet. - **Keine Himmelsrichtungsauswertung**: Azimut-basierte Sensorauswahl ist deaktiviert. Alle gueltigen Sensoren werden mit der eingestellten Aggregation (Mittelwert/Maximum) zusammengefasst. - -#### Fassadenneigung + +#### Fassadenneigung/Fensterneigung -Die Neigung der Fassade in Grad, gemessen gegenüber der Senkrechten (0° = senkrechte Wand). +Die Neigung der Fassade oder eines Dachfenster in Grad, gemessen gegenüber der Senkrechten (0° = senkrechte Wand). -- **0°**: Senkrechte Fassade (Standardfall) -- **Positiver Wert**: Fassade neigt sich nach außen (z.B. überhängende Dachkante) -- **Negativer Wert**: Fassade neigt sich nach innen (z.B. nach innen geneigte Wand) +- **0°**: Senkrechte Fassade/Senkrechtes Fenster (Standardfall) +- **Positiver Wert**: Fassade/Fenster neigt sich nach außen (z.B. überhängende Dachkante) +- **Negativer Wert**: Fassade/Fenster neigt sich nach innen (z.B. nach innen geneigte Wand) -Dieser Wert wird für die Berechnung des Profilwinkels bei der **Lamellennachführung (Experte)** und der **Schattenkantennachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. +Dieser Wert wird für die Berechnung des Profilwinkels bei der **Lamellennachführung (Experte)** und der **Geometrischen Positionsnachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. #### Nur starten wenn aktuelle Position kleiner gleich @@ -824,6 +824,8 @@ Eine Einstellung von 0% startet die Beschattung nur, wenn die Jalousie zuvor vol Position die bei Beschattungsstart angefahren wird. +Bei den Modi **Geometrische Positionsnachführung**, **Geometrische Positions- und Lamellennachführung** und **Geo. Positions- und Lamellennachführung (Min/Max)** wird die Position normalerweise dynamisch anhand des Sonnenstands berechnet. Die Beschattungsposition wirkt in diesen Modi nur als **Fallback**: Wenn die Sonne so flach steht, dass die berechnete Schattenkante oberhalb der Fensterhöhe liegt (d.h. der Behang muss vollständig abgesenkt sein), wird die hier konfigurierte Position als Zielwert verwendet. + #### Lamellenstellenung an Sonnenstand anpassen @@ -832,6 +834,7 @@ Diese Einstellung ist nur für den Gerätetype "Jalousie" vorhanden. - **Nein**: Die Lamellenstellung wird nicht an den Sonnenstand angepasst. Stattdessen wird die unter "Lamellenstellung" konfigurierte feste Position verwendet. - **Standard**: Die Lamellenstellung wird anhand des Höhenwinkels der Sonne automatisch berechnet (bewährte Formel). - **Benutzerdefiniert**: Die Lamellenstellung wird linear zwischen dem Wert bei tiefem Sonnenstand und dem Wert bei hohem Sonnenstand interpoliert. +- **Geo. Positions- und Lamellennachführung (Min/Max)**: Wie "Geometrische Positions- und Lamellennachführung", jedoch werden Position und Lamellenstellung auf konfigurierbare Min./Max.-Grenzen begrenzt. Die berechneten Werte können den konfigurierten Bereich nicht über- oder unterschreiten. #### Lamellenstellung @@ -887,26 +890,26 @@ Der Abstand zwischen zwei benachbarten Lamellenachsen in Millimetern (Achsmaß). Dieser Wert wird zusammen mit der Lamellenbreite für die geometrische Berechnung des kritischen Kippwinkels verwendet, ab dem die Lamellen den Lichtstrahl vollständig sperren. Den Wert finden Sie in der technischen Dokumentation des Jalousie-Herstellers. - -#### Lamellenwinkel bei Fahrbefehl 0% + +#### Lamellenwinkel vollständig geöffnet (0%) Der physische Winkel der Lamellen in Grad, wenn der Aktor den Stellwert 0% empfängt. Dieser Kalibrierungswert wird benötigt, damit die berechnete optimale Lamellenstellung korrekt auf den vom Aktor erwarteten Prozentwert abgebildet wird. Verschiedene Aktoren und Montagevarianten können unterschiedliche Konventionen verwenden. - -#### Lamellenwinkel bei Fahrbefehl 100% + +#### Lamellenwinkel vollständig geschlossen (100%) Der physische Winkel der Lamellen in Grad, wenn der Aktor den Stellwert 100% empfängt. -**Hinweis:** Ein Wert kleiner als "Lamellenwinkel bei Fahrbefehl 0%" ist ausdrücklich erlaubt und notwendig für Lamellen, die umgekehrt montiert sind. Die Nachführungsformel invertiert das Mapping automatisch. +**Hinweis:** Ein Wert kleiner als "Lamellenwinkel vollständig geöffnet (0%)" ist ausdrücklich erlaubt und notwendig für Lamellen, die umgekehrt montiert sind. Die Nachführungsformel invertiert das Mapping automatisch. #### Fensterhöhe Die lichte Höhe der Fensterscheibe in Zentimetern (Innenkante Blendrahmen oben bis Innenkante Blendrahmen unten bzw. Fensterbank). -Dieser Wert wird für die Berechnung der optimalen Jalousie- oder Rolladenposition bei der **Schattenkantennachführung** benötigt. Zusammen mit der aktuellen Sonnenposition wird berechnet, wie weit der Behang abgesenkt sein muss, damit die Schattenkante die konfigurierte Eindringtiefe nicht überschreitet. +Dieser Wert wird für die Berechnung der optimalen Jalousie- oder Rolladenposition bei der **Geometrischen Positionsnachführung** benötigt. Zusammen mit der aktuellen Sonnenposition wird berechnet, wie weit der Behang abgesenkt sein muss, damit die Schattenkante die konfigurierte Eindringtiefe nicht überschreitet. #### Max. Eindringtiefe @@ -935,7 +938,49 @@ Dieser Wert wird zusammen mit der Fensterhöhe verwendet, um die absolute Positi Aktiviert die dynamische Positionsnachführung für Rollläden basierend auf der aktuellen Sonnenposition. - **Nein**: Keine Positionsnachführung. Der Rollladen fährt auf die konfigurierte Beschattungsposition und bleibt dort. -- **Schattenkantennachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. +- **Geometrische Positionsnachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. + + +#### Min. Position + + +Diese Einstellung ist nur vorhanden, wenn unter "Lamellenstellenung an Sonnenstand anpassen" "Geo. Positions- und Lamellennachführung (Min/Max)" eingestellt wurde. + +Minimale Jalousieposition in Prozent. Die dynamisch berechnete Position wird niemals unter diesen Wert abgesenkt. + +Typischer Anwendungsfall: Min. Position = 20% → Die Jalousie ist immer mindestens zu 20% geschlossen, unabhängig vom Sonnenstand. So bleibt ein minimaler Sichtschutz immer erhalten. + +Wenn Min. Position größer als Max. Position konfiguriert ist, wird die Begrenzung ignoriert und das Berechnungsergebnis unverändert ausgegeben. + + +#### Max. Position + + +Diese Einstellung ist nur vorhanden, wenn unter "Lamellenstellenung an Sonnenstand anpassen" "Geo. Positions- und Lamellennachführung (Min/Max)" eingestellt wurde. + +Maximale Jalousieposition in Prozent. Die dynamisch berechnete Position wird niemals über diesen Wert erhöht. + +Typischer Anwendungsfall: Max. Position = 80% → Die Jalousie schließt nie vollständig zu. Immer etwas Tageslicht bleibt erhalten, und die Lamellenstellung übernimmt den Blend- und Wärmeschutz. + +Wenn Min. Position größer als Max. Position konfiguriert ist, wird die Begrenzung ignoriert und das Berechnungsergebnis unverändert ausgegeben. + + +#### Lamellenstellung bei min. Höhenwinkel (Begrenzung) + + +In diesem Modus dient dieser Parameter als **untere Begrenzung** der Lamellenstellung. Die dynamisch berechnete Lamellenstellung wird niemals unter den kleineren der beiden Werte (min./max. Höhenwinkel) abgesenkt. + +Wenn beide Werte gleich sind, wird die Lamellenstellung auf diesen festen Wert fixiert. + + +#### Lamellenstellung bei max. Höhenwinkel (Begrenzung) + + +In diesem Modus dient dieser Parameter als **obere Begrenzung** der Lamellenstellung. Die dynamisch berechnete Lamellenstellung wird niemals über den größeren der beiden Werte (min./max. Höhenwinkel) erhöht. + +Die Reihenfolge der beiden Werte (welcher ist kleiner, welcher größer) ist unerheblich – das Clipping verwendet automatisch min(Wert1, Wert2) als untere und max(Wert1, Wert2) als obere Grenze. + +Standardwerte sind 80%/50% (sinnvoll für Modus "Benutzerdefiniert"). Für kein Lamellen-Clipping: 0% und 100% konfigurieren. ### Temperaturgrenzen @@ -1143,6 +1188,8 @@ Bit 19: Temperatur zu niedrig Bit 20: Vorhergesagte Temperatur zu niedrig Bit 21: Bewölkungsgrad zu hoch Bit 22: UV-Index zu niedrig +Bit 23: Profilwinkel nicht berechenbar (Sonne trifft Fassade nicht) +Bit 24: Flachdach-Schutz aktiv (Fassadenneigung zu gering) ### 'Nicht erlaubt' Grund @@ -1174,6 +1221,8 @@ Gibt es mehr als einen Grund, wird der erste dieser Liste angezeigt. 21: Vorhergesagte Temperatur zu niedrig 22: Bewölkungsgrad zu hoch 23: UV-Index zu niedrig +24: Profilwinkel nicht berechenbar (Sonne trifft Fassade nicht) +25: Flachdach-Schutz aktiv (Fassadenneigung zu gering) diff --git a/src/Baggages/Help_de/SHC-Beschattungsposition.md b/src/Baggages/Help_de/SHC-Beschattungsposition.md index 1f406e5..b4a22eb 100644 --- a/src/Baggages/Help_de/SHC-Beschattungsposition.md +++ b/src/Baggages/Help_de/SHC-Beschattungsposition.md @@ -2,3 +2,5 @@ Position die bei Beschattungsstart angefahren wird. +Bei den Modi **Geometrische Positionsnachführung**, **Geometrische Positions- und Lamellennachführung** und **Geo. Positions- und Lamellennachführung (Min/Max)** wird die Position normalerweise dynamisch anhand des Sonnenstands berechnet. Die Beschattungsposition wirkt in diesen Modi nur als **Fallback**: Wenn die Sonne so flach steht, dass die berechnete Schattenkante oberhalb der Fensterhöhe liegt (d.h. der Behang muss vollständig abgesenkt sein), wird die hier konfigurierte Position als Zielwert verwendet. + diff --git a/src/Baggages/Help_de/SHC-Fassadenneigung.md b/src/Baggages/Help_de/SHC-Fassadenneigung.md deleted file mode 100644 index 5b572f8..0000000 --- a/src/Baggages/Help_de/SHC-Fassadenneigung.md +++ /dev/null @@ -1,10 +0,0 @@ -### Fassadenneigung - -Die Neigung der Fassade in Grad, gemessen gegenüber der Senkrechten (0° = senkrechte Wand). - -- **0°**: Senkrechte Fassade (Standardfall) -- **Positiver Wert**: Fassade neigt sich nach außen (z.B. überhängende Dachkante) -- **Negativer Wert**: Fassade neigt sich nach innen (z.B. nach innen geneigte Wand) - -Dieser Wert wird für die Berechnung des Profilwinkels bei der **Lamellennachführung (Experte)** und der **Schattenkantennachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. - diff --git a/src/Baggages/Help_de/SHC-FassadenneigungFensterneigung.md b/src/Baggages/Help_de/SHC-FassadenneigungFensterneigung.md new file mode 100644 index 0000000..1ffd74a --- /dev/null +++ b/src/Baggages/Help_de/SHC-FassadenneigungFensterneigung.md @@ -0,0 +1,10 @@ +### Fassadenneigung/Fensterneigung + +Die Neigung der Fassade oder eines Dachfenster in Grad, gemessen gegenüber der Senkrechten (0° = senkrechte Wand). + +- **0°**: Senkrechte Fassade/Senkrechtes Fenster (Standardfall) +- **Positiver Wert**: Fassade/Fenster neigt sich nach außen (z.B. überhängende Dachkante) +- **Negativer Wert**: Fassade/Fenster neigt sich nach innen (z.B. nach innen geneigte Wand) + +Dieser Wert wird für die Berechnung des Profilwinkels bei der **Lamellennachführung (Experte)** und der **Geometrischen Positionsnachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. + diff --git a/src/Baggages/Help_de/SHC-Fenster-Behangausrichtung-und-Azimut-Auswertung.md b/src/Baggages/Help_de/SHC-Fenster-Behangausrichtung-und-Azimut-Auswertung.md index b235bdd..71ac5d1 100644 --- a/src/Baggages/Help_de/SHC-Fenster-Behangausrichtung-und-Azimut-Auswertung.md +++ b/src/Baggages/Help_de/SHC-Fenster-Behangausrichtung-und-Azimut-Auswertung.md @@ -15,6 +15,6 @@ Beispiele (vereinfachte Sicht): | 3x Sensor mit Azimut (O/S/W) | Dachflaeche | Max(alle 3 Sensoren) — Fallback, da kein unzugeordneter Sensor | | 4x Sensor mit Azimut + 1x Sensor ohne Azimut | Dachflaeche | Max(nur der Sensor ohne Azimut) | | 4x Sensor mit Azimut + 1x Sensor ohne Azimut | Sued | Azimut-Interpolation nur mit den 4 Azimut-Sensoren | -| 2x Sensor ohne Azimut | Keine Himmelsrichtungsauswertung | Aggregation ueber alle Sensoren ohne Azimut | -| 1x Sensor mit Azimut | Keine Himmelsrichtungsauswertung | Aggregation ueber alle gueltigen Sensoren | +| 2x Sensor ohne Azimut | Keine Himmelsrichtungsauswertung | Aggregation über alle Sensoren ohne Azimut | +| 1x Sensor mit Azimut | Keine Himmelsrichtungsauswertung | Aggregation über alle gueltigen Sensoren | diff --git a/src/Baggages/Help_de/SHC-Fensterhoehe.md b/src/Baggages/Help_de/SHC-Fensterhoehe.md index 1769db1..ada9486 100644 --- a/src/Baggages/Help_de/SHC-Fensterhoehe.md +++ b/src/Baggages/Help_de/SHC-Fensterhoehe.md @@ -2,5 +2,5 @@ Die lichte Höhe der Fensterscheibe in Zentimetern (Innenkante Blendrahmen oben bis Innenkante Blendrahmen unten bzw. Fensterbank). -Dieser Wert wird für die Berechnung der optimalen Jalousie- oder Rolladenposition bei der **Schattenkantennachführung** benötigt. Zusammen mit der aktuellen Sonnenposition wird berechnet, wie weit der Behang abgesenkt sein muss, damit die Schattenkante die konfigurierte Eindringtiefe nicht überschreitet. +Dieser Wert wird für die Berechnung der optimalen Jalousie- oder Rolladenposition bei der **Geometrischen Positionsnachführung** benötigt. Zusammen mit der aktuellen Sonnenposition wird berechnet, wie weit der Behang abgesenkt sein muss, damit die Schattenkante die konfigurierte Eindringtiefe nicht überschreitet. diff --git a/src/Baggages/Help_de/SHC-Lamellenstellenung-an-Sonnenstand-anpassen.md b/src/Baggages/Help_de/SHC-Lamellenstellenung-an-Sonnenstand-anpassen.md index dec8b7f..da34f94 100644 --- a/src/Baggages/Help_de/SHC-Lamellenstellenung-an-Sonnenstand-anpassen.md +++ b/src/Baggages/Help_de/SHC-Lamellenstellenung-an-Sonnenstand-anpassen.md @@ -5,4 +5,5 @@ Diese Einstellung ist nur für den Gerätetype "Jalousie" vorhanden. - **Nein**: Die Lamellenstellung wird nicht an den Sonnenstand angepasst. Stattdessen wird die unter "Lamellenstellung" konfigurierte feste Position verwendet. - **Standard**: Die Lamellenstellung wird anhand des Höhenwinkels der Sonne automatisch berechnet (bewährte Formel). - **Benutzerdefiniert**: Die Lamellenstellung wird linear zwischen dem Wert bei tiefem Sonnenstand und dem Wert bei hohem Sonnenstand interpoliert. +- **Geo. Positions- und Lamellennachführung (Min/Max)**: Wie "Geometrische Positions- und Lamellennachführung", jedoch werden Position und Lamellenstellung auf konfigurierbare Min./Max.-Grenzen begrenzt. Die berechneten Werte können den konfigurierten Bereich nicht über- oder unterschreiten. diff --git a/src/Baggages/Help_de/SHC-Lamellenstellung-bei-max-Hoehenwinkel-Begrenzung.md b/src/Baggages/Help_de/SHC-Lamellenstellung-bei-max-Hoehenwinkel-Begrenzung.md new file mode 100644 index 0000000..5bdfa6b --- /dev/null +++ b/src/Baggages/Help_de/SHC-Lamellenstellung-bei-max-Hoehenwinkel-Begrenzung.md @@ -0,0 +1,6 @@ +### Lamellenstellung bei max. Höhenwinkel (Begrenzung) + +Die Reihenfolge der beiden Werte (welcher ist kleiner, welcher größer) ist unerheblich – das Clipping verwendet automatisch min(Wert1, Wert2) als untere und max(Wert1, Wert2) als obere Grenze. + +Standardwerte sind 80%/50% (sinnvoll für Modus "Benutzerdefiniert"). Für kein Lamellen-Clipping: 0% und 100% konfigurieren. + diff --git a/src/Baggages/Help_de/SHC-Lamellenstellung-bei-min-Hoehenwinkel-Begrenzung.md b/src/Baggages/Help_de/SHC-Lamellenstellung-bei-min-Hoehenwinkel-Begrenzung.md new file mode 100644 index 0000000..582fe00 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Lamellenstellung-bei-min-Hoehenwinkel-Begrenzung.md @@ -0,0 +1,4 @@ +### Lamellenstellung bei min. Höhenwinkel (Begrenzung) + +Wenn beide Werte gleich sind, wird die Lamellenstellung auf diesen festen Wert fixiert. + diff --git a/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-100.md b/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-100.md deleted file mode 100644 index 948bd7c..0000000 --- a/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-100.md +++ /dev/null @@ -1,6 +0,0 @@ -### Lamellenwinkel bei Fahrbefehl 100% - -Der physische Winkel der Lamellen in Grad, wenn der Aktor den Stellwert 100% empfängt. - -**Hinweis:** Ein Wert kleiner als "Lamellenwinkel bei Fahrbefehl 0%" ist ausdrücklich erlaubt und notwendig für Lamellen, die umgekehrt montiert sind. Die Nachführungsformel invertiert das Mapping automatisch. - diff --git a/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-0.md b/src/Baggages/Help_de/SHC-Lamellenwinkel-vollstaendig-geoeffnet-0.md similarity index 86% rename from src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-0.md rename to src/Baggages/Help_de/SHC-Lamellenwinkel-vollstaendig-geoeffnet-0.md index e4dc0d3..ab1b972 100644 --- a/src/Baggages/Help_de/SHC-Lamellenwinkel-bei-Fahrbefehl-0.md +++ b/src/Baggages/Help_de/SHC-Lamellenwinkel-vollstaendig-geoeffnet-0.md @@ -1,4 +1,4 @@ -### Lamellenwinkel bei Fahrbefehl 0% +### Lamellenwinkel vollständig geöffnet (0%) Der physische Winkel der Lamellen in Grad, wenn der Aktor den Stellwert 0% empfängt. diff --git a/src/Baggages/Help_de/SHC-Lamellenwinkel-vollstaendig-geschlossen-100.md b/src/Baggages/Help_de/SHC-Lamellenwinkel-vollstaendig-geschlossen-100.md new file mode 100644 index 0000000..9aafd27 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Lamellenwinkel-vollstaendig-geschlossen-100.md @@ -0,0 +1,6 @@ +### Lamellenwinkel vollständig geschlossen (100%) + +Der physische Winkel der Lamellen in Grad, wenn der Aktor den Stellwert 100% empfängt. + +**Hinweis:** Ein Wert kleiner als "Lamellenwinkel vollständig geöffnet (0%)" ist ausdrücklich erlaubt und notwendig für Lamellen, die umgekehrt montiert sind. Die Nachführungsformel invertiert das Mapping automatisch. + diff --git a/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md b/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md new file mode 100644 index 0000000..4c7cb07 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md @@ -0,0 +1,8 @@ +### Max. Position + +Maximale Jalousieposition in Prozent. Die dynamisch berechnete Position wird niemals über diesen Wert erhöht. + +Typischer Anwendungsfall: Max. Position = 80% → Die Jalousie schließt nie vollständig zu. Immer etwas Tageslicht bleibt erhalten, und die Lamellenstellung übernimmt den Blend- und Wärmeschutz. + +Wenn Min. Position größer als Max. Position konfiguriert ist, wird die Begrenzung ignoriert und das Berechnungsergebnis unverändert ausgegeben. + diff --git a/src/Baggages/Help_de/SHC-Min-Position-Begrenzung.md b/src/Baggages/Help_de/SHC-Min-Position-Begrenzung.md new file mode 100644 index 0000000..c877594 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Min-Position-Begrenzung.md @@ -0,0 +1,8 @@ +### Min. Position + +Minimale Jalousieposition in Prozent. Die dynamisch berechnete Position wird niemals unter diesen Wert abgesenkt. + +Typischer Anwendungsfall: Min. Position = 20% → Die Jalousie ist immer mindestens zu 20% geschlossen, unabhängig vom Sonnenstand. So bleibt ein minimaler Sichtschutz immer erhalten. + +Wenn Min. Position größer als Max. Position konfiguriert ist, wird die Begrenzung ignoriert und das Berechnungsergebnis unverändert ausgegeben. + diff --git a/src/Baggages/Help_de/SHC-Nicht-erlaubt-Bits-Nur-fuer-Experten.md b/src/Baggages/Help_de/SHC-Nicht-erlaubt-Bits-Nur-fuer-Experten.md index e36ea37..799fe55 100644 --- a/src/Baggages/Help_de/SHC-Nicht-erlaubt-Bits-Nur-fuer-Experten.md +++ b/src/Baggages/Help_de/SHC-Nicht-erlaubt-Bits-Nur-fuer-Experten.md @@ -26,4 +26,6 @@ Bit 19: Temperatur zu niedrig Bit 20: Vorhergesagte Temperatur zu niedrig Bit 21: Bewölkungsgrad zu hoch Bit 22: UV-Index zu niedrig +Bit 23: Profilwinkel nicht berechenbar (Sonne trifft Fassade nicht) +Bit 24: Flachdach-Schutz aktiv (Fassadenneigung zu gering) diff --git a/src/Baggages/Help_de/SHC-Nicht-erlaubt-Grund.md b/src/Baggages/Help_de/SHC-Nicht-erlaubt-Grund.md index 557b83e..f3a8131 100644 --- a/src/Baggages/Help_de/SHC-Nicht-erlaubt-Grund.md +++ b/src/Baggages/Help_de/SHC-Nicht-erlaubt-Grund.md @@ -27,5 +27,7 @@ Gibt es mehr als einen Grund, wird der erste dieser Liste angezeigt. 21: Vorhergesagte Temperatur zu niedrig 22: Bewölkungsgrad zu hoch 23: UV-Index zu niedrig +24: Profilwinkel nicht berechenbar (Sonne trifft Fassade nicht) +25: Flachdach-Schutz aktiv (Fassadenneigung zu gering) diff --git a/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md b/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md index 81262a0..98b7e4c 100644 --- a/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md +++ b/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md @@ -3,5 +3,5 @@ Aktiviert die dynamische Positionsnachführung für Rollläden basierend auf der aktuellen Sonnenposition. - **Nein**: Keine Positionsnachführung. Der Rollladen fährt auf die konfigurierte Beschattungsposition und bleibt dort. -- **Schattenkantennachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. +- **Geometrische Positionsnachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. From 0cb7228f43a45f4411a7debb944bc3ee150cf6b3 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:57:52 +0200 Subject: [PATCH 19/34] feat(shading): restructure jalousie and rollo shading modes - Jalousie: 7 modes (None, Standard, Slat tracking min/max, Geo position, Geo position min/max, Geo position+slat, Geo position+slat min/max) - Mode 3 replaced: 'Slat tracking expert' -> 'Geo position tracking' (penetration depth + fixed slat position); breaking change for existing ETS projects using mode 3 - Mode 4: new 'Geo position tracking (min/max)' with min/max clip via PPP+65/66; _lastSentShadowPos stored unclipped - Modes 5 and 6: unchanged logic, only renamed in enums - Rollo: 3 modes (None, Geo position, Geo position min/max); mode condition changed from == 1 to >= 1; mode 2 adds min/max clip - Fix typo in parameter text: 'Lamellenstellenung' -> 'Positions- und Lamellennachfuehrung' - Fix: critical tilt angle (theta_krit) was computed as 90-atan2(..), corrected to atan2(..) directly - Fix: sin_alpha computed without fabsf for pitched surfaces, now fixed - Fix: OffsetSlatPosition not applied in mode 5, now applied - Fix: hysteresis (MinChangeForSlatAdaption) missing in mode 2, now added - Change: max penetration depth range extended from 1-200 to 10-255 cm --- src/ModeShading.cpp | 144 +++++++++++++------- src/ShutterControllerModule.ModeShading.xml | 42 +++--- src/ShutterControllerModule.share.xml | 11 +- 3 files changed, 127 insertions(+), 70 deletions(-) diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index dca27b4..f37f5f9 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -706,8 +706,8 @@ void ModeShading::control(const CallContext &callContext, PositionController &po // // - // ── Rollo-Pfad: Schattenkantennachführung für Rollo (kein hasSlat!) ── - if (ParamSHC_CType == 2 && ParamSHC_CShading1RolloPositionMode == 1) + // ── Rollo-Pfad: Geo. Positionsnachführung für Rollo (kein hasSlat!) ── + if (ParamSHC_CType == 2 && ParamSHC_CShading1RolloPositionMode >= 1) { static const float DEG2RAD = (float)M_PI / 180.0f; _notAllowedReason &= ~(ModeShadingNotAllowedReasonProfileAngleSentinel | ModeShadingNotAllowedReasonFlatRoofGuard); @@ -774,10 +774,23 @@ void ModeShading::control(const CallContext &callContext, PositionController &po } if (callContext.diagnosticLog) - logInfoP("Rollo shadow tracking: gamma=%.1f° s_crit=%.1f targetPos=%.1f", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos); + logInfoP("Rollo geo position: gamma=%.1f° s_crit=%.1f targetPos=%.1f", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos); - _lastSentShadowPos = targetPos; - positionController.setAutomaticPosition((uint8_t)targetPos); + _lastSentShadowPos = targetPos; // ungeclippt für korrekte Hysterese + + float sendPos = targetPos; + if (ParamSHC_CShading1RolloPositionMode == 2) + { + uint8_t minClipPos = ParamSHC_CShading1MinClipPosition; + uint8_t maxClipPos = ParamSHC_CShading1MaxClipPosition; + if (minClipPos <= maxClipPos) + { + if (sendPos < (float)minClipPos) sendPos = (float)minClipPos; + if (sendPos > (float)maxClipPos) sendPos = (float)maxClipPos; + } + } + + positionController.setAutomaticPosition((uint8_t)sendPos); return; } @@ -842,58 +855,85 @@ void ModeShading::control(const CallContext &callContext, PositionController &po } case 3: { - // Lamellennachführung Experte: geometrisch berechnet - if (ParamSHC_CType == 2) break; // Schutzguard: Rollo hat kein Slat + // Geo. Positionsnachführung (Jalousie): Position + feste Lamellenstellung + if (ParamSHC_CType == 2) break; // Schutzguard static const float DEG2RAD = (float)M_PI / 180.0f; - _notAllowedReason &= ~ModeShadingNotAllowedReasonProfileAngleSentinel; - float beta_deg = calculateProfileAngle( - (float)callContext.elevation, - (float)callContext.azimuth, - ParamSHC_CWindowOrientation, - (float)(int8_t)ParamSHC_CFacadeInclination); - - if (beta_deg <= 0.0f) + _notAllowedReason &= ~(ModeShadingNotAllowedReasonProfileAngleSentinel | ModeShadingNotAllowedReasonFlatRoofGuard); + + float gamma_rad; + float sin_alpha; + const uint8_t orientation = ParamSHC_CWindowOrientation; + const float facadeInclination = (float)(int8_t)ParamSHC_CFacadeInclination; + + if (orientation == 5) { - _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; - return; // Sonne trifft Fassade nicht + // Dachfläche: gamma = Höhenwinkel, sin_alpha = |sin(Dachneigung)| + float elev = (float)callContext.elevation; + if (elev <= 0.0f) + return; + sin_alpha = fabsf(sinf(facadeInclination * DEG2RAD)); + if (sin_alpha < 0.05f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonFlatRoofGuard; + return; + } + gamma_rad = elev * DEG2RAD; + } + else + { + float beta_deg = calculateProfileAngle( + (float)callContext.elevation, + (float)callContext.azimuth, + orientation, + facadeInclination); + if (beta_deg <= 0.0f) + { + _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; + return; + } + gamma_rad = beta_deg * DEG2RAD; + sin_alpha = 1.0f; } - float beta_rad = beta_deg * DEG2RAD; - float a = (float)ParamSHC_CShading1SlatSpacing; // Lamellenabstand [mm] - float b = (float)ParamSHC_CShading1SlatWidth; // Lamellenbreite [mm] + float windowHeight = (float)ParamSHC_CShading1WindowHeight; + float maxPenetration = (float)ParamSHC_CShading1MaxPenetrationDepth; + float windowSillHeight = (float)ParamSHC_CShading1WindowSillHeight; + float minShadowEdgeChange = (float)ParamSHC_CShading1MinShadowEdgeChange; - uint8_t angleAtMin = ParamSHC_CShading1SlatAngleAtMin; - uint8_t angleAtMax = ParamSHC_CShading1SlatAngleAtMax; - if (angleAtMin == angleAtMax) - break; // Guard: Division durch Null + float s_crit = (maxPenetration * tanf(gamma_rad) - windowSillHeight) / sin_alpha; + + float targetPos; + if (s_crit <= 0.0f) + targetPos = 100.0f; + else if (s_crit >= windowHeight) + targetPos = (float)ParamSHC_CShading1ShadingPosition; + else + targetPos = (1.0f - s_crit / windowHeight) * 100.0f; - // Kritischer Kippwinkel (in Nutzerkonvention: von der Senkrechten nach unten) - float theta_krit = atan2f(a * sinf(beta_rad), b - a * cosf(beta_rad)) * (180.0f / (float)M_PI); + if (targetPos < 0.0f) targetPos = 0.0f; + if (targetPos > 100.0f) targetPos = 100.0f; - // Guard: Lamelle kann den Strahl nicht sperren (z.B. SlatSpacing > SlatWidth) - if (theta_krit < 0.0f) + if (_lastSentShadowPos >= 0.0f && s_crit < windowHeight && s_crit > 0.0f) { - positionController.setAutomaticSlat(100); - return; + float delta_cm = fabsf(targetPos - _lastSentShadowPos) * windowHeight / 100.0f; + if (delta_cm < minShadowEdgeChange) + { + positionController.setAutomaticSlat(ParamSHC_CShading1SlatShadingPosition); + return; + } } - float slatPercent = ((float)theta_krit - (float)angleAtMin) / ((float)angleAtMax - (float)angleAtMin) * 100.0f; - if (slatPercent < 0.0f) slatPercent = 0.0f; - if (slatPercent > 100.0f) slatPercent = 100.0f; - auto slatPosition = (uint8_t)slatPercent; - if (callContext.diagnosticLog) - logInfoP("Case3 Expert slat: beta=%.1f° theta_krit=%.1f° → slat=%d%%", beta_deg, theta_krit, (int)slatPosition); - - if (!callContext.modeNewStarted && abs((uint8_t)KoSHC_CShutterSlatOutput.value(DPT_Scaling) - slatPosition) < ParamSHC_CShading1MinChangeForSlatAdaption) - return; + logInfoP("Case3 geo position: gamma=%.1f° s_crit=%.1f targetPos=%.1f", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos); - positionController.setAutomaticSlat(slatPosition); + _lastSentShadowPos = targetPos; + positionController.setAutomaticPosition((uint8_t)targetPos); + positionController.setAutomaticSlat(ParamSHC_CShading1SlatShadingPosition); break; } case 4: { - // Schattenkantennachführung (Jalousie): Position + feste Lamellenstellung + // Geo. Positionsnachführung (Min/Max) (Jalousie): wie case 3 + Position-Clipping if (ParamSHC_CType == 2) break; // Schutzguard static const float DEG2RAD = (float)M_PI / 180.0f; _notAllowedReason &= ~(ModeShadingNotAllowedReasonProfileAngleSentinel | ModeShadingNotAllowedReasonFlatRoofGuard); @@ -905,7 +945,6 @@ void ModeShading::control(const CallContext &callContext, PositionController &po if (orientation == 5) { - // Dachfläche: gamma = Höhenwinkel, sin_alpha = |sin(Dachneigung)| float elev = (float)callContext.elevation; if (elev <= 0.0f) return; @@ -913,7 +952,7 @@ void ModeShading::control(const CallContext &callContext, PositionController &po if (sin_alpha < 0.05f) { _notAllowedReason |= ModeShadingNotAllowedReasonFlatRoofGuard; - return; // Flachdach — kein sinnvolles Positionstracking + return; } gamma_rad = elev * DEG2RAD; } @@ -930,7 +969,7 @@ void ModeShading::control(const CallContext &callContext, PositionController &po return; } gamma_rad = beta_deg * DEG2RAD; - sin_alpha = 1.0f; // senkrechte Fassade: sin(90°) = 1 + sin_alpha = 1.0f; } float windowHeight = (float)ParamSHC_CShading1WindowHeight; @@ -951,7 +990,6 @@ void ModeShading::control(const CallContext &callContext, PositionController &po if (targetPos < 0.0f) targetPos = 0.0f; if (targetPos > 100.0f) targetPos = 100.0f; - // Hysterese in cm if (_lastSentShadowPos >= 0.0f && s_crit < windowHeight && s_crit > 0.0f) { float delta_cm = fabsf(targetPos - _lastSentShadowPos) * windowHeight / 100.0f; @@ -962,11 +1000,21 @@ void ModeShading::control(const CallContext &callContext, PositionController &po } } + _lastSentShadowPos = targetPos; // ungeclippt für korrekte Hysterese + + float sendPos = targetPos; + uint8_t minClipPos = ParamSHC_CShading1MinClipPosition; + uint8_t maxClipPos = ParamSHC_CShading1MaxClipPosition; + if (minClipPos <= maxClipPos) + { + if (sendPos < (float)minClipPos) sendPos = (float)minClipPos; + if (sendPos > (float)maxClipPos) sendPos = (float)maxClipPos; + } + if (callContext.diagnosticLog) - logInfoP("Case4 shadow tracking: gamma=%.1f° s_crit=%.1f targetPos=%.1f", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos); + logInfoP("Case4 geo pos min/max: gamma=%.1f° s_crit=%.1f targetPos=%.1f sendPos=%.1f", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos, sendPos); - _lastSentShadowPos = targetPos; - positionController.setAutomaticPosition((uint8_t)targetPos); + positionController.setAutomaticPosition((uint8_t)sendPos); positionController.setAutomaticSlat(ParamSHC_CShading1SlatShadingPosition); break; } diff --git a/src/ShutterControllerModule.ModeShading.xml b/src/ShutterControllerModule.ModeShading.xml index b5bc847..51b51a2 100755 --- a/src/ShutterControllerModule.ModeShading.xml +++ b/src/ShutterControllerModule.ModeShading.xml @@ -52,8 +52,7 @@ - - + @@ -566,20 +565,17 @@ - - - - - - - - - - - + + + + + + + + - + @@ -587,6 +583,9 @@ + + + @@ -608,7 +607,6 @@ - @@ -624,7 +622,6 @@ - @@ -640,13 +637,24 @@ - + + + + + + + + + + + + diff --git a/src/ShutterControllerModule.share.xml b/src/ShutterControllerModule.share.xml index 7015dfa..1990664 100755 --- a/src/ShutterControllerModule.share.xml +++ b/src/ShutterControllerModule.share.xml @@ -197,17 +197,18 @@ - - - - + + + + - + + From d4a0d52279a2774a11036b229044dc4dda39ecec Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:58:18 +0200 Subject: [PATCH 20/34] docs: update application description and context help for new shading modes - Update mode list for jalousie (7 options) and rollo (3 options) - Fix section heading typo: 'Lamellenstellenung' -> 'Positions- und Lamellennachfuehrung' - Remove reference to 'Lamellennachfuehrung (Experte)' from facade tilt description - Update skip-text references to match new parameter names - Min/Max position skip-texts now also reference rollo position mode - Add new context help file SHC-Positions-und-Lamellennachfuehrung.md - Remove obsolete context help file with typo in filename (SHC-Lamellenstellenung-an-Sonnenstand-anpassen.md) - Update SHC-Positionsnachfuehrung.md, SHC-Min/Max-Position-Begrenzung.md, SHC-FassadenneigungFensterneigung.md --- ...likationsbeschreibung-ShutterController.md | 30 +++++++++++-------- .../SHC-FassadenneigungFensterneigung.md | 2 +- ...ellenstellenung-an-Sonnenstand-anpassen.md | 9 ------ .../Help_de/SHC-Max-Position-Begrenzung.md | 2 +- .../Help_de/SHC-Min-Position-Begrenzung.md | 2 +- .../SHC-Positions-und-Lamellennachfuehrung.md | 12 ++++++++ .../Help_de/SHC-Positionsnachfuehrung.md | 3 +- 7 files changed, 34 insertions(+), 26 deletions(-) delete mode 100644 src/Baggages/Help_de/SHC-Lamellenstellenung-an-Sonnenstand-anpassen.md create mode 100644 src/Baggages/Help_de/SHC-Positions-und-Lamellennachfuehrung.md diff --git a/doc/Applikationsbeschreibung-ShutterController.md b/doc/Applikationsbeschreibung-ShutterController.md index 3151863..3144b9a 100644 --- a/doc/Applikationsbeschreibung-ShutterController.md +++ b/doc/Applikationsbeschreibung-ShutterController.md @@ -809,7 +809,7 @@ Die Neigung der Fassade oder eines Dachfenster in Grad, gemessen gegenüber der - **Positiver Wert**: Fassade/Fenster neigt sich nach außen (z.B. überhängende Dachkante) - **Negativer Wert**: Fassade/Fenster neigt sich nach innen (z.B. nach innen geneigte Wand) -Dieser Wert wird für die Berechnung des Profilwinkels bei der **Lamellennachführung (Experte)** und der **Geometrischen Positionsnachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. +Dieser Wert wird für die Berechnung des Profilwinkels bei der **Geometrischen Positionsnachführung** und der **Geo. Positions- und Lamellennachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. #### Nur starten wenn aktuelle Position kleiner gleich @@ -827,20 +827,23 @@ Position die bei Beschattungsstart angefahren wird. Bei den Modi **Geometrische Positionsnachführung**, **Geometrische Positions- und Lamellennachführung** und **Geo. Positions- und Lamellennachführung (Min/Max)** wird die Position normalerweise dynamisch anhand des Sonnenstands berechnet. Die Beschattungsposition wirkt in diesen Modi nur als **Fallback**: Wenn die Sonne so flach steht, dass die berechnete Schattenkante oberhalb der Fensterhöhe liegt (d.h. der Behang muss vollständig abgesenkt sein), wird die hier konfigurierte Position als Zielwert verwendet. -#### Lamellenstellenung an Sonnenstand anpassen +#### Positions- und Lamellennachführung Diese Einstellung ist nur für den Gerätetype "Jalousie" vorhanden. - **Nein**: Die Lamellenstellung wird nicht an den Sonnenstand angepasst. Stattdessen wird die unter "Lamellenstellung" konfigurierte feste Position verwendet. - **Standard**: Die Lamellenstellung wird anhand des Höhenwinkels der Sonne automatisch berechnet (bewährte Formel). -- **Benutzerdefiniert**: Die Lamellenstellung wird linear zwischen dem Wert bei tiefem Sonnenstand und dem Wert bei hohem Sonnenstand interpoliert. -- **Geo. Positions- und Lamellennachführung (Min/Max)**: Wie "Geometrische Positions- und Lamellennachführung", jedoch werden Position und Lamellenstellung auf konfigurierbare Min./Max.-Grenzen begrenzt. Die berechneten Werte können den konfigurierten Bereich nicht über- oder unterschreiten. +- **Lamellenführung (Min/Max)**: Wie "Standard", jedoch wird die berechnete Lamellenstellung auf konfigurierbare Min./Max.-Grenzen begrenzt. +- **Geo. Positionsnachführung**: Berechnet die Jalousieposition geometrisch anhand der Sonneneindringtiefe. Die Lamelle wird auf eine feste konfigurierte Stellung gesetzt. +- **Geo. Positionsnachführung (Min/Max)**: Wie "Geo. Positionsnachführung", jedoch wird die berechnete Position auf konfigurierbare Min./Max.-Grenzen begrenzt. +- **Geo. Positions- und Lamellennachführung**: Berechnet sowohl Position als auch Lamellenstellung geometrisch anhand der Sonnengeometrie. +- **Geo. Positions- und Lamellennachführung (Min/Max)**: Wie "Geo. Positions- und Lamellennachführung", jedoch werden Position und Lamellenstellung auf konfigurierbare Min./Max.-Grenzen begrenzt. #### Lamellenstellung -Diese Einstellung ist nur vorhanden, wenn unter "Lamellenstellenung an Sonnenstand anpassen" "Nein" eingestellt wurde und der Gerätetype "Jalousie" verwendet wird. +Diese Einstellung ist nur vorhanden, wenn unter "Positions- und Lamellennachführung" "Nein" eingestellt wurde und der Gerätetype "Jalousie" verwendet wird. Der Wert gibt die Kippstellung der Lamelle in Prozent an. 50% entsprechen der waagrechten Stellung. @@ -848,7 +851,7 @@ Der Wert gibt die Kippstellung der Lamelle in Prozent an. 50% entsprechen der wa #### Mindestaenderung Lamellennachfuehrung -Diese Einstellung ist nur vorhanden, wenn unter "Lamellenstellenung an Sonnenstand anpassen" "Standard" eingestellt wurde und der Gerätetype "Jalousie" verwendet wird. +Diese Einstellung ist nur vorhanden, wenn unter "Positions- und Lamellennachführung" "Standard" eingestellt wurde und der Gerätetype "Jalousie" verwendet wird. Der Wert gibt an, wie oft die Lamellenstellung während des Sonnenverlaufs angepasst wird. @@ -856,7 +859,7 @@ Der Wert gibt an, wie oft die Lamellenstellung während des Sonnenverlaufs angep #### Lamellenstellung bei min. Höhenwinkel -Diese Einstellung ist nur vorhanden, wenn unter "Lamellenstellenung an Sonnenstand anpassen" "Benutzerdefiniert" eingestellt wurde und der Gerätetype "Jalousie" verwendet wird. +Diese Einstellung ist nur vorhanden, wenn unter "Positions- und Lamellennachführung" "Lamellenführung (Min/Max)" eingestellt wurde und der Gerätetype "Jalousie" verwendet wird. Lamellenposition in Prozent beim minimalen Höhenwinkel der Sonne. Typischerweise nahezu geschlossen (z.B. 80%), da der flache Sonnenstand mehr Blendschutz erfordert. @@ -864,7 +867,7 @@ Lamellenposition in Prozent beim minimalen Höhenwinkel der Sonne. Typischerweis #### Lamellenstellung bei max. Höhenwinkel -Diese Einstellung ist nur vorhanden, wenn unter "Lamellenstellenung an Sonnenstand anpassen" "Benutzerdefiniert" eingestellt wurde und der Gerätetype "Jalousie" verwendet wird. +Diese Einstellung ist nur vorhanden, wenn unter "Positions- und Lamellennachführung" "Lamellenführung (Min/Max)" eingestellt wurde und der Gerätetype "Jalousie" verwendet wird. Lamellenposition in Prozent beim maximalen Höhenwinkel der Sonne. Typischerweise offener (z.B. 50% = waagrecht), da der steile Sonnenstand weniger Kippwinkel benötigt. @@ -938,15 +941,16 @@ Dieser Wert wird zusammen mit der Fensterhöhe verwendet, um die absolute Positi Aktiviert die dynamische Positionsnachführung für Rollläden basierend auf der aktuellen Sonnenposition. - **Nein**: Keine Positionsnachführung. Der Rollladen fährt auf die konfigurierte Beschattungsposition und bleibt dort. -- **Geometrische Positionsnachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. +- **Geo. Positionsnachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. +- **Geo. Positionsnachführung (Min/Max)**: Wie "Geo. Positionsnachführung", jedoch wird die berechnete Position auf konfigurierbare Min./Max.-Grenzen begrenzt. #### Min. Position -Diese Einstellung ist nur vorhanden, wenn unter "Lamellenstellenung an Sonnenstand anpassen" "Geo. Positions- und Lamellennachführung (Min/Max)" eingestellt wurde. +Diese Einstellung ist nur vorhanden, wenn unter "Positions- und Lamellennachführung" "Geo. Positionsnachführung (Min/Max)" oder "Geo. Positions- und Lamellennachführung (Min/Max)" eingestellt wurde, oder wenn unter "Positionsnachführung" "Geo. Positionsnachführung (Min/Max)" eingestellt wurde. -Minimale Jalousieposition in Prozent. Die dynamisch berechnete Position wird niemals unter diesen Wert abgesenkt. +Minimale Position in Prozent. Die dynamisch berechnete Position wird niemals unter diesen Wert abgesenkt. Typischer Anwendungsfall: Min. Position = 20% → Die Jalousie ist immer mindestens zu 20% geschlossen, unabhängig vom Sonnenstand. So bleibt ein minimaler Sichtschutz immer erhalten. @@ -956,9 +960,9 @@ Wenn Min. Position größer als Max. Position konfiguriert ist, wird die Begrenz #### Max. Position -Diese Einstellung ist nur vorhanden, wenn unter "Lamellenstellenung an Sonnenstand anpassen" "Geo. Positions- und Lamellennachführung (Min/Max)" eingestellt wurde. +Diese Einstellung ist nur vorhanden, wenn unter "Positions- und Lamellennachführung" "Geo. Positionsnachführung (Min/Max)" oder "Geo. Positions- und Lamellennachführung (Min/Max)" eingestellt wurde, oder wenn unter "Positionsnachführung" "Geo. Positionsnachführung (Min/Max)" eingestellt wurde. -Maximale Jalousieposition in Prozent. Die dynamisch berechnete Position wird niemals über diesen Wert erhöht. +Maximale Position in Prozent. Die dynamisch berechnete Position wird niemals über diesen Wert erhöht. Typischer Anwendungsfall: Max. Position = 80% → Die Jalousie schließt nie vollständig zu. Immer etwas Tageslicht bleibt erhalten, und die Lamellenstellung übernimmt den Blend- und Wärmeschutz. diff --git a/src/Baggages/Help_de/SHC-FassadenneigungFensterneigung.md b/src/Baggages/Help_de/SHC-FassadenneigungFensterneigung.md index 1ffd74a..a44a3ca 100644 --- a/src/Baggages/Help_de/SHC-FassadenneigungFensterneigung.md +++ b/src/Baggages/Help_de/SHC-FassadenneigungFensterneigung.md @@ -6,5 +6,5 @@ Die Neigung der Fassade oder eines Dachfenster in Grad, gemessen gegenüber der - **Positiver Wert**: Fassade/Fenster neigt sich nach außen (z.B. überhängende Dachkante) - **Negativer Wert**: Fassade/Fenster neigt sich nach innen (z.B. nach innen geneigte Wand) -Dieser Wert wird für die Berechnung des Profilwinkels bei der **Lamellennachführung (Experte)** und der **Geometrischen Positionsnachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. +Dieser Wert wird für die Berechnung des Profilwinkels bei der **Geometrischen Positionsnachführung** und der **Geo. Positions- und Lamellennachführung** verwendet. Bei senkrechten Fenstern kann der Standardwert 0° belassen werden. diff --git a/src/Baggages/Help_de/SHC-Lamellenstellenung-an-Sonnenstand-anpassen.md b/src/Baggages/Help_de/SHC-Lamellenstellenung-an-Sonnenstand-anpassen.md deleted file mode 100644 index da34f94..0000000 --- a/src/Baggages/Help_de/SHC-Lamellenstellenung-an-Sonnenstand-anpassen.md +++ /dev/null @@ -1,9 +0,0 @@ -### Lamellenstellenung an Sonnenstand anpassen - -Diese Einstellung ist nur für den Gerätetype "Jalousie" vorhanden. - -- **Nein**: Die Lamellenstellung wird nicht an den Sonnenstand angepasst. Stattdessen wird die unter "Lamellenstellung" konfigurierte feste Position verwendet. -- **Standard**: Die Lamellenstellung wird anhand des Höhenwinkels der Sonne automatisch berechnet (bewährte Formel). -- **Benutzerdefiniert**: Die Lamellenstellung wird linear zwischen dem Wert bei tiefem Sonnenstand und dem Wert bei hohem Sonnenstand interpoliert. -- **Geo. Positions- und Lamellennachführung (Min/Max)**: Wie "Geometrische Positions- und Lamellennachführung", jedoch werden Position und Lamellenstellung auf konfigurierbare Min./Max.-Grenzen begrenzt. Die berechneten Werte können den konfigurierten Bereich nicht über- oder unterschreiten. - diff --git a/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md b/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md index 4c7cb07..35c795b 100644 --- a/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md +++ b/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md @@ -1,6 +1,6 @@ ### Max. Position -Maximale Jalousieposition in Prozent. Die dynamisch berechnete Position wird niemals über diesen Wert erhöht. +Maximale Position in Prozent. Die dynamisch berechnete Position wird niemals über diesen Wert erhöht. Typischer Anwendungsfall: Max. Position = 80% → Die Jalousie schließt nie vollständig zu. Immer etwas Tageslicht bleibt erhalten, und die Lamellenstellung übernimmt den Blend- und Wärmeschutz. diff --git a/src/Baggages/Help_de/SHC-Min-Position-Begrenzung.md b/src/Baggages/Help_de/SHC-Min-Position-Begrenzung.md index c877594..d8785bc 100644 --- a/src/Baggages/Help_de/SHC-Min-Position-Begrenzung.md +++ b/src/Baggages/Help_de/SHC-Min-Position-Begrenzung.md @@ -1,6 +1,6 @@ ### Min. Position -Minimale Jalousieposition in Prozent. Die dynamisch berechnete Position wird niemals unter diesen Wert abgesenkt. +Minimale Position in Prozent. Die dynamisch berechnete Position wird niemals unter diesen Wert abgesenkt. Typischer Anwendungsfall: Min. Position = 20% → Die Jalousie ist immer mindestens zu 20% geschlossen, unabhängig vom Sonnenstand. So bleibt ein minimaler Sichtschutz immer erhalten. diff --git a/src/Baggages/Help_de/SHC-Positions-und-Lamellennachfuehrung.md b/src/Baggages/Help_de/SHC-Positions-und-Lamellennachfuehrung.md new file mode 100644 index 0000000..90e3512 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Positions-und-Lamellennachfuehrung.md @@ -0,0 +1,12 @@ +### Positions- und Lamellennachführung + +Diese Einstellung ist nur für den Gerätetype "Jalousie" vorhanden. + +- **Nein**: Die Lamellenstellung wird nicht an den Sonnenstand angepasst. Stattdessen wird die unter "Lamellenstellung" konfigurierte feste Position verwendet. +- **Standard**: Die Lamellenstellung wird anhand des Höhenwinkels der Sonne automatisch berechnet (bewährte Formel). +- **Lamellenführung (Min/Max)**: Wie "Standard", jedoch wird die berechnete Lamellenstellung auf konfigurierbare Min./Max.-Grenzen begrenzt. +- **Geo. Positionsnachführung**: Berechnet die Jalousieposition geometrisch anhand der Sonneneindringtiefe. Die Lamelle wird auf eine feste konfigurierte Stellung gesetzt. +- **Geo. Positionsnachführung (Min/Max)**: Wie "Geo. Positionsnachführung", jedoch wird die berechnete Position auf konfigurierbare Min./Max.-Grenzen begrenzt. +- **Geo. Positions- und Lamellennachführung**: Berechnet sowohl Position als auch Lamellenstellung geometrisch anhand der Sonnengeometrie. +- **Geo. Positions- und Lamellennachführung (Min/Max)**: Wie "Geo. Positions- und Lamellennachführung", jedoch werden Position und Lamellenstellung auf konfigurierbare Min./Max.-Grenzen begrenzt. + diff --git a/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md b/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md index 98b7e4c..82e952c 100644 --- a/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md +++ b/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md @@ -3,5 +3,6 @@ Aktiviert die dynamische Positionsnachführung für Rollläden basierend auf der aktuellen Sonnenposition. - **Nein**: Keine Positionsnachführung. Der Rollladen fährt auf die konfigurierte Beschattungsposition und bleibt dort. -- **Geometrische Positionsnachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. +- **Geo. Positionsnachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. +- **Geo. Positionsnachführung (Min/Max)**: Wie "Geo. Positionsnachführung", jedoch wird die berechnete Position auf konfigurierbare Min./Max.-Grenzen begrenzt. From 3159b28a49bcf7f24af0dfd9c24b86f04f24c701 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:58:23 +0200 Subject: [PATCH 21/34] chore: consolidate release notes into v0.6.3 --- ReleaseNotes.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 5ae9e57..7a8faa8 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,7 +1,12 @@ v 0.6.3 +- Redesign: Beschattungsmodi Jalousie komplett neu strukturiert – aufbauende Modi-Reihe: Nein → Standard → Lamellenführung (Min/Max) → Geo. Positionsnachführung → Geo. Positionsnachführung (Min/Max) → Geo. Positions- und Lamellennachführung → Geo. Positions- und Lamellennachführung (Min/Max). - Feature: Neuer Beschattungsmodus 6 „Geo. Positions- und Lamellennachführung (Min/Max)" – entspricht Modus 5 mit konfigurierbarer Min./Max.-Begrenzung für Position und Lamellenstellung. Wenn Min. Position > Max. Position konfiguriert ist, wird die Positionsbegrenzung ignoriert. Die Lamellenstellungsbegrenzung verwendet automatisch min/max der beiden Werte (Reihenfolge egal). +- Feature: Neuer Rollo-Modus „Geo. Positionsnachführung (Min/Max)" (Wert 2) – entspricht Modus 1 mit Min./Max.-Positionsbegrenzung. - Breaking: Parameterlayout des Beschattungsmodus-Blocks vergrößert (increment 50 → 52). Betrifft nur Kanäle mit zwei oder mehr konfigurierten Beschattungsmodi. Bestehende ETS-Projekte mit nur einem Beschattungsmodus pro Kanal sind nicht betroffen. -- Rename: Beschattungsmodus 4 umbenannt: „Schattenkantennachführung" → „Geometrische Positionsnachführung". Beschattungsmodus 5 umbenannt: „Schattenkanten- und Lamellenführung" → „Geometrische Positions- und Lamellennachführung". Die Parameterwerte (4, 5) sind unverändert. +- Breaking: Modus 3 (war „Lamellennachführung Experte") wird jetzt „Geo. Positionsnachführung" (Eindringtiefe + feste Lamellenstellung). Der alte Experte-Modus entfällt. Bestehende ETS-Projekte mit Modus 3 müssen neu konfiguriert werden. +- Rename: Beschattungsmodus 4 umbenannt: „Schattenkantennachführung" → „Geo. Positionsnachführung (Min/Max)". Beschattungsmodus 5 umbenannt: „Schattenkanten- und Lamellenführung" → „Geo. Positions- und Lamellennachführung". Die Parameterwerte (4, 5) sind unverändert. +- Rename: ETS-Parameter „Lamellenstellenung an Sonnenstand anpassen" (Tippfehler) → „Positions- und Lamellennachführung". +- Rename: Rollo-Parameter „Schattenkantennachführung" → „Geo. Positionsnachführung" (Wert 1 unverändert). - Fix: Kritischer Kippwinkel (theta_krit) wurde fälschlicherweise als 90°−atan2(...) berechnet; korrekt ist direkt atan2(...) (Winkelkonvention zur Senkrechten). - Fix: Bei Dachflächen-Orientierung wurde sin_alpha ohne Betrag berechnet, was bei negativen Neigungswinkeln zu falschen Positionen führte (jetzt fabsf). - Fix: OffsetSlatPosition wurde in Modus 5 nicht auf die berechnete Lamellenstellung angewendet. From 101cf8e5a77e9043e2ff9fe7ff0dd79d4a4dc28f Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 26 May 2026 22:39:10 +0200 Subject: [PATCH 22/34] Remove obsolete test files --- test/README.md | 19 ----- test/brightness_logic_test.py | 149 ---------------------------------- 2 files changed, 168 deletions(-) delete mode 100644 test/README.md delete mode 100644 test/brightness_logic_test.py diff --git a/test/README.md b/test/README.md deleted file mode 100644 index 00984c0..0000000 --- a/test/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Tests (OFM-ShutterControllerModule) - -## Python (Logik-Simulation) - -Dieser Test laeuft ohne Hardware und simuliert die Helligkeitslogik. - -```bash -python lib/OFM-ShutterControllerModule/test/brightness_logic_test.py -``` - -## C++ (PlatformIO Unity) - -Diese Tests werden ueber PlatformIO ausgefuehrt. - -```bash -pio test -e develop_RP2040 -f test_brightness_logic -``` - -Hinweis: Die Tests laufen im PlatformIO-Umfeld und nutzen Unity. diff --git a/test/brightness_logic_test.py b/test/brightness_logic_test.py deleted file mode 100644 index 1cb89aa..0000000 --- a/test/brightness_logic_test.py +++ /dev/null @@ -1,149 +0,0 @@ -import math -import unittest - - -def normalize_azimuth(azimuth): - while azimuth < 0.0: - azimuth += 360.0 - while azimuth >= 360.0: - azimuth -= 360.0 - return azimuth - - -def aggregate_states(sensors, only_unassigned): - values = [] - for sensor in sensors: - if not sensor["valid"]: - continue - if only_unassigned and sensor["has_azimuth"]: - continue - values.append(sensor["value"]) - if not values: - return {"count": 0, "mean": None, "max": None} - return { - "count": len(values), - "mean": sum(values) / float(len(values)), - "max": max(values), - } - - -def build_azimuth_state(sensors, sun_azimuth, sun_valid, aggregate_value): - values = [] - for sensor in sensors: - if not sensor["valid"]: - continue - if not sensor["has_azimuth"]: - continue - values.append((float(sensor["azimuth"]), float(sensor["value"]))) - - if not sun_valid: - values = [] - - if not values: - return aggregate_value - - values.sort(key=lambda x: x[0]) - merged = [] - for az, val in values: - if merged and abs(merged[-1][0] - az) < 0.001: - merged[-1] = (az, (merged[-1][1] + val) / 2.0) - else: - merged.append((az, val)) - - if len(merged) == 1: - return merged[0][1] - - target = normalize_azimuth(sun_azimuth) - result = merged[0][1] - for i in range(len(merged)): - a0, v0 = merged[i] - a1, v1 = merged[(i + 1) % len(merged)] - segment_start = a0 - segment_end = a1 - if i == len(merged) - 1: - segment_end += 360.0 - - target_wrapped = target - if target_wrapped < segment_start: - target_wrapped += 360.0 - - if segment_start <= target_wrapped <= segment_end: - ratio = (target_wrapped - segment_start) / (segment_end - segment_start) if segment_end > segment_start else 0.0 - result = v0 + (v1 - v0) * ratio - break - - return result - - -def evaluate_brightness(sensors, aggregation, use_azimuth, prefer_unassigned, force_max, sun_azimuth, sun_valid): - states_all = aggregate_states(sensors, False) - states_unassigned = aggregate_states(sensors, True) - use_unassigned = prefer_unassigned and states_unassigned["count"] > 0 - - mean_val = states_unassigned["mean"] if use_unassigned else states_all["mean"] - max_val = states_unassigned["max"] if use_unassigned else states_all["max"] - - aggregate_value = max_val if (force_max or aggregation == "max") else mean_val - - if aggregate_value is None: - aggregate_value = 0.0 - - if use_azimuth: - return build_azimuth_state(sensors, sun_azimuth, sun_valid, aggregate_value) - - return aggregate_value - - -class BrightnessLogicTests(unittest.TestCase): - def test_azimuth_interpolation(self): - sensors = [ - {"value": 100.0, "has_azimuth": True, "azimuth": 90, "valid": True}, - {"value": 200.0, "has_azimuth": True, "azimuth": 180, "valid": True}, - ] - value = evaluate_brightness( - sensors, - aggregation="mean", - use_azimuth=True, - prefer_unassigned=False, - force_max=False, - sun_azimuth=135.0, - sun_valid=True, - ) - self.assertTrue(math.isclose(value, 150.0, rel_tol=0.0, abs_tol=0.01)) - - def test_roof_prefers_unassigned(self): - sensors = [ - {"value": 100.0, "has_azimuth": True, "azimuth": 90, "valid": True}, - {"value": 200.0, "has_azimuth": True, "azimuth": 180, "valid": True}, - {"value": 50.0, "has_azimuth": False, "azimuth": 0, "valid": True}, - ] - value = evaluate_brightness( - sensors, - aggregation="mean", - use_azimuth=False, - prefer_unassigned=True, - force_max=True, - sun_azimuth=0.0, - sun_valid=False, - ) - self.assertTrue(math.isclose(value, 50.0, rel_tol=0.0, abs_tol=0.01)) - - def test_roof_fallback_all_max(self): - sensors = [ - {"value": 100.0, "has_azimuth": True, "azimuth": 90, "valid": True}, - {"value": 200.0, "has_azimuth": True, "azimuth": 180, "valid": True}, - ] - value = evaluate_brightness( - sensors, - aggregation="mean", - use_azimuth=False, - prefer_unassigned=True, - force_max=True, - sun_azimuth=0.0, - sun_valid=False, - ) - self.assertTrue(math.isclose(value, 200.0, rel_tol=0.0, abs_tol=0.01)) - - -if __name__ == "__main__": - unittest.main() From 2a2429452eeaa40f86db38b6d4e92b47e6b24607 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 26 May 2026 22:39:17 +0200 Subject: [PATCH 23/34] Add slat tracking modes: table-based interpolation and anti-glare (Betriebsart param) --- src/ShutterControllerModule.ModeShading.xml | 198 +++++++++++++++++--- src/ShutterControllerModule.share.xml | 13 ++ src/ShutterControllerModule.templ.xml | 2 +- 3 files changed, 190 insertions(+), 23 deletions(-) diff --git a/src/ShutterControllerModule.ModeShading.xml b/src/ShutterControllerModule.ModeShading.xml index 51b51a2..b169a1c 100755 --- a/src/ShutterControllerModule.ModeShading.xml +++ b/src/ShutterControllerModule.ModeShading.xml @@ -33,7 +33,7 @@ - + @@ -86,6 +86,9 @@ + + + @@ -183,7 +186,52 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -321,8 +369,32 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -566,6 +638,7 @@ + @@ -576,6 +649,7 @@ + @@ -589,44 +663,122 @@ + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + @@ -638,6 +790,7 @@ + @@ -646,6 +799,7 @@ + diff --git a/src/ShutterControllerModule.share.xml b/src/ShutterControllerModule.share.xml index 1990664..88a23bb 100755 --- a/src/ShutterControllerModule.share.xml +++ b/src/ShutterControllerModule.share.xml @@ -204,6 +204,19 @@ + + + + + + + + + + + + + diff --git a/src/ShutterControllerModule.templ.xml b/src/ShutterControllerModule.templ.xml index c71d043..3b58e8d 100755 --- a/src/ShutterControllerModule.templ.xml +++ b/src/ShutterControllerModule.templ.xml @@ -21,7 +21,7 @@ - + From ed4d6a824baa7a2cb72079c9c7c1813a65773b18 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 26 May 2026 22:39:22 +0200 Subject: [PATCH 24/34] Implement slat tracking table mode, anti-glare mode and require-facade-hit check --- src/BrightnessMeasurement.cpp | 49 +++------- src/ModeShading.cpp | 179 ++++++++++++++++++++++++++++------ 2 files changed, 163 insertions(+), 65 deletions(-) diff --git a/src/BrightnessMeasurement.cpp b/src/BrightnessMeasurement.cpp index c9f94e8..ef7430b 100644 --- a/src/BrightnessMeasurement.cpp +++ b/src/BrightnessMeasurement.cpp @@ -318,47 +318,28 @@ BrightnessMeasurement::ValueState BrightnessMeasurement::buildAzimuthState(float merged.push_back(entry); } - if (merged.size() == 1) + static constexpr float kDegToRad = 3.14159265358979323846f / 180.0f; + float weightSum = 0.0f; + float valueSum = 0.0f; + for (const auto& entry : merged) { - state.ignoreValue = false; - state.useFallback = false; - state.waitForValue = false; - state.valueLux = merged.front().valueLux; - return state; + float delta = sunAzimuth - entry.azimuth; + while (delta > 180.0f) delta -= 360.0f; + while (delta < -180.0f) delta += 360.0f; + float weight = std::cos(delta * kDegToRad); + if (weight <= 0.0f) + continue; + weightSum += weight; + valueSum += weight * entry.valueLux; } - float target = normalizeAzimuth(sunAzimuth); - float result = merged.front().valueLux; - - for (size_t i = 0; i < merged.size(); i++) - { - float a0 = merged[i].azimuth; - float a1 = merged[(i + 1) % merged.size()].azimuth; - float v0 = merged[i].valueLux; - float v1 = merged[(i + 1) % merged.size()].valueLux; - float segmentStart = a0; - float segmentEnd = a1; - - if (i == merged.size() - 1) - segmentEnd += 360.0f; - - float targetWrapped = target; - if (targetWrapped < segmentStart) - targetWrapped += 360.0f; - - if (targetWrapped >= segmentStart && targetWrapped <= segmentEnd) - { - float ratio = (segmentEnd - segmentStart) > 0.0f ? - (targetWrapped - segmentStart) / (segmentEnd - segmentStart) : 0.0f; - result = v0 + (v1 - v0) * ratio; - break; - } - } + if (weightSum < 1e-6f) + return aggregateState; state.ignoreValue = false; state.useFallback = false; state.waitForValue = false; - state.valueLux = result; + state.valueLux = valueSum / weightSum; return state; } diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index f37f5f9..1e106b9 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -457,6 +457,33 @@ bool ModeShading::allowedBySun(const CallContext &callContext) _notAllowedReason |= ModeShadingNotAllowedReasonSunBreak; else _notAllowedReason &= ~ModeShadingNotAllowedReasonSunBreak; + + if (ParamSHC_CShading1RequireFacadeHit == 1) + { + bool isGeoTracking = (ParamSHC_CType == 2) + ? (ParamSHC_CShading1RolloPositionMode >= 1) + : (ParamSHC_CShading1SlatElevationDepending >= 3); + + const uint8_t orientation = ParamSHC_CWindowOrientation; + if (isGeoTracking && orientation != 5 && orientation != 6) + { + float beta = calculateProfileAngle( + (float)callContext.elevation, + (float)callContext.azimuth, + orientation, + (float)(int8_t)ParamSHC_CFacadeInclination); + if (beta <= 0.0f) + { + if (diagnosticLog) + logInfoP("RequireFacadeHit: Profilwinkel %.2f <= 0, nicht erlaubt", beta); + allowed = false; + _notAllowedReason |= ModeShadingNotAllowedReasonProfileAngleSentinel; + } + else + _notAllowedReason &= ~ModeShadingNotAllowedReasonProfileAngleSentinel; + } + } + return allowed; } @@ -1099,9 +1126,51 @@ void ModeShading::control(const CallContext &callContext, PositionController &po positionController.setAutomaticPosition((uint8_t)targetPos); } - // Lamellenwinkel (Experte-Formel) — nur wenn Profilwinkel berechenbar + // Lamellenwinkel — Betriebsart: 0=Tageslicht, 1=Blendschutz, 2=Tabelle + uint8_t betriebsart = ParamSHC_CShading1SlatTrackingBetriebsart; if (slatTrackingPossible) { + float gamma_deg = gamma_rad * (180.0f / (float)M_PI); + if (betriebsart == 2) + { + float startPos = (float)ParamSHC_CShading1SlatTableStartPos; + float minElevation = (float)ParamSHC_CShading1SlatTableMinElevation; + float e1 = (float)ParamSHC_CShading1SlatTable1Elevation; + float p1 = (float)ParamSHC_CShading1SlatTable1Position; + float e2 = (float)ParamSHC_CShading1SlatTable2Elevation; + float p2 = (float)ParamSHC_CShading1SlatTable2Position; + float e3 = (float)ParamSHC_CShading1SlatTable3Elevation; + float p3 = (float)ParamSHC_CShading1SlatTable3Position; + float e4 = (float)ParamSHC_CShading1SlatTable4Elevation; + float p4 = (float)ParamSHC_CShading1SlatTable4Position; + float e5 = (float)ParamSHC_CShading1SlatTable5Elevation; + float p5 = (float)ParamSHC_CShading1SlatTable5Position; + float e6 = (float)ParamSHC_CShading1SlatTable6Elevation; + float p6 = (float)ParamSHC_CShading1SlatTable6Position; + float slatPercent; + if (gamma_deg <= minElevation) + slatPercent = startPos; + else if (gamma_deg >= e6) + slatPercent = p6; + else + { + float lo_e, lo_p, hi_e, hi_p; + if (gamma_deg < e2) { lo_e = e1; lo_p = p1; hi_e = e2; hi_p = p2; } + else if (gamma_deg < e3) { lo_e = e2; lo_p = p2; hi_e = e3; hi_p = p3; } + else if (gamma_deg < e4) { lo_e = e3; lo_p = p3; hi_e = e4; hi_p = p4; } + else if (gamma_deg < e5) { lo_e = e4; lo_p = p4; hi_e = e5; hi_p = p5; } + else { lo_e = e5; lo_p = p5; hi_e = e6; hi_p = p6; } + slatPercent = (hi_e > lo_e) ? lo_p + (hi_p - lo_p) * (gamma_deg - lo_e) / (hi_e - lo_e) : lo_p; + } + slatPercent += (float)(int8_t)ParamSHC_CShading1OffsetSlatPosition; + if (slatPercent < 0.0f) slatPercent = 0.0f; + if (slatPercent > 100.0f) slatPercent = 100.0f; + auto slatPosition = (uint8_t)slatPercent; + if (callContext.modeNewStarted || abs((uint8_t)KoSHC_CShutterSlatOutput.value(DPT_Scaling) - slatPosition) >= ParamSHC_CShading1MinChangeForSlatAdaption) + positionController.setAutomaticSlat(slatPosition); + } + else + { float a = (float)ParamSHC_CShading1SlatSpacing; float b = (float)ParamSHC_CShading1SlatWidth; uint8_t angleAtMin = ParamSHC_CShading1SlatAngleAtMin; @@ -1109,7 +1178,9 @@ void ModeShading::control(const CallContext &callContext, PositionController &po if (angleAtMin != angleAtMax) { - float theta_krit = atan2f(a * sinf(gamma_rad), b - a * cosf(gamma_rad)) * (180.0f / (float)M_PI); + float theta_krit = (betriebsart == 1) + ? gamma_deg + : atan2f(a * sinf(gamma_rad), b - a * cosf(gamma_rad)) * (180.0f / (float)M_PI); if (theta_krit < 0.0f) { @@ -1128,9 +1199,10 @@ void ModeShading::control(const CallContext &callContext, PositionController &po } } } + } if (callContext.diagnosticLog) - logInfoP("Case5 combined: gamma=%.1f° s_crit=%.1f targetPos=%.1f slatPossible=%d", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos, (int)slatTrackingPossible); + logInfoP("Case5 combined: gamma=%.1f° s_crit=%.1f targetPos=%.1f slatPossible=%d betriebsart=%d", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos, (int)slatTrackingPossible, (int)betriebsart); break; } @@ -1222,45 +1294,90 @@ void ModeShading::control(const CallContext &callContext, PositionController &po positionController.setAutomaticPosition((uint8_t)clippedPos); } + uint8_t betriebsart = ParamSHC_CShading1SlatTrackingBetriebsart; if (slatTrackingPossible) { - float a = (float)ParamSHC_CShading1SlatSpacing; - float b = (float)ParamSHC_CShading1SlatWidth; - uint8_t angleAtMin = ParamSHC_CShading1SlatAngleAtMin; - uint8_t angleAtMax = ParamSHC_CShading1SlatAngleAtMax; - - if (angleAtMin != angleAtMax) + float gamma_deg = gamma_rad * (180.0f / (float)M_PI); + if (betriebsart == 2) { - float theta_krit = atan2f(a * sinf(gamma_rad), b - a * cosf(gamma_rad)) * (180.0f / (float)M_PI); - - if (theta_krit < 0.0f) + float startPos = (float)ParamSHC_CShading1SlatTableStartPos; + float minElevation = (float)ParamSHC_CShading1SlatTableMinElevation; + float e1 = (float)ParamSHC_CShading1SlatTable1Elevation; + float p1 = (float)ParamSHC_CShading1SlatTable1Position; + float e2 = (float)ParamSHC_CShading1SlatTable2Elevation; + float p2 = (float)ParamSHC_CShading1SlatTable2Position; + float e3 = (float)ParamSHC_CShading1SlatTable3Elevation; + float p3 = (float)ParamSHC_CShading1SlatTable3Position; + float e4 = (float)ParamSHC_CShading1SlatTable4Elevation; + float p4 = (float)ParamSHC_CShading1SlatTable4Position; + float e5 = (float)ParamSHC_CShading1SlatTable5Elevation; + float p5 = (float)ParamSHC_CShading1SlatTable5Position; + float e6 = (float)ParamSHC_CShading1SlatTable6Elevation; + float p6 = (float)ParamSHC_CShading1SlatTable6Position; + float slatPercent; + if (gamma_deg <= minElevation) + slatPercent = startPos; + else if (gamma_deg >= e6) + slatPercent = p6; + else { - positionController.setAutomaticSlat(100); + float lo_e, lo_p, hi_e, hi_p; + if (gamma_deg < e2) { lo_e = e1; lo_p = p1; hi_e = e2; hi_p = p2; } + else if (gamma_deg < e3) { lo_e = e2; lo_p = p2; hi_e = e3; hi_p = p3; } + else if (gamma_deg < e4) { lo_e = e3; lo_p = p3; hi_e = e4; hi_p = p4; } + else if (gamma_deg < e5) { lo_e = e4; lo_p = p4; hi_e = e5; hi_p = p5; } + else { lo_e = e5; lo_p = p5; hi_e = e6; hi_p = p6; } + slatPercent = (hi_e > lo_e) ? lo_p + (hi_p - lo_p) * (gamma_deg - lo_e) / (hi_e - lo_e) : lo_p; } - else + slatPercent += (float)(int8_t)ParamSHC_CShading1OffsetSlatPosition; + if (slatPercent < 0.0f) slatPercent = 0.0f; + if (slatPercent > 100.0f) slatPercent = 100.0f; + auto slatPosition = (uint8_t)slatPercent; + if (callContext.modeNewStarted || abs((uint8_t)KoSHC_CShutterSlatOutput.value(DPT_Scaling) - slatPosition) >= ParamSHC_CShading1MinChangeForSlatAdaption) + positionController.setAutomaticSlat(slatPosition); + } + else + { + float a = (float)ParamSHC_CShading1SlatSpacing; + float b = (float)ParamSHC_CShading1SlatWidth; + uint8_t angleAtMin = ParamSHC_CShading1SlatAngleAtMin; + uint8_t angleAtMax = ParamSHC_CShading1SlatAngleAtMax; + + if (angleAtMin != angleAtMax) { - float slatPercent = ((float)theta_krit - (float)angleAtMin) / ((float)angleAtMax - (float)angleAtMin) * 100.0f; - slatPercent += (float)(int8_t)ParamSHC_CShading1OffsetSlatPosition; - if (slatPercent < 0.0f) slatPercent = 0.0f; - if (slatPercent > 100.0f) slatPercent = 100.0f; - - // Lamellen-Clipping: min(PPP+51, PPP+52) bis max(PPP+51, PPP+52) - float slatLow = (float)ParamSHC_CShading1SlatLowSunPosition; - float slatHigh = (float)ParamSHC_CShading1SlatHighSunPosition; - float slatClipMin = (slatLow < slatHigh) ? slatLow : slatHigh; - float slatClipMax = (slatLow > slatHigh) ? slatLow : slatHigh; - if (slatPercent < slatClipMin) slatPercent = slatClipMin; - if (slatPercent > slatClipMax) slatPercent = slatClipMax; - - auto slatPosition = (uint8_t)slatPercent; - if (callContext.modeNewStarted || abs((uint8_t)KoSHC_CShutterSlatOutput.value(DPT_Scaling) - slatPosition) >= ParamSHC_CShading1MinChangeForSlatAdaption) - positionController.setAutomaticSlat(slatPosition); + float theta_krit = (betriebsart == 1) + ? gamma_deg + : atan2f(a * sinf(gamma_rad), b - a * cosf(gamma_rad)) * (180.0f / (float)M_PI); + + if (theta_krit < 0.0f) + { + positionController.setAutomaticSlat(100); + } + else + { + float slatPercent = ((float)theta_krit - (float)angleAtMin) / ((float)angleAtMax - (float)angleAtMin) * 100.0f; + slatPercent += (float)(int8_t)ParamSHC_CShading1OffsetSlatPosition; + if (slatPercent < 0.0f) slatPercent = 0.0f; + if (slatPercent > 100.0f) slatPercent = 100.0f; + + // Lamellen-Clipping: min(PPP+51, PPP+52) bis max(PPP+51, PPP+52) + float slatLow = (float)ParamSHC_CShading1SlatLowSunPosition; + float slatHigh = (float)ParamSHC_CShading1SlatHighSunPosition; + float slatClipMin = (slatLow < slatHigh) ? slatLow : slatHigh; + float slatClipMax = (slatLow > slatHigh) ? slatLow : slatHigh; + if (slatPercent < slatClipMin) slatPercent = slatClipMin; + if (slatPercent > slatClipMax) slatPercent = slatClipMax; + + auto slatPosition = (uint8_t)slatPercent; + if (callContext.modeNewStarted || abs((uint8_t)KoSHC_CShutterSlatOutput.value(DPT_Scaling) - slatPosition) >= ParamSHC_CShading1MinChangeForSlatAdaption) + positionController.setAutomaticSlat(slatPosition); + } } } } if (callContext.diagnosticLog) - logInfoP("Case6 clip: gamma=%.1f° s_crit=%.1f targetPos=%.1f slatPossible=%d", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos, (int)slatTrackingPossible); + logInfoP("Case6 clip: gamma=%.1f° s_crit=%.1f targetPos=%.1f slatPossible=%d betriebsart=%d", gamma_rad * (180.0f / (float)M_PI), s_crit, targetPos, (int)slatTrackingPossible, (int)betriebsart); break; } From 25b3d8cc063a054abb4e259b54812c85c7f2c4bd Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 26 May 2026 22:39:30 +0200 Subject: [PATCH 25/34] Improve shading readiness condition and add ReadyUser diagnostics log --- src/ShutterControllerChannel.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/ShutterControllerChannel.cpp b/src/ShutterControllerChannel.cpp index e0c7f87..5dac854 100755 --- a/src/ShutterControllerChannel.cpp +++ b/src/ShutterControllerChannel.cpp @@ -80,6 +80,7 @@ void ShutterControllerChannel::setup() KoSHC_CShadingControl.value(shadingControlActive(), DPT_Switch); KoSHC_CShadingControlActive.value(shadingControlActive(), DPT_Switch); KoSHC_CShadingActive.value(false, DPT_Switch); + KoSHC_CShadingReadyUser.value(false, DPT_Switch); if (KoSHC_CLock.initialized()) { @@ -711,16 +712,19 @@ void ShutterControllerChannel::execute(CallContext &callContext) KoSHC_CShading2LockActive.value(DPT_Switch) || KoSHC_CShading2BreakLockActive.value(DPT_Switch); } - bool shadingPositionAllowed = false; - for (auto mode : _modes) + bool shadingPositionAllowed = _currentMode != nullptr && _currentMode->isModeShading(); + if (!shadingPositionAllowed) { - if (mode->isModeShading()) + for (auto mode : _modes) { - auto modeShading = (ModeShading *)mode; - if (modeShading->isPositionAllowed(callContext)) + if (mode->isModeShading()) { - shadingPositionAllowed = true; - break; + auto modeShading = (ModeShading *)mode; + if (modeShading->isPositionAllowed(callContext)) + { + shadingPositionAllowed = true; + break; + } } } } @@ -746,6 +750,15 @@ void ShutterControllerChannel::execute(CallContext &callContext) logInfoP("Shading active: %d", anyShadingModeActive()); logInfoP("Active shading period: %d", _shadingPeriodActive); logInfoP("Current active mode: %s", _currentMode->name()); + logInfoP("ReadyUser: %d (shadCtrl=%d lock=%d winHandler=%d winState=%d manual=%d modeLock=%d posAllowed=%d)", + readinessUser, + (int)shadingControlActive(), + (int)_channelLockActive, + (int)(_currentWindowOpenHandler != nullptr), + (int)_windowOpenState, + (int)(_currentMode == _modeManual), + (int)shadingModeLockActive, + (int)shadingPositionAllowed); } } From 76b13957e962d3272b9b62145cdb086cb2d04469 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 26 May 2026 22:39:38 +0200 Subject: [PATCH 26/34] Add and update help texts for new shading and slat tracking parameters --- src/Baggages/Help_de/SHC-Beschattungsposition.md | 4 ++++ .../Help_de/SHC-Beschattungsstart-Modus.md | 14 ++++++++++++++ .../SHC-Betriebsart-Lamellennachfuehrung.md | 9 +++++++++ src/Baggages/Help_de/SHC-Bis-Hoehenwinkel.md | 4 ++++ .../Help_de/SHC-Hoehenwinkel-Stuetzpunkt-1-6.md | 4 ++++ src/Baggages/Help_de/SHC-Hoehenwinkel.md | 4 ++++ .../Help_de/SHC-Lamelle-ist-waagrecht-bei.md | 6 ++++++ .../Help_de/SHC-Max-Position-Begrenzung.md | 2 ++ .../Help_de/SHC-Position-Stuetzpunkt-1-6.md | 4 ++++ ...HC-Position-bei-Sonne-unter-min-Hoehenwinkel.md | 4 ++++ src/Baggages/Help_de/SHC-Positionsnachfuehrung.md | 2 ++ 11 files changed, 57 insertions(+) create mode 100644 src/Baggages/Help_de/SHC-Beschattungsstart-Modus.md create mode 100644 src/Baggages/Help_de/SHC-Betriebsart-Lamellennachfuehrung.md create mode 100644 src/Baggages/Help_de/SHC-Bis-Hoehenwinkel.md create mode 100644 src/Baggages/Help_de/SHC-Hoehenwinkel-Stuetzpunkt-1-6.md create mode 100644 src/Baggages/Help_de/SHC-Hoehenwinkel.md create mode 100644 src/Baggages/Help_de/SHC-Lamelle-ist-waagrecht-bei.md create mode 100644 src/Baggages/Help_de/SHC-Position-Stuetzpunkt-1-6.md create mode 100644 src/Baggages/Help_de/SHC-Position-bei-Sonne-unter-min-Hoehenwinkel.md diff --git a/src/Baggages/Help_de/SHC-Beschattungsposition.md b/src/Baggages/Help_de/SHC-Beschattungsposition.md index b4a22eb..751cd6b 100644 --- a/src/Baggages/Help_de/SHC-Beschattungsposition.md +++ b/src/Baggages/Help_de/SHC-Beschattungsposition.md @@ -4,3 +4,7 @@ Position die bei Beschattungsstart angefahren wird. Bei den Modi **Geometrische Positionsnachführung**, **Geometrische Positions- und Lamellennachführung** und **Geo. Positions- und Lamellennachführung (Min/Max)** wird die Position normalerweise dynamisch anhand des Sonnenstands berechnet. Die Beschattungsposition wirkt in diesen Modi nur als **Fallback**: Wenn die Sonne so flach steht, dass die berechnete Schattenkante oberhalb der Fensterhöhe liegt (d.h. der Behang muss vollständig abgesenkt sein), wird die hier konfigurierte Position als Zielwert verwendet. +**Schutzposition:** Bei Beschattungsstart kann die Sonne zwar im konfigurierten Azimut- und Helligkeitsfenster liegen, aber geometrisch noch nicht auf die Fassade treffen (z.B. Westfenster, Sonne kommt noch aus Südosten). In diesem Fall fährt der Behang sofort auf die Beschattungsposition als Schutz gegen indirektes Licht und Reflexionen. Sobald die Sonne die Fassade trifft, übernimmt die geometrische Nachführung. + +Hinweis: Die Beschattungsposition wird beim Start **nicht** durch Min./Max.-Begrenzungen eingeschränkt. Soll dieses Verhalten vermieden werden, kann unter **Beschattungsstart** die Option "Nur bei direktem Sonnenlicht" gewählt werden. + diff --git a/src/Baggages/Help_de/SHC-Beschattungsstart-Modus.md b/src/Baggages/Help_de/SHC-Beschattungsstart-Modus.md new file mode 100644 index 0000000..5990bfc --- /dev/null +++ b/src/Baggages/Help_de/SHC-Beschattungsstart-Modus.md @@ -0,0 +1,14 @@ +### Beschattungsstart + +Legt fest, unter welcher Bedingung die Beschattung gestartet wird, wenn alle Mess- und Sonnenwert-Freigaben erfüllt sind. Diese Einstellung ist nur bei aktivem Geo-Tracking sichtbar (Jalousie: Modi 3–6; Rollo: Modi 1–2). + +**Sofort (mit Schutzposition)** (Standard): +Die Beschattung startet sofort, sobald Azimut, Elevation und Helligkeitswerte die konfigurierten Grenzen erfüllen. Es ist dabei möglich, dass die Sonne geometrisch noch nicht direkt auf die Fassade trifft (z.B. beim Westfenster kommt die Sonne zunächst noch aus Südosten). In diesem Fall fährt der Behang unmittelbar auf die konfigurierte **Beschattungsposition** als Schutz gegen indirektes Licht und Reflexionen. Sobald die Sonne die Fassade trifft (Profilwinkel > 0°), übernimmt die geometrische Nachführung die Positionsberechnung. + +**Nur bei direktem Sonnenlicht**: +Die Beschattung startet erst, wenn die Sonne sowohl alle Mess- und Sonnenwert-Bedingungen erfüllt als auch geometrisch auf die Fassade trifft (Profilwinkel > 0°). Solange die Sonne noch nicht auf die Fassade trifft, bleibt der Behang geöffnet – die Beschattungsposition wird nicht angefahren. + +**Beispiel Westfenster** (Azimut 120°–290°): Die Sonne trifft eine Westfassade erst ab ca. 180° Azimut. +- *Sofort*: Behang fährt ab 120° auf Beschattungsposition, geometrische Nachführung ab ~180°. +- *Nur bei direktem Sonnenlicht*: Behang bleibt bis ~180° vollständig geöffnet. + diff --git a/src/Baggages/Help_de/SHC-Betriebsart-Lamellennachfuehrung.md b/src/Baggages/Help_de/SHC-Betriebsart-Lamellennachfuehrung.md new file mode 100644 index 0000000..146db74 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Betriebsart-Lamellennachfuehrung.md @@ -0,0 +1,9 @@ +### Betriebsart Lamellennachführung + +Legt fest, wie die Lamellenstellung automatisch berechnet wird: + +- **Direktlicht abschneiden (Cut-Off)**: Geometrische Berechnung des kritischen Kippwinkels θ, bei dem gerade kein direktes Sonnenlicht durch die Lamellen eindringt (Schattenkante). Hierfür werden Lamellenbreite und Lamellenabstand benötigt. +- **Blendschutz (Retro-Reflexion)**: Die Lamellen werden so gestellt, dass Sonnenstrahlen parallel reflektiert werden (θ = 90° − Profilwinkel). Maximaler Blendschutz ohne geometrische Kalibrierung. +- **Adaptiv (Daylight)**: Kombiniert Cut-Off und Retro-Reflexion automatisch. Wählt jeweils die offenere Lamellenstellung (θ = min(θ_CutOff, θ_Retro)). Maximale Tageslichtnutzung bei gleichzeitigem Blendschutz — der Wechsel zwischen beiden Betriebsarten erfolgt am geometrischen Schnittpunkt, sprungfrei und ohne zusätzliche Parameter. +- **Über Tabelle**: Die Lamellenstellung wird anhand einer konfigurierbaren Tabelle mit 6 Stützpunkten (Höhenwinkel → Position in %) per linearer Interpolation berechnet. Geeignet für herstellerspezifische Vorgaben (z. B. Warema-Tabellen). + diff --git a/src/Baggages/Help_de/SHC-Bis-Hoehenwinkel.md b/src/Baggages/Help_de/SHC-Bis-Hoehenwinkel.md new file mode 100644 index 0000000..2168d2d --- /dev/null +++ b/src/Baggages/Help_de/SHC-Bis-Hoehenwinkel.md @@ -0,0 +1,4 @@ +### Höhenwinkel Stützpunkt (bis) + +Sonnenhöhe in Grad (°) als Obergrenze dieses Intervalls. Ab diesem Wert gilt der Lamellenstellungswert des nächsten Stützpunkts. Die Werte müssen aufsteigend sortiert sein. + diff --git a/src/Baggages/Help_de/SHC-Hoehenwinkel-Stuetzpunkt-1-6.md b/src/Baggages/Help_de/SHC-Hoehenwinkel-Stuetzpunkt-1-6.md new file mode 100644 index 0000000..7a9cca3 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Hoehenwinkel-Stuetzpunkt-1-6.md @@ -0,0 +1,4 @@ +### Höhenwinkel Stützpunkt 1...6 + +Sonnenhöhe in Grad (°) für jeden der 6 Stützpunkte der Tabelle. Die Werte müssen aufsteigend sortiert sein (Stützpunkt 1 ≤ 2 ≤ ... ≤ 6). Unterhalb von Stützpunkt 1 gilt die "Position bei Sonne unter min. Höhenwinkel", oberhalb von Stützpunkt 6 bleibt die Lamellenstellung konstant auf dem Wert von Stützpunkt 6. + diff --git a/src/Baggages/Help_de/SHC-Hoehenwinkel.md b/src/Baggages/Help_de/SHC-Hoehenwinkel.md new file mode 100644 index 0000000..62c9544 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Hoehenwinkel.md @@ -0,0 +1,4 @@ +### Min. Höhenwinkel (Tabelle) + +Minimaler Sonnenhöhenwinkel in Grad (°). Unterhalb dieses Werts wird die "Position bei Sonne unter min. Höhenwinkel" verwendet. + diff --git a/src/Baggages/Help_de/SHC-Lamelle-ist-waagrecht-bei.md b/src/Baggages/Help_de/SHC-Lamelle-ist-waagrecht-bei.md new file mode 100644 index 0000000..ef2e071 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Lamelle-ist-waagrecht-bei.md @@ -0,0 +1,6 @@ +### Lamelle ist waagrecht bei + + +- **0%**: 0% = waagrecht (herstellerspezifisch, selten). +- **50%**: 50% = waagrecht (KNX-Standard, Vorgabewert). + diff --git a/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md b/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md index 35c795b..0bf0476 100644 --- a/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md +++ b/src/Baggages/Help_de/SHC-Max-Position-Begrenzung.md @@ -6,3 +6,5 @@ Typischer Anwendungsfall: Max. Position = 80% → Die Jalousie schließt nie vol Wenn Min. Position größer als Max. Position konfiguriert ist, wird die Begrenzung ignoriert und das Berechnungsergebnis unverändert ausgegeben. +Hinweis: Die Begrenzung gilt **nicht** für die Beschattungsposition, die beim Start angefahren wird (Schutzposition). Nur die dynamisch berechneten Geo-Nachführungs-Positionen werden begrenzt. + diff --git a/src/Baggages/Help_de/SHC-Position-Stuetzpunkt-1-6.md b/src/Baggages/Help_de/SHC-Position-Stuetzpunkt-1-6.md new file mode 100644 index 0000000..1be6f57 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Position-Stuetzpunkt-1-6.md @@ -0,0 +1,4 @@ +### Lamellenstellung Stützpunkt 1...6 + +Lamellenstellung (0–100%) beim jeweiligen Höhenwinkel-Stützpunkt. Zwischen zwei Stützpunkten wird die Lamellenstellung linear interpoliert. + diff --git a/src/Baggages/Help_de/SHC-Position-bei-Sonne-unter-min-Hoehenwinkel.md b/src/Baggages/Help_de/SHC-Position-bei-Sonne-unter-min-Hoehenwinkel.md new file mode 100644 index 0000000..9b64033 --- /dev/null +++ b/src/Baggages/Help_de/SHC-Position-bei-Sonne-unter-min-Hoehenwinkel.md @@ -0,0 +1,4 @@ +### Position bei Sonne unter min. Höhenwinkel (Tabelle) + +Lamellenstellung (0–100%), die verwendet wird, wenn der Sonnen-Höhenwinkel unterhalb des konfigurierten Höhenwinkels von Stützpunkt 1 liegt. + diff --git a/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md b/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md index 82e952c..51d0c0d 100644 --- a/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md +++ b/src/Baggages/Help_de/SHC-Positionsnachfuehrung.md @@ -6,3 +6,5 @@ Aktiviert die dynamische Positionsnachführung für Rollläden basierend auf der - **Geo. Positionsnachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. - **Geo. Positionsnachführung (Min/Max)**: Wie "Geo. Positionsnachführung", jedoch wird die berechnete Position auf konfigurierbare Min./Max.-Grenzen begrenzt. +Siehe auch: **Beschattungsstart** – steuert, ob der Behang beim Start sofort auf die Beschattungsposition fährt oder erst wartet, bis die Sonne geometrisch auf die Fassade trifft. + From 994cb21144f4b4e826ef63164691f298e7d27c8b Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 26 May 2026 22:39:43 +0200 Subject: [PATCH 27/34] Update documentation for new shading modes and slat tracking --- ...likationsbeschreibung-ShutterController.md | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/doc/Applikationsbeschreibung-ShutterController.md b/doc/Applikationsbeschreibung-ShutterController.md index 3144b9a..89d6d6c 100644 --- a/doc/Applikationsbeschreibung-ShutterController.md +++ b/doc/Applikationsbeschreibung-ShutterController.md @@ -826,6 +826,25 @@ Position die bei Beschattungsstart angefahren wird. Bei den Modi **Geometrische Positionsnachführung**, **Geometrische Positions- und Lamellennachführung** und **Geo. Positions- und Lamellennachführung (Min/Max)** wird die Position normalerweise dynamisch anhand des Sonnenstands berechnet. Die Beschattungsposition wirkt in diesen Modi nur als **Fallback**: Wenn die Sonne so flach steht, dass die berechnete Schattenkante oberhalb der Fensterhöhe liegt (d.h. der Behang muss vollständig abgesenkt sein), wird die hier konfigurierte Position als Zielwert verwendet. +**Schutzposition:** Bei Beschattungsstart kann die Sonne zwar im konfigurierten Azimut- und Helligkeitsfenster liegen, aber geometrisch noch nicht auf die Fassade treffen (z.B. Westfenster, Sonne kommt noch aus Südosten). In diesem Fall fährt der Behang sofort auf die Beschattungsposition als Schutz gegen indirektes Licht und Reflexionen. Sobald die Sonne die Fassade trifft, übernimmt die geometrische Nachführung. + +Hinweis: Die Beschattungsposition wird beim Start **nicht** durch Min./Max.-Begrenzungen eingeschränkt. Soll dieses Verhalten vermieden werden, kann unter **Beschattungsstart** die Option "Nur bei direktem Sonnenlicht" gewählt werden. + + +#### Beschattungsstart + +Legt fest, unter welcher Bedingung die Beschattung gestartet wird, wenn alle Mess- und Sonnenwert-Freigaben erfüllt sind. Diese Einstellung ist nur bei aktivem Geo-Tracking sichtbar (Jalousie: Modi 3–6; Rollo: Modi 1–2). + +**Sofort (mit Schutzposition)** (Standard): +Die Beschattung startet sofort, sobald Azimut, Elevation und Helligkeitswerte die konfigurierten Grenzen erfüllen. Es ist dabei möglich, dass die Sonne geometrisch noch nicht direkt auf die Fassade trifft (z.B. beim Westfenster kommt die Sonne zunächst noch aus Südosten). In diesem Fall fährt der Behang unmittelbar auf die konfigurierte **Beschattungsposition** als Schutz gegen indirektes Licht und Reflexionen. Sobald die Sonne die Fassade trifft (Profilwinkel > 0°), übernimmt die geometrische Nachführung die Positionsberechnung. + +**Nur bei direktem Sonnenlicht**: +Die Beschattung startet erst, wenn die Sonne sowohl alle Mess- und Sonnenwert-Bedingungen erfüllt als auch geometrisch auf die Fassade trifft (Profilwinkel > 0°). Solange die Sonne noch nicht auf die Fassade trifft, bleibt der Behang geöffnet – die Beschattungsposition wird nicht angefahren. + +**Beispiel Westfenster** (Azimut 120°–290°): Die Sonne trifft eine Westfassade erst ab ca. 180° Azimut. +- *Sofort*: Behang fährt ab 120° auf Beschattungsposition, geometrische Nachführung ab ~180°. +- *Nur bei direktem Sonnenlicht*: Behang bleibt bis ~180° vollständig geöffnet. + #### Positions- und Lamellennachführung @@ -839,6 +858,58 @@ Diese Einstellung ist nur für den Gerätetype "Jalousie" vorhanden. - **Geo. Positions- und Lamellennachführung**: Berechnet sowohl Position als auch Lamellenstellung geometrisch anhand der Sonnengeometrie. - **Geo. Positions- und Lamellennachführung (Min/Max)**: Wie "Geo. Positions- und Lamellennachführung", jedoch werden Position und Lamellenstellung auf konfigurierbare Min./Max.-Grenzen begrenzt. + +#### Betriebsart Lamellennachführung + + +Diese Einstellung ist nur vorhanden, wenn unter "Positions- und Lamellennachführung" "Geo. Positions- und Lamellennachführung" oder "Geo. Positions- und Lamellennachführung (Min/Max)" eingestellt wurde und der Gerätetype "Jalousie" verwendet wird. + +Legt fest, wie die Lamellenstellung automatisch berechnet wird: + +- **Tageslicht-optimiert**: Geometrische Berechnung des kritischen Kippwinkels θ, bei dem gerade kein direktes Sonnenlicht durch die Lamellen eindringt (Schattenkante). Hierfür werden Lamellenbreite und Lamellenabstand benötigt. +- **Blendschutz-optimiert**: Die Lamellen werden so gestellt, dass Sonnenstrahlen parallel reflektiert werden (θ = Profilwinkel). Maximaler Blendschutz ohne geometrische Kalibrierung. +- **Über Tabelle**: Die Lamellenstellung wird anhand einer konfigurierbaren Tabelle mit 6 Stützpunkten (Höhenwinkel → Position in %) per linearer Interpolation berechnet. Geeignet für herstellerspezifische Vorgaben (z. B. Warema-Tabellen). + + +#### Position bei Sonne unter min. Höhenwinkel (Tabelle) + + +Diese Einstellung ist nur vorhanden, wenn "Betriebsart Lamellennachführung" auf "Über Tabelle" gesetzt ist. + +Lamellenstellung (0–100%), die verwendet wird, wenn der Sonnen-Höhenwinkel unterhalb des konfigurierten Höhenwinkels von Stützpunkt 1 liegt. + + +#### Min. Höhenwinkel (Tabelle) + + +Diese Einstellung ist nur vorhanden, wenn "Betriebsart Lamellennachführung" auf "Über Tabelle" gesetzt ist. + +Minimaler Sonnenhöhenwinkel in Grad (°). Unterhalb dieses Werts wird die "Position bei Sonne unter min. Höhenwinkel" verwendet. + + +#### Höhenwinkel Stützpunkt (bis) + + +Diese Einstellung ist nur vorhanden, wenn "Betriebsart Lamellennachführung" auf "Über Tabelle" gesetzt ist. + +Sonnenhöhe in Grad (°) als Obergrenze dieses Intervalls. Ab diesem Wert gilt der Lamellenstellungswert des nächsten Stützpunkts. Die Werte müssen aufsteigend sortiert sein. + + +#### Höhenwinkel Stützpunkt 1...6 + + +Diese Einstellungen sind nur vorhanden, wenn "Betriebsart Lamellennachführung" auf "Über Tabelle" gesetzt ist. + +Sonnenhöhe in Grad (°) für jeden der 6 Stützpunkte der Tabelle. Die Werte müssen aufsteigend sortiert sein (Stützpunkt 1 ≤ 2 ≤ ... ≤ 6). Unterhalb von Stützpunkt 1 gilt die "Position bei Sonne unter min. Höhenwinkel", oberhalb von Stützpunkt 6 bleibt die Lamellenstellung konstant auf dem Wert von Stützpunkt 6. + + +#### Lamellenstellung Stützpunkt 1...6 + + +Diese Einstellungen sind nur vorhanden, wenn "Betriebsart Lamellennachführung" auf "Über Tabelle" gesetzt ist. + +Lamellenstellung (0–100%) beim jeweiligen Höhenwinkel-Stützpunkt. Zwischen zwei Stützpunkten wird die Lamellenstellung linear interpoliert. + #### Lamellenstellung @@ -944,6 +1015,8 @@ Aktiviert die dynamische Positionsnachführung für Rollläden basierend auf der - **Geo. Positionsnachführung**: Die Rolladenposition wird kontinuierlich berechnet, sodass die Sonneneindringtiefe die konfigurierte maximale Eindringtiefe nicht überschreitet. Die Position ändert sich dynamisch mit dem Sonnenverlauf. - **Geo. Positionsnachführung (Min/Max)**: Wie "Geo. Positionsnachführung", jedoch wird die berechnete Position auf konfigurierbare Min./Max.-Grenzen begrenzt. +Siehe auch: **Beschattungsstart** – steuert, ob der Behang beim Start sofort auf die Beschattungsposition fährt oder erst wartet, bis die Sonne geometrisch auf die Fassade trifft. + #### Min. Position @@ -968,6 +1041,8 @@ Typischer Anwendungsfall: Max. Position = 80% → Die Jalousie schließt nie vol Wenn Min. Position größer als Max. Position konfiguriert ist, wird die Begrenzung ignoriert und das Berechnungsergebnis unverändert ausgegeben. +Hinweis: Die Begrenzung gilt **nicht** für die Beschattungsposition, die beim Start angefahren wird (Schutzposition). Nur die dynamisch berechneten Geo-Nachführungs-Positionen werden begrenzt. + #### Lamellenstellung bei min. Höhenwinkel (Begrenzung) From 994e9353d5fa7aae6388416e800fd89b6ec9be0c Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 26 May 2026 22:39:47 +0200 Subject: [PATCH 28/34] Update release notes, bump version and update todos --- ReleaseNotes.md | 19 +++++++++++++++++++ ToDos.md | 9 +-------- library.json | 2 +- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 7a8faa8..4759e9c 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,3 +1,13 @@ +v 0.6.4 +- Feature: Neue Einstellung „Betriebsart Lamellennachführung" für die Modi 5 und 6 (Geo. Positions- und Lamellennachführung). Vier Optionen: „Direktlicht abschneiden (Cut-Off)" (bisheriges Verhalten: geometrische Cut-Off-Formel), „Blendschutz (Retro-Reflexion)" (Retro-Reflexion: θ = Profilwinkel), „Adaptiv (Daylight)" (θ = min(θ_CutOff, θ_Retro) – maximale Tageslichtnutzung bei gleichzeitigem Blendschutz) und „Über Tabelle" (6 konfigurierbare Stützpunkte Höhenwinkel → Lamellenposition % mit linearer Interpolation, Standardwerte gemäß Warema-Tabelle). +- Feature: Neuer Parameter „Beschattungsstart" – legt fest, ob die Beschattung sofort (mit Schutzposition) oder erst bei direktem Sonnenlicht (Profilwinkel > 0°) startet. +- Change: Helligkeitsmessung über Azimut auf cosinus-gewichtete Interpolation umgestellt – alle freigegebenen Helligkeitssensoren werden gleichzeitig nach ihrer Winkeldifferenz zur Sonne gewichtet (statt nur die zwei nächstliegenden linear zu interpolieren). Liefert stabilere Werte bei mehreren Sensoren. +- Change: „Status Beschattung Bereit (Benutzer)" wird jetzt auch ohne aktiven Beschattungsmodus korrekt als bereit gemeldet, sobald alle anderen Bedingungen erfüllt sind (vorher fälschlich nur bereit, wenn bereits ein Beschattungsmodus aktiv war). +- Fix: Lamellenwinkel-Skalierung bei geometrischer Berechnung korrigiert – der Nenner war fälschlicherweise „Lamellenwinkel vollständig geschlossen" (Winkel von der Vertikalen), korrekt ist (90° − dieser Wert) (Winkel von der Waagrechten bis vollständig geschlossen). Auswirkung: Lamellen wurden bisher fast immer auf 100% (oder den Clip-Maximalwert) gesetzt, jetzt werden physikalisch korrekte Zwischenstellungen berechnet. +- Fix: KO „Status Beschattung Bereit (Benutzer)" wird beim Geräteneustart explizit mit „false" initialisiert (vorher uninitialisiert auf dem Bus sichtbar). +- Breaking: Parameterlayout des Beschattungsmodus-Blocks vergrößert (increment 52 → 66 Bytes / 416 → 528 Bit). Betrifft Kanäle mit zwei konfigurierten Beschattungsmodi. Bestehende ETS-Projekte müssen neu parametriert werden. +- Breaking: Parameterwert für „Über Tabelle" verschoben (Value 2 → 3). Bestehende ETS-Projekte mit „Über Tabelle"-Konfiguration müssen neu parametriert werden. + v 0.6.3 - Redesign: Beschattungsmodi Jalousie komplett neu strukturiert – aufbauende Modi-Reihe: Nein → Standard → Lamellenführung (Min/Max) → Geo. Positionsnachführung → Geo. Positionsnachführung (Min/Max) → Geo. Positions- und Lamellennachführung → Geo. Positions- und Lamellennachführung (Min/Max). - Feature: Neuer Beschattungsmodus 6 „Geo. Positions- und Lamellennachführung (Min/Max)" – entspricht Modus 5 mit konfigurierbarer Min./Max.-Begrenzung für Position und Lamellenstellung. Wenn Min. Position > Max. Position konfiguriert ist, wird die Positionsbegrenzung ignoriert. Die Lamellenstellungsbegrenzung verwendet automatisch min/max der beiden Werte (Reihenfolge egal). @@ -12,21 +22,25 @@ v 0.6.3 - Fix: OffsetSlatPosition wurde in Modus 5 nicht auf die berechnete Lamellenstellung angewendet. - Fix: Hysterese (MinChangeForSlatAdaption) fehlte in Modus 2 (Standard-Lamellennachführung) vor dem Setzen der Lamellenstellung. - Change: Wertebereich „Max. Eindringtiefe" angepasst: 1–200 → 10–255 cm. + v 0.6.2 - Feature: Neue Beschattungsmodi 3 „Lamellennachführung Experte", 4 „Schattenkantennachführung", 5 „Schattenkanten- und Lamellennachführung" - Feature: Schattenkantennachführung für Rollo (neuer Parameter „Positionsnachführung" im Rollo-Kanal) - Feature: Neuer Kanal-Parameter „Fassadenneigung/Fensterneigung" für geometrische Berechnung - Breaking/Rename: Beschattungsmodus-Lamellensteuerung Wert 2 umbenannt: „Benutzerdefiniert" → „Lamellennachführung Min/Max". Der Parameterwert (2) ist unverändert. Bestehende ETS-Projekte laufen ohne Migration weiter; lediglich der angezeigte Text im ETS-Dropdown ändert sich nach einem Update der knxprod. + v 0.6.1 - Feature: Positionsprüfung im Status „Beschattung Bereit (Benutzer)" – Beschattungsbereitschaft wird nur signalisiert, wenn die aktuelle Zielposition ≤ dem konfigurierten Grenzwert „Nur wenn Position kleiner als" ist - Feature: KO-Freigabe-Parameter je Kanal – alle optionalen KOs können nun einzeln in der ETS ein-/ausgeblendet werden (Sperren Kanal, Status aktiver Modus, Aktorrückmeldung, Beschattung ein/aus, Beschattung aktiv, Beschattungsbereitschaft, Handbetrieb Schalten, Handbetrieb Position, Fenster offen/gekippt Status und Sperren) - Feature: KO-Freigabe-Parameter je Beschattungsmodus – Sperren, Status Aktiv und Status Bereitschaft je Beschattungsmodus separat freigebbar - Feature: KO-Freigabe-Parameter je Fensterkontakt – Status und Sperren für Fenster offen / Fenster gekippt separat freigebbar - Rename: KO-Freigabe-Parameter für Beschattungsmodus konsistent benannt (PPP+53 = Sperren freigeben, PPP+54 = Bereitschaft freigeben, PPP+55 = Status Aktiv freigeben) + v 0.6.0 - Refactor: "Helligkeit" (bool) + "Weitere Helligkeitssensoren" (count) zusammengefasst zu "Helligkeitssensoren" (Enum: Nein/1-5 Sensoren) - Rename: "Helligkeit Sensor 1..5" -> "Ausrichtung Sensor 1..5" - Breaking: Offset 19 Semantik geaendert (SHC_VerifyVersion 0.5 -> 0.6) + v 0.5.0 - Feature: Neues KO "Status Beschattung Bereit" je Kanal - Feature: Dachflaeche bevorzugt unzugeordnete Helligkeitssensoren (z.B. Dachsensor) @@ -34,10 +48,13 @@ v 0.5.0 - Fix: Azimut-/Helligkeit-UI klarer (Helligkeit Sensor 1..5, "Keine Himmelsrichtung (Azimut-Auswertung aus)") - Fix: Schreibfehler und Himmelsrichtungsbezeichnungen korrigiert - Doc: Help-Context und Applikationsbeschreibung aktualisiert + v 0.4.2 - Fix: Stopp von Rolladen bei manueller Bedienung durch Jalousiensteuerung + v 0.4.1 - Feature: Bessere Bennenung der Gruppenobjekte + v 0.4.0 - Feature: Invertieren der Fensterkontakt KO - Feature: Auswahl verhalten der Fensterkontakte @@ -46,8 +63,10 @@ v 0.4.0 - Fix: die KOs für offen und kipp waren vertauscht - Fix: Wenn man mehrmals zwischen kipp und offen wechselt, fährt der Rollladen beim Schließen nicht in die Position vor dem öffnen, sondern in eine der kipp- oder offen-Positionen. - Fix: Gruppe offen / gekippt in der ETS Baumansicht vertauscht + v 0.3.0 - Fix: Handsteuerungslogik + v 0.2.0 - Bugfix: Lammellen-KO wird bei Gerätetpye 'Rollo' angezeigt - Bugfix: Position anfahren bei Fenster offen/gekippt diff --git a/ToDos.md b/ToDos.md index fd63d7a..ffc1773 100644 --- a/ToDos.md +++ b/ToDos.md @@ -1,14 +1,7 @@ To Do's -- Optionale Status KO´s & Sperren ausblendbar um die Anzahl der sichtbaren KO´s zu reduzieren (falls sie nicht benötigt werden). -> erledigt und implementiert - -- Neigung der Fassade & Schattenkantennachführung - https://www.youtube.com/watch?v=WLM9XcgRE_Q - https://www.youtube.com/watch?v=-t9FiaM6x4Y +- Neigung der Fassade & Schattenkantennachführung & Erweiterung Lamellennachführung -- Vertikale Lamellen in "Lamellennachführung Experte": Berechnung auf Azimut-Basis - (statt Profilwinkel) für vertikal montierte Lamellen. - Long Term To Do'S - Szenen diff --git a/library.json b/library.json index a3152af..07a8919 100755 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "OFM-ShutterControllerModule", - "version": "0.6.3", + "version": "0.6.4", "dependencies": { }, From 6eec25735f7027d49b9d6ed67219023d04e044ed Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Tue, 26 May 2026 22:40:20 +0200 Subject: [PATCH 29/34] fix: shading activates despite open window when manual wait time expires ModeShading::allowed() correctly set the _notAllowedReason diagnostic flag for WindowOpen (reason 14), but did not include the window-open state in the actual return value. As a result, shading became active again after the manual-mode reactivation wait time elapsed, even though the window was open. Add isWindowTiltActive to CallContext to distinguish open vs. tilted window. Add isTiltHandler() getter to WindowOpenHandler. In ModeShading::allowed(), return false when window is open and windowOpenAllowed() is false, or when window is tilted and windowTiltAllowed() is false. --- src/CallContext.h | 1 + src/ModeShading.cpp | 4 ++++ src/ShutterControllerChannel.cpp | 1 + src/WindowOpenHandler.h | 1 + 4 files changed, 7 insertions(+) diff --git a/src/CallContext.h b/src/CallContext.h index 1d6072f..735a2f6 100644 --- a/src/CallContext.h +++ b/src/CallContext.h @@ -15,6 +15,7 @@ class CallContext bool modeNewStarted = false; bool fastSimulationActive = false; bool isWindowOpenActive = false; + bool isWindowTiltActive = false; bool hasSlat = false; bool diagnosticLog = false; bool shadingControlActive = false; diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index 1e106b9..bed60a4 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -362,6 +362,10 @@ bool ModeShading::allowed(const CallContext &callContext) return false; if (startWaitTimeActive) return false; + if (callContext.isWindowTiltActive && !windowTiltAllowed()) + return false; + if (callContext.isWindowOpenActive && !callContext.isWindowTiltActive && !windowOpenAllowed()) + return false; if (stopWaitTimeActive) return true; if (!allowedByHeatingOff) diff --git a/src/ShutterControllerChannel.cpp b/src/ShutterControllerChannel.cpp index 5dac854..9ec8b18 100755 --- a/src/ShutterControllerChannel.cpp +++ b/src/ShutterControllerChannel.cpp @@ -596,6 +596,7 @@ void ShutterControllerChannel::execute(CallContext &callContext) _currentWindowOpenHandler->start(callContext, _currentWindowOpenHandler, _positionController); } callContext.isWindowOpenActive = _currentWindowOpenHandler != nullptr; + callContext.isWindowTiltActive = _currentWindowOpenHandler != nullptr && _currentWindowOpenHandler->isTiltHandler(); // State machine handling for mode activation ModeBase *nextMode = nullptr; diff --git a/src/WindowOpenHandler.h b/src/WindowOpenHandler.h index 0e58040..721945e 100644 --- a/src/WindowOpenHandler.h +++ b/src/WindowOpenHandler.h @@ -26,6 +26,7 @@ class WindowOpenHandler protected: const std::string& logPrefix() const; public: + bool isTiltHandler() const { return _isTiltHandler; } uint8_t getParamterOpenPositionControl(); uint8_t getParamterOpenSlatPositionControl(); uint8_t getParamterOpenPosition(); From 5f663b012dd8e2bcda07d6b62e5372a6cba330c0 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Sat, 13 Jun 2026 16:03:01 +0200 Subject: [PATCH 30/34] Fix: Restart shading start wait time when measurement values recover When shading is inactive and measurement values become favorable again after a previous under-shoot (_needWaitTime == true), (re)arm the start wait time so a short favorable spell does not immediately reactivate shading. Previously the start wait time was only set in stop(); once it had elapsed while values stayed unfavorable, the next favorable change reactivated shading instantly, bypassing the configured start delay. --- src/ModeShading.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index bed60a4..e718b06 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -271,6 +271,25 @@ bool ModeShading::allowed(const CallContext &callContext) _waitTimeAfterMeasurmentValueChange = 0; // stop wait time, because allowed again } } + else + { + // Mode is currently inactive (shading not running): + // When the measurement values become favorable again after a previous under-shoot + // within the same shading period (_needWaitTime == true), (re)start the start wait time + // so a short favorable spell does not immediately reactivate shading. + // Previously the start wait time was only armed in stop(); once that timer had elapsed + // while the values stayed unfavorable, the next favorable change reactivated shading + // instantly, so the configured "Beschattungsstart" wait time was effectively ignored. + if (_needWaitTime && allowedByMeasurementValuesAndHeatingOffWaitTime) + { + logDebugP("Restart starting wait time"); + _waitTimeAfterMeasurmentValueChange = callContext.currentMillis; // (re)activate start wait time + } + else if (!allowedByMeasurementValuesAndHeatingOffWaitTime) + { + _waitTimeAfterMeasurmentValueChange = 0; // values unfavorable again, cancel pending start wait time + } + } } // Handle start and stop wait time bool stopWaitTimeActive = false; From d04704fc82d0fcd84ecb82d29d04cf7d5a6a03f7 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Sat, 13 Jun 2026 16:03:29 +0200 Subject: [PATCH 31/34] Fix: Correct memory overlap of RequireFacadeHit and SlatTableMinElevation Both parameters (PPP+82 and PPP+81, added in v0.6.4) were placed at byte offsets that fall inside existing 4-byte IEEE-754 float parameters: RequireFacadeHit (1 B) at offset 16 inside UVIMin (4 B, offsets 15-18) SlatTableMinElevation (1 B) at offset 34 inside RoomTemperaturMinimum (4 B, offsets 33-36) The visibility conditions for the two pairs are independent, so both parameters in each pair could be active simultaneously, causing silent value corruption at runtime. Fix: grow the shading union from 528 to 544 bits (66 to 68 bytes) and move both 1-byte parameters to the two new tail bytes (offsets 66 and 67). Update the part increment in templ.xml accordingly (66 -> 68). The AfterShading/HeadingFallback overlaps remain intentional (mutually-exclusive choose/when union aliasing, same size). Breaking change: shading parameter block layout changed (68 bytes/ 544 bits per shading instance). Existing ETS projects must be re-parametrized (already required by other v0.6.4 breaking changes). --- ReleaseNotes.md | 3 ++- src/ShutterControllerModule.ModeShading.xml | 15 ++++++++------- src/ShutterControllerModule.templ.xml | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 4759e9c..ffcd72f 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -5,7 +5,8 @@ v 0.6.4 - Change: „Status Beschattung Bereit (Benutzer)" wird jetzt auch ohne aktiven Beschattungsmodus korrekt als bereit gemeldet, sobald alle anderen Bedingungen erfüllt sind (vorher fälschlich nur bereit, wenn bereits ein Beschattungsmodus aktiv war). - Fix: Lamellenwinkel-Skalierung bei geometrischer Berechnung korrigiert – der Nenner war fälschlicherweise „Lamellenwinkel vollständig geschlossen" (Winkel von der Vertikalen), korrekt ist (90° − dieser Wert) (Winkel von der Waagrechten bis vollständig geschlossen). Auswirkung: Lamellen wurden bisher fast immer auf 100% (oder den Clip-Maximalwert) gesetzt, jetzt werden physikalisch korrekte Zwischenstellungen berechnet. - Fix: KO „Status Beschattung Bereit (Benutzer)" wird beim Geräteneustart explizit mit „false" initialisiert (vorher uninitialisiert auf dem Bus sichtbar). -- Breaking: Parameterlayout des Beschattungsmodus-Blocks vergrößert (increment 52 → 66 Bytes / 416 → 528 Bit). Betrifft Kanäle mit zwei konfigurierten Beschattungsmodi. Bestehende ETS-Projekte müssen neu parametriert werden. +- Fix: Speicherüberlappung im Beschattungsmodus-Block behoben – die Parameter „Beschattungsstart" (RequireFacadeHit) und der Tabellen-Höhenwinkel (SlatTableMinElevation) lagen fälschlicherweise innerhalb der 4-Byte-Float-Parameter „Minimaler UV-Index" bzw. „Minimale Raumtemperatur" und überschrieben sich gegenseitig. Beide wurden auf eigene Bytes am Blockende verschoben. +- Breaking: Parameterlayout des Beschattungsmodus-Blocks vergrößert (increment 52 → 68 Bytes / 416 → 544 Bit). Betrifft Kanäle mit zwei konfigurierten Beschattungsmodi. Bestehende ETS-Projekte müssen neu parametriert werden. - Breaking: Parameterwert für „Über Tabelle" verschoben (Value 2 → 3). Bestehende ETS-Projekte mit „Über Tabelle"-Konfiguration müssen neu parametriert werden. v 0.6.3 diff --git a/src/ShutterControllerModule.ModeShading.xml b/src/ShutterControllerModule.ModeShading.xml index b169a1c..74dabc5 100755 --- a/src/ShutterControllerModule.ModeShading.xml +++ b/src/ShutterControllerModule.ModeShading.xml @@ -33,7 +33,7 @@ - + @@ -83,11 +83,12 @@ - + - + + @@ -153,8 +154,8 @@ - - + + @@ -230,8 +231,8 @@ - - + + diff --git a/src/ShutterControllerModule.templ.xml b/src/ShutterControllerModule.templ.xml index 3b58e8d..ff5571e 100755 --- a/src/ShutterControllerModule.templ.xml +++ b/src/ShutterControllerModule.templ.xml @@ -21,7 +21,7 @@ - + From 1c1b34184bd1c5c32adc04d09a8189e9e3c1f44e Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:34:22 +0200 Subject: [PATCH 32/34] Fix wrong parameter names in ModeNight stop log message --- src/ModeNight.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModeNight.cpp b/src/ModeNight.cpp index a805139..f568c18 100644 --- a/src/ModeNight.cpp +++ b/src/ModeNight.cpp @@ -244,7 +244,7 @@ void ModeNight::stop(const CallContext &callContext, const ModeBase *next, Posit KoSHC_CNightActive.value(false, DPT_Switch); if (next != (const ModeBase *)callContext.modeManual && ParamSHC_CNightStopPositionEnabled) { - logDebugP("Set night stop position %d slat %d", (int) ParamSHC_CNightStartPosition, (int) ParamSHC_CNightStartSlatPosition); + logDebugP("Set night stop position %d slat %d", (int) ParamSHC_CNightStopPosition, (int) ParamSHC_CNightStopSlatPosition); positionController.setAutomaticPositionAndStoreForRestore(ParamSHC_CNightStopPosition); positionController.setAutomaticSlatAndStoreForRestore(ParamSHC_CNightStopSlatPosition); } From bf39dfb5a52fb8a12f0656acb4b7880dda9748cc Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:34:32 +0200 Subject: [PATCH 33/34] Store restore position only on first shading start, not on shading-to-shading transitions --- src/ModeShading.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ModeShading.cpp b/src/ModeShading.cpp index e718b06..85d9f25 100644 --- a/src/ModeShading.cpp +++ b/src/ModeShading.cpp @@ -738,6 +738,13 @@ void ModeShading::start(const CallContext &callContext, const ModeBase *previous _active = true; _lastSentShadowPos = -1.0f; KoSHC_CShading1Active.value(true, DPT_Switch); + // Restore-Position nur beim ersten Shading-Start speichern (nicht bei Shading->Shading-Wechsel), + // damit "Position vor Beschattungsstart" die tatsächliche Position vor der gesamten Beschattungsphase ist. + if (previous == nullptr || !previous->isModeShading()) + { + positionController.storeCurrentPositionForRestore(); + positionController.setRestoreSlat(positionController.slat()); + } positionController.setAutomaticPosition(ParamSHC_CShading1ShadingPosition); // From df89654e8af6e69eb653c64003f34190ca294ec8 Mon Sep 17 00:00:00 2001 From: estebanri87 <42171761+estebanri87@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:34:42 +0200 Subject: [PATCH 34/34] Increase max values for BrightnessLimit (50->100) and BrightnessHysteresis (20->50) parameters --- src/ShutterControllerModule.share.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ShutterControllerModule.share.xml b/src/ShutterControllerModule.share.xml index 88a23bb..94ee272 100755 --- a/src/ShutterControllerModule.share.xml +++ b/src/ShutterControllerModule.share.xml @@ -35,10 +35,10 @@ - + - +