Compare commits

...
15 Commits
Author SHA1 Message Date
randy 29b375df68 nspanel: move shared config to common/nspanel.yaml, add TFT version sensor
CI / ESPHome config validation (push) Failing after 7s
CI / YAML lint (push) Failing after 37s
2026-09-28 20:51:05 +02:00
randy 5db5987405 ipv6_only: pin patched lwip so .local names resolve over IPv6 (esp-lwip#94) 2026-09-28 20:12:22 +02:00
randy dec5e5de0a ipv6_only: pin audio override so HTTP reader connects over IPv6 2026-09-28 16:01:28 +02:00
randy 88e1214058 ipv6_only: pin patched espressif/mdns to stop advertising A 0.0.0.0
CI / ESPHome config validation (push) Successful in 10s
CI / YAML lint (push) Failing after 33s
2026-09-28 12:49:33 +02:00
randy 61032ad248 ipv6_only: switch to network enable_ipv4: false (renamed in esphome#19750) 2026-09-28 12:42:17 +02:00
randy 148c51c4df mcu-lvgl-4848s040: add Guition ESP32-S3-4848S040 initial config
CI / ESPHome config validation (push) Failing after 6s
CI / YAML lint (push) Failing after 37s
- mipi_rgb GUITION-4848S040 model, ST7701S init over SPI (48/47)
- GT911 touch mirrored like the image, backlight on GPIO38
- clock and status pages (status until Home Assistant connects),
  same navigation bar as the other LVGL displays
- IPv6-only network
2026-09-27 23:13:33 +02:00
randy cc2ab4c467 Move more devices to the IPv6-only network
Add common/ipv6_only.yaml to mcu-clock-1, mcu-desk-lamp, mcu-dimmer,
mcu-fireplace, mcu-gate, mcu-heater, mcu-kitchen, mcu-mains-power,
mcu-nspanel-office-r, mcu-office-r, mcu-patio, mcu-pow-dishwasher,
mcu-rf, mcu-shed, mcu-well.
2026-09-27 22:51:47 +02:00
randy 504aa06d10 mcu-lvgl, mcu-lvgl-trainer: status page, colored status lines
mcu-lvgl (CYD 4.3"):
- normalized: rpi_dpi_rgb -> mipi_rgb (ESP32-8048S050 pins/timing, pclk
  12.5 MHz), GT911 touch added, platformio_options and default CPU
  frequency dropped, demo meters and duplicate uptime sensors removed
- pages: clock, status (logo, WiFi, IPv6 first, HA, version, uptime);
  status page shown until Home Assistant connects
- same navigation bar and theme as mcu-lvgl-trainer

mcu-lvgl-trainer:
- status lines colored like on the other displays (connected green,
  waiting orange, details gray), stacked in a flex column
- stopwatch format warning fixed
2026-09-27 22:51:47 +02:00
randy 4b6544adfa mcu-tft: status screen until Home Assistant connects
Same boot screen as on the other displays: "Made for ESPHome" logo,
name, WiFi with signal, IPv6 addresses first (link-local skipped),
Home Assistant state, ESPHome version and uptime.
2026-09-27 21:26:07 +02:00
randy 6a51099789 common: ipv6_only package for the IPv6-only IoT network
- common/wifi.yaml: the network gets id wifi_iot, so devices can
  override it with '!extend wifi_iot'
- common/ipv6_only.yaml: add-on package next to any common package;
  switches to the IPv6-only SSID, wifi ipv6_only (SLAAC, no DHCPv4,
  esphome/esphome#19750 as external component until merged) and
  min_ipv6_addr_count: 1
- mcu-lvgl-trainer, mcu-nspanel-dev, mcu-rfid-reader, mcu-stackchan,
  mcu-tft moved to the IPv6-only network
2026-09-27 21:26:02 +02:00
randy 369578a63a mcu-nspanel-office-j: dim the display at night like the bedroom panel
CI / ESPHome config validation (push) Failing after 8s
CI / YAML lint (push) Failing after 38s
Inactive display dim follows the sun (5 % by day, off after dusk at
-6 degrees) and stays at 5 % while light.office_j_lights is on.
Re-applied on time sync, at dawn/dusk and when the lights change.
2026-09-27 20:36:59 +02:00
randy bdf49db1c2 mcu-lvgl-trainer: six pages for the pain cave, fix touch rotation
- touch: drop calibration/transform, the driver defaults to the native
  320x480 panel and LVGL rotates touch points itself (taps were rotated
  by 90 degrees)
- pages: clock (default), basement lights with brightness and color
  temperature for the exercise room (sliders hidden while it is off),
  training start/stop, fan off/50-100 %, stopwatch, status screen with
  "Made for ESPHome" logo and IPv6 first
- light attributes read as text, 'None' while off is skipped
- unused fonts, colors and leftovers removed; ext_uart kept for a
  future ANT+ bridge
2026-09-27 20:31:37 +02:00
randy 349f699572 mcu-stackchan: "Made for ESPHome" logo, IPv6 on status screen
- status screen logo replaced by the official "Made for ESPHome" badge,
  the plain ESPHome logo is not meant for third-party devices
- status screen shows one global IPv6 and one IPv4 address (link-local
  skipped) instead of the IPv4 sensor value
2026-09-27 19:49:57 +02:00
randy 329830e553 mcu-m5paper: rework on M5Stack components, battery mode with forecast
Rewrite of the old config on the core ESPHome components (it8951, gt911,
bm8563, sht3xd):
- USB: always on, forecast screen redrawn every 15 min, forecast
  refreshed every hour and when Home Assistant connects
- battery: BM8563 timer wakes the board every 15 min (off 1:00-5:00),
  it draws the stored forecast and cuts power; WiFi only when the
  forecast is older than 6 h
- mode detected by trying to power off; battery voltage drop on USB
  triggers the battery cycle
- hourly forecast via weather.get_forecasts, 48 h kept in the FM24C02
  EEPROM (include/m5paper_weather.h)
- OTA image confirmed before power-off, battery wake-ups are shorter
  than boot_is_good_after
- status screen with "Made for ESPHome" logo, IPv6 addresses first
2026-09-27 19:49:57 +02:00
randy d813717d2c common: enable ha_time in common packages, devices extend it
Move the homeassistant time platform (id ha_time) into all common
packages. Devices with custom triggers now use '!extend ha_time';
plain duplicate time blocks are removed. mcu-lvgl's unused time_comp
is unified to ha_time.
2026-09-27 16:43:35 +02:00
49 changed files with 2272 additions and 1564 deletions

No files matched your search

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1194" height="240" fill="none" viewBox="0 0 1194 240"><g clip-path="url(#clip0_2_63)"><path fill="#18BCF2" d="M240 219C240 227.24 233.24 234 225 234H15C6.76 234 0 227.24 0 219V129C0 120.76 4.78 109.22 10.6 103.4L109.4 4.61999C115.24 -1.22001 124.78 -1.22001 130.62 4.61999L229.4 103.4C235.24 109.24 240 120.76 240 129V219Z"/><path fill="#F2F4F9" d="M160 78.66H80C76.68 78.66 74 81.34 74 84.66V234H86V90.66H154V102.66H104C100.68 102.66 98 105.34 98 108.66V132.66C98 135.98 100.68 138.66 104 138.66H154V150.66H104C100.68 150.66 98 153.34 98 156.66V180.66C98 183.98 100.68 186.66 104 186.66H154V198.66H104C100.68 198.66 98 201.34 98 204.66C98 207.98 100.68 210.66 104 210.66H160C163.32 210.66 166 207.98 166 204.66V180.66C166 177.34 163.32 174.66 160 174.66H110V162.66H160C163.32 162.66 166 159.98 166 156.66V132.66C166 129.34 163.32 126.66 160 126.66H110V114.66H160C163.32 114.66 166 111.98 166 108.66V84.66C166 81.34 163.32 78.66 160 78.66Z"/><path fill="#F2F4F9" d="M423 167.61V190.11H336V49.95H423V72.63H359.82V108.91H416.26V130.01H359.82V167.61H423Z"/><path fill="#F2F4F9" d="M491.08 47.41C503.9 47.41 514.54 50.53 523.04 56.79C531.54 63.03 536.76 71.39 538.7 81.83L516.3 87.93C515.04 81.93 512.12 77.31 507.54 74.05C502.94 70.81 497.28 69.17 490.52 69.17C483.26 69.17 477.52 70.91 473.28 74.37C469.02 77.83 466.9 82.45 466.9 88.19C466.9 97.25 472.52 103.13 483.78 105.81L506.66 111.53C518.48 114.59 527.32 119.45 533.2 126.11C539.08 132.77 542.02 140.97 542.02 150.71C542.02 163.21 537.48 173.31 528.42 181.03C519.36 188.75 507.24 192.61 492.04 192.61C478.36 192.61 466.78 189.39 457.36 182.95C448.04 176.27 442.54 167.45 440.86 156.51L463.26 150.69C464.08 156.87 467.16 161.73 472.5 165.23C477.84 168.73 484.64 170.47 492.9 170.47C501.16 170.47 507.08 168.79 511.6 165.45C516.14 162.11 518.4 157.57 518.4 151.81C518.4 142.81 512.78 136.87 501.52 133.99L478.64 128.45C466.88 125.77 458.06 120.99 452.16 114.15C446.26 107.31 443.3 98.95 443.3 89.07C443.3 76.69 447.7 66.65 456.48 58.93C465.26 51.21 476.8 47.35 491.12 47.35L491.08 47.41Z"/><path fill="#F2F4F9" d="M666.1 95.61C666.1 109.37 661.92 120.49 653.58 128.99C645.24 137.49 634 141.75 619.88 141.75H588.38V190.13H564.56V49.95H620.06C634.3 49.95 645.54 54.07 653.76 62.27C661.98 70.49 666.08 81.59 666.08 95.59L666.1 95.61ZM641.16 95.05C641.16 87.99 638.96 82.25 634.56 77.85C630.16 73.45 623.92 71.25 615.86 71.25H588.4V120.85H615.5C623.74 120.85 630.1 118.51 634.54 113.81C638.98 109.13 641.2 102.87 641.2 95.05H641.16Z"/><path fill="#F2F4F9" d="M687.38 49.95H711.2V108.55L772.98 108.73V49.95H796.98V190.11H772.98V129.83L711.2 129.65V190.11H687.38V49.95Z"/><path fill="#F2F4F9" d="M868.6 92.79C883.1 92.79 894.92 97.45 904.04 106.75C913.16 116.07 917.72 128.03 917.72 142.65C917.72 157.27 913.16 169.15 904.04 178.47C894.92 187.79 883.1 192.43 868.6 192.43C854.1 192.43 842.1 187.77 832.98 178.47C823.86 169.15 819.3 157.23 819.3 142.65C819.3 128.07 823.86 115.97 832.98 106.69C842.1 97.41 853.98 92.77 868.6 92.77V92.79ZM868.6 172.01C876.34 172.01 882.64 169.27 887.5 163.81C892.34 158.35 894.76 151.23 894.76 142.49C894.76 133.75 892.34 126.67 887.5 121.25C882.66 115.85 876.36 113.15 868.6 113.15C860.84 113.15 854.26 115.85 849.38 121.25C844.5 126.65 842.06 133.73 842.06 142.49C842.06 151.25 844.5 158.35 849.38 163.81C854.26 169.27 860.66 172.01 868.6 172.01Z"/><path fill="#F2F4F9" d="M1081.6 129.83V190.11H1059.56V133.29C1059.56 126.73 1057.88 121.61 1054.5 117.91C1051.12 114.23 1046.54 112.37 1040.72 112.37C1034.54 112.37 1029.6 114.39 1025.9 118.41C1022.22 122.45 1020.36 128.09 1020.36 135.33V190.09H998.04V133.27C998.04 126.71 996.4 121.59 993.12 117.89C989.84 114.21 985.3 112.35 979.48 112.35C973.3 112.35 968.32 114.37 964.58 118.39C960.84 122.41 958.96 128.07 958.96 135.31V190.07H936.28V95.39H957.38L958.14 104.49C963.64 96.43 972.6 92.39 985.04 92.39C992.28 92.39 998.58 93.89 1003.88 96.89C1009.2 99.89 1013.22 104.27 1015.98 110.01C1018.3 104.51 1022.12 100.19 1027.46 97.07C1032.8 93.95 1039.16 92.39 1046.54 92.39C1057.28 92.39 1065.82 95.71 1072.14 102.33C1078.46 108.95 1081.6 118.11 1081.6 129.79V129.83Z"/><path fill="#F2F4F9" d="M1193.54 151.11H1120.98C1121.74 158.23 1124.42 163.67 1129.04 167.43C1133.66 171.19 1139.54 173.05 1146.66 173.05C1158.22 173.05 1166.32 168.23 1170.94 158.61L1189.88 166.11C1186.32 174.43 1180.7 180.93 1173.06 185.61C1165.4 190.29 1156.6 192.65 1146.66 192.65C1132.66 192.65 1121.3 188.07 1112.58 178.91C1103.86 169.75 1099.5 157.73 1099.5 142.87C1099.5 128.01 1103.9 115.91 1112.68 106.59C1121.46 97.27 1132.94 92.63 1147.14 92.63C1161.34 92.63 1172.3 97.23 1180.8 106.41C1189.3 115.59 1193.56 127.69 1193.56 142.69V151.13L1193.54 151.11ZM1121.36 135.17H1170.48C1170.04 127.49 1167.72 121.63 1163.5 117.59C1159.28 113.55 1153.6 111.55 1146.48 111.55C1139.36 111.55 1133.66 113.59 1129.24 117.69C1124.8 121.79 1122.18 127.61 1121.36 135.17Z"/></g><defs><clipPath id="clip0_2_63"><rect width="1193.56" height="240" fill="#fff"/></cliLine truncated

Before

Width:  |  Height:  |  Size: 4.9 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_2" data-name="Layer 2" viewBox="0 0 681.98 201.98"><defs><style>.cls-1{fill:none;stroke:#1d2126;stroke-miterlimit:10;stroke-width:1.01px}.cls-2{fill:#1d2126}.cls-2,.cls-3,.cls-4{stroke-width:0}.cls-3{fill:#f2f4f9}.cls-4{fill:#18bcf2}</style></defs><g id="Layer_1-2" data-name="Layer 1"><g><g><rect width="680.97" height="200.97" x=".51" y=".51" class="cls-3" rx="40.48" ry="40.48"/><rect width="680.97" height="200.97" x=".51" y=".51" class="cls-1" rx="40.48" ry="40.48"/></g><g><path d="M236.13,43.11v26.53h-5.29v-14.5l-5.68,14.5h-5.08l-5.61-14.4v14.4h-5.12v-26.53h5.12l8.16,20.27,8.21-20.27h5.29Z" class="cls-4"/><path d="M259.54,63.84h-9.04l-2.02,5.8h-5.59l9.51-26.53h5.33l9.47,26.53h-5.7l-1.97-5.8ZM258,59.34l-2.95-8.7-2.99,8.7h5.94Z" class="cls-4"/><path d="M296.93,56.42c.02,1.89-.3,3.66-.98,5.31s-1.61,3.06-2.8,4.22-2.61,2.07-4.26,2.73-3.4.98-5.26.96h-9.65v-26.53h9.65c2.43-.04,4.66.52,6.71,1.68s3.67,2.76,4.86,4.82,1.78,4.33,1.74,6.81ZM291.25,56.42c0-2.46-.71-4.46-2.12-5.99s-3.27-2.29-5.58-2.29h-4.22v16.52h4.22c2.34,0,4.21-.75,5.61-2.26s2.09-3.5,2.09-5.99Z" class="cls-4"/><path d="M321.38,64.59v5.04h-16.7v-26.53h16.7v5.1h-11.34v5.66h10.07v4.75h-10.07v5.98h11.34Z" class="cls-4"/><path d="M348.35,48.17v6.1h9.58l-.04,4.82h-9.54v10.55h-5.36v-26.53h16.73l-.04,5.06h-11.34Z" class="cls-4"/><path d="M378.62,42.64c1.9,0,3.66.34,5.27,1.03s2.99,1.63,4.11,2.84,2,2.66,2.63,4.35.94,3.52.94,5.49c.02,1.95-.28,3.77-.91,5.48s-1.52,3.17-2.65,4.39-2.52,2.17-4.15,2.86-3.39,1.03-5.27,1c-2.5.04-4.74-.54-6.72-1.72s-3.52-2.82-4.6-4.92-1.6-4.45-1.56-7.05c-.02-1.95.28-3.77.91-5.48s1.51-3.18,2.65-4.39,2.51-2.17,4.13-2.87,3.37-1.03,5.23-1ZM378.65,64.82c2.24,0,4.01-.76,5.32-2.28s1.96-3.58,1.96-6.2-.65-4.71-1.95-6.22-3.08-2.27-5.33-2.27-4.03.76-5.33,2.27-1.95,3.59-1.95,6.22.65,4.71,1.95,6.21,3.08,2.26,5.33,2.26Z" class="cls-4"/><path d="M419.5,69.64h-6.01l-4.08-9.58h-4.89v9.58h-5.36v-26.53h10.21c3.28.01,5.76.74,7.44,2.19s2.51,3.55,2.51,6.3c0,1.79-.39,3.34-1.17,4.65s-1.89,2.28-3.33,2.91l4.68,10.48ZM413.63,51.6c0-1.15-.36-2.05-1.07-2.72s-1.78-.99-3.18-.99h-4.85v7.49h4.85c1.41,0,2.47-.34,3.18-1.01s1.07-1.6,1.07-2.77Z" class="cls-4"/></g><g><path d="M251.92,146.83v11.25h-43.5v-70.08h43.5v11.34h-31.59v18.14h28.22v10.55h-28.22v18.8h31.59Z" class="cls-2"/><path d="M285,86.74c6.41,0,11.73,1.56,15.98,4.69,4.25,3.12,6.86,7.3,7.83,12.52l-11.2,3.05c-.62-3-2.09-5.31-4.38-6.94-2.3-1.62-5.13-2.44-8.51-2.44-3.62,0-6.5.87-8.62,2.6s-3.19,4.04-3.19,6.91c0,4.53,2.81,7.47,8.44,8.81l11.44,2.86c5.91,1.53,10.33,3.96,13.27,7.29s4.41,7.43,4.41,12.3c0,6.25-2.27,11.3-6.8,15.16-4.53,3.86-10.59,5.79-18.19,5.79-6.84,0-12.62-1.61-17.34-4.83-4.66-3.34-7.41-7.75-8.25-13.22l11.2-2.91c.41,3.09,1.95,5.52,4.62,7.27,2.67,1.75,6.07,2.62,10.2,2.62s7.09-.84,9.35-2.51c2.27-1.67,3.4-3.94,3.4-6.82,0-4.5-2.81-7.47-8.44-8.91l-11.44-2.77c-5.88-1.34-10.29-3.73-13.24-7.15s-4.43-7.6-4.43-12.54c0-6.19,2.2-11.21,6.59-15.07,4.39-3.86,10.16-5.79,17.32-5.79Z" class="cls-2"/><path d="M371.55,110.83c0,6.88-2.09,12.44-6.26,16.69-4.17,4.25-9.79,6.38-16.85,6.38h-15.75v24.19h-11.91v-70.08h27.75c7.12,0,12.74,2.06,16.85,6.16,4.11,4.11,6.16,9.66,6.16,16.66ZM359.08,110.55c0-3.53-1.1-6.4-3.3-8.6s-5.32-3.3-9.35-3.3h-13.73v24.8h13.55c4.12,0,7.3-1.17,9.52-3.52s3.33-5.47,3.33-9.38Z" class="cls-2"/><path d="M381.23,88h11.91v29.3l30.89.09v-29.39h12v70.08h-12v-30.14l-30.89-.09v30.23h-11.91v-70.08Z" class="cls-2"/><path d="M470.88,109.43c7.25,0,13.16,2.33,17.72,6.98,4.56,4.66,6.84,10.64,6.84,17.95s-2.28,13.25-6.84,17.91c-4.56,4.66-10.47,6.98-17.72,6.98s-13.25-2.33-17.81-6.98c-4.56-4.66-6.84-10.62-6.84-17.91s2.28-13.34,6.84-17.98,10.5-6.96,17.81-6.96ZM470.88,149.04c3.88,0,7.02-1.37,9.45-4.1,2.42-2.73,3.63-6.29,3.63-10.66s-1.21-7.91-3.63-10.62c-2.42-2.7-5.57-4.05-9.45-4.05s-7.17,1.35-9.61,4.05c-2.44,2.7-3.66,6.24-3.66,10.62s1.22,7.93,3.66,10.66c2.44,2.73,5.64,4.1,9.61,4.1Z" class="cls-2"/><path d="M576.42,127.94v30.14h-11.02v-28.41c0-3.28-.84-5.84-2.53-7.69-1.69-1.84-3.98-2.77-6.89-2.77-3.09,0-5.56,1.01-7.41,3.02s-2.77,4.84-2.77,8.46v27.38h-11.16v-28.41c0-3.28-.82-5.84-2.46-7.69-1.64-1.84-3.91-2.77-6.82-2.77-3.09,0-5.58,1.01-7.45,3.02s-2.81,4.84-2.81,8.46v27.38h-11.34v-47.34h10.55l.38,4.55c2.75-4.03,7.23-6.05,13.45-6.05,3.62,0,6.77.75,9.42,2.25s4.67,3.69,6.05,6.56c1.16-2.75,3.07-4.91,5.74-6.47,2.67-1.56,5.85-2.34,9.54-2.34,5.38,0,9.64,1.66,12.8,4.97,3.16,3.31,4.73,7.89,4.73,13.73Z" class="cls-2"/><path d="M631.43,138.58h-36.28c.38,3.56,1.72,6.28,4.03,8.16s5.25,2.81,8.81,2.81c5.78,0,9.83-2.41,12.14-7.22l9.47,3.75c-1.78,4.16-4.59,7.41-8.41,9.75s-8.23,3.52-13.2,3.52c-7,0-12.68-2.29-17.04-6.87-4.36-4.58-6.54-10.59-6.54-18.02s2.2-13.48,6.59-18.14c4.39-4.66,10.13-6.98,17.23-6.98s12.58,2.3,16.83,6.89,6.38,10.64,6.38,18.14v4.22ZM595.34,130.61h24.56c-.22-3.84-1.38-6.77-3.49-8.79s-4.95-3.02-8.51-3.02-6.41,1.02-8.62,3.07c-2.22,2.05-3.53,4.96-3.94,8.74Z" class="cls-2"/></g><g><path d="M170.55,153.49c0,4.12-3.38,7.5-7.5,7.5H58.05c-4.12,0-7.5-3.38-7.5-7.5v-45c0-4.12,2.39-9.89,5.3-12Line truncated

After

Width:  |  Height:  |  Size: 5.3 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_2" data-name="Layer 2" viewBox="0 0 681.98 201.98"><defs><style>.cls-1{fill:#1d2126}.cls-1,.cls-2,.cls-3{stroke-width:0}.cls-2{fill:#f2f4f9}.cls-3{fill:#18bcf2}.cls-4{fill:none;stroke:#f2f4f9;stroke-miterlimit:10;stroke-width:1.01px}</style></defs><g id="Layer_1-2" data-name="Layer 1"><g><g><rect width="680.97" height="200.97" x=".51" y=".51" class="cls-1" rx="40.48" ry="40.48"/><rect width="680.97" height="200.97" x=".51" y=".51" class="cls-4" rx="40.48" ry="40.48"/></g><g><path d="M236.13,43.11v26.53h-5.29v-14.5l-5.68,14.5h-5.08l-5.61-14.4v14.4h-5.12v-26.53h5.12l8.16,20.27,8.21-20.27h5.29Z" class="cls-3"/><path d="M259.54,63.84h-9.04l-2.02,5.8h-5.59l9.51-26.53h5.33l9.47,26.53h-5.7l-1.97-5.8ZM258,59.34l-2.95-8.7-2.99,8.7h5.94Z" class="cls-3"/><path d="M296.93,56.42c.02,1.89-.3,3.66-.98,5.31s-1.61,3.06-2.8,4.22-2.61,2.07-4.26,2.73-3.4.98-5.26.96h-9.65v-26.53h9.65c2.43-.04,4.66.52,6.71,1.68s3.67,2.76,4.86,4.82,1.78,4.33,1.74,6.81ZM291.25,56.42c0-2.46-.71-4.46-2.12-5.99s-3.27-2.29-5.58-2.29h-4.22v16.52h4.22c2.34,0,4.21-.75,5.61-2.26s2.09-3.5,2.09-5.99Z" class="cls-3"/><path d="M321.38,64.59v5.04h-16.7v-26.53h16.7v5.1h-11.34v5.66h10.07v4.75h-10.07v5.98h11.34Z" class="cls-3"/><path d="M348.35,48.17v6.1h9.58l-.04,4.82h-9.54v10.55h-5.36v-26.53h16.73l-.04,5.06h-11.34Z" class="cls-3"/><path d="M378.62,42.64c1.9,0,3.66.34,5.27,1.03s2.99,1.63,4.11,2.84,2,2.66,2.63,4.35.94,3.52.94,5.49c.02,1.95-.28,3.77-.91,5.48s-1.52,3.17-2.65,4.39-2.52,2.17-4.15,2.86-3.39,1.03-5.27,1c-2.5.04-4.74-.54-6.72-1.72s-3.52-2.82-4.6-4.92-1.6-4.45-1.56-7.05c-.02-1.95.28-3.77.91-5.48s1.51-3.18,2.65-4.39,2.51-2.17,4.13-2.87,3.37-1.03,5.23-1ZM378.65,64.82c2.24,0,4.01-.76,5.32-2.28s1.96-3.58,1.96-6.2-.65-4.71-1.95-6.22-3.08-2.27-5.33-2.27-4.03.76-5.33,2.27-1.95,3.59-1.95,6.22.65,4.71,1.95,6.21,3.08,2.26,5.33,2.26Z" class="cls-3"/><path d="M419.5,69.64h-6.01l-4.08-9.58h-4.89v9.58h-5.36v-26.53h10.21c3.28.01,5.76.74,7.44,2.19s2.51,3.55,2.51,6.3c0,1.79-.39,3.34-1.17,4.65s-1.89,2.28-3.33,2.91l4.68,10.48ZM413.63,51.6c0-1.15-.36-2.05-1.07-2.72s-1.78-.99-3.18-.99h-4.85v7.49h4.85c1.41,0,2.47-.34,3.18-1.01s1.07-1.6,1.07-2.77Z" class="cls-3"/></g><g><path d="M251.92,146.83v11.25h-43.5v-70.08h43.5v11.34h-31.59v18.14h28.22v10.55h-28.22v18.8h31.59Z" class="cls-2"/><path d="M285,86.74c6.41,0,11.73,1.56,15.98,4.69,4.25,3.12,6.86,7.3,7.83,12.52l-11.2,3.05c-.62-3-2.09-5.31-4.38-6.94-2.3-1.62-5.13-2.44-8.51-2.44-3.62,0-6.5.87-8.62,2.6s-3.19,4.04-3.19,6.91c0,4.53,2.81,7.47,8.44,8.81l11.44,2.86c5.91,1.53,10.33,3.96,13.27,7.29s4.41,7.43,4.41,12.3c0,6.25-2.27,11.3-6.8,15.16-4.53,3.86-10.59,5.79-18.19,5.79-6.84,0-12.62-1.61-17.34-4.83-4.66-3.34-7.41-7.75-8.25-13.22l11.2-2.91c.41,3.09,1.95,5.52,4.62,7.27,2.67,1.75,6.07,2.62,10.2,2.62s7.09-.84,9.35-2.51c2.27-1.67,3.4-3.94,3.4-6.82,0-4.5-2.81-7.47-8.44-8.91l-11.44-2.77c-5.88-1.34-10.29-3.73-13.24-7.15s-4.43-7.6-4.43-12.54c0-6.19,2.2-11.21,6.59-15.07,4.39-3.86,10.16-5.79,17.32-5.79Z" class="cls-2"/><path d="M371.55,110.83c0,6.88-2.09,12.44-6.26,16.69-4.17,4.25-9.79,6.38-16.85,6.38h-15.75v24.19h-11.91v-70.08h27.75c7.12,0,12.74,2.06,16.85,6.16,4.11,4.11,6.16,9.66,6.16,16.66ZM359.08,110.55c0-3.53-1.1-6.4-3.3-8.6s-5.32-3.3-9.35-3.3h-13.73v24.8h13.55c4.12,0,7.3-1.17,9.52-3.52s3.33-5.47,3.33-9.38Z" class="cls-2"/><path d="M381.23,88h11.91v29.3l30.89.09v-29.39h12v70.08h-12v-30.14l-30.89-.09v30.23h-11.91v-70.08Z" class="cls-2"/><path d="M470.88,109.43c7.25,0,13.16,2.33,17.72,6.98,4.56,4.66,6.84,10.64,6.84,17.95s-2.28,13.25-6.84,17.91c-4.56,4.66-10.47,6.98-17.72,6.98s-13.25-2.33-17.81-6.98c-4.56-4.66-6.84-10.62-6.84-17.91s2.28-13.34,6.84-17.98,10.5-6.96,17.81-6.96ZM470.88,149.04c3.88,0,7.02-1.37,9.45-4.1,2.42-2.73,3.63-6.29,3.63-10.66s-1.21-7.91-3.63-10.62c-2.42-2.7-5.57-4.05-9.45-4.05s-7.17,1.35-9.61,4.05c-2.44,2.7-3.66,6.24-3.66,10.62s1.22,7.93,3.66,10.66c2.44,2.73,5.64,4.1,9.61,4.1Z" class="cls-2"/><path d="M576.42,127.94v30.14h-11.02v-28.41c0-3.28-.84-5.84-2.53-7.69-1.69-1.84-3.98-2.77-6.89-2.77-3.09,0-5.56,1.01-7.41,3.02s-2.77,4.84-2.77,8.46v27.38h-11.16v-28.41c0-3.28-.82-5.84-2.46-7.69-1.64-1.84-3.91-2.77-6.82-2.77-3.09,0-5.58,1.01-7.45,3.02s-2.81,4.84-2.81,8.46v27.38h-11.34v-47.34h10.55l.38,4.55c2.75-4.03,7.23-6.05,13.45-6.05,3.62,0,6.77.75,9.42,2.25s4.67,3.69,6.05,6.56c1.16-2.75,3.07-4.91,5.74-6.47,2.67-1.56,5.85-2.34,9.54-2.34,5.38,0,9.64,1.66,12.8,4.97,3.16,3.31,4.73,7.89,4.73,13.73Z" class="cls-2"/><path d="M631.43,138.58h-36.28c.38,3.56,1.72,6.28,4.03,8.16s5.25,2.81,8.81,2.81c5.78,0,9.83-2.41,12.14-7.22l9.47,3.75c-1.78,4.16-4.59,7.41-8.41,9.75s-8.23,3.52-13.2,3.52c-7,0-12.68-2.29-17.04-6.87-4.36-4.58-6.54-10.59-6.54-18.02s2.2-13.48,6.59-18.14c4.39-4.66,10.13-6.98,17.23-6.98s12.58,2.3,16.83,6.89,6.38,10.64,6.38,18.14v4.22ZM595.34,130.61h24.56c-.22-3.84-1.38-6.77-3.49-8.79s-4.95-3.02-8.51-3.02-6.41,1.02-8.62,3.07c-2.22,2.05-3.53,4.96-3.94,8.74Z" class="cls-2"/></g><g><path d="M170.55,153.49c0,4.12-3.38,7.5-7.5,7.5H58.05c-4.12,0-7.5-3.38-7.5-7.5v-45c0-4.12,2.39-9.89,5.3-12Line truncated

After

Width:  |  Height:  |  Size: 5.3 KiB

+2 -2
View File
@@ -24,8 +24,8 @@ text_sensor:
# - <<: !include text_sensor/uptime.yaml # - <<: !include text_sensor/uptime.yaml
- <<: !include text_sensor/wifi_info.yaml - <<: !include text_sensor/wifi_info.yaml
# time: time:
#. - <<: !include time/homeassistant.yaml - <<: !include time/homeassistant.yaml
network: network:
enable_ipv6: true enable_ipv6: true
+3
View File
@@ -24,5 +24,8 @@ text_sensor:
- <<: !include text_sensor/version.yaml - <<: !include text_sensor/version.yaml
- <<: !include text_sensor/wifi_info.yaml - <<: !include text_sensor/wifi_info.yaml
time:
- <<: !include time/homeassistant.yaml
network: network:
enable_ipv6: true enable_ipv6: true
+3
View File
@@ -23,5 +23,8 @@ text_sensor:
- <<: !include text_sensor/version.yaml - <<: !include text_sensor/version.yaml
- <<: !include text_sensor/wifi_info.yaml - <<: !include text_sensor/wifi_info.yaml
time:
- <<: !include time/homeassistant.yaml
network: network:
enable_ipv6: true enable_ipv6: true
+3
View File
@@ -21,5 +21,8 @@ text_sensor:
- <<: !include text_sensor/version.yaml - <<: !include text_sensor/version.yaml
- <<: !include text_sensor/ethernet_info.yaml - <<: !include text_sensor/ethernet_info.yaml
time:
- <<: !include time/homeassistant.yaml
network: network:
enable_ipv6: true enable_ipv6: true
+3
View File
@@ -27,3 +27,6 @@ text_sensor:
- <<: !include text_sensor/git_ref.yaml - <<: !include text_sensor/git_ref.yaml
- <<: !include text_sensor/version.yaml - <<: !include text_sensor/version.yaml
- <<: !include text_sensor/wifi_info.yaml - <<: !include text_sensor/wifi_info.yaml
time:
- <<: !include time/homeassistant.yaml
+38
View File
@@ -0,0 +1,38 @@
---
# IPv6-only IoT network (SLAAC, no DHCPv4); add next to a common package:
# packages:
# common: !include common/common.yaml
# ipv6_only: !include common/ipv6_only.yaml
external_components:
# network enable_ipv4 (esphome/esphome#19750), drop once merged
- source: github://pr#19750
components: [const, network, wifi]
# audio HTTP reader over IPv6 (dual-stack hosts resolve A first), drop once in #19750
- source: github://randybb/esphome-components
components: [audio]
refresh: 1d
wifi:
networks:
- id: !extend wifi_iot
ssid: !secret wifi_ssid_ipv6
password: !secret wifi_password_ipv6
esp32:
framework:
components:
# mdns 1.12.0 + fix for "A 0.0.0.0" without IPv4 (espressif/esp-protocols PR pending), drop once released
- name: espressif/mdns
source: https://github.com/randybb/esp-protocols.git
ref: mdns-v1.12.0-zero-ipv4-test
path: components/mdns
# IDF v5.5.5 lwip + .local resolution over IPv6 (espressif/esp-lwip#94), drop once in IDF
- name: lwip
source: https://github.com/randybb/esp-lwip.git
ref: idf-v5.5.5-lwip-component
path: lwip
network:
enable_ipv6: true
enable_ipv4: false
min_ipv6_addr_count: 1
+162
View File
@@ -0,0 +1,162 @@
---
# Sonoff NSPanel with the native Lovelace UI (randybb/esphome-nspanel-lovelace-native).
# The device file adds the cards, buttons and relays:
# packages:
# common: !include common/common.yaml
# nspanel: !include
# file: common/nspanel.yaml
# vars:
# temperature_entity: sensor.zigbee_bedroom_temperature
# nspanel_lovelace:
# cards: [...]
defaults:
temperature_name: Temperature
ntc_reference_temperature: 25°C
esphome:
on_boot:
then:
- switch.turn_off: screen_power
- delay: 1s
- switch.turn_on: screen_power
- delay: 2s
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
esp32:
variant: esp32
toolchain: esp-idf
framework:
type: esp-idf
advanced:
minimum_chip_revision: 3.0
sram1_as_iram: true
external_components:
- source:
type: git
url: https://github.com/randybb/esphome-nspanel-lovelace-native
ref: devices
refresh: 0s
components: [nspanel_lovelace]
api:
homeassistant_services: true
homeassistant_states: true
actions:
- action: play_rtttl
variables:
song: string
then:
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify
variables:
heading: string
message: string
song: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify_fullscreen
variables:
title: string
description: string
button1: string
button2: string
timeout: int
icon: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
uart:
id: display_uart
tx_pin: 16
rx_pin: 17
baud_rate: 115200
nspanel_lovelace:
id: nspanel_id
sleep_timeout: 10
experimental_vertical_ui: true
locale:
language: sk
temperature_unit: celcius
screensaver:
time_id: ha_time
time_format: '%H:%M'
date_format: '%A, %d. %b. %Y'
weather:
entity_id: sensor.weather_forecast_daily
icon_color: [255, 255, 255]
entities:
- entity_id: ${temperature_entity}
icon:
value: home-thermometer-outline
color: [255, 255, 255]
precision: 1
output:
- platform: ledc
id: buzzer_out
pin:
number: 21
switch:
- platform: gpio
name: Screen Power
id: screen_power
entity_category: config
pin:
number: 4
inverted: true
restore_mode: ALWAYS_ON
rtttl:
id: buzzer
output: buzzer_out
sensor:
- platform: adc
id: ntc_source
pin: 38
update_interval: 5min
attenuation: 12db
- platform: resistance
id: resistance_sensor
sensor: ntc_source
configuration: DOWNSTREAM
resistor: 11.2kOhm
- platform: ntc
name: ${temperature_name}
id: temperature
sensor: resistance_sensor
calibration:
b_constant: 3950
reference_temperature: ${ntc_reference_temperature}
reference_resistance: 10kOhm
text_sensor:
- platform: homeassistant
id: name_day
entity_id: sensor.nameday_sk
on_value:
- lambda: |-
id(nspanel_id).set_time_format("%H:%M~" + x);
id(nspanel_id).update_time();
# HMI protocol version reported by the TFT on startup (52/53 for this UI)
- platform: template
name: TFT Version
entity_category: diagnostic
update_interval: 60s
lambda: |-
using esphome::nspanel_lovelace::Configuration;
const auto ver = Configuration::get_version();
if (ver == 0) return {"unknown"};
return {std::to_string(ver) + " (" + Configuration::get_model_str() + ")"};
+42
View File
@@ -0,0 +1,42 @@
---
# NSPanel screensaver dims to 0 at night (sun below -6°), unless the room lights are on.
# packages:
# sleep_dim: !include
# file: common/nspanel_sleep_dim.yaml
# vars:
# sleep_override_entity: light.bedroom_lights
script:
- id: apply_sleep_dim
then:
- lambda: |-
uint8_t dim;
if (id(sleep_override).state) dim = 5; // sleepOverride
else if (id(sun_id).is_above_horizon(-6)) dim = 5; // day
else dim = 0; // night
id(nspanel_id).set_display_inactive_dim(dim);
time:
- id: !extend ha_time
on_time_sync:
- script.execute: apply_sleep_dim
binary_sensor:
- platform: homeassistant # sleepOverride
id: sleep_override
entity_id: ${sleep_override_entity}
on_state:
- script.execute: apply_sleep_dim
sun:
id: sun_id
time_id: ha_time
latitude: 48.15°
longitude: 17.11°
on_sunrise:
- elevation: -6°
then:
- script.execute: apply_sleep_dim
on_sunset:
- elevation: -6°
then:
- script.execute: apply_sleep_dim
+3 -1
View File
@@ -2,7 +2,9 @@
wifi: wifi:
# reboot_timeout: 0s # reboot_timeout: 0s
networks: networks:
- ssid: !secret wifi_ssid # id lets devices override the network with '!extend wifi_iot'
- id: wifi_iot
ssid: !secret wifi_ssid
password: !secret wifi_password password: !secret wifi_password
enable_btm: true enable_btm: true
enable_rrm: true enable_rrm: true
+176
View File
@@ -0,0 +1,176 @@
#pragma once
// Hourly weather forecast for mcu-m5paper: parsed from the weather.get_forecasts
// response and kept in the on-board FM24C02 EEPROM (256 B, I2C 0x50), so it
// survives the power-off between RTC wake-ups.
#include <cmath>
#include <cstdio>
#include <cstring>
#include "esphome/components/i2c/i2c_bus.h"
#include "esphome/components/json/json_util.h"
#include "esphome/core/hal.h"
namespace m5paper_weather {
static constexpr uint8_t EEPROM_ADDR = 0x50;
static constexpr uint8_t EEPROM_PAGE = 8;
static constexpr uint8_t MAGIC = 0xA5;
static constexpr uint8_t VERSION = 1;
static constexpr uint8_t MAX_HOURS = 48;
// magic, version, fetched_at (u32), first_ts (u32), count
static constexpr size_t HEADER = 11;
static constexpr size_t MAX_SIZE = HEADER + MAX_HOURS * 4;
static_assert(MAX_SIZE <= 256, "forecast does not fit into the EEPROM");
static constexpr uint8_t NO_VALUE = 0xFF;
static constexpr int8_t NO_TEMP = INT8_MIN;
struct Hour {
uint8_t cond; // index into CONDITIONS, NO_VALUE if unknown
int8_t temp2; // temperature in 0.5 °C, NO_TEMP if unknown
uint8_t wind; // km/h, NO_VALUE if unknown
uint8_t pop; // precipitation probability %, NO_VALUE if unknown
};
static_assert(sizeof(Hour) == 4, "Hour must stay 4 bytes");
struct Forecast {
uint32_t fetched_at{0}; // UTC epoch of the download
uint32_t first_ts{0}; // UTC epoch of hours[0]; entries are 1 h apart
uint8_t count{0};
Hour hours[MAX_HOURS]{};
bool valid() const { return this->count > 0; }
// hour covering the given UTC epoch, nullptr if outside the forecast
const Hour *at(uint32_t ts) const {
if (!this->valid() || ts < this->first_ts)
return nullptr;
uint32_t idx = (ts - this->first_ts) / 3600;
return idx < this->count ? &this->hours[idx] : nullptr;
}
};
// the forecast in RAM, loaded from the EEPROM at boot
inline Forecast &forecast() {
static Forecast fc;
return fc;
}
// Home Assistant weather conditions; the index is the stored condition code
static const char *const CONDITIONS[] = {
"clear-night", "cloudy", "exceptional", "fog", "hail", "lightning", "lightning-rainy", "partlycloudy",
"pouring", "rainy", "snowy", "snowy-rainy", "sunny", "windy", "windy-variant",
};
static const char *const CONDITION_NAMES[] = {
"Jasno", "Oblačno", "Extrémne počasie", "Hmla", "Krupobitie", "Búrka", "Búrka s dažďom", "Polooblačno",
"Lejak", "Dážď", "Sneženie", "Dážď so snehom", "Slnečno", "Veterno", "Veterno, oblačno",
};
static constexpr uint8_t NUM_CONDITIONS = sizeof(CONDITIONS) / sizeof(CONDITIONS[0]);
inline uint8_t condition_code(const char *name) {
for (uint8_t i = 0; i < NUM_CONDITIONS; i++) {
if (strcmp(name, CONDITIONS[i]) == 0)
return i;
}
return NO_VALUE;
}
inline const char *condition_name(uint8_t code) { return code < NUM_CONDITIONS ? CONDITION_NAMES[code] : "?"; }
// days since 1970-01-01 for a proleptic Gregorian date (H. Hinnant's algorithm)
inline int32_t days_from_civil(int y, int m, int d) {
y -= m <= 2;
const int era = (y >= 0 ? y : y - 399) / 400;
const int yoe = y - era * 400;
const int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
const int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + doe - 719468;
}
// "2026-09-27T16:00:00+00:00" -> UTC epoch, 0 on error
inline uint32_t parse_iso8601(const char *s) {
int y, mo, d, h, mi, sec = 0, oh = 0, om = 0;
char sign = '+';
int n = sscanf(s, "%d-%d-%dT%d:%d:%d%c%d:%d", &y, &mo, &d, &h, &mi, &sec, &sign, &oh, &om);
if (n < 5)
return 0;
int64_t ts = int64_t(days_from_civil(y, mo, d)) * 86400 + h * 3600 + mi * 60 + sec;
if (n >= 8 && (sign == '+' || sign == '-')) {
int64_t offset = oh * 3600 + om * 60;
ts += sign == '+' ? -offset : offset;
}
return ts > 0 ? uint32_t(ts) : 0;
}
// Parse the weather.get_forecasts response for the given entity;
// Home Assistant wraps the action response as {"response": {...}}
inline bool parse(ArduinoJson::JsonObjectConst response, const char *entity, uint32_t now, Forecast &fc) {
ArduinoJson::JsonObjectConst root = response["response"].is<ArduinoJson::JsonObjectConst>()
? response["response"].as<ArduinoJson::JsonObjectConst>()
: response;
ArduinoJson::JsonArrayConst items = root[entity]["forecast"];
if (items.isNull() || items.size() == 0)
return false;
Forecast out;
for (ArduinoJson::JsonObjectConst item : items) {
if (out.count >= MAX_HOURS)
break;
if (out.count == 0) {
out.first_ts = parse_iso8601(item["datetime"] | "");
if (out.first_ts == 0)
return false;
}
Hour &hour = out.hours[out.count++];
hour.cond = condition_code(item["condition"] | "");
float temp = item["temperature"] | NAN;
hour.temp2 = std::isnan(temp) ? NO_TEMP : int8_t(std::max(-127L, std::min(127L, lroundf(temp * 2))));
float wind = item["wind_speed"] | NAN;
hour.wind = std::isnan(wind) ? NO_VALUE : uint8_t(std::max(0L, std::min(254L, lroundf(wind))));
float pop = item["precipitation_probability"] | NAN;
hour.pop = std::isnan(pop) ? NO_VALUE : uint8_t(std::max(0L, std::min(100L, lroundf(pop))));
}
out.fetched_at = now;
fc = out;
return true;
}
inline bool load(esphome::i2c::I2CBus *bus, Forecast &fc) {
uint8_t buf[MAX_SIZE];
const uint8_t start = 0;
fc.count = 0;
if (bus->write_readv(EEPROM_ADDR, &start, 1, buf, sizeof(buf)) != esphome::i2c::ERROR_OK)
return false;
if (buf[0] != MAGIC || buf[1] != VERSION)
return false;
memcpy(&fc.fetched_at, buf + 2, 4);
memcpy(&fc.first_ts, buf + 6, 4);
fc.count = std::min(buf[10], MAX_HOURS);
memcpy(fc.hours, buf + HEADER, fc.count * sizeof(Hour));
return true;
}
inline bool save(esphome::i2c::I2CBus *bus, const Forecast &fc) {
uint8_t buf[MAX_SIZE];
buf[0] = MAGIC;
buf[1] = VERSION;
memcpy(buf + 2, &fc.fetched_at, 4);
memcpy(buf + 6, &fc.first_ts, 4);
buf[10] = fc.count;
memcpy(buf + HEADER, fc.hours, fc.count * sizeof(Hour));
const size_t len = HEADER + fc.count * sizeof(Hour);
// page writes must not cross an 8-byte page boundary; 5 ms write cycle per page
for (size_t off = 0; off < len; off += EEPROM_PAGE) {
uint8_t page[EEPROM_PAGE + 1];
const size_t n = std::min<size_t>(EEPROM_PAGE, len - off);
page[0] = uint8_t(off);
memcpy(page + 1, buf + off, n);
if (bus->write(EEPROM_ADDR, page, n + 1) != esphome::i2c::ERROR_OK)
return false;
esphome::delay(6);
}
return true;
}
} // namespace m5paper_weather
+1 -2
View File
@@ -491,8 +491,7 @@ sensor:
# name: Dallas Temp 4 # name: Dallas Temp 4
# one_wire_id: hub_1 # one_wire_id: hub_1
time: # time:
- !include common/time/homeassistant.yaml
# - platform: sntp # - platform: sntp
# id: sntp_time # id: sntp_time
# timezone: Europe/Bratislava # timezone: Europe/Bratislava
+1 -2
View File
@@ -139,8 +139,7 @@ switch:
id: relay_2 id: relay_2
time: time:
- platform: homeassistant - id: !extend ha_time
timezone: Europe/Bratislava
on_time: on_time:
- seconds: 0 - seconds: 0
minutes: /15 minutes: /15
-5
View File
@@ -31,11 +31,6 @@ packages:
debug: debug:
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
i2c: i2c:
# - id: i2c0 # - id: i2c0
sda: 32 sda: 32
+1 -5
View File
@@ -23,6 +23,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# GPIO21 UART TX # GPIO21 UART TX
# GPIO20 UART RX # GPIO20 UART RX
@@ -109,11 +110,6 @@ globals:
restore_value: no restore_value: no
initial_value: "false" initial_value: "false"
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
display: display:
- platform: tm1637 - platform: tm1637
id: tm1637_display id: tm1637_display
-5
View File
@@ -51,11 +51,6 @@ globals:
restore_value: no restore_value: no
initial_value: "false" initial_value: "false"
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
name: Button name: Button
+1
View File
@@ -19,6 +19,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# Mi Desk Lamp # Mi Desk Lamp
# Pin Function (Name) # Pin Function (Name)
+1
View File
@@ -34,6 +34,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# fan: # fan:
# - platform: speed # - platform: speed
-5
View File
@@ -242,11 +242,6 @@ binary_sensor:
# on_clockwise: # on_clockwise:
# - display_menu.down: # - display_menu.down:
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
# display: # display:
# - platform: tm1637 # - platform: tm1637
# id: tm1637_display # id: tm1637_display
+1 -5
View File
@@ -27,6 +27,7 @@ external_components:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# PINOUTS: M5ATOM+Atom Hub Switch # PINOUTS: M5ATOM+Atom Hub Switch
@@ -44,11 +45,6 @@ packages:
# Relay1 G22 # Relay1 G22
# Relay2 G19 # Relay2 G19
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
i2c: i2c:
# - id: i2c0 # - id: i2c0
# sda: 26 # sda: 26
+1
View File
@@ -19,6 +19,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# PINOUTS: M5ATOM # PINOUTS: M5ATOM
# IR G12 # IR G12
+2 -3
View File
@@ -37,6 +37,7 @@ external_components:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# PINOUTS: M5ATOM # PINOUTS: M5ATOM
# IR G12 # IR G12
@@ -129,9 +130,7 @@ globals:
initial_value: '20.5' initial_value: '20.5'
time: time:
- platform: homeassistant - id: !extend ha_time
id: ha_time
timezone: Europe/Bratislava
on_time: on_time:
- seconds: 0 - seconds: 0
minutes: /1 minutes: /1
+1 -5
View File
@@ -36,6 +36,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
api: api:
reboot_timeout: 300s reboot_timeout: 300s
@@ -55,11 +56,6 @@ api:
# Relay1 G22 # Relay1 G22
# Relay2 G19 # Relay2 G19
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
i2c: i2c:
# - id: i2c0 # - id: i2c0
# sda: 26 # sda: 26
+306
View File
@@ -0,0 +1,306 @@
---
substitutions:
device: lvgl-4848s040
name: LVGL 4848S040
area: DEV
comment: "${area} | Guition ESP32-S3-4848S040"
esphome:
name: mcu-${device}
friendly_name: ${name}
area: ${area}
comment: ${comment}
on_boot:
- priority: -100
then:
- lvgl.page.show: status_page
esp32:
variant: esp32s3
flash_size: 16MB
framework:
type: esp-idf
# PSRAM bandwidth for the RGB panel (not set by ESPHome); without it the
# image shifts during flash/WiFi activity
sdkconfig_options:
CONFIG_ESP32S3_DATA_CACHE_64KB: y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
CONFIG_SPIRAM_RODATA: y
psram:
mode: octal
speed: 80MHz
packages:
common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# PINOUTS: Guition ESP32-S3-4848S040, 4", 480x480, ST7701S, capacitive
## Display (RGB, pins in the mipi_rgb model)
# CS 39
# SCK 48 ST7701S init (3-wire SPI)
# SDA 47
# DE 18
# HSYNC 16
# VSYNC 17
# PCLK 21
# BL 38
## Capacitive Touch (GT911)
# TP_SDA 19
# TP_SCL 45
## Relays (86 box variant)
# RELAY1 40
# RELAY2 2
# RELAY3 1
api:
# status page until Home Assistant connects (log viewers don't subscribe to states)
on_client_connected:
- delay: 2s
- if:
condition:
api.connected:
state_subscription_only: true
then:
- lvgl.page.show: main_page
on_client_disconnected:
- if:
condition:
not:
api.connected:
state_subscription_only: true
then:
- lvgl.page.show: status_page
i2c:
- id: i2c_main
sda: 19
scl: 45
scan: true
spi:
- id: lcd_spi
clk_pin: 48
mosi_pin: 47
display:
- platform: mipi_rgb
model: GUITION-4848S040
id: main_display
spi_id: lcd_spi
update_interval: never
auto_clear_enabled: false
touchscreen:
- platform: gt911
id: main_touch
display: main_display
i2c_id: i2c_main
# the mipi_rgb model mirrors the image on both axes, touch has to follow
transform:
mirror_x: true
mirror_y: true
output:
- platform: ledc
id: gpio_backlight_pwm
pin: 38
frequency: 1220
light:
- platform: monochromatic
id: back_light
name: Backlight
output: gpio_backlight_pwm
restore_mode: ALWAYS_ON
script:
- id: update_status
then:
- lambda: |-
// same colors as the other status screens: ok green, waiting orange, details gray
static const uint32_t OK = 0x4DD966, WAIT = 0xFFB300;
auto set = [](lv_obj_t *label, const std::string &text, uint32_t color) {
lv_label_set_text(label, text.c_str());
lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
};
const bool wifi_ok = wifi::global_wifi_component->is_connected();
set(id(status_wifi),
wifi_ok ? str_sprintf("WiFi: connected (%d dBm)", wifi::global_wifi_component->wifi_rssi())
: std::string("WiFi: connecting..."),
wifi_ok ? OK : WAIT);
// IPv6 first (skip link-local), then IPv4
std::string ips;
if (wifi_ok) {
char buf[network::IP_ADDRESS_BUFFER_SIZE];
for (bool v6 : {true, false}) {
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
if (!ip.is_set() || ip.is_ip6() != v6)
continue;
ip.str_to(buf);
if (v6 && strncmp(buf, "fe80", 4) == 0)
continue;
if (!ips.empty())
ips += "\n";
ips += buf;
}
}
}
lv_label_set_text(id(status_ips), ips.c_str());
if (ips.empty())
lv_obj_add_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
else
lv_obj_remove_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
const bool api_ok = api::global_api_server->is_connected();
set(id(status_ha), api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...",
api_ok ? OK : WAIT);
const uint32_t up = millis() / 1000;
lv_label_set_text(id(status_footer),
str_sprintf("ESPHome %s · up %ud %02u:%02u:%02u", ESPHOME_VERSION, up / 86400,
(up / 3600) % 24, (up / 60) % 60, up % 60).c_str());
interval:
- interval: 1s
then:
- lvgl.label.update:
id: clock_label
text: !lambda |-
auto now = id(ha_time).now();
if (!now.is_valid())
return std::string("--:--");
return now.strftime("%H:%M");
- if:
condition:
lvgl.page.is_showing: status_page
then:
- script.execute: update_status
image:
- platform: file
file: assets/images/made-for-esphome-white-on-black.svg
id: esphome_logo
resize: 320x95
type: RGB565
transparency: alpha_channel
font:
- file: "gfonts://Roboto@500"
id: font_clock
size: 150
glyphs: "0123456789:-"
- file: "gfonts://Roboto"
id: font_title
size: 32
- file: "gfonts://Roboto"
id: font_status
size: 20
glyphs: "·"
glyphsets: [GF_Latin_Kernel]
lvgl:
log_level: WARN
color_depth: 16
bg_color: 0x000000
text_color: 0xFFFFFF
theme:
buttonmatrix:
bg_opa: TRANSP
border_width: 0
items:
bg_color: 0x2B2F36
text_color: 0xFFFFFF
radius: 12
checked:
bg_color: 0xE08A00
top_layer:
widgets:
# same navigation bar as on mcu-lvgl-trainer
- buttonmatrix:
id: nav_bar
align: bottom_mid
width: 100%
height: 60
pad_all: 4
pad_column: 6
items:
text_font: montserrat_28
rows:
- buttons:
- id: page_prev
text: ""
on_press:
then:
lvgl.page.previous:
- id: page_home
text: ""
on_press:
then:
lvgl.page.show: main_page
- id: page_next
text: ""
on_press:
then:
lvgl.page.next:
pages:
# 1: clock
- id: main_page
widgets:
- label:
id: clock_label
align: CENTER
y: -30
text_font: font_clock
text: "--:--"
# 2: status (shown until Home Assistant connects)
- id: status_page
on_load:
- script.execute: update_status
widgets:
- image:
src: esphome_logo
align: TOP_MID
y: 16
- label:
align: TOP_MID
y: 120
text_font: font_title
text: "${name}"
- obj:
id: status_lines
x: 24
y: 170
width: 440
height: SIZE_CONTENT
bg_opa: TRANSP
border_width: 0
pad_all: 0
scrollbar_mode: "OFF"
layout:
type: flex
flex_flow: column
pad_row: 6
widgets:
- label:
id: status_wifi
text_font: font_status
text: ""
- label:
id: status_ips
text_font: font_status
text_color: 0x8C8C8C
pad_left: 20
text: ""
- label:
id: status_ha
text_font: font_status
text: ""
- label:
id: status_footer
text_font: font_status
text_color: 0x8C8C8C
text: ""
+611 -150
View File
@@ -4,6 +4,13 @@ substitutions:
name: Trainer Display name: Trainer Display
area: Basement area: Basement
comment: "${area} | Pain Cave" comment: "${area} | Pain Cave"
light_basement: light.mcu_basement_2_light
light_workbench: light.mcu_basement_2_workbench_light
light_exercise: light.exercise_room
fan: light.mcu_dimmer_exercise_room_fan
# Pages (480x320, navigation bar at the bottom):
# clock, lights, training, fan, stopwatch, status
esphome: esphome:
name: mcu-${device} name: mcu-${device}
@@ -17,9 +24,9 @@ esp32:
framework: framework:
type: esp-idf type: esp-idf
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
i2c: i2c:
- id: touchscreen_bus - id: touchscreen_bus
@@ -36,71 +43,17 @@ spi:
clk_pin: 47 clk_pin: 47
data_pins: [21, 48, 40, 39] data_pins: [21, 48, 40, 39]
color:
- id: my_red
red: 100%
green: 0%
blue: 0%
- id: my_orange
red: 100%
green: 50%
blue: 0%
- id: my_yellow
red: 100%
green: 100%
blue: 0%
- id: my_green
red: 0%
green: 100%
blue: 0%
- id: my_blue
red: 0%
green: 0%
blue: 100%
- id: my_teal
red: 0%
green: 100%
blue: 100%
- id: my_gray
red: 70%
green: 70%
blue: 70%
- id: my_white
red: 100%
green: 100%
blue: 100%
font:
- file: "gfonts://Roboto"
id: Roboto_48
size: 48
- file: "gfonts://Roboto"
id: Roboto_36
size: 36
- file: "gfonts://Roboto"
id: Roboto_24
size: 24
- file: "gfonts://Roboto"
id: Roboto_12
size: 12
- file: "gfonts://Roboto"
id: font_std
size: 40
glyphs: "!\"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/\\[]|&@#'"
- file: "gfonts://Roboto@700"
id: font_title
size: 40
glyphs: "!\"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/\\[]|&@#'"
globals: globals:
- id: bgcolor # stopwatch: accumulated time of finished runs + start of the current run
type: Color - id: sw_running
initial_value: "Color::random_color()" type: bool
initial_value: 'false'
# power_supply: - id: sw_start
# id: main_backlight type: uint32_t
# pin: 1 initial_value: '0'
# enable_on_boot: true - id: sw_accum
type: uint32_t
initial_value: '0'
output: output:
- platform: ledc - platform: ledc
@@ -119,7 +72,6 @@ display:
- platform: mipi_spi - platform: mipi_spi
model: AXS15231 model: AXS15231
data_rate: 40MHz #20mhz is default data_rate: 40MHz #20mhz is default
#spi_mode: MODE2
id: main_display id: main_display
spi_id: display_qspi spi_id: display_qspi
dimensions: dimensions:
@@ -127,11 +79,6 @@ display:
width: 320 width: 320
cs_pin: 45 cs_pin: 45
auto_clear_enabled: false auto_clear_enabled: false
# show_test_card: true
# lambda: |-
# it.fill(id(bgcolor));
# it.print(it.get_width()/2, it.get_height()/2-20, id(font_title), TextAlign::CENTER, "ESPHome");
# it.print(it.get_width()/2, it.get_height()/2+20, id(font_std), TextAlign::CENTER, "@UTBDK");
touchscreen: touchscreen:
- platform: axs15231 - platform: axs15231
@@ -142,27 +89,8 @@ touchscreen:
# interrupt_pin: # interrupt_pin:
# number: 3 # number: 3
# ignore_strapping_warning: true # ignore_strapping_warning: true
calibration: # no calibration/transform: the driver defaults to the native 320x480 panel and
x_min: 0 # LVGL applies its own rotation to touch points
x_max: 479
y_min: 0
y_max: 319
transform:
swap_xy: true
mirror_x: false
mirror_y: true
# on_touch:
# - lambda: |-
# Color newColor;
# do { newColor = Color::random_color(); } while (newColor == id(bgcolor));
# id(bgcolor) = newColor;
# ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
# touch.x,
# touch.y,
# touch.x_raw,
# touch.y_raw
# );
on_release: on_release:
- if: - if:
condition: lvgl.is_paused condition: lvgl.is_paused
@@ -183,107 +111,640 @@ uart:
binary_sensor: binary_sensor:
- platform: homeassistant - platform: homeassistant
id: exercise_room_light id: basement_light
entity_id: light.exercise_room entity_id: ${light_basement}
trigger_on_initial_state: true trigger_on_initial_state: true
on_state: on_state:
then: - lvgl.widget.update:
lvgl.widget.update: id: basement_light_btn
id: exercise_room_light_btn
state: state:
checked: !lambda return x; checked: !lambda return x;
- platform: homeassistant - platform: homeassistant
id: exercise_room_fan id: workbench_light
entity_id: light.mcu_dimmer_exercise_room_fan entity_id: ${light_workbench}
trigger_on_initial_state: true trigger_on_initial_state: true
on_state: on_state:
then: - lvgl.widget.update:
lvgl.widget.update: id: workbench_light_btn
id: exercise_room_fan_btn
state: state:
checked: !lambda return x; checked: !lambda return x;
- platform: homeassistant
id: exercise_light
entity_id: ${light_exercise}
trigger_on_initial_state: true
on_state:
- lvgl.widget.update:
id: exercise_light_btn
state:
checked: !lambda return x;
# brightness and color temperature only make sense while the light is on
- if:
condition:
lambda: return x;
then:
- lvgl.widget.show: exercise_controls
else:
- lvgl.widget.hide: exercise_controls
- platform: homeassistant
id: fan_on
entity_id: ${fan}
trigger_on_initial_state: true
on_state:
- script.execute: update_fan_buttons
sensor:
- platform: homeassistant
id: fan_brightness
entity_id: ${fan}
attribute: brightness
on_value:
- script.execute: update_fan_buttons
text_sensor:
# light attributes read as text: they are 'None' while the light is off
- platform: homeassistant
id: exercise_brightness
entity_id: ${light_exercise}
attribute: brightness
on_value:
- if:
condition:
lambda: return parse_number<float>(x).has_value();
then:
- lvgl.slider.update:
id: exercise_brightness_slider
value: !lambda return roundf(*parse_number<float>(x) * 100.0f / 255.0f);
- platform: homeassistant
id: exercise_color_temp
entity_id: ${light_exercise}
attribute: color_temp_kelvin
on_value:
- if:
condition:
lambda: return parse_number<float>(x).has_value();
then:
- lvgl.slider.update:
id: exercise_color_temp_slider
value: !lambda return *parse_number<float>(x);
# a running automation (e.g. its delay) has current > 0
- platform: homeassistant
id: training_start_running
entity_id: automation.zwift_enable_fan
attribute: current
on_value:
- script.execute: update_training_status
- platform: homeassistant
id: training_stop_running
entity_id: automation.zwift_disable_fan
attribute: current
on_value:
- script.execute: update_training_status
script:
- id: update_fan_buttons
then:
- lambda: |-
// fan level rounded to the 10 % steps of the buttons, 0 = off
int pct = 0;
if (id(fan_on).state && !std::isnan(id(fan_brightness).state))
pct = (int) roundf(id(fan_brightness).state * 10.0f / 255.0f) * 10;
const int levels[] = {0, 50, 60, 70, 80, 90, 100};
lv_obj_t *matrix = id(fan_matrix)->obj;
for (int i = 0; i < 7; i++) {
if (levels[i] == pct)
lv_buttonmatrix_set_button_ctrl(matrix, i, LV_BUTTONMATRIX_CTRL_CHECKED);
else
lv_buttonmatrix_clear_button_ctrl(matrix, i, LV_BUTTONMATRIX_CTRL_CHECKED);
}
- lvgl.label.update:
id: fan_status_label
text: !lambda |-
if (!id(fan_on).state)
return std::string("Ventilátor vypnutý");
if (std::isnan(id(fan_brightness).state))
return std::string("Ventilátor zapnutý");
return str_sprintf("Ventilátor %.0f %%", id(fan_brightness).state * 100.0f / 255.0f);
- id: update_training_status
then:
- lvgl.label.update:
id: training_status_label
text: !lambda |-
if (id(training_start_running).state != "0" && !id(training_start_running).state.empty())
return std::string("Tréning sa spúšťa, ventilátor sa zapne o 10 min");
if (id(training_stop_running).state != "0" && !id(training_stop_running).state.empty())
return std::string("Tréning sa ukončuje, ventilátor sa vypne o 2 min");
return std::string("");
- id: update_stopwatch
then:
- lvgl.label.update:
id: stopwatch_label
text: !lambda |-
const unsigned ms = id(sw_accum) + (id(sw_running) ? millis() - id(sw_start) : 0);
const unsigned s = ms / 1000;
if (s >= 3600)
return str_sprintf("%u:%02u:%02u", s / 3600, (s / 60) % 60, s % 60);
return str_sprintf("%02u:%02u.%u", s / 60, s % 60, (ms / 100) % 10);
- lvgl.label.update:
id: stopwatch_start_label
text: !lambda |-
return std::string(id(sw_running) ? "Stop" : "Štart");
- id: update_status
then:
- lambda: |-
// same colors as the other status screens: ok green, waiting orange, details gray
static const uint32_t OK = 0x4DD966, WAIT = 0xFFB300;
auto set = [](lv_obj_t *label, const std::string &text, uint32_t color) {
lv_label_set_text(label, text.c_str());
lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
};
const bool wifi_ok = wifi::global_wifi_component->is_connected();
set(id(status_wifi),
wifi_ok ? str_sprintf("WiFi: connected (%d dBm)", wifi::global_wifi_component->wifi_rssi())
: std::string("WiFi: connecting..."),
wifi_ok ? OK : WAIT);
// IPv6 first (skip link-local), then IPv4
std::string ips;
if (wifi_ok) {
char buf[network::IP_ADDRESS_BUFFER_SIZE];
for (bool v6 : {true, false}) {
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
if (!ip.is_set() || ip.is_ip6() != v6)
continue;
ip.str_to(buf);
if (v6 && strncmp(buf, "fe80", 4) == 0)
continue;
if (!ips.empty())
ips += "\n";
ips += buf;
}
}
}
lv_label_set_text(id(status_ips), ips.c_str());
if (ips.empty())
lv_obj_add_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
else
lv_obj_remove_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
const bool api_ok = api::global_api_server->is_connected();
set(id(status_ha), api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...",
api_ok ? OK : WAIT);
const uint32_t up = millis() / 1000;
lv_label_set_text(id(status_footer),
str_sprintf("ESPHome %s · up %ud %02u:%02u:%02u", ESPHOME_VERSION, up / 86400,
(up / 3600) % 24, (up / 60) % 60, up % 60).c_str());
interval:
- interval: 1s
then:
- lvgl.label.update:
id: clock_label
text: !lambda |-
auto now = id(ha_time).now();
if (!now.is_valid())
return std::string("--:--:--");
return now.strftime("%H:%M:%S");
- interval: 100ms
then:
- if:
condition:
and:
- lambda: return id(sw_running);
- lvgl.page.is_showing: stopwatch_page
then:
- script.execute: update_stopwatch
- interval: 2s
then:
- if:
condition:
lvgl.page.is_showing: status_page
then:
- script.execute: update_status
image:
- platform: file
file: assets/images/made-for-esphome-white-on-black.svg
id: esphome_logo
resize: 280x83
type: RGB565
transparency: alpha_channel
font:
- file: "gfonts://Roboto@500"
id: font_digits
size: 110
glyphs: "0123456789:.-"
- file: "gfonts://Roboto"
id: font_ui
size: 24
glyphs: &sk_glyphs "ÁÄČĎÉÍĹĽŇÓÔŔŠŤÚÝŽáäčďéíĺľňóôŕšťúýž°·"
glyphsets: [GF_Latin_Kernel]
- file: "gfonts://Roboto@500"
id: font_button
size: 34
glyphs: *sk_glyphs
glyphsets: [GF_Latin_Kernel]
- file: "gfonts://Roboto"
id: font_small
size: 18
glyphs: *sk_glyphs
glyphsets: [GF_Latin_Kernel]
lvgl: lvgl:
rotation: 90 rotation: 90
buffer_size: 100% buffer_size: 100%
log_level: WARN log_level: WARN
color_depth: 16 color_depth: 16
default_font: montserrat_18 default_font: font_ui
# on_idle: bg_color: 0x000000
# - timeout: !lambda "return (id(display_timeout).state * 1000);" text_color: 0xFFFFFF
# then: theme:
# - logger.log: "LVGL is idle" button:
# - light.turn_off: bg_color: 0x2B2F36
# id: display_backlight radius: 12
# transition_length: 2500ms checked:
# - lvgl.pause: bg_color: 0xE08A00
buttonmatrix:
bg_opa: TRANSP
border_width: 0
items:
bg_color: 0x2B2F36
text_color: 0xFFFFFF
radius: 12
checked:
bg_color: 0xE08A00
slider:
bg_color: 0x2B2F36
indicator:
bg_color: 0xE08A00
knob:
bg_color: 0xFFFFFF
top_layer: top_layer:
widgets: widgets:
- buttonmatrix: - buttonmatrix:
id: nav_bar
align: bottom_mid align: bottom_mid
# styles: header_footer width: 100%
pad_all: 0 height: 56
outline_width: 0 pad_all: 4
id: top_layer pad_column: 6
# items: items:
# styles: header_footer text_font: montserrat_28
rows: rows:
- buttons: - buttons:
- id: page_prev - id: page_prev
text: "\uF053" text: ""
on_press: on_press:
then: then:
lvgl.page.previous: lvgl.page.previous:
- id: page_home - id: page_home
text: "\uF015" text: ""
on_press: on_press:
then: then:
lvgl.page.show: main_page lvgl.page.show: clock_page
- id: page_next - id: page_next
text: "\uF054" text: ""
on_press: on_press:
then: then:
lvgl.page.next: lvgl.page.next:
pages: pages:
- id: main_page # 1: clock (default after boot)
- id: clock_page
widgets: widgets:
- label: - label:
text: "ESPHome LVGL Display" id: clock_label
align: CENTER align: CENTER
text_align: CENTER y: -28
text_color: 0xFFFFFF text_font: font_digits
- id: excercise_page text: "--:--:--"
# 2: basement lights
- id: lights_page
pad_all: 10
widgets: widgets:
- label:
align: CENTER
text: 'Hello World!'
- button: - button:
id: exercise_room_light_btn id: basement_light_btn
align: TOP_LEFT align: TOP_LEFT
width: 100 width: 148
height: 70 height: 80
checkable: true checkable: true
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: 'Light' text: "Pivnica"
on_click: on_click:
- homeassistant.action: - homeassistant.action:
action: light.toggle action: light.toggle
data: data:
entity_id: light.exercise_room entity_id: ${light_basement}
- button: - button:
id: exercise_room_fan_btn id: workbench_light_btn
align: TOP_RIGHT align: TOP_MID
width: 100 width: 148
height: 70 height: 80
checkable: true checkable: true
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: 'Fan' text: "Pracovný stôl"
on_click: on_click:
- homeassistant.action: - homeassistant.action:
action: light.toggle action: light.toggle
data: data:
entity_id: light.mcu_dimmer_exercise_room_fan entity_id: ${light_workbench}
- button:
id: exercise_light_btn
align: TOP_RIGHT
width: 148
height: 80
checkable: true
widgets:
- label:
align: CENTER
text: "Posilka"
on_click:
- homeassistant.action:
action: light.toggle
data:
entity_id: ${light_exercise}
- obj:
id: exercise_controls
hidden: true
y: 110
width: 460
height: 120
pad_all: 0
bg_opa: TRANSP
border_width: 0
scrollbar_mode: "OFF"
widgets:
- label:
x: 4
y: 0
text_font: font_small
text: "Posilka jas"
- slider:
id: exercise_brightness_slider
x: 14
y: 32
width: 430
height: 16
min_value: 1
max_value: 100
value: 100
on_release:
- homeassistant.action:
action: light.turn_on
data:
entity_id: ${light_exercise}
brightness_pct: !lambda return (int) x;
- label:
x: 4
y: 62
text_font: font_small
text: "Posilka teplota (2700 – 6500 K)"
- slider:
id: exercise_color_temp_slider
x: 14
y: 94
width: 430
height: 16
min_value: 2700
max_value: 6500
value: 2700
on_release:
- homeassistant.action:
action: light.turn_on
data:
entity_id: ${light_exercise}
color_temp_kelvin: !lambda return (int) x;
# 3: training start / stop
- id: training_page
pad_all: 10
widgets:
- button:
align: TOP_LEFT
y: 10
width: 222
height: 150
bg_color: 0x1E7D32
widgets:
- label:
align: CENTER
text_font: font_button
text: "Štart"
on_click:
- homeassistant.action:
action: automation.trigger
data:
entity_id: automation.zwift_enable_fan
- button:
align: TOP_RIGHT
y: 10
width: 222
height: 150
bg_color: 0xB71C1C
widgets:
- label:
align: CENTER
text_font: font_button
text: "Stop"
on_click:
- homeassistant.action:
action: automation.trigger
data:
entity_id: automation.zwift_disable_fan
- label:
id: training_status_label
align: TOP_MID
y: 185
width: 440
text_align: CENTER
text_font: font_small
text: ""
# 4: fan
- id: fan_page
pad_all: 10
widgets:
- buttonmatrix:
id: fan_matrix
align: TOP_MID
width: 460
height: 180
pad_row: 8
pad_column: 8
one_checked: true
items:
text_font: font_button
rows:
- buttons:
- text: "Vyp"
control:
checkable: true
on_press:
- homeassistant.action:
action: light.turn_off
data:
entity_id: ${fan}
- text: "50"
control:
checkable: true
on_press:
- homeassistant.action:
action: light.turn_on
data:
entity_id: ${fan}
brightness_pct: "50"
- text: "60"
control:
checkable: true
on_press:
- homeassistant.action:
action: light.turn_on
data:
entity_id: ${fan}
brightness_pct: "60"
- text: "70"
control:
checkable: true
on_press:
- homeassistant.action:
action: light.turn_on
data:
entity_id: ${fan}
brightness_pct: "70"
- buttons:
- text: "80"
control:
checkable: true
on_press:
- homeassistant.action:
action: light.turn_on
data:
entity_id: ${fan}
brightness_pct: "80"
- text: "90"
control:
checkable: true
on_press:
- homeassistant.action:
action: light.turn_on
data:
entity_id: ${fan}
brightness_pct: "90"
- text: "100"
control:
checkable: true
on_press:
- homeassistant.action:
action: light.turn_on
data:
entity_id: ${fan}
brightness_pct: "100"
- label:
id: fan_status_label
align: TOP_MID
y: 200
text_font: font_small
text: ""
# 5: stopwatch
- id: stopwatch_page
pad_all: 10
widgets:
- label:
id: stopwatch_label
align: TOP_MID
y: 10
text_font: font_digits
text: "00:00.0"
- button:
align: TOP_LEFT
y: 150
width: 222
height: 90
widgets:
- label:
id: stopwatch_start_label
align: CENTER
text_font: font_button
text: "Štart"
on_click:
- lambda: |-
if (id(sw_running)) {
id(sw_accum) += millis() - id(sw_start);
id(sw_running) = false;
} else {
id(sw_start) = millis();
id(sw_running) = true;
}
- script.execute: update_stopwatch
- button:
align: TOP_RIGHT
y: 150
width: 222
height: 90
widgets:
- label:
align: CENTER
text_font: font_button
text: "Reset"
on_click:
- lambda: |-
id(sw_accum) = 0;
id(sw_start) = millis();
- script.execute: update_stopwatch
# 6: status
- id: status_page
on_load:
- script.execute: update_status
widgets:
- image:
src: esphome_logo
align: TOP_MID
y: 8
- label:
align: TOP_MID
y: 96
text: "${name}"
- obj:
id: status_lines
x: 20
y: 126
width: 440
height: SIZE_CONTENT
bg_opa: TRANSP
border_width: 0
pad_all: 0
scrollbar_mode: "OFF"
layout:
type: flex
flex_flow: column
pad_row: 4
widgets:
- label:
id: status_wifi
text_font: font_small
text: ""
- label:
id: status_ips
text_font: font_small
text_color: 0x8C8C8C
pad_left: 16
text: ""
- label:
id: status_ha
text_font: font_small
text: ""
- label:
id: status_footer
text_font: font_small
text_color: 0x8C8C8C
text: ""
+218 -545
View File
@@ -10,8 +10,10 @@ esphome:
friendly_name: ${name} friendly_name: ${name}
area: ${area} area: ${area}
comment: ${comment} comment: ${comment}
platformio_options: on_boot:
board_build.flash_mode: dio - priority: -100
then:
- lvgl.page.show: status_page
esp32: esp32:
variant: esp32s3 variant: esp32s3
@@ -19,9 +21,9 @@ esp32:
toolchain: esp-idf toolchain: esp-idf
framework: framework:
type: esp-idf type: esp-idf
# Required to achieve sufficient PSRAM bandwidth # PSRAM bandwidth for the RGB panel (not set by ESPHome); without it the
# image shifts during flash/WiFi activity
sdkconfig_options: sdkconfig_options:
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: y
CONFIG_ESP32S3_DATA_CACHE_64KB: y CONFIG_ESP32S3_DATA_CACHE_64KB: y
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y CONFIG_SPIRAM_FETCH_INSTRUCTIONS: y
CONFIG_SPIRAM_RODATA: y CONFIG_SPIRAM_RODATA: y
@@ -32,6 +34,7 @@ psram:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# PINOUTS: ESP32 Cheap Yellow Display, 4.3", IPS, Capacitive # PINOUTS: ESP32 Cheap Yellow Display, 4.3", IPS, Capacitive
## Display ## Display
@@ -63,6 +66,7 @@ packages:
# HSYNC 39 # HSYNC 39
# VSYNC 41 # VSYNC 41
# DE 40 # DE 40
# BL 2
## Capacitive Touch ## Capacitive Touch
# TP_INT # TP_INT
# TP_RST 38 # TP_RST 38
@@ -89,579 +93,248 @@ packages:
# 3 19 TP_SDA # 3 19 TP_SDA
# 4 20 TP_SCL # 4 20 TP_SCL
api:
# status page until Home Assistant connects (log viewers don't subscribe to states)
on_client_connected:
- delay: 2s
- if:
condition:
api.connected:
state_subscription_only: true
then:
- lvgl.page.show: main_page
on_client_disconnected:
- if:
condition:
not:
api.connected:
state_subscription_only: true
then:
- lvgl.page.show: status_page
i2c: i2c:
- id: i2c_main - id: i2c_main
sda: 19 sda: 19
scl: 20 scl: 20
scan: true scan: true
# same pins and timing as the 5" Sunton board, pclk lowered for the 4.3" panel
display: display:
- platform: rpi_dpi_rgb - platform: mipi_rgb
model: ESP32-8048S050
id: main_display id: main_display
color_order: RGB color_order: RGB
invert_colors: true invert_colors: true
pclk_frequency: 12.5MHz
update_interval: never update_interval: never
auto_clear_enabled: false # takes 2.8 seconds to clear the display auto_clear_enabled: false # takes 2.8 seconds to clear the display
dimensions:
width: 800
height: 480
de_pin: 40
hsync_pin: 39
vsync_pin: 41
pclk_pin: 42
pclk_frequency: 12.5MHz
hsync_pulse_width: 4
hsync_front_porch: 8
hsync_back_porch: 8
vsync_pulse_width: 4
vsync_front_porch: 8
vsync_back_porch: 8
data_pins:
red:
- 45 # R1
- 48 # R2
- 47 # R3
- 21 # R4
- 14 # R5
green:
- 5 # G0
- 6 # G1
- 7 # G2
- 15 # G3
- 16 # G4
- 4 # G5
blue:
- 8 # B1
- 3 # B2
- 46 # B3
- 9 # B4
- 1 # B5
touchscreen:
- platform: gt911
id: main_touch
display: main_display
i2c_id: i2c_main
reset_pin: 38
# Define a PWM output on the ESP32
output: output:
- platform: ledc - platform: ledc
id: gpio_backlight_pwm
pin: 2 pin: 2
frequency: 1220 frequency: 1220
id: gpio_backlight_pwm
light: light:
- platform: monochromatic - platform: monochromatic
output: gpio_backlight_pwm
id: back_light id: back_light
name: Backlight
output: gpio_backlight_pwm
restore_mode: ALWAYS_ON restore_mode: ALWAYS_ON
text_sensor:
- platform: template
# name: Uptime Human Readable
id: uptime_human
icon: mdi:clock-start
internal: True
time:
- platform: homeassistant
id: time_comp
number:
- platform: template
initial_value: 0
id: counting_number
internal: True
min_value: -10
max_value: 10
step: 1
optimistic: True
# debug:
# update_interval: 5s
sensor:
- platform: uptime
id: uptime_counter
update_interval: 1s
accuracy_decimals: 0
on_raw_value:
then:
- light.turn_on:
id: back_light
brightness: 90%
- number.increment:
id: counting_number
cycle: True
- script.execute: update_display
# - platform: debug
# free:
# name: "${name} Heap Free"
# block:
# name: "${name} Heap Max Block"
# loop_time:
# name: "${name} Loop Time"
# psram:
# name: "${name} Free PSRAM"
- platform: uptime
# name: Uptime Sensor
internal: True
id: uptime_sensor
update_interval: 1s
on_raw_value:
then:
- text_sensor.template.publish:
id: uptime_human
state: !lambda |-
int seconds = round(id(uptime_sensor).raw_state);
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
return (
("Uptime ") +
(days ? to_string(days) + "d " : "") +
(hours ? to_string(hours) + "h " : "") +
(minutes ? to_string(minutes) + "m " : "") +
(to_string(seconds) + "s")
).c_str();
script: script:
- id: update_display - id: update_status
then:
- lambda: |-
// same colors as the other status screens: ok green, waiting orange, details gray
static const uint32_t OK = 0x4DD966, WAIT = 0xFFB300;
auto set = [](lv_obj_t *label, const std::string &text, uint32_t color) {
lv_label_set_text(label, text.c_str());
lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
};
const bool wifi_ok = wifi::global_wifi_component->is_connected();
set(id(status_wifi),
wifi_ok ? str_sprintf("WiFi: connected (%d dBm)", wifi::global_wifi_component->wifi_rssi())
: std::string("WiFi: connecting..."),
wifi_ok ? OK : WAIT);
// IPv6 first (skip link-local), then IPv4
std::string ips;
if (wifi_ok) {
char buf[network::IP_ADDRESS_BUFFER_SIZE];
for (bool v6 : {true, false}) {
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
if (!ip.is_set() || ip.is_ip6() != v6)
continue;
ip.str_to(buf);
if (v6 && strncmp(buf, "fe80", 4) == 0)
continue;
if (!ips.empty())
ips += "\n";
ips += buf;
}
}
}
lv_label_set_text(id(status_ips), ips.c_str());
if (ips.empty())
lv_obj_add_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
else
lv_obj_remove_flag(id(status_ips), LV_OBJ_FLAG_HIDDEN);
const bool api_ok = api::global_api_server->is_connected();
set(id(status_ha), api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...",
api_ok ? OK : WAIT);
const uint32_t up = millis() / 1000;
lv_label_set_text(id(status_footer),
str_sprintf("ESPHome %s · up %ud %02u:%02u:%02u", ESPHOME_VERSION, up / 86400,
(up / 3600) % 24, (up / 60) % 60, up % 60).c_str());
interval:
- interval: 1s
then: then:
# - lvgl.indicator.line.update:
# id: power_meter_input
# value: !lambda return id(counting_number).state;
- lvgl.label.update: - lvgl.label.update:
id: battery_kw id: clock_label
text: !lambda |- text: !lambda |-
static char buf[8]; auto now = id(ha_time).now();
snprintf(buf, sizeof(buf), "%.1fkW", id(counting_number).state); if (!now.is_valid())
return buf; return std::string("--:--");
- lvgl.label.update: return now.strftime("%H:%M");
id: battery_status - if:
text_color: 0xFF0000 condition:
#text: "charging" lvgl.page.is_showing: status_page
text: "discharging" then:
- lvgl.label.update: - script.execute: update_status
id: battery_soc
text: !lambda |-
static char buf[8];
snprintf(buf, sizeof(buf), "%.1f%%", id(counting_number).state);
return buf;
- lvgl.label.update:
id: solar_kw
text: !lambda |-
static char buf[8];
snprintf(buf, sizeof(buf), "%.1fkW", id(counting_number).state);
return buf;
# - lvgl.img.update:
# id: img_solar_power
# src: solar_power_icon
# img_recolor: 0xFFF000 #mixes this color with the base image
# #img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
# #bg_color: 0xFFFFFF
# # - lvgl.indicator.line.update:
# # id: power_meter_input2
# # value: !lambda return id(counting_number).state;
- lvgl.label.update:
id: battery_kw2
text: !lambda |-
static char buf[8];
snprintf(buf, sizeof(buf), "%.1fkW", id(counting_number).state);
return buf;
- lvgl.label.update:
id: battery_status2
text_color: 0xFF0000
#text: "charging"
text: "discharging"
- lvgl.label.update:
id: battery_soc2
text: !lambda |-
static char buf[8];
snprintf(buf, sizeof(buf), "%.1f%%", id(counting_number).state);
return buf;
- lvgl.label.update:
id: solar_kw2
text: !lambda |-
static char buf[8];
snprintf(buf, sizeof(buf), "%.1fkW", id(counting_number).state);
return buf;
# - lvgl.img.update:
# id: img_solar_power2
# src: solar_power_icon
# img_recolor: 0xFFF000 #mixes this color with the base image
# #img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
# #bg_color: 0xFFFFFF
image: image:
- file: mdi:sun-wireless-outline - platform: file
#"solar-power-invert.png" file: assets/images/made-for-esphome-white-on-black.svg
# mdi:solar-power id: esphome_logo
# mdi:battery-charging-outline resize: 400x118
# mdi:battery-arrow-up-outline type: RGB565
# mdi:battery-arrow-down-outline transparency: alpha_channel
id: solar_power_icon
resize: 50x50
type: BINARY
- file: mdi:battery-arrow-down-outline font:
id: home_battery_icon - file: "gfonts://Roboto@500"
resize: 30x30 id: font_clock
type: BINARY size: 200
glyphs: "0123456789:-"
- file: "gfonts://Roboto"
id: font_title
size: 40
- file: "gfonts://Roboto"
id: font_status
size: 26
glyphs: "·"
glyphsets: [GF_Latin_Kernel]
lvgl: lvgl:
log_level: INFO log_level: WARN
color_depth: 16 color_depth: 16
#buffer_size: 25% bg_color: 0x000000
bg_color: 0 text_color: 0xFFFFFF
border_width: 0 theme:
outline_width: 0 buttonmatrix:
shadow_width: 0
text_font: unscii_16
align: center
style_definitions:
- id: meter_style
text_font: unscii_8
#state_default: null
- id: font_style
text_font: MONTSERRAT_24
#text_font: unscii_16
align: center
text_color: 0xFFFFFF
#bg_opa: cover
bg_opa: TRANSP bg_opa: TRANSP
bg_color: 0 border_width: 0
radius: 4 items:
pad_all: 2 bg_color: 0x2B2F36
- id: details_style text_color: 0xFFFFFF
text_font: MONTSERRAT_18 radius: 12
align: center checked:
text_color: 0xFFFFFF bg_color: 0xE08A00
#bg_opa: cover top_layer:
bg_opa: TRANSP widgets:
bg_color: 0 # same navigation bar as on mcu-lvgl-trainer
radius: 4 - buttonmatrix:
pad_all: 2 id: nav_bar
widgets: align: bottom_mid
- obj: # Meter width: 100%
height: 240 height: 64
width: 240 pad_all: 4
#bg_opa: TRANSP pad_column: 6
bg_color: 0 items:
border_width: 0 text_font: montserrat_28
outline_width: 0 rows:
shadow_width: 0 - buttons:
pad_all: 4 - id: page_prev
align: TOP_LEFT text: "\uF053"
#scrollbar_mode: "off" on_press:
widgets: then:
- meter: # Gradient color arc lvgl.page.previous:
height: 100% - id: page_home
width: 100% text: "\uF015"
border_width: 0 on_press:
outline_width: 0 then:
align: center lvgl.page.show: main_page
bg_color: 0 - id: page_next
styles: meter_style text: "\uF054"
#bg_opa: TRANSP on_press:
scales: then:
angle_range: 180 # sets the total angle to 180 = starts mid left and ends mid right lvgl.page.next:
range_to: 10 pages:
range_from: -10 # 1: clock
ticks: - id: main_page
count: 0 widgets:
indicators: - label:
- line: id: clock_label
id: power_meter_input align: CENTER
width: 8 y: -32
color: 0xFFFFFF text_font: font_clock
r_mod: 12 #sets line length by this much difference from the scale default radius text: "--:--"
value: 50
# - image: # 2: status (shown until Home Assistant connects)
# id: power_meter_input_img - id: status_page
- arc: on_load:
color: 0xFF3000 - script.execute: update_status
r_mod: 10 #radius difference from the scale default radius widgets:
width: 20 - image:
start_value: -10 src: esphome_logo
end_value: 0 align: TOP_MID
- arc: y: 16
color: 0x00FF00 - label:
r_mod: 10 #radius difference from the scale default radius align: TOP_MID
width: 20 y: 140
start_value: 0 text_font: font_title
end_value: 10 text: "${name}"
#- canvas: - obj:
- arc: # black arc to erase middle part of meter indicator line id: status_lines
height: 160 x: 60
width: 160 y: 196
align: center width: 700
arc_color: 0x000000 # background color height: SIZE_CONTENT
arc_width: 150 bg_opa: TRANSP
start_angle: 0 border_width: 0
end_angle: 360 pad_all: 0
indicator: scrollbar_mode: "OFF"
arc_width: 150 layout:
arc_color: 0x000000 type: flex
- label: # gauge lower and higher range indicators flex_flow: column
styles: font_style pad_row: 8
text_font: MONTSERRAT_18 # override font size widgets:
y: 8 #negative = higher - label:
x: -99 id: status_wifi
text: "-10" text_font: font_status
- label: text: ""
styles: font_style - label:
text_font: MONTSERRAT_18 # override font size id: status_ips
y: 8 #negative = higher text_font: font_status
x: 99 text_color: 0x8C8C8C
text: "+10" pad_left: 30
- label: text: ""
styles: font_style - label:
id: battery_status id: status_ha
y: -35 #negative = higher text_font: font_status
- label: text: ""
styles: font_style - label:
id: battery_kw id: status_footer
y: -60 text_font: font_status
- label: text_color: 0x8C8C8C
styles: font_style text: ""
text_font: MONTSERRAT_40 # override font size
id: battery_soc
y: 0
- label:
styles: font_style
id: solar_kw
text_color: 0xFFFF00
y: 90
#- img:
# src: home_battery_icon
#id: img_solar_power
#align: center
#img_recolor: 0xFFFFFF
#img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
#bg_color: 0xFFFFFF
#y: -35
#x: -60
- image:
src: solar_power_icon
id: img_solar_power
align: center
image_recolor: 0xFFFF00
image_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
#bg_color: 0xFFFFFF
y: 50
#x: -60
- obj: # Meter
height: 240
width: 240
#bg_opa: TRANSP
bg_color: 0
border_width: 0
outline_width: 0
shadow_width: 0
pad_all: 4
align: TOP_MID
#scrollbar_mode: "off"
widgets:
- meter: # Gradient color arc
height: 100%
width: 100%
border_width: 0
outline_width: 0
align: center
bg_color: 0
styles: meter_style
#bg_opa: TRANSP
scales:
angle_range: 180 # sets the total angle to 180 = starts mid left and ends mid right
range_to: 10
range_from: -10
ticks:
count: 0
indicators:
- line:
id: power_meter_input2
width: 8
color: 0xFFFFFF
r_mod: 12 #sets line length by this much difference from the scale default radius
value: 50
# - image:
# id: power_meter_input_img2
- arc:
color: 0xFF3000
r_mod: 10 #radius difference from the scale default radius
width: 20
start_value: -10
end_value: 0
- arc:
color: 0x00FF00
r_mod: 10 #radius difference from the scale default radius
width: 20
start_value: 0
end_value: 10
#- canvas:
- arc: # black arc to erase middle part of meter indicator line
height: 160
width: 160
align: center
arc_color: 0x000000 # background color
arc_width: 150
start_angle: 0
end_angle: 360
indicator:
arc_width: 150
arc_color: 0x000000
- label: # gauge lower and higher range indicators
styles: font_style
text_font: MONTSERRAT_18 # override font size
y: 8 #negative = higher
x: -99
text: "-10"
- label:
styles: font_style
text_font: MONTSERRAT_18 # override font size
y: 8 #negative = higher
x: 99
text: "+10"
- label:
styles: font_style
id: battery_status2
y: -35 #negative = higher
- label:
styles: font_style
id: battery_kw2
y: -60
- label:
styles: font_style
text_font: MONTSERRAT_40 # override font size
id: battery_soc2
y: 0
- label:
styles: font_style
id: solar_kw2
text_color: 0xFFFF00
y: 90
#- img:
# src: home_battery_icon
#id: img_solar_power
#align: center
#img_recolor: 0xFFFFFF
#img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
#bg_color: 0xFFFFFF
#y: -35
#x: -60
- image:
src: solar_power_icon
id: img_solar_power2
align: center
image_recolor: 0xFFFF00
image_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
#bg_color: 0xFFFFFF
y: 50
#x: -60
- obj: # Meter
height: 240
width: 240
#bg_opa: TRANSP
bg_color: 0
border_width: 0
outline_width: 0
shadow_width: 0
pad_all: 4
align: BOTTOM_LEFT
#scrollbar_mode: "off"
widgets:
- meter: # Gradient color arc
height: 100%
width: 100%
border_width: 0
outline_width: 0
align: center
bg_color: 0
styles: meter_style
#bg_opa: TRANSP
scales:
angle_range: 180 # sets the total angle to 180 = starts mid left and ends mid right
range_to: 10
range_from: -10
ticks:
count: 0
indicators:
- line:
id: power_meter_input3
width: 8
color: 0xFFFFFF
r_mod: 12 #sets line length by this much difference from the scale default radius
value: 50
# - image:
# id: power_meter_input_img3
- arc:
color: 0xFF3000
r_mod: 10 #radius difference from the scale default radius
width: 20
start_value: -10
end_value: 0
- arc:
color: 0x00FF00
r_mod: 10 #radius difference from the scale default radius
width: 20
start_value: 0
end_value: 10
#- canvas:
- arc: # black arc to erase middle part of meter indicator line
height: 160
width: 160
align: center
arc_color: 0x000000 # background color
arc_width: 150
start_angle: 0
end_angle: 360
indicator:
arc_width: 150
arc_color: 0x000000
- label: # gauge lower and higher range indicators
styles: font_style
text_font: MONTSERRAT_18 # override font size
y: 8 #negative = higher
x: -99
text: "-10"
- label:
styles: font_style
text_font: MONTSERRAT_18 # override font size
y: 8 #negative = higher
x: 99
text: "+10"
- label:
styles: font_style
id: battery_status3
y: -35 #negative = higher
- label:
styles: font_style
id: battery_kw3
y: -60
- label:
styles: font_style
text_font: MONTSERRAT_40 # override font size
id: battery_soc3
y: 0
- label:
styles: font_style
id: solar_kw3
text_color: 0xFFFF00
y: 90
#- img:
# src: home_battery_icon
#id: img_solar_power
#align: center
#img_recolor: 0xFFFFFF
#img_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
#bg_color: 0xFFFFFF
#y: -35
#x: -60
- image:
src: solar_power_icon
id: img_solar_power3
align: center
image_recolor: 0xFFFF00
image_recolor_opa: 100% #opacity defaults to 0% = must set this for recolor to take effect
#bg_color: 0xFFFFFF
y: 50
#x: -60
+564 -77
View File
@@ -4,94 +4,370 @@ substitutions:
name: M5Paper name: M5Paper
area: DEV area: DEV
comment: M5Paper comment: M5Paper
weather_entity: weather.openweathermap
# Power modes
# - USB: always on, WiFi + API, forecast refreshed every hour and redrawn every 15 min
# - battery: RTC wakes the board every 15 min (off 1:00-5:00), it draws the forecast
# from EEPROM and powers itself off; WiFi only to refresh the forecast every 6 h
# Screens: status (after power-on, until Home Assistant connects) and forecast
# The mode is detected by trying to power off: on USB the board keeps running.
esphome: esphome:
name: mcu-${device} name: mcu-${device}
friendly_name: ${name} friendly_name: ${name}
area: ${area} area: ${area}
comment: ${comment} comment: ${comment}
includes:
- include/m5paper_weather.h
on_boot:
- priority: 600
then:
- lambda: |-
// BM8563 CONTROL_STATUS_2: TF (bit 2) is set when the timer woke us up
const uint8_t reg = 0x01;
uint8_t status = 0;
if (id(i2c_main)->write_readv(0x51, &reg, 1, &status, 1) == i2c::ERROR_OK) {
id(rtc_wake) = status & 0x04;
// clear TF and TIE so the RTC releases the power latch
const uint8_t clear[2] = {reg, uint8_t(status & ~0x05)};
id(i2c_main)->write(0x51, clear, 2);
}
m5paper_weather::load(id(i2c_main), m5paper_weather::forecast());
ESP_LOGI("m5paper", "Wake-up: %s, forecast: %u h", id(rtc_wake) ? "RTC" : "power-on", m5paper_weather::forecast().count);
- priority: -100
then:
- if:
condition:
lambda: return id(rtc_wake);
then:
- script.execute: battery_cycle
else:
# power button / USB: stay awake for a while (logs, OTA), then decide the mode
- wifi.enable:
# IT8951 init runs asynchronously after setup
- delay: 3s
- component.update: m5paper_display
- delay: 2min
- script.execute: battery_cycle
esp32: esp32:
variant: esp32 variant: esp32
toolchain: esp-idf toolchain: esp-idf
framework: framework:
type: esp-idf type: esp-idf
advanced:
flash_chip: generic
minimum_chip_revision: 3.0
sram1_as_iram: true
psram: psram:
external_components:
- source: github://Passific/m5paper_esphome
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
time: globals:
- platform: homeassistant - id: show_status
id: ha_time type: bool
timezone: Europe/Bratislava initial_value: 'true'
on_time_sync: - id: usb_mode
- bm8563.write_time type: bool
- platform: bm8563 initial_value: 'false'
id: rtc_time - id: rtc_wake
sleep_duration: 3600000ms type: bool
initial_value: 'false'
# battery cycle running: forecast screen regardless of the connection state
- id: weather_page
type: bool
initial_value: 'false'
- id: fetch_done
type: bool
initial_value: 'false'
- id: sleep_s
type: int
initial_value: '900'
m5paper: wifi:
battery_power_pin: GPIO5 enable_on_boot: false
main_power_pin: GPIO2 on_connect:
# update_interval: 10s - if:
# battery_voltage: condition:
# name: "${name} Battery" lambda: return id(show_status) && !id(weather_page);
then:
- component.update: m5paper_display
api:
# status screen until Home Assistant connects (log viewers don't subscribe to states)
on_client_connected:
- delay: 2s
- if:
condition:
api.connected:
state_subscription_only: true
then:
- lambda: id(show_status) = false;
- if:
condition:
lambda: return !id(weather_page);
then:
- component.update: m5paper_display
# outside the battery cycle: refresh a forecast older than an hour
- if:
condition:
and:
- lambda: |-
const auto &fc = m5paper_weather::forecast();
const auto now = id(rtc_time).now();
return !id(weather_page) && (!fc.valid() || !now.is_valid() || now.timestamp - fc.fetched_at >= 3600);
- api.connected:
state_subscription_only: true
then:
- script.execute: fetch_forecast
on_client_disconnected:
- if:
condition:
not:
api.connected:
state_subscription_only: true
then:
- lambda: id(show_status) = true;
- if:
condition:
lambda: return !id(weather_page);
then:
- component.update: m5paper_display
# I2C devices (i2c_main)
# 0x44 SHT30 # 0x44 SHT30
# 0x50 # 0x50 FM24C02 EEPROM (forecast storage, see include/m5paper_weather.h)
# 0x51 # 0x51 BM8563 RTC
# 0x5D GT911 # 0x5D GT911 touch (docs say 0x14; the driver tries 0x5D, then 0x14)
i2c: i2c:
- id: i2c_main - id: i2c_main
sda: 21 sda: 21
scl: 22 scl: 22
scan: true #false scan: true
# Port A
# - id: i2c_a # - id: i2c_a
# sda: 25 # sda: 25
# scl: 32 # scl: 32
# scan: true #false # Port B
# - id: i2c_b # - id: i2c_b
# sda: 26 # sda: 26
# scl: 33 # scl: 33
# scan: true #false # Port C
# - id: i2c_c # - id: i2c_c
# sda: 18 # sda: 18
# scl: 19 # scl: 19
# scan: true #false
spi: spi:
clk_pin: 14 clk_pin: 14
miso_pin: 13 miso_pin: 13
mosi_pin: 12 mosi_pin: 12
switch:
# Power control MOS (no PMU on M5Paper); on battery the device powers off when this goes low
- platform: gpio
id: main_power
pin:
number: GPIO2
ignore_strapping_warning: true
restore_mode: ALWAYS_ON
internal: true
# External power (Grove ports)
- platform: gpio
id: ext_power
pin:
number: GPIO5
ignore_strapping_warning: true
restore_mode: ALWAYS_ON
internal: true
script:
# One wake-up on battery: refresh the forecast if it is old, draw it, power off
- id: battery_cycle
mode: single
then:
- lambda: id(weather_page) = true;
- wait_until:
condition:
lambda: return id(rtc_time).now().is_valid();
timeout: 10s
- if:
condition:
lambda: |-
const auto &fc = m5paper_weather::forecast();
const auto now = id(rtc_time).now();
return !fc.valid() || !now.is_valid() || now.timestamp - fc.fetched_at >= 6 * 3600 - 600;
then:
- wifi.enable:
- wait_until:
condition:
api.connected:
state_subscription_only: true
timeout: 45s
- if:
condition:
api.connected:
state_subscription_only: true
then:
- script.execute: fetch_forecast
- wait_until:
condition:
lambda: return id(fetch_done);
timeout: 15s
- wait_until:
condition:
lambda: return !std::isnan(id(current_temperature).state);
timeout: 5s
- component.update: m5paper_display
# let the e-paper refresh finish before cutting power
- delay: 4s
- script.execute: try_sleep
- id: fetch_forecast
mode: single
then:
- lambda: id(fetch_done) = false;
- homeassistant.action:
action: weather.get_forecasts
data:
entity_id: ${weather_entity}
type: hourly
capture_response: true
on_success:
- lambda: |-
auto &fc = m5paper_weather::forecast();
if (m5paper_weather::parse(response, "${weather_entity}", id(rtc_time).now().timestamp, fc)) {
const bool saved = m5paper_weather::save(id(i2c_main), fc);
ESP_LOGI("m5paper", "Forecast: %u h%s", fc.count, saved ? "" : " (EEPROM write failed)");
} else {
ESP_LOGW("m5paper", "Forecast: unexpected response");
}
id(fetch_done) = true;
# the battery cycle draws on its own
- if:
condition:
lambda: return !id(weather_page);
then:
- component.update: m5paper_display
on_error:
- lambda: |-
ESP_LOGW("m5paper", "Forecast: %s", error.c_str());
id(fetch_done) = true;
# Arm the RTC and cut power; if we are still running afterwards, we are on USB
- id: try_sleep
mode: single
then:
- lambda: |-
// next quarter hour, 1:00-5:00 in one go
int duration = 900;
const auto now = id(rtc_time).now();
if (now.is_valid()) {
const int sod = now.hour * 3600 + now.minute * 60 + now.second;
int next = (sod / 900 + 1) * 900;
if (next - sod < 90) // the 1/60 Hz RTC timer may wake us up to a minute early
next += 900;
const int next_hour = (next / 3600) % 24;
if (next_hour >= 1 && next_hour < 5)
next = (next / 86400) * 86400 + 5 * 3600;
duration = next - sod;
}
if (duration > 255) // longer timers count whole minutes
duration = (duration + 59) / 60 * 60;
id(sleep_s) = std::min(duration, 255 * 60);
ESP_LOGI("m5paper", "Powering off for %d s", id(sleep_s));
- bm8563.start_timer:
duration: !lambda return id(sleep_s);
# a battery wake-up is shorter than boot_is_good_after: confirm the OTA image
# before cutting power, otherwise the bootloader rolls back
- safe_mode.mark_successful:
- delay: 200ms
- switch.turn_off: main_power
- delay: 2s
# still alive -> USB power
- switch.turn_on: main_power
- lambda: |-
ESP_LOGI("m5paper", "Still running, USB power");
id(usb_mode) = true;
id(weather_page) = false;
id(show_status) = !api::global_api_server->is_connected_with_state_subscription();
- wifi.enable:
- component.update: m5paper_display
interval:
# USB mode: the battery voltage drops once USB is unplugged -> go to battery mode
- interval: 5min
then:
- if:
condition:
lambda: |-
static float last = NAN;
const float v = id(battery_voltage).state;
const bool dropped = id(usb_mode) && !std::isnan(last) && last - v > 0.03f;
last = v;
return dropped;
then:
- lambda: |-
ESP_LOGI("m5paper", "Battery voltage dropping, checking USB");
id(usb_mode) = false;
- script.execute: battery_cycle
time:
- id: !extend ha_time
on_time_sync:
- bm8563.write_time
- if:
condition:
lambda: return !id(weather_page);
then:
- component.update: m5paper_display
on_time:
- seconds: 0
minutes: /15
then:
- if:
condition:
lambda: return !id(show_status) && !id(weather_page);
then:
- component.update: m5paper_display
# USB mode: keep the stored forecast fresh for battery mode
- seconds: 0
minutes: 5
then:
- if:
condition:
and:
- lambda: return id(usb_mode);
- api.connected:
state_subscription_only: true
then:
- script.execute: fetch_forecast
- platform: bm8563
id: rtc_time
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
name: ${name} Button UP name: ${name} Button UP
id: button_up id: button_up
pin: pin:
number: 37 number: GPIO37
inverted: true inverted: true
on_press: on_press:
- component.update: m5paper_display - component.update: m5paper_display
on_release:
- component.update: m5paper_display
- platform: gpio - platform: gpio
name: ${name} Button Press name: ${name} Button Press
id: button_press id: button_press
pin: pin:
number: 38 number: GPIO38
inverted: true inverted: true
- platform: gpio - platform: gpio
name: ${name} Button Down name: ${name} Button Down
id: button_down id: button_down
pin: pin:
number: 39 number: GPIO39
inverted: true inverted: true
- platform: touchscreen - platform: touchscreen
name: ${name} Top Left Touch Button name: ${name} Top Left Touch Button
@@ -103,51 +379,19 @@ binary_sensor:
y_min: 0 y_min: 0
y_max: 100 y_max: 100
on_press: on_press:
# Example of applying the sleep duration and shutting down to the lowest power mode - script.execute: battery_cycle
- bm8563.apply_sleep_duration
- delay: 1s
- m5paper.shutdown_main_power
font: button:
- file: "gfonts://Roboto" - platform: template
id: font_default name: Refresh Display
size: 20 entity_category: config
- file: "gfonts://Roboto" on_press:
id: roboto_32 - component.update: m5paper_display
size: 32 - platform: template
- file: "gfonts://Roboto" name: Refresh Forecast
id: roboto_24 entity_category: config
size: 24 on_press:
- file: "gfonts://Roboto" - script.execute: fetch_forecast
id: roboto_12
size: 12
display:
- platform: it8951e
id: m5paper_display
cs_pin: GPIO15
reset_pin: GPIO23
busy_pin: GPIO27
rotation: 90
reversed: False
update_interval: "never"
lambda: |-
it.printf(25, 25, id(roboto_32), "%.1f°", id(current_temperature).state);
it.strftime(350, 25, id(roboto_32), "%H:%M:%S", id(rtc_time).now());
touchscreen:
- platform: gt911
display: m5paper_display
id: gt911_touchscreen
interrupt_pin: GPIO36
# display:
# - platform: waveshare_epaper
# cs_pin: 15
# model: 2.90in
# full_update_every: 30
# lambda: |-
# it.print(0, 0, id(font1), "Hello World!");
sensor: sensor:
- platform: sht3xd - platform: sht3xd
@@ -156,9 +400,252 @@ sensor:
id: current_temperature id: current_temperature
humidity: humidity:
name: Humidity name: Humidity
id: current_humidity
address: 0x44 address: 0x44
update_interval: 60s update_interval: 60s
- platform: adc
id: battery_voltage
name: Battery Voltage
pin: GPIO35
attenuation: 12db
samples: 16
update_interval: 60s
entity_category: diagnostic
filters:
- multiply: 2.0 # 1:2 voltage divider
- platform: copy
source_id: battery_voltage
id: battery_level
name: Battery
unit_of_measurement: "%"
device_class: battery
accuracy_decimals: 0
filters:
- calibrate_linear:
- 3.3 -> 0.0
- 4.15 -> 100.0
- clamp:
min_value: 0
max_value: 100
# touchscreen: .image_defaults:
# - platform: gt911 icon_large: &icon_large {platform: file, type: BINARY, transparency: chroma_key, resize: 150x150}
# interrupt_pin: 36 icon_small: &icon_small {platform: file, type: BINARY, transparency: chroma_key, resize: 44x44}
image:
- platform: file
file: assets/images/made-for-esphome-white-on-black.svg
id: esphome_logo
resize: 460x136
type: GRAYSCALE
transparency: alpha_channel
# weather icons, same order as m5paper_weather::CONDITIONS
- {<<: *icon_large, file: "mdi:weather-night", id: wl_clear_night}
- {<<: *icon_large, file: "mdi:weather-cloudy", id: wl_cloudy}
- {<<: *icon_large, file: "mdi:alert-circle-outline", id: wl_exceptional}
- {<<: *icon_large, file: "mdi:weather-fog", id: wl_fog}
- {<<: *icon_large, file: "mdi:weather-hail", id: wl_hail}
- {<<: *icon_large, file: "mdi:weather-lightning", id: wl_lightning}
- {<<: *icon_large, file: "mdi:weather-lightning-rainy", id: wl_lightning_rainy}
- {<<: *icon_large, file: "mdi:weather-partly-cloudy", id: wl_partlycloudy}
- {<<: *icon_large, file: "mdi:weather-pouring", id: wl_pouring}
- {<<: *icon_large, file: "mdi:weather-rainy", id: wl_rainy}
- {<<: *icon_large, file: "mdi:weather-snowy", id: wl_snowy}
- {<<: *icon_large, file: "mdi:weather-snowy-rainy", id: wl_snowy_rainy}
- {<<: *icon_large, file: "mdi:weather-sunny", id: wl_sunny}
- {<<: *icon_large, file: "mdi:weather-windy", id: wl_windy}
- {<<: *icon_large, file: "mdi:weather-windy-variant", id: wl_windy_variant}
- {<<: *icon_small, file: "mdi:weather-night", id: ws_clear_night}
- {<<: *icon_small, file: "mdi:weather-cloudy", id: ws_cloudy}
- {<<: *icon_small, file: "mdi:alert-circle-outline", id: ws_exceptional}
- {<<: *icon_small, file: "mdi:weather-fog", id: ws_fog}
- {<<: *icon_small, file: "mdi:weather-hail", id: ws_hail}
- {<<: *icon_small, file: "mdi:weather-lightning", id: ws_lightning}
- {<<: *icon_small, file: "mdi:weather-lightning-rainy", id: ws_lightning_rainy}
- {<<: *icon_small, file: "mdi:weather-partly-cloudy", id: ws_partlycloudy}
- {<<: *icon_small, file: "mdi:weather-pouring", id: ws_pouring}
- {<<: *icon_small, file: "mdi:weather-rainy", id: ws_rainy}
- {<<: *icon_small, file: "mdi:weather-snowy", id: ws_snowy}
- {<<: *icon_small, file: "mdi:weather-snowy-rainy", id: ws_snowy_rainy}
- {<<: *icon_small, file: "mdi:weather-sunny", id: ws_sunny}
- {<<: *icon_small, file: "mdi:weather-windy", id: ws_windy}
- {<<: *icon_small, file: "mdi:weather-windy-variant", id: ws_windy_variant}
font:
- file: "gfonts://Roboto"
id: font_title
size: 48
- file: "gfonts://Roboto"
id: font_status
size: 28
glyphs: &sk_glyphs "ÁÄČĎÉÍĹĽŇÓÔŔŠŤÚÝŽáäčďéíĺľňóôŕšťúýž°·"
glyphsets: [GF_Latin_Kernel]
- file: "gfonts://Roboto"
id: font_small
size: 24
glyphs: *sk_glyphs
glyphsets: [GF_Latin_Kernel]
- file: "gfonts://Roboto"
id: font_tiny
size: 18
glyphs: "0123456789% "
- file: "gfonts://Roboto@500"
id: font_date
size: 34
glyphs: *sk_glyphs
glyphsets: [GF_Latin_Kernel]
- file: "gfonts://Roboto@500"
id: font_hour
size: 28
glyphs: "0123456789-°"
- file: "gfonts://Roboto"
id: font_temp
size: 110
glyphs: "0123456789.,-°C "
color:
- id: ink
red: 0%
green: 0%
blue: 0%
- id: paper
red: 100%
green: 100%
blue: 100%
display:
- platform: it8951
id: m5paper_display
model: m5stack-m5paper
rotation: 270
update_interval: never
# 1bpp: updates use the fast DU waveform, full GC16 refresh every 30 updates
grayscale: false
dithering: false
lambda: |-
// 540x960 portrait
it.fill(id(paper));
const int w = it.get_width();
const int cx = w / 2;
const float batt = id(battery_level).state;
const float temp = id(current_temperature).state;
const float hum = id(current_humidity).state;
// status screen after power-on, until Home Assistant connects
if (id(show_status) && !id(weather_page)) {
it.image(cx, 200, id(esphome_logo), ImageAlign::CENTER);
it.print(cx, 320, id(font_title), id(ink), TextAlign::CENTER, "${name}");
const bool wifi_ok = wifi::global_wifi_component->is_connected();
if (wifi_ok) {
it.printf(40, 440, id(font_status), id(ink), "WiFi: connected (%d dBm)", wifi::global_wifi_component->wifi_rssi());
// IPv6 first (skip link-local), then IPv4
int y = 484;
char buf[network::IP_ADDRESS_BUFFER_SIZE];
for (bool v6 : {true, false}) {
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
if (!ip.is_set() || ip.is_ip6() != v6 || y > 590)
continue;
ip.str_to(buf);
if (v6 && strncmp(buf, "fe80", 4) == 0)
continue;
it.print(60, y, id(font_small), id(ink), buf);
y += 34;
}
}
} else {
it.print(40, 440, id(font_status), id(ink), "WiFi: connecting...");
}
const bool api_ok = api::global_api_server->is_connected_with_state_subscription();
it.print(40, 620, id(font_status), id(ink),
api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...");
if (!std::isnan(batt))
it.printf(40, 670, id(font_status), id(ink), "Battery: %.0f%% (%.2f V)", batt, id(battery_voltage).state);
it.printf(40, 720, id(font_status), id(ink), "Forecast: %u h stored", m5paper_weather::forecast().count);
it.printf(40, 900, id(font_small), id(ink), "ESPHome %s", ESPHOME_VERSION);
return;
}
// forecast screen
using namespace m5paper_weather;
image::Image *large[] = {id(wl_clear_night), id(wl_cloudy), id(wl_exceptional), id(wl_fog),
id(wl_hail), id(wl_lightning), id(wl_lightning_rainy), id(wl_partlycloudy),
id(wl_pouring), id(wl_rainy), id(wl_snowy), id(wl_snowy_rainy),
id(wl_sunny), id(wl_windy), id(wl_windy_variant)};
image::Image *small[] = {id(ws_clear_night), id(ws_cloudy), id(ws_exceptional), id(ws_fog),
id(ws_hail), id(ws_lightning), id(ws_lightning_rainy), id(ws_partlycloudy),
id(ws_pouring), id(ws_rainy), id(ws_snowy), id(ws_snowy_rainy),
id(ws_sunny), id(ws_windy), id(ws_windy_variant)};
static const char *const DAYS[] = {"Nedeľa", "Pondelok", "Utorok", "Streda", "Štvrtok", "Piatok", "Sobota"};
const auto &fc = m5paper_weather::forecast();
const auto now = id(rtc_time).now();
// header: date, battery
if (now.is_valid())
it.printf(24, 16, id(font_date), id(ink), "%s %d. %d.", DAYS[(now.day_of_week + 6) % 7], now.day_of_month,
now.month);
if (!std::isnan(batt))
it.printf(w - 24, 22, id(font_small), id(ink), TextAlign::TOP_RIGHT, "%.0f %%", batt);
const Hour *cur = now.is_valid() ? fc.at(now.timestamp) : nullptr;
if (cur == nullptr) {
it.print(cx, 230, id(font_status), id(ink), TextAlign::CENTER, "Predpoveď nie je k dispozícii");
} else {
// current hour
if (cur->cond < NUM_CONDITIONS)
it.image(24, 80, large[cur->cond], id(ink));
if (cur->temp2 != NO_TEMP)
it.printf(200, 70, id(font_temp), id(ink), "%.0f°", cur->temp2 / 2.0f);
it.print(204, 200, id(font_status), id(ink), condition_name(cur->cond));
if (cur->wind != NO_VALUE)
it.printf(204, 245, id(font_small), id(ink), "vietor %u km/h", cur->wind);
if (cur->pop != NO_VALUE)
it.printf(204, 280, id(font_small), id(ink), "zrážky %u %%", cur->pop);
// next 24 hours: 3 columns x 8 rows
const uint32_t first = (now.timestamp / 3600 + 1) * 3600;
const int top = 440, row_h = 60, col_w = w / 3;
for (int i = 0; i < 24; i++) {
const Hour *h = fc.at(first + i * 3600);
if (h == nullptr)
break;
const int x = (i / 8) * col_w + 12;
const int y = top + (i % 8) * row_h;
const auto t = ESPTime::from_epoch_local(first + i * 3600);
it.printf(x, y + 14, id(font_hour), id(ink), "%02d", t.hour);
if (h->cond < NUM_CONDITIONS)
it.image(x + 42, y + 8, small[h->cond], id(ink));
if (h->temp2 != NO_TEMP)
it.printf(x + 94, y + 6, id(font_hour), id(ink), "%.0f°", h->temp2 / 2.0f);
if (h->pop != NO_VALUE && h->pop >= 10)
it.printf(x + 94, y + 38, id(font_tiny), id(ink), "%u %%", h->pop);
}
for (int c = 1; c < 3; c++)
it.vertical_line(c * col_w, top, 8 * row_h, id(ink));
}
// indoor
it.horizontal_line(24, 340, w - 48, id(ink));
it.printf(cx, 380, id(font_status), id(ink), TextAlign::CENTER, "Doma %.1f °C · %.0f %%", temp, hum);
it.horizontal_line(24, 420, w - 48, id(ink));
// footer
if (fc.valid()) {
const auto f = ESPTime::from_epoch_local(fc.fetched_at);
it.printf(24, 944, id(font_small), id(ink), TextAlign::BOTTOM_LEFT, "predpoveď %d. %d. %02d:%02d",
f.day_of_month, f.month, f.hour, f.minute);
}
if (now.is_valid())
it.printf(w - 24, 944, id(font_small), id(ink), TextAlign::BOTTOM_RIGHT, "%02d:%02d", now.hour, now.minute);
touchscreen:
- platform: gt911
id: gt911_touchscreen
display: m5paper_display
# GPIO36 is input-only; INT is only driven as output when reset_pin is set
interrupt_pin:
number: GPIO36
mode:
input: true
output: false
+2 -3
View File
@@ -26,6 +26,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# PINOUTS: M5ATOM # PINOUTS: M5ATOM
# IR G12 # IR G12
@@ -228,9 +229,7 @@ button:
- pzemac.reset_energy: pzemac_l3 - pzemac.reset_energy: pzemac_l3
time: time:
- platform: homeassistant - id: !extend ha_time
id: ha_time
timezone: Europe/Bratislava
on_time: on_time:
- cron: '0 0 0 * * *' - cron: '0 0 0 * * *'
then: then:
+8 -168
View File
@@ -11,103 +11,19 @@ esphome:
friendly_name: ${name} friendly_name: ${name}
area: ${area} area: ${area}
comment: ${comment} comment: ${comment}
on_boot:
then:
- switch.turn_off: screen_power
- delay: 1s
- switch.turn_on: screen_power
- delay: 2s
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
esp32:
variant: esp32
toolchain: esp-idf
framework:
type: esp-idf
advanced:
minimum_chip_revision: 3.0
sram1_as_iram: true
external_components:
- source:
type: git
url: https://github.com/randybb/esphome-nspanel-lovelace-native
ref: devices
refresh: 0s
components: [nspanel_lovelace]
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
nspanel: !include
script: file: common/nspanel.yaml
- id: apply_sleep_dim vars:
then: temperature_entity: sensor.zigbee_bedroom_temperature
- lambda: |- sleep_dim: !include
uint8_t dim; file: common/nspanel_sleep_dim.yaml
if (id(bedroom_lights).state) dim = 5; // sleepOverride vars:
else if (id(sun_id).is_above_horizon(-6)) dim = 5; // day sleep_override_entity: light.bedroom_lights
else dim = 0; // night
id(nspanel_id).set_display_inactive_dim(dim);
api:
homeassistant_services: true
homeassistant_states: true
actions:
- action: play_rtttl
variables:
song: string
then:
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify
variables:
heading: string
message: string
song: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify_fullscreen
variables:
title: string
description: string
button1: string
button2: string
timeout: int
icon: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
uart:
id: display_uart
tx_pin: 16
rx_pin: 17
baud_rate: 115200
nspanel_lovelace: nspanel_lovelace:
id: nspanel_id
sleep_timeout: 10
experimental_vertical_ui: true
locale:
language: sk
temperature_unit: celcius
screensaver:
time_id: ha_time
time_format: '%H:%M'
date_format: '%A, %d. %b. %Y'
weather:
entity_id: sensor.weather_forecast_daily
icon_color: [255, 255, 255]
entities:
- entity_id: sensor.zigbee_bedroom_temperature
icon:
value: home-thermometer-outline
color: [255, 255, 255]
precision: 1
cards: cards:
- type: cardGrid - type: cardGrid
title: Spálňa title: Spálňa
@@ -135,13 +51,6 @@ nspanel_lovelace:
title: Kúrenie title: Kúrenie
thermo_entity_id: climate.bt_trv_bedroom thermo_entity_id: climate.bt_trv_bedroom
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
on_time_sync:
- script.execute: apply_sleep_dim
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
name: Left Button name: Left Button
@@ -166,12 +75,6 @@ binary_sensor:
entity_id: light.bedroom_lamps entity_id: light.bedroom_lamps
- lambda: 'id(nspanel_id).render_default_page();' - lambda: 'id(nspanel_id).render_default_page();'
- platform: homeassistant # sleepOverride
id: bedroom_lights
entity_id: light.bedroom_lights
on_state:
- script.execute: apply_sleep_dim
light: light:
- platform: binary - platform: binary
name: Light name: Light
@@ -179,10 +82,6 @@ light:
output: relay_1 output: relay_1
output: output:
- platform: ledc
id: buzzer_out
pin:
number: 21
- id: relay_1 - id: relay_1
platform: gpio platform: gpio
pin: pin:
@@ -191,62 +90,3 @@ output:
# platform: gpio # platform: gpio
# pin: # pin:
# number: 19 # number: 19
sun:
id: sun_id
time_id: ha_time
latitude: 48.15°
longitude: 17.11°
on_sunrise:
- elevation: -6°
then:
- script.execute: apply_sleep_dim
on_sunset:
- elevation: -6°
then:
- script.execute: apply_sleep_dim
switch:
- platform: gpio
name: Screen Power
id: screen_power
entity_category: config
pin:
number: 4
inverted: true
restore_mode: ALWAYS_ON
rtttl:
id: buzzer
output: buzzer_out
sensor:
- platform: adc
id: ntc_source
pin: 38
update_interval: 5min
attenuation: 12db
- platform: resistance
id: resistance_sensor
sensor: ntc_source
configuration: DOWNSTREAM
resistor: 11.2kOhm
- platform: ntc
name: Temperature
id: temperature
sensor: resistance_sensor
calibration:
b_constant: 3950
reference_temperature: 25°C
reference_resistance: 10kOhm
text_sensor:
- platform: homeassistant
id: name_day
entity_id: sensor.nameday_sk
on_value:
- lambda: |-
id(nspanel_id).set_time_format("%H:%M~" + x);
id(nspanel_id).update_time();
+1 -5
View File
@@ -59,6 +59,7 @@ external_components:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# common: !include common/common_dev.yaml # common: !include common/common_dev.yaml
# web_server: # web_server:
@@ -540,11 +541,6 @@ sensor:
- platform: template - platform: template
id: room_temperature_id id: room_temperature_id
time:
platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
text_sensor: text_sensor:
- platform: homeassistant - platform: homeassistant
id: name_day id: name_day
+5 -136
View File
@@ -10,93 +10,16 @@ esphome:
friendly_name: ${name} friendly_name: ${name}
area: ${area} area: ${area}
comment: ${comment} comment: ${comment}
on_boot:
then:
- switch.turn_off: screen_power
- delay: 1s
- switch.turn_on: screen_power
- delay: 2s
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
esp32:
variant: esp32
toolchain: esp-idf
framework:
type: esp-idf
advanced:
minimum_chip_revision: 3.0
sram1_as_iram: true
external_components:
- source:
type: git
url: https://github.com/randybb/esphome-nspanel-lovelace-native
ref: devices
refresh: 0s
components: [nspanel_lovelace]
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
nspanel: !include
api: file: common/nspanel.yaml
homeassistant_services: true vars:
homeassistant_states: true temperature_entity: sensor.zigbee_living_room_kitchen_temperature
actions: temperature_name: Living Room Temperature
- action: play_rtttl
variables:
song: string
then:
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify
variables:
heading: string
message: string
song: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify_fullscreen
variables:
title: string
description: string
button1: string
button2: string
timeout: int
icon: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
uart:
id: display_uart
tx_pin: 16
rx_pin: 17
baud_rate: 115200
nspanel_lovelace: nspanel_lovelace:
id: nspanel_id
sleep_timeout: 10
experimental_vertical_ui: true
locale:
language: sk
temperature_unit: celcius
screensaver:
time_id: ha_time
time_format: '%H:%M'
date_format: '%A, %d. %b. %Y'
weather:
entity_id: sensor.weather_forecast_daily
icon_color: [255, 255, 255]
entities:
- entity_id: sensor.zigbee_living_room_kitchen_temperature
icon:
value: home-thermometer-outline
color: [255, 255, 255]
precision: 1
cards: cards:
- type: cardGrid - type: cardGrid
title: Kuchyňa / Obývačka title: Kuchyňa / Obývačka
@@ -185,11 +108,6 @@ nspanel_lovelace:
- name: Auto-zatváranie - name: Auto-zatváranie
entity_id: switch.gate_auto_close entity_id: switch.gate_auto_close
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
name: Left Button name: Left Button
@@ -240,11 +158,6 @@ binary_sensor:
entity_id: light.mcu_kitchen_stove entity_id: light.mcu_kitchen_stove
- lambda: 'id(nspanel_id).render_default_page();' - lambda: 'id(nspanel_id).render_default_page();'
- lambda: 'id(nspanel_id).render_next_page();' - lambda: 'id(nspanel_id).render_next_page();'
output:
- platform: ledc
id: buzzer_out
pin:
number: 21
switch: switch:
- platform: gpio - platform: gpio
@@ -259,47 +172,3 @@ switch:
entity_category: config entity_category: config
pin: pin:
number: 19 number: 19
- platform: gpio
name: Screen Power
id: screen_power
entity_category: config
pin:
number: 4
inverted: true
restore_mode: ALWAYS_ON
rtttl:
id: buzzer
output: buzzer_out
sensor:
- platform: adc
id: ntc_source
pin: 38
update_interval: 5min
attenuation: 12db
- platform: resistance
id: resistance_sensor
sensor: ntc_source
configuration: DOWNSTREAM
resistor: 11.2kOhm
- platform: ntc
name: "Living Room Temperature"
id: temperature
sensor: resistance_sensor
calibration:
b_constant: 3950
reference_temperature: 25°C
reference_resistance: 10kOhm
text_sensor:
- platform: homeassistant
id: name_day
entity_id: sensor.nameday_sk
on_value:
- lambda: |-
id(nspanel_id).set_time_format("%H:%M~" + x);
id(nspanel_id).update_time();
+9 -137
View File
@@ -10,93 +10,20 @@ esphome:
friendly_name: ${name} friendly_name: ${name}
area: ${area} area: ${area}
comment: ${comment} comment: ${comment}
on_boot:
then:
- switch.turn_off: screen_power
- delay: 1s
- switch.turn_on: screen_power
- delay: 2s
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
esp32:
variant: esp32
toolchain: esp-idf
framework:
type: esp-idf
advanced:
minimum_chip_revision: 3.0
sram1_as_iram: true
external_components:
- source:
type: git
url: https://github.com/randybb/esphome-nspanel-lovelace-native
ref: devices
refresh: 0s
components: [nspanel_lovelace]
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
nspanel: !include
api: file: common/nspanel.yaml
homeassistant_services: true vars:
homeassistant_states: true temperature_entity: sensor.zigbee_office_j_temperature
actions: ntc_reference_temperature: 22°C
- action: play_rtttl sleep_dim: !include
variables: file: common/nspanel_sleep_dim.yaml
song: string vars:
then: sleep_override_entity: light.office_j_lights
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify
variables:
heading: string
message: string
song: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify_fullscreen
variables:
title: string
description: string
button1: string
button2: string
timeout: int
icon: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
uart:
id: display_uart
tx_pin: 16
rx_pin: 17
baud_rate: 115200
nspanel_lovelace: nspanel_lovelace:
id: nspanel_id
sleep_timeout: 10
experimental_vertical_ui: true
locale:
language: sk
temperature_unit: celcius
screensaver:
time_id: ha_time
time_format: '%H:%M'
date_format: '%A, %d. %b. %Y'
weather:
entity_id: sensor.weather_forecast_daily
icon_color: [255, 255, 255]
entities:
- entity_id: sensor.zigbee_office_j_temperature
icon:
value: home-thermometer-outline
color: [255, 255, 255]
precision: 1
cards: cards:
- type: cardGrid - type: cardGrid
title: Janko title: Janko
@@ -134,11 +61,6 @@ nspanel_lovelace:
- name: Auto-zatváranie - name: Auto-zatváranie
entity_id: switch.gate_auto_close entity_id: switch.gate_auto_close
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
name: Left Button name: Left Button
@@ -162,11 +84,6 @@ binary_sensor:
- lambda: 'id(nspanel_id).render_default_page();' - lambda: 'id(nspanel_id).render_default_page();'
output: output:
- platform: ledc
id: buzzer_out
pin:
number: 21
- id: relay_1 - id: relay_1
platform: gpio platform: gpio
pin: pin:
@@ -185,48 +102,3 @@ light:
# # - platform: binary # # - platform: binary
# # name: Relay 2 # # name: Relay 2
# # output: relay_2 # # output: relay_2
switch:
- platform: gpio
name: Screen Power
id: screen_power
entity_category: config
pin:
number: 4
inverted: true
restore_mode: ALWAYS_ON
rtttl:
id: buzzer
output: buzzer_out
sensor:
- platform: adc
id: ntc_source
pin: 38
update_interval: 5min
attenuation: 12db
- platform: resistance
id: resistance_sensor
sensor: ntc_source
configuration: DOWNSTREAM
resistor: 11.2kOhm
- platform: ntc
name: Temperature
id: temperature
sensor: resistance_sensor
calibration:
b_constant: 3950
reference_temperature: 22°C
reference_resistance: 10kOhm
text_sensor:
- platform: homeassistant
id: name_day
entity_id: sensor.nameday_sk
on_value:
- lambda: |-
id(nspanel_id).set_time_format("%H:%M~" + x);
id(nspanel_id).update_time();
+7 -135
View File
@@ -10,33 +10,15 @@ esphome:
friendly_name: ${name} friendly_name: ${name}
area: ${area} area: ${area}
comment: ${comment} comment: ${comment}
on_boot:
then:
- switch.turn_off: screen_power
- delay: 1s
- switch.turn_on: screen_power
- delay: 2s
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
esp32:
variant: esp32
toolchain: esp-idf
framework:
type: esp-idf
advanced:
minimum_chip_revision: 3.0
sram1_as_iram: true
external_components:
- source:
type: git
url: https://github.com/randybb/esphome-nspanel-lovelace-native
ref: devices
refresh: 0s
components: [nspanel_lovelace]
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
nspanel: !include
file: common/nspanel.yaml
vars:
temperature_entity: sensor.zigbee_office_r_temperature
ntc_reference_temperature: 22°C
script: script:
- id: geiger_click - id: geiger_click
@@ -55,65 +37,7 @@ script:
return esphome::random_uint32() % 450 + 50; return esphome::random_uint32() % 450 + 50;
- script.execute: geiger_click - script.execute: geiger_click
api:
homeassistant_services: true
homeassistant_states: true
actions:
- action: play_rtttl
variables:
song: string
then:
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify
variables:
heading: string
message: string
song: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify_fullscreen
variables:
title: string
description: string
button1: string
button2: string
timeout: int
icon: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
uart:
id: display_uart
tx_pin: 16
rx_pin: 17
baud_rate: 115200
nspanel_lovelace: nspanel_lovelace:
id: nspanel_id
sleep_timeout: 10
experimental_vertical_ui: true
locale:
language: sk
temperature_unit: celcius
screensaver:
time_id: ha_time
time_format: '%H:%M'
date_format: '%A, %d. %b. %Y'
weather:
entity_id: sensor.weather_forecast_daily
icon_color: [255, 255, 255]
entities:
- entity_id: sensor.zigbee_office_r_temperature
icon:
value: home-thermometer-outline
color: [255, 255, 255]
precision: 1
cards: cards:
- type: cardGrid - type: cardGrid
title: Romanko title: Romanko
@@ -158,11 +82,6 @@ nspanel_lovelace:
- name: Auto-zatváranie - name: Auto-zatváranie
entity_id: switch.gate_auto_close entity_id: switch.gate_auto_close
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
name: Left Button name: Left Button
@@ -175,7 +94,7 @@ binary_sensor:
data: data:
entity_id: light.office_r_desk_strip entity_id: light.office_r_desk_strip
- lambda: 'id(nspanel_id).render_default_page();' - lambda: 'id(nspanel_id).render_default_page();'
- platform: gpio - platform: gpio
name: Right Button name: Right Button
pin: pin:
@@ -186,10 +105,6 @@ binary_sensor:
- lambda: 'id(nspanel_id).render_default_page();' - lambda: 'id(nspanel_id).render_default_page();'
output: output:
- platform: ledc
id: buzzer_out
pin:
number: 21
- id: relay_1 - id: relay_1
platform: gpio platform: gpio
pin: pin:
@@ -229,14 +144,6 @@ switch:
id: relay_2 id: relay_2
pin: pin:
number: 19 number: 19
- platform: gpio
name: Screen Power
id: screen_power
entity_category: config
pin:
number: 4
inverted: true
restore_mode: ALWAYS_ON
- platform: template - platform: template
name: "Geiger Counter" name: "Geiger Counter"
optimistic: True optimistic: True
@@ -244,38 +151,3 @@ switch:
- script.execute: geiger_click - script.execute: geiger_click
turn_off_action: turn_off_action:
- script.stop: geiger_click - script.stop: geiger_click
rtttl:
id: buzzer
output: buzzer_out
sensor:
- platform: adc
id: ntc_source
pin: 38
update_interval: 5min
attenuation: 12db
- platform: resistance
id: resistance_sensor
sensor: ntc_source
configuration: DOWNSTREAM
resistor: 11.2kOhm
- platform: ntc
name: Temperature
id: temperature
sensor: resistance_sensor
calibration:
b_constant: 3950
reference_temperature: 22°C
reference_resistance: 10kOhm
text_sensor:
- platform: homeassistant
id: name_day
entity_id: sensor.nameday_sk
on_value:
- lambda: |-
id(nspanel_id).set_time_format("%H:%M~" + x);
id(nspanel_id).update_time();
+5 -137
View File
@@ -10,93 +10,16 @@ esphome:
friendly_name: ${name} friendly_name: ${name}
area: ${area} area: ${area}
comment: ${comment} comment: ${comment}
on_boot:
then:
- switch.turn_off: screen_power
- delay: 1s
- switch.turn_on: screen_power
- delay: 2s
- lambda: 'id(nspanel_id).set_display_dim(5, 80);'
esp32:
variant: esp32
toolchain: esp-idf
framework:
type: esp-idf
advanced:
minimum_chip_revision: 3.0
sram1_as_iram: true
external_components:
- source:
type: git
url: https://github.com/randybb/esphome-nspanel-lovelace-native
ref: devices
refresh: 0s
components: [nspanel_lovelace]
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
nspanel: !include
api: file: common/nspanel.yaml
homeassistant_services: true vars:
homeassistant_states: true temperature_entity: sensor.zigbee_living_room_kitchen_temperature
actions: temperature_name: Living Room Temperature
- action: play_rtttl
variables:
song: string
then:
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify
variables:
heading: string
message: string
song: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(heading, message, 10000);'
- rtttl.play:
rtttl: !lambda 'return song;'
- action: notify_fullscreen
variables:
title: string
description: string
button1: string
button2: string
timeout: int
icon: string
then:
- lambda: 'id(nspanel_id).notify_on_screensaver(title, description, timeout);'
- rtttl.play: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b"
uart:
id: display_uart
tx_pin: 16
rx_pin: 17
baud_rate: 115200
nspanel_lovelace: nspanel_lovelace:
id: nspanel_id
sleep_timeout: 10
experimental_vertical_ui: true
locale:
language: sk
temperature_unit: celcius
screensaver:
time_id: ha_time
time_format: '%H:%M'
date_format: '%A, %d. %b. %Y'
weather:
entity_id: sensor.weather_forecast_daily
icon_color: [255, 255, 255]
entities:
- entity_id: sensor.zigbee_living_room_kitchen_temperature
icon:
value: home-thermometer-outline
color: [255, 255, 255]
precision: 1
cards: cards:
- type: cardGrid - type: cardGrid
title: Kuchyňa / Obývačka title: Kuchyňa / Obývačka
@@ -214,11 +137,6 @@ nspanel_lovelace:
- name: Rast trávy - name: Rast trávy
entity_id: automation.irrigation_grow_grass entity_id: automation.irrigation_grow_grass
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
name: Left Button name: Left Button
@@ -251,12 +169,6 @@ binary_sensor:
else: else:
- switch.toggle: relay_2 - switch.toggle: relay_2
output:
- platform: ledc
id: buzzer_out
pin:
number: 21
switch: switch:
- platform: gpio - platform: gpio
name: Outside Light Back name: Outside Light Back
@@ -271,47 +183,3 @@ switch:
entity_category: config entity_category: config
pin: pin:
number: 19 number: 19
- platform: gpio
name: Screen Power
id: screen_power
entity_category: config
pin:
number: 4
inverted: true
restore_mode: ALWAYS_ON
rtttl:
id: buzzer
output: buzzer_out
sensor:
- platform: adc
id: ntc_source
pin: 38
update_interval: 5min
attenuation: 12db
- platform: resistance
id: resistance_sensor
sensor: ntc_source
configuration: DOWNSTREAM
resistor: 11.2kOhm
- platform: ntc
name: Living Room Temperature
id: temperature
sensor: resistance_sensor
calibration:
b_constant: 3950
reference_temperature: 25°C
reference_resistance: 10kOhm
text_sensor:
- platform: homeassistant
id: name_day
entity_id: sensor.nameday_sk
on_value:
- lambda: |-
id(nspanel_id).set_time_format("%H:%M~" + x);
id(nspanel_id).update_time();
+1
View File
@@ -22,6 +22,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# GPIO21 UART TX # GPIO21 UART TX
# GPIO20 UART RX # GPIO20 UART RX
+1 -5
View File
@@ -37,6 +37,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# PINOUTS: M5ATOM+Atom Hub Switch # PINOUTS: M5ATOM+Atom Hub Switch
@@ -54,11 +55,6 @@ packages:
# Relay1 G22 # Relay1 G22
# Relay2 G19 # Relay2 G19
time:
- platform: homeassistant
id: ha_time
timezone: Europe/Bratislava
i2c: i2c:
# - id: i2c0 # - id: i2c0
# sda: 26 # sda: 26
+1 -2
View File
@@ -23,6 +23,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
uart: uart:
- id: cse7766_uart - id: cse7766_uart
@@ -187,5 +188,3 @@ switch:
- switch.turn_off: relay_off # bi-stable relay so no need to keep on - switch.turn_off: relay_off # bi-stable relay so no need to keep on
interlock: [relay_on] interlock: [relay_on]
time:
- !include common/time/homeassistant.yaml
+1 -4
View File
@@ -2,7 +2,7 @@
substitutions: substitutions:
device: pow-washer device: pow-washer
name: Washer POW name: Washer POW
comment: comment: "spare"
area: Washroom area: Washroom
update_interval: 10s update_interval: 10s
@@ -186,6 +186,3 @@ switch:
- delay: 500ms - delay: 500ms
- switch.turn_off: relay_off # bi-stable relay so no need to keep on - switch.turn_off: relay_off # bi-stable relay so no need to keep on
interlock: [relay_on] interlock: [relay_on]
time:
- !include common/time/homeassistant.yaml
+1
View File
@@ -19,6 +19,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
spi: spi:
clk_pin: 23 clk_pin: 23
+1
View File
@@ -40,6 +40,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
# logger: # logger:
# hardware_uart: UART0 # hardware_uart: UART0
+1 -2
View File
@@ -21,6 +21,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
i2c: i2c:
sda: 25 sda: 25
@@ -109,5 +110,3 @@ switch:
number: 19 number: 19
mode: OUTPUT mode: OUTPUT
time:
- !include common/time/homeassistant.yaml
+1 -2
View File
@@ -121,8 +121,7 @@ switch:
# number: GPIO00 # number: GPIO00
# inverted: True # inverted: True
time: # time:
- !include common/time/homeassistant.yaml
# - platform: homeassistant # - platform: homeassistant
# timezone: Europe/Bratislava # timezone: Europe/Bratislava
# on_time: # on_time:
+1 -2
View File
@@ -122,8 +122,7 @@ switch:
on_turn_off: on_turn_off:
- light.turn_off: light_red - light.turn_off: light_red
time: # time:
- !include common/time/homeassistant.yaml
# - platform: homeassistant # - platform: homeassistant
# timezone: Europe/Bratislava # timezone: Europe/Bratislava
# on_time: # on_time:
+1 -2
View File
@@ -144,8 +144,7 @@ switch:
# number: GPIO00 # number: GPIO00
# inverted: True # inverted: True
time: # time:
- !include common/time/homeassistant.yaml
# - platform: homeassistant # - platform: homeassistant
# timezone: Europe/Bratislava # timezone: Europe/Bratislava
# on_time: # on_time:
+23 -9
View File
@@ -33,6 +33,7 @@ psram:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
external_components: external_components:
- source: github://m5stack/esphome-yaml/components - source: github://m5stack/esphome-yaml/components
@@ -464,9 +465,9 @@ spi:
image: image:
- platform: file - platform: file
file: assets/images/esphome-logo-text-on-dark.svg file: assets/images/made-for-esphome-white-on-black.svg
id: esphome_logo id: esphome_logo
resize: 280x56 resize: 230x68
type: RGB565 type: RGB565
transparency: alpha_channel transparency: alpha_channel
- platform: file - platform: file
@@ -545,22 +546,36 @@ display:
if (id(show_status)) { if (id(show_status)) {
first_draw = true; first_draw = true;
it.fill(id(face_bg)); it.fill(id(face_bg));
it.image(160, 45, id(esphome_logo), ImageAlign::CENTER); it.image(160, 46, id(esphome_logo), ImageAlign::CENTER);
it.print(160, 95, id(font_title), id(face_fg), TextAlign::CENTER, "${name}"); it.print(160, 95, id(font_title), id(face_fg), TextAlign::CENTER, "${name}");
if (wifi_ok) { if (wifi_ok) {
it.printf(20, 135, id(font_status), id(status_ok), "WiFi: %s (%d dBm)", it.printf(20, 114, id(font_status), id(status_ok), "WiFi: connected (%d dBm)", rssi);
id(ip_address_sensor).state.c_str(), rssi); // one global IPv6 (skip link-local) and one IPv4 address
int y = 133;
char buf[network::IP_ADDRESS_BUFFER_SIZE];
for (bool v6 : {true, false}) {
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
if (!ip.is_set() || ip.is_ip6() != v6)
continue;
ip.str_to(buf);
if (v6 && strncmp(buf, "fe80", 4) == 0)
continue;
it.print(30, y, id(font_status), id(status_dim), buf);
y += 19;
break;
}
}
} else { } else {
it.print(20, 135, id(font_status), id(status_wait), "WiFi: connecting..."); it.print(20, 114, id(font_status), id(status_wait), "WiFi: connecting...");
} }
const bool api_ok = api::global_api_server->is_connected(); const bool api_ok = api::global_api_server->is_connected();
it.print(20, 162, id(font_status), api_ok ? id(status_ok) : id(status_wait), it.print(20, 172, id(font_status), api_ok ? id(status_ok) : id(status_wait),
api_ok ? "Home Assistant: connected" : "Home Assistant: waiting..."); api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...");
if (batt >= 0) { if (batt >= 0) {
it.printf(20, 189, id(font_status), batt <= 20 && !charging ? id(status_low) : id(status_dim), it.printf(20, 191, id(font_status), batt <= 20 && !charging ? id(status_low) : id(status_dim),
"Battery: %d%%%s", batt, charging ? " (charging)" : ""); "Battery: %d%%%s", batt, charging ? " (charging)" : "");
} }
it.printf(20, 212, id(font_status), id(status_dim), "ESPHome %s", ESPHOME_VERSION); it.printf(20, 212, id(font_status), id(status_dim), "ESPHome %s", ESPHOME_VERSION);
@@ -883,4 +898,3 @@ time:
id: rtc_time id: rtc_time
i2c_id: i2c_bsp i2c_id: i2c_bsp
update_interval: never update_interval: never
- !include common/time/homeassistant.yaml
+55
View File
@@ -21,6 +21,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
logger: logger:
logs: logs:
@@ -49,6 +50,20 @@ color:
hex: ff0000 hex: ff0000
- id: my_white - id: my_white
hex: ffffff hex: ffffff
- id: status_ok
hex: 4dd966
- id: status_wait
hex: ffb300
- id: status_dim
hex: 8c8c8c
image:
- platform: file
file: assets/images/made-for-esphome-white-on-black.svg
id: esphome_logo
resize: 220x65
type: RGB565
transparency: alpha_channel
font: font:
- file: "gfonts://Roboto" - file: "gfonts://Roboto"
@@ -107,6 +122,46 @@ display:
invert_colors: false invert_colors: false
lambda: |- lambda: |-
it.fill(Color::BLACK); it.fill(Color::BLACK);
// status screen until Home Assistant connects (240x320)
if (!api::global_api_server->is_connected_with_state_subscription()) {
const int cx = it.get_width() / 2;
it.image(cx, 48, id(esphome_logo), ImageAlign::CENTER);
it.print(cx, 100, id(roboto_24), id(my_white), TextAlign::CENTER, "${name}");
int y = 130;
if (wifi::global_wifi_component->is_connected()) {
it.printf(8, y, id(roboto_16), id(status_ok), "WiFi: connected (%d dBm)",
wifi::global_wifi_component->wifi_rssi());
y += 22;
// IPv6 first (skip link-local), then IPv4
char buf[network::IP_ADDRESS_BUFFER_SIZE];
for (bool v6 : {true, false}) {
for (auto &ip : wifi::global_wifi_component->get_ip_addresses()) {
if (!ip.is_set() || ip.is_ip6() != v6 || y > 200)
continue;
ip.str_to(buf);
if (v6 && strncmp(buf, "fe80", 4) == 0)
continue;
it.print(8, y, id(roboto_12), id(status_dim), buf);
y += 16;
}
}
} else {
it.print(8, y, id(roboto_16), id(status_wait), "WiFi: connecting...");
y += 22;
}
y += 8;
it.print(8, y, id(roboto_16), api::global_api_server->is_connected() ? id(status_ok) : id(status_wait),
api::global_api_server->is_connected() ? "Home Assistant: connected" : "Home Assistant: waiting...");
it.printf(8, it.get_height() - 8, id(roboto_12), id(status_dim), TextAlign::BOTTOM_LEFT, "ESPHome %s",
ESPHOME_VERSION);
it.printf(it.get_width() - 8, it.get_height() - 8, id(roboto_12), id(status_dim), TextAlign::BOTTOM_RIGHT,
"up %us", (unsigned) (millis() / 1000));
return;
}
it.print(0, 0, id(b612_12), id(my_white), TextAlign::TOP_LEFT, "HELLO WORLD!"); it.print(0, 0, id(b612_12), id(my_white), TextAlign::TOP_LEFT, "HELLO WORLD!");
it.print(0, 14, id(b612_12), id(my_green), TextAlign::TOP_LEFT, "HELLO WORLD!"); it.print(0, 14, id(b612_12), id(my_green), TextAlign::TOP_LEFT, "HELLO WORLD!");
it.print(0, 28, id(b612_12), id(my_magenta), TextAlign::TOP_LEFT, "HELLO WORLD!"); it.print(0, 28, id(b612_12), id(my_magenta), TextAlign::TOP_LEFT, "HELLO WORLD!");
+1 -2
View File
@@ -34,6 +34,7 @@ esp32:
packages: packages:
common: !include common/common.yaml common: !include common/common.yaml
ipv6_only: !include common/ipv6_only.yaml
api: api:
actions: actions:
@@ -358,5 +359,3 @@ valve:
close_action: close_action:
- switch.turn_off: irrigation_relay_8 - switch.turn_off: irrigation_relay_8
time:
- !include common/time/homeassistant.yaml