feat(shelly): add intelligent mold-risk check based on Sedlbauer LIM I curve and document in AGENTS.md
This commit is contained in:
parent
85bfabd387
commit
1ad356a7b4
|
|
@ -0,0 +1,40 @@
|
|||
# Shelly Fan Control Agent Documentation
|
||||
|
||||
This directory manages the configuration, code templating, and deployment for the Shelly Plug S (Gen 3) that controls the cellar ventilation fan to prevent mold.
|
||||
|
||||
## Domain Structure & Key Files
|
||||
|
||||
* **[main.tf](file:///Users/moritz/src/infrapuzzle/terraform_shelly/main.tf)**: Coordinates rendering the JavaScript template and executing the Python upload script.
|
||||
* **[variables.tf](file:///Users/moritz/src/infrapuzzle/terraform_shelly/variables.tf)**: Defines MAC addresses for the Bluetooth sensors, safety parameters, and the mold calculation coefficients.
|
||||
* **[scripts/Taupi-4.0.js](file:///Users/moritz/src/infrapuzzle/terraform_shelly/scripts/Taupi-4.0.js)**: The source Shelly control script containing the logic for parsing Bluetooth BTHome events, calculating dew points, evaluating mold danger, and switching the relay.
|
||||
* **[scripts/Taupi-4.0.templated.js](file:///Users/moritz/src/infrapuzzle/terraform_shelly/scripts/Taupi-4.0.templated.js)**: The locally generated script rendering the variables passed from Terraform. Do not edit directly.
|
||||
* **[scripts/upload.py](file:///Users/moritz/src/infrapuzzle/terraform_shelly/scripts/upload.py)**: Helper script executing during `terraform apply` to upload the templated script to the Shelly device over local HTTP RPC commands.
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
### 1. Linear Isopleth Approximation (LIM I)
|
||||
To determine if indoor relative humidity is high enough to warrant ventilation, we use a linear approximation of the **Lowest Isopleth for Mould (LIM I)** curve developed by Klaus Sedlbauer for biodegradable substrates (like wood or wallpaper).
|
||||
|
||||
Between $5^\circ\text{C}$ and $25^\circ\text{C}$, the critical relative humidity threshold is calculated dynamically based on room temperature ($T$):
|
||||
|
||||
$$RH_{crit}(T) = 80\% - 0.5 \cdot (T - 10)$$
|
||||
|
||||
* At $10^\circ\text{C}$, the critical threshold is $80\%$.
|
||||
* At $20^\circ\text{C}$, the critical threshold is $75\%$.
|
||||
|
||||
### 2. Room-to-Wall Safety Buffer (15%)
|
||||
Since the sensor measures ambient room air, but mold grows on colder outer wall surfaces, we must compensate for the thermal gradient.
|
||||
* **Physical Effect**: A $5^\circ\text{C}$ drop in temperature between room air ($20^\circ\text{C}$) and the wall surface ($15^\circ\text{C}$) causes the local relative humidity at the wall to spike from $60\%$ to $82\%$, crossing the mold germination threshold.
|
||||
* **Action**: We apply a **$15\%$ safety buffer** to the threshold. This targets keeping the ambient room air relative humidity below **$60\%$** (when the room is at $20^\circ\text{C}$) to keep wall surfaces safely below $80\%$ relative humidity.
|
||||
|
||||
The fan is only allowed to run if:
|
||||
|
||||
$$\text{humidity\_innen} \ge RH_{crit}(T) - 15\%$$
|
||||
|
||||
---
|
||||
|
||||
## Sources & References
|
||||
|
||||
* **[BoeserBob/Taupi-4.0](https://github.com/BoeserBob/Taupi-4.0)**: The original upstream Shelly dew point ventilation controller repository.
|
||||
* **[Klaus Sedlbauer Biography (Wikipedia)](https://de.wikipedia.org/wiki/Klaus_Sedlbauer)**: The researcher who developed the Lowest Isopleth for Mould (LIM) curves at Fraunhofer IBP.
|
||||
* **[WUFI Biohygrothermal Model](https://wufi.de/en/wufi-software/wufi-bio/)**: Fraunhofer IBP documentation explaining dynamic mold growth and spore germination models.
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
locals {
|
||||
templated_code = templatefile("${path.module}/scripts/Taupi-4.0.js", {
|
||||
sensor_aussen_mac = var.sensor_aussen_mac
|
||||
sensor_innen_mac = var.sensor_innen_mac
|
||||
taupunktschwelle = var.taupunktschwelle
|
||||
mindesttemperatur = var.mindesttemperatur
|
||||
mindesthumi = var.mindesthumi
|
||||
schaltzeit = var.schaltzeit
|
||||
battery_warngrenze = var.battery_warngrenze
|
||||
lost_connection = var.lost_connection
|
||||
sensor_aussen_mac = var.sensor_aussen_mac
|
||||
sensor_innen_mac = var.sensor_innen_mac
|
||||
taupunktschwelle = var.taupunktschwelle
|
||||
mindesttemperatur = var.mindesttemperatur
|
||||
mindesthumi = var.mindesthumi
|
||||
schaltzeit = var.schaltzeit
|
||||
battery_warngrenze = var.battery_warngrenze
|
||||
lost_connection = var.lost_connection
|
||||
critical_humi_base = var.critical_humi_base
|
||||
critical_humi_temp_coeff = var.critical_humi_temp_coeff
|
||||
critical_humi_ref_temp = var.critical_humi_ref_temp
|
||||
critical_humi_buffer = var.critical_humi_buffer
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ var mindesthumi = ${mindesthumi}; // [%] ...und RHinnen
|
|||
var schaltzeit = ${schaltzeit}; // [s] Schaltbedingung prüfen alle X Sekunden
|
||||
var battery_warngrenze = ${battery_warngrenze}; // [%] wenn dieser Schwellwert unterschritten ist blinkt der Plug rot
|
||||
var lost_connection = ${lost_connection}; // [s] Zeit nach der frische Sensordaten gekommen sein müssen um tote Verbindungen zu finden
|
||||
//========== Kritische Feuchte-Konfiguration (Schimmelgefahr) ==========
|
||||
var critical_humi_base = ${critical_humi_base}; // [%] Kritische relative Feuchte bei Referenztemp.
|
||||
var critical_humi_temp_coeff = ${critical_humi_temp_coeff}; // [%/°C] Steigung des Grenzwerts pro °C
|
||||
var critical_humi_ref_temp = ${critical_humi_ref_temp}; // [°C] Referenztemperatur
|
||||
var critical_humi_buffer = ${critical_humi_buffer}; // [%] Sicherheitspuffer
|
||||
//===== Ende Sensor-Konfiguration === AB HIER MUSS NICHTS MEHR GEÄNDERT WERDEN =====================================
|
||||
|
||||
var taupunkt_aussen;
|
||||
|
|
@ -59,6 +64,13 @@ function schalten() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Kritische Feuchte (Schimmelgrenzkurve) berechnen
|
||||
var rh_crit = critical_humi_base - critical_humi_temp_coeff * (temperatur_innen - critical_humi_ref_temp);
|
||||
var rh_activation = rh_crit - critical_humi_buffer;
|
||||
var is_critical = humidity_innen >= rh_activation;
|
||||
|
||||
print("Schimmelprüfung: T_innen =", temperatur_innen, "°C, RH_innen =", humidity_innen, "%, RH_crit =", rh_crit, "%, Aktivierung ab =", rh_activation, "%, Kritisch =", is_critical);
|
||||
|
||||
// Sicherheitsprüfung kommen regelmäßig frische Daten von den Sensoren?
|
||||
lost_connection_innen = lost_connection_innen + schaltzeit
|
||||
lost_connection_aussen = lost_connection_aussen + schaltzeit
|
||||
|
|
@ -82,8 +94,9 @@ if (battery_innen < battery_warngrenze ||
|
|||
farbring(100,0,0,100);
|
||||
}
|
||||
|
||||
// Schaltlogik (immer schalten, der Shelly schaltet nur, wenn er schalten muss).
|
||||
if ( temperatur_innen > mindesttemperatur &&
|
||||
// Schaltlogik (immer schalten, der Shelly schaltet nur, wenn er schaltet muss).
|
||||
if ( is_critical &&
|
||||
temperatur_innen > mindesttemperatur &&
|
||||
humidity_innen > mindesthumi &&
|
||||
taupunkt_innen > taupunkt_aussen + taupunktschwelle
|
||||
)
|
||||
|
|
@ -141,9 +154,12 @@ function checkBlu(event) {
|
|||
|
||||
// Haupt-Timer für Steuerlogik
|
||||
Timer.set(schaltzeit * 1000, true, function () {
|
||||
var rh_crit_t = (typeof temperatur_innen !== "undefined") ? (critical_humi_base - critical_humi_temp_coeff * (temperatur_innen - critical_humi_ref_temp)) : undefined;
|
||||
var rh_act_t = (typeof rh_crit_t !== "undefined") ? (rh_crit_t - critical_humi_buffer) : undefined;
|
||||
|
||||
print("----- Steuerung alle", schaltzeit, "s -----");
|
||||
print("Innen: T =", temperatur_innen, "°C, RH =", humidity_innen, "%, Tp =", taupunkt_innen, "Batterie: ", battery_innen, " % ");
|
||||
print("Außen: T =", temperatur_aussen, "°C, RH =", humidity_aussen, "%, Tp =", taupunkt_aussen, "Batterie: ", battery_aussen, " % ");
|
||||
print("Innen: T =", temperatur_innen, "°C, RH =", humidity_innen, "%, Tp =", taupunkt_innen, "°C, Aktivierung ab RH =", rh_act_t, "% (Batterie:", battery_innen, "%)");
|
||||
print("Außen: T =", temperatur_aussen, "°C, RH =", humidity_aussen, "%, Tp =", taupunkt_aussen, "°C (Batterie:", battery_aussen, "%)");
|
||||
schalten();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -49,3 +49,27 @@ variable "lost_connection" {
|
|||
description = "Time in seconds after which data is considered stale, causing the fan to turn off for safety"
|
||||
default = 600
|
||||
}
|
||||
|
||||
variable "critical_humi_base" {
|
||||
type = number
|
||||
description = "Critical indoor relative humidity [%] at reference temperature (for mold warning curve)"
|
||||
default = 80
|
||||
}
|
||||
|
||||
variable "critical_humi_temp_coeff" {
|
||||
type = number
|
||||
description = "Temperature coefficient [%/°C] for the mold warning curve"
|
||||
default = 0.5
|
||||
}
|
||||
|
||||
variable "critical_humi_ref_temp" {
|
||||
type = number
|
||||
description = "Reference temperature [°C] for the mold warning curve"
|
||||
default = 10
|
||||
}
|
||||
|
||||
variable "critical_humi_buffer" {
|
||||
type = number
|
||||
description = "Safety buffer [%] subtracted from the critical relative humidity threshold"
|
||||
default = 15
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue