diff --git a/esphome/energymonitor.yaml b/esphome/energymonitor.yaml index 22a1197..5e3fe9b 100644 --- a/esphome/energymonitor.yaml +++ b/esphome/energymonitor.yaml @@ -2,6 +2,7 @@ substitutions: device_description: "M5Stack Core dashboard for Home Assistant energy and internet usage, SCD30 room climate, analog/digital clock and WireGuard" name: "energymonitor" friendly_name: "Energy Monitor" + co2_beep_gain: "1%" esphome: comment: ${device_description} @@ -101,6 +102,16 @@ output: - platform: gpio pin: GPIO32 id: display_backlight_output + # The original M5Stack Core's speaker amplifier is connected to GPIO25. + - platform: ledc + pin: GPIO25 + id: speaker_output + frequency: 880Hz + +rtttl: + id: co2_tones + output: speaker_output + gain: ${co2_beep_gain} light: - platform: binary @@ -180,6 +191,10 @@ globals: type: int restore_value: true initial_value: '0' + - id: co2_alert_level + type: int + restore_value: false + initial_value: '-1' - id: climate_graph_view type: bool restore_value: true @@ -233,6 +248,19 @@ sensor: name: "CO2" filters: - timeout: 30s + on_value: + then: + - lambda: |- + if (!std::isfinite(x)) return; + const int level = x > 1600.0f ? 2 : x > 1200.0f ? 1 : 0; + const int previous_level = id(co2_alert_level); + id(co2_alert_level) = level; + // Establish a silent baseline at startup; only upward crossings beep. + if (previous_level < 0 || level <= previous_level) return; + id(co2_tones).stop(); + id(co2_tones).play(level == 2 + ? "co2high:d=16,o=5,b=150:a,p,a" + : "co2warn:d=16,o=5,b=150:a"); temperature: id: scd30_temperature name: "Temperature" @@ -969,7 +997,7 @@ display: it.print(80, 140, id(font_small), temperature_color, TextAlign::TOP_CENTER, "Temperatur"); it.printf(80, 164, id(font_title), temperature_color, TextAlign::TOP_CENTER, temperature_valid ? "%.1f °C" : "-- °C", id(scd30_temperature).state); - it.print(240, 140, id(font_small), humidity_color, TextAlign::TOP_CENTER, "Feuchte"); + it.print(240, 140, id(font_small), humidity_color, TextAlign::TOP_CENTER, "Feuchtigkeit"); it.printf(240, 164, id(font_title), humidity_color, TextAlign::TOP_CENTER, humidity_valid ? "%.1f %%" : "-- %%", id(scd30_humidity).state); if (!co2_valid && !temperature_valid && !humidity_valid) { @@ -978,18 +1006,23 @@ display: } } else { draw_page_title("Raumklima · 1 h"); - const int left = 28, right = 308; + const int left = 28, right = 308, top = 40, bottom = 142; const int count = id(climate_history_count); - const char *labels[] = {"CO2", "Temperatur", "Feuchte"}; + const char *labels[] = {"CO2", "Temperatur", "Feuchtigkeit"}; const char *units[] = {"ppm", "°C", "%"}; const Color colors[] = {co2_color, temperature_color, humidity_color}; const std::array *histories[] = { &id(climate_co2_history), &id(climate_temperature_history), &id(climate_humidity_history)}; const float scale_steps[] = {100.0f, 1.0f, 5.0f}; const float minimum_ranges[] = {200.0f, 2.0f, 10.0f}; + for (int minute = 0; minute <= 60; minute += 15) { + const int x = left + minute * (right - left) / 60; + it.line(x, top, x, bottom, Color(42, 42, 42)); + } + it.line(left, top, right, top, Color(42, 42, 42)); + it.line(left, bottom, right, bottom, Color(42, 42, 42)); for (int graph = 0; graph < 3; graph++) { - const int top = 52 + graph * 54; - const int bottom = top + 31; + const int legend_y = 168 + graph * 16; float minimum = INFINITY, maximum = -INFINITY; for (int sample = 0; sample < count; sample++) { const int slot = (id(climate_history_next) - count + sample + 361) % 361; @@ -1006,20 +1039,14 @@ display: minimum = floorf((middle - range / 2.0f) / scale_steps[graph]) * scale_steps[graph]; maximum = ceilf((middle + range / 2.0f) / scale_steps[graph]) * scale_steps[graph]; } - it.print(12, top - 16, id(font_device), colors[graph], labels[graph]); + it.print(12, legend_y, id(font_device), colors[graph], labels[graph]); if (has_samples) { - it.printf(right, top - 16, id(font_device), colors[graph], TextAlign::TOP_RIGHT, + it.printf(right, legend_y, id(font_device), colors[graph], TextAlign::TOP_RIGHT, "%.0f - %.0f %s", minimum, maximum, units[graph]); } else { - it.print(right, top - 16, id(font_device), colors[graph], TextAlign::TOP_RIGHT, + it.print(right, legend_y, id(font_device), colors[graph], TextAlign::TOP_RIGHT, "Warte auf Messwerte"); } - for (int minute = 0; minute <= 60; minute += 15) { - const int x = left + minute * (right - left) / 60; - it.line(x, top, x, bottom, Color(42, 42, 42)); - } - it.line(left, top, right, top, Color(42, 42, 42)); - it.line(left, bottom, right, bottom, Color(42, 42, 42)); int previous_x = 0, previous_y = 0; bool previous_valid = false; for (int sample = 0; sample < count; sample++) { @@ -1037,10 +1064,10 @@ display: previous_valid = valid; } } - it.print(left, 202, id(font_device), Color::WHITE, "-60 min"); - it.print((left + right) / 2, 202, id(font_device), Color::WHITE, + it.print(left, 144, id(font_device), Color::WHITE, "-60 min"); + it.print((left + right) / 2, 144, id(font_device), Color::WHITE, TextAlign::TOP_CENTER, "-30 min"); - it.print(right, 202, id(font_device), Color::WHITE, TextAlign::TOP_RIGHT, "Jetzt"); + it.print(right, 144, id(font_device), Color::WHITE, TextAlign::TOP_RIGHT, "Jetzt"); } }