diff --git a/.cache/brands/integrations/astroweather/dark_icon.png b/.cache/brands/integrations/astroweather/dark_icon.png index f5b3b28..33f5493 100644 Binary files a/.cache/brands/integrations/astroweather/dark_icon.png and b/.cache/brands/integrations/astroweather/dark_icon.png differ diff --git a/.cache/brands/integrations/astroweather/icon.png b/.cache/brands/integrations/astroweather/icon.png index 192cef1..478ea3c 100644 Binary files a/.cache/brands/integrations/astroweather/icon.png and b/.cache/brands/integrations/astroweather/icon.png differ diff --git a/.cache/brands/integrations/braviatv/icon.png b/.cache/brands/integrations/braviatv/icon.png index 65b8ed2..6ac928a 100644 Binary files a/.cache/brands/integrations/braviatv/icon.png and b/.cache/brands/integrations/braviatv/icon.png differ diff --git a/.cache/brands/integrations/brother/dark_logo.png b/.cache/brands/integrations/brother/dark_logo.png new file mode 100644 index 0000000..ed13652 Binary files /dev/null and b/.cache/brands/integrations/brother/dark_logo.png differ diff --git a/.cache/brands/integrations/brother/icon@2x.png b/.cache/brands/integrations/brother/icon@2x.png new file mode 100644 index 0000000..952b865 Binary files /dev/null and b/.cache/brands/integrations/brother/icon@2x.png differ diff --git a/.cache/brands/integrations/browser_mod/dark_logo.png b/.cache/brands/integrations/browser_mod/dark_logo.png new file mode 100644 index 0000000..217b78d Binary files /dev/null and b/.cache/brands/integrations/browser_mod/dark_logo.png differ diff --git a/.cache/brands/integrations/ge_home/dark_icon@2x.png b/.cache/brands/integrations/ge_home/dark_icon@2x.png new file mode 100644 index 0000000..54cad22 Binary files /dev/null and b/.cache/brands/integrations/ge_home/dark_icon@2x.png differ diff --git a/.cache/brands/integrations/ge_home/dark_logo.png b/.cache/brands/integrations/ge_home/dark_logo.png new file mode 100644 index 0000000..9865a36 Binary files /dev/null and b/.cache/brands/integrations/ge_home/dark_logo.png differ diff --git a/.cache/brands/integrations/hacs/dark_logo.png b/.cache/brands/integrations/hacs/dark_logo.png new file mode 100644 index 0000000..cffa310 Binary files /dev/null and b/.cache/brands/integrations/hacs/dark_logo.png differ diff --git a/.cache/brands/integrations/ipp/dark_logo.png b/.cache/brands/integrations/ipp/dark_logo.png new file mode 100644 index 0000000..27877b6 Binary files /dev/null and b/.cache/brands/integrations/ipp/dark_logo.png differ diff --git a/.cache/brands/integrations/ld2410_ble/dark_icon.png b/.cache/brands/integrations/ld2410_ble/dark_icon.png new file mode 100644 index 0000000..8da27d2 Binary files /dev/null and b/.cache/brands/integrations/ld2410_ble/dark_icon.png differ diff --git a/.cache/brands/integrations/lovelace/icon.png b/.cache/brands/integrations/lovelace/icon.png new file mode 100644 index 0000000..e5267fa Binary files /dev/null and b/.cache/brands/integrations/lovelace/icon.png differ diff --git a/.cache/brands/integrations/mjpeg/dark_icon@2x.png b/.cache/brands/integrations/mjpeg/dark_icon@2x.png new file mode 100644 index 0000000..1ace8ae Binary files /dev/null and b/.cache/brands/integrations/mjpeg/dark_icon@2x.png differ diff --git a/.cache/brands/integrations/mjpeg/dark_logo.png b/.cache/brands/integrations/mjpeg/dark_logo.png new file mode 100644 index 0000000..a0748c3 Binary files /dev/null and b/.cache/brands/integrations/mjpeg/dark_logo.png differ diff --git a/.cache/brands/integrations/mpd/dark_logo.png b/.cache/brands/integrations/mpd/dark_logo.png new file mode 100644 index 0000000..977d124 Binary files /dev/null and b/.cache/brands/integrations/mpd/dark_logo.png differ diff --git a/.cache/brands/integrations/nodered/dark_icon@2x.png b/.cache/brands/integrations/nodered/dark_icon@2x.png new file mode 100644 index 0000000..894584b Binary files /dev/null and b/.cache/brands/integrations/nodered/dark_icon@2x.png differ diff --git a/.cache/brands/integrations/openmediavault/dark_icon.png b/.cache/brands/integrations/openmediavault/dark_icon.png index c2616f9..45dad3e 100644 Binary files a/.cache/brands/integrations/openmediavault/dark_icon.png and b/.cache/brands/integrations/openmediavault/dark_icon.png differ diff --git a/.cache/brands/integrations/openmediavault/dark_icon@2x.png b/.cache/brands/integrations/openmediavault/dark_icon@2x.png new file mode 100644 index 0000000..04f5dff Binary files /dev/null and b/.cache/brands/integrations/openmediavault/dark_icon@2x.png differ diff --git a/.cache/brands/integrations/openmediavault/dark_logo.png b/.cache/brands/integrations/openmediavault/dark_logo.png new file mode 100644 index 0000000..7eaf1b8 Binary files /dev/null and b/.cache/brands/integrations/openmediavault/dark_logo.png differ diff --git a/.cache/brands/integrations/panasonic_cc/dark_logo.png b/.cache/brands/integrations/panasonic_cc/dark_logo.png new file mode 100644 index 0000000..0113c8f Binary files /dev/null and b/.cache/brands/integrations/panasonic_cc/dark_logo.png differ diff --git a/.cache/brands/integrations/waze_travel_time/icon.png b/.cache/brands/integrations/waze_travel_time/icon.png index 5e67038..78db530 100644 Binary files a/.cache/brands/integrations/waze_travel_time/icon.png and b/.cache/brands/integrations/waze_travel_time/icon.png differ diff --git a/.cache/brands/integrations/xiaomi_ble/dark_logo.png b/.cache/brands/integrations/xiaomi_ble/dark_logo.png new file mode 100644 index 0000000..fe40070 Binary files /dev/null and b/.cache/brands/integrations/xiaomi_ble/dark_logo.png differ diff --git a/.ha_run.lock b/.ha_run.lock index fb25d3f..2365633 100644 --- a/.ha_run.lock +++ b/.ha_run.lock @@ -1 +1 @@ -{"pid": 67, "version": 1, "ha_version": "2026.5.0", "start_ts": 1778146972.604623} \ No newline at end of file +{"pid": 67, "version": 1, "ha_version": "2026.5.4", "start_ts": 1779529638.1563158} \ No newline at end of file diff --git a/automations.yaml b/automations.yaml index fe51488..8f87b12 100644 --- a/automations.yaml +++ b/automations.yaml @@ -1 +1,114 @@ -[] +- id: '1778927514490' + alias: NSPanel Easy Configuration (v2026.5.5) + description: '' + use_blueprint: + path: edwardtfn/nspanel_easy_blueprint.yaml + input: + nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f + weather_entity: weather.astroweather_backyard + indoortemp: sensor.lounge_inside_temperature +- id: '1778927924212' + alias: NSPanel Easy Configuration (v2026.5.5) + description: '' + use_blueprint: + path: edwardtfn/nspanel_easy_blueprint.yaml + input: + nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f + button_page01_label: Lounge Scenes + button_pages_icon_size: '8' + entity01: script.nspanel_lounge_scene_bright + entity01_name: Bright + entity01_icon: mdi:brightness-7 + entity01_confirm: false + entity02: script.nspanel_lounge_scene_tv_general + entity02_name: TV General + entity02_icon: mdi:television + entity02_confirm: false + entity03: script.nspanel_lounge_scene_movie + entity03_name: Movie + entity03_icon: mdi:movie-open + entity03_confirm: false + entity04: script.nspanel_lounge_scene_balanced + entity04_name: Balanced + entity04_icon: mdi:scale-balance + entity04_confirm: false + entity05: script.nspanel_lounge_scene_warm_cozy + entity05_name: Warm Cozy + entity05_icon: mdi:fireplace + entity05_confirm: false + entity06: script.nspanel_lounge_scene_reading + entity06_name: Reading + entity06_icon: mdi:book-open-page-variant + entity06_confirm: false + entity07: script.nspanel_lounge_scene_dining + entity07_name: Dining + entity07_icon: mdi:silverware-fork-knife + entity07_confirm: false + entity08: script.nspanel_lounge_scene_all_off + entity08_name: All Off + entity08_icon: mdi:power + entity08_confirm: false + mode: single +- id: '1778929633433' + alias: NSPanel Easy Configuration (v2026.5.5) + description: '' + use_blueprint: + path: edwardtfn/nspanel_easy_blueprint.yaml + input: + nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f + button_page01_label: Lounge Scenes + button_pages_icon_size: '8' + entity01: script.nspanel_lounge_scene_bright + entity01_name: Bright + entity01_icon: mdi:brightness-7 + entity01_confirm: false + entity02: script.nspanel_lounge_scene_tv_general + entity02_name: TV General + entity02_icon: mdi:television + entity02_confirm: false + entity03: script.nspanel_lounge_scene_movie + entity03_name: Movie + entity03_icon: mdi:movie-open + entity03_confirm: false + entity04: script.nspanel_lounge_scene_balanced + entity04_name: Balanced + entity04_icon: mdi:scale-balance + entity04_confirm: false + entity05: script.nspanel_lounge_scene_warm_cozy + entity05_name: Warm Cozy + entity05_icon: mdi:fireplace + entity05_confirm: false + entity06: script.nspanel_lounge_scene_reading + entity06_name: Reading + entity06_icon: mdi:book-open-page-variant + entity06_confirm: false + entity07: script.nspanel_lounge_scene_dining + entity07_name: Dining + entity07_icon: mdi:silverware-fork-knife + entity07_confirm: false + entity08: script.nspanel_lounge_scene_all_off + entity08_name: All Off + entity08_icon: mdi:power + entity08_confirm: false + mode: single +- id: '1778930023427' + alias: NSPanel Lounge Controller + description: '' + use_blueprint: + path: edwardtfn/nspanel_easy_blueprint.yaml + input: + nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f +- id: '1778930202677' + alias: NSPanel Lounge Controller + description: '' + use_blueprint: + path: edwardtfn/nspanel_easy_blueprint.yaml + input: + nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f +- id: '1778930948144' + alias: Lounge NSPanel Controls + description: '' + use_blueprint: + path: edwardtfn/nspanel_easy_blueprint.yaml + input: + nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f diff --git a/automations/quiet_time_settings.yaml b/automations/quiet_time_settings.yaml deleted file mode 100644 index 1247dff..0000000 --- a/automations/quiet_time_settings.yaml +++ /dev/null @@ -1,10 +0,0 @@ -- id: quiet_time_settings_to_mqtt - alias: Publish quiet time value to mqtt - trigger: - platform: state - entity_id: input_boolean.quiet_time - action: - service: mqtt.publish - data_template: - payload: "{{trigger.to_state.state}}" - topic: viewroad-status/homeassistant/quiet-time diff --git a/esphome/.device-builder-peer-link-key.bin b/esphome/.device-builder-peer-link-key.bin new file mode 100644 index 0000000..c4b9811 --- /dev/null +++ b/esphome/.device-builder-peer-link-key.bin @@ -0,0 +1 @@ +È(vÔOØÛ«3÷Ñm0Ì “ІUääq·äp½,@R \ No newline at end of file diff --git a/esphome/.device-builder.json b/esphome/.device-builder.json new file mode 100644 index 0000000..659188f --- /dev/null +++ b/esphome/.device-builder.json @@ -0,0 +1,732 @@ +{ + "esp-entbtproxy.yaml": { + "board_id": "generic-esp32", + "mac_address": "0C:B8:15:F5:2A:CC" + }, + "esp-spare1-athomv1.yaml": { + "board_id": "esp8285" + }, + "esp-centralstairs-top.yaml": { + "board_id": "generic-esp8266", + "mac_address": "48:3F:DA:16:51:20" + }, + "esp-officeelvcontrol.yaml": { + "board_id": "d1-mini", + "mac_address": "5C:CF:7F:BD:7B:42" + }, + "esp-gasheater.yaml": { + "board_id": "generic-esp8266" + }, + "esp-bedrm1arlecbulb.yaml": { + "board_id": "generic-esp8266" + }, + "esp-maindishwasherpower.yaml": { + "board_id": "generic-esp32c3", + "mac_address": "24:EC:4A:B0:C9:AC" + }, + "esp-downstbathtowelrail.yaml": { + "board_id": "d1-mini" + }, + "esp-bedrm2fanswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "24:62:AB:05:A3:24" + }, + "esp-downstmasterbedrmlights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "E8:DB:84:92:DE:1C" + }, + "esp-loungenorthswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "EC:FA:BC:5C:4C:8B" + }, + "esp-garagelights.yaml": { + "board_id": "d1-mini", + "mac_address": "5C:CF:7F:3B:C1:97" + }, + "esp-underhouselights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "60:01:94:84:70:87" + }, + "esp-mainbathfancombo.yaml": { + "board_id": "generic-esp8266", + "mac_address": "8C:AA:B5:E0:7D:19" + }, + "esp-entranceoutsideledlights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "60:01:94:6F:91:75" + }, + "esp-centralstairs-bottom.yaml": { + "board_id": "generic-esp8266", + "mac_address": "48:3F:DA:16:86:A8" + }, + "esp-arcademachine1.yaml": { + "board_id": "esp8285", + "mac_address": "10:52:1C:FF:D9:8E" + }, + "esp-officedeskrightgpo.yaml": { + "board_id": "d1-mini", + "mac_address": "2C:3A:E8:0F:4E:0E" + }, + "esp-mainhallwaymainlights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "50:02:91:43:0B:7C" + }, + "esp-downstbedrm2lights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "50:02:91:43:0C:40" + }, + "esp-mainhallwaynorthlightswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "CC:50:E3:EE:5E:45" + }, + "esp-garageextcornerlight.yaml": { + "board_id": "d1-mini", + "mac_address": "2C:3A:E8:0F:54:90" + }, + "esp-downstairsdishwasher.yaml": { + "board_id": "esp8285", + "mac_address": "78:42:1C:F2:4E:AD" + }, + "esp-entrancebathrmlights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "8C:AA:B5:E0:A1:2E" + }, + "esp-bedrm2lights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "DC:4F:22:72:0E:C1" + }, + "esp-weatherstation.yaml": { + "board_id": "d1-mini" + }, + "esp-garagedbcontrols.yaml": { + "board_id": "esp8285", + "mac_address": "60:01:94:88:DD:AA" + }, + "esp-datacupboardfans.yaml": { + "board_id": "aspen_asp_200_smart_window_fan", + "mac_address": "50:02:91:5F:D6:A7" + }, + "esp-mainkitchenlights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "8C:AA:B5:DF:B7:DE" + }, + "esp-loungepelmetleds2.yaml": { + "board_id": "generic-esp8266" + }, + "esp-entmulti.yaml": { + "board_id": "generic-esp32", + "mac_address": "E4:65:B8:77:76:EC" + }, + "esp-occupancystair.yaml": { + "board_id": "generic-esp32", + "mac_address": "0C:B8:15:C1:F1:0C" + }, + "esp-datacupboardpower-left.yaml": { + "board_id": "esp8285", + "mac_address": "78:42:1C:F2:70:20" + }, + "esp-officelights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "EC:FA:BC:48:9E:A6" + }, + "esp-mainbathroomlights.yaml": { + "board_id": "generic-bk7231n-qfn32-tuya", + "mac_address": "CC:8C:BF:2D:B1:C4" + }, + "esp-loungesouthrightswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "84:CC:A8:8C:52:32" + }, + "esp-bedrm3ceilingfan.yaml": { + "board_id": "d1-mini" + }, + "esp-laundryplugpow.yaml": { + "board_id": "generic-esp8266", + "mac_address": "2C:3A:E8:07:5C:88" + }, + "esp-mainhallwaynightlights.yaml": { + "board_id": "generic-bk7231n-qfn32-tuya", + "mac_address": "50:8A:06:67:3E:4A" + }, + "esp-datacupboardpower-right.yaml": { + "board_id": "esp8285", + "mac_address": "78:42:1C:F2:6F:8B" + }, + "esp-poolpumppower.yaml": { + "board_id": "generic-esp32c3", + "mac_address": "24:EC:4A:B0:C9:88" + }, + "esp-laundrydrypow.yaml": { + "board_id": "generic-esp8266", + "mac_address": "5C:CF:7F:92:35:96" + }, + "esp-loungecabinetleds.yaml": { + "board_id": "generic-esp8266" + }, + "esp-6buttontest-pmb.yaml": { + "board_id": "generic-esp32" + }, + "esp-bedrm3lights.yaml": { + "board_id": "generic-esp8266" + }, + "esp-laundrywashpow.yaml": { + "board_id": "generic-esp8266", + "mac_address": "5C:CF:7F:B4:A7:A5" + }, + "esp-breakfastbarleds.yaml": { + "board_id": "generic-esp8266" + }, + "esp-laundryenv.yaml": { + "board_id": "generic-esp8266", + "mac_address": "5C:CF:7F:92:41:F5" + }, + "esp-occupancyoffice.yaml": { + "board_id": "generic-esp32", + "mac_address": "0C:B8:15:C0:E7:00" + }, + "esp-flysprayer1.yaml": { + "board_id": "esp8285", + "mac_address": "2C:F4:32:E5:22:5D" + }, + "esp-3dprinterpow.yaml": { + "board_id": "generic-esp8266", + "mac_address": "5C:CF:7F:A0:36:73" + }, + "esp-rocktumbler.yaml": { + "board_id": "esp8285", + "mac_address": "78:42:1C:F2:5A:E7" + }, + "esp-rgblampbulb1.yaml": { + "board_id": "generic-esp8266" + }, + "esp-downstloungeentry.yaml": { + "board_id": "generic-esp8266", + "mac_address": "50:02:91:43:0D:A3" + }, + "esp-downstloungeoutside.yaml": { + "board_id": "generic-esp8266", + "mac_address": "24:62:AB:05:9B:7A" + }, + "esp-masterbathtowelrail.yaml": { + "board_id": "d1-mini" + }, + "esp-loungeenvironment.yaml": { + "board_id": "generic-esp32c3", + "mac_address": "8C:D0:B2:A9:72:E5" + }, + "esp-loungewestswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "EC:FA:BC:5C:4C:03" + }, + "esp-lounge6chdimmer.yaml": { + "board_id": "d1-mini-pro", + "mac_address": "C8:C9:A3:3B:0B:59" + }, + "esp-downstbathswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "84:CC:A8:8B:44:9D" + }, + "esp-loungemiddleswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "24:62:AB:05:A2:BE" + }, + "esp-officeusbhubpower.yaml": { + "board_id": "generic-esp8266", + "mac_address": "2C:3A:E8:0F:53:52" + }, + "esp-leafbattery.yaml": { + "board_id": "d1-mini" + }, + "esp-garagebtproxy.yaml": { + "board_id": "generic-esp32", + "mac_address": "10:06:1C:86:D0:1C" + }, + "esp-bedrm1airfilter.yaml": { + "board_id": "d1-mini", + "mac_address": "E8:9F:6D:87:C1:5D" + }, + "esp-downstkitchlights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "24:62:AB:05:A8:90" + }, + "esp-garageentrylights.yaml": { + "board_id": "generic-bk7231n-qfn32-tuya" + }, + "esp-mainpowermonitor.yaml": { + "board_id": "d1-mini", + "mac_address": "2C:F4:32:4B:BC:70" + }, + "esp-loungebookshelfleds.yaml": { + "board_id": "generic-esp8266" + }, + "esp-officeduallights.yaml": { + "board_id": "generic-esp8266" + }, + "esp-poollightpower2.yaml": { + "board_id": "esp8285", + "mac_address": "78:42:1C:F2:50:87" + }, + "esp-laundrylights.yaml": { + "board_id": "generic-esp8266", + "mac_address": "E8:DB:84:92:D8:44" + }, + "esp-downstloungemain.yaml": { + "board_id": "generic-esp8266", + "mac_address": "24:62:AB:05:A2:C3" + }, + "esp-loungesouthleftswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "8C:AA:B5:DF:F5:ED" + }, + "esp-attobattery.yaml": { + "board_id": "d1-mini" + }, + "esp-downstairskitchleds.yaml": { + "board_id": "generic-esp8266" + }, + "esp-entrancelightswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "40:F5:20:E0:44:DA" + }, + "esp-bedrm3fanswitch.yaml": { + "board_id": "generic-esp8266", + "mac_address": "84:F3:EB:EC:6E:3A" + }, + "esp-bedrm2ceilingfan.yaml": { + "board_id": "d1-mini" + }, + "_remote_build": { + "dashboard_id": "vhamVDnWEnx-FvAa24uVv49Wsddurh84" + }, + "esp-entbtproxy copy.yaml": { + "board_id": "generic-esp32" + }, + "esp-nspanel2lounge.yaml": { + "board_id": "generic-esp32", + "mac_address": "88:13:BF:68:39:2C" + }, + "_firmware_jobs": [ + { + "job_id": "126762467ae7", + "configuration": "esp-nspanel1masterbdrm.yaml", + "job_type": "install", + "status": "completed", + "created_at": "2026-05-17T10:36:36.272476+00:00", + "started_at": "2026-05-17T10:36:36.272693+00:00", + "completed_at": "2026-05-17T10:37:14.499200+00:00", + "exit_code": 0, + "output": [ + "\\033[32mINFO ESPHome 2026.5.0b1\\033[0m\n", + "\\033[32mINFO Reading configuration /config/esphome/esp-nspanel1masterbdrm.yaml...\\033[0m\n", + "\\033[32mINFO Detected timezone 'Pacific/Auckland'\\033[0m\n", + "\\033[33mWARNING Found and merged multiple configurations for ota platform esphome port(s) [3232]\\033[0m\n", + "\\033[32mINFO Generating C++ source...\\033[0m\n", + "\\033[32mINFO Setting CONFIG_LWIP_MAX_SOCKETS to 13 (TCP=6 [api=3, captive_portal=3], UDP=3 [captive_portal=1, mdns=2], TCP_LISTEN=4 [api=1, ota=2, web_server_base=1])\\033[0m\n", + "\\033[32mINFO Compiling app... Build path: /data/build/esp-nspanel1masterbdrm\\033[0m\n", + "Processing \u001b[36m\u001b[1mesp-nspanel1masterbdrm\u001b[0m (board: esp32dev; framework: espidf; platform: https://github.com/pioarduino/platform-espressif32/releases/download/55.03.38-1/platform-espressif32.zip)\n", + "\u001b[1m--------------------------------------------------------------------------------\u001b[0m\n", + "\u001b[0mHARDWARE: ESP32 240MHz, 320KB RAM, 4MB Flash\n", + "\u001b[0m - contrib-piohome @ 3.4.4 \n", + "\u001b[0m - framework-espidf @ 3.50504.0 (5.5.4) \n", + "\u001b[0m - tool-cmake @ 4.0.3 \n", + "\u001b[0m - tool-esp-rom-elfs @ 2024.10.11 \n", + "\u001b[0m - tool-esptoolpy @ 5.2.0 \n", + "\u001b[0m - tool-ninja @ 1.13.1 \n", + "\u001b[0m - tool-scons @ 4.40801.0 (4.8.1) \n", + "\u001b[0m - tool-xtensa-esp-elf-gdb @ 16.3.0+20250913 \n", + "\u001b[0m - toolchain-xtensa-esp-elf @ 14.2.0+20260121\n", + "\u001b[33m\u001b[2mUsing Python 3.12.10 environment at: /root/.platformio/penv/.espidf-5.5.4\u001b[0m\n", + "\u001b[0mReading CMake configuration...\n", + "\u001b[0mNo dependencies\n", + "\u001b[0mRAM: [== ] 19.9% (used 65232 bytes from 327680 bytes)\n", + "\u001b[0mFlash: [====== ] 61.5% (used 1127679 bytes from 1835008 bytes)\n", + "========================= [\u001b[32m\u001b[1mSUCCESS\u001b[0m] Took 20.43 seconds =========================\u001b[0m\n", + "\\033[32mINFO Build Info: config_hash=0xb6775f3e build_time_str=2026-05-17 22:24:40 +1200\\033[0m\n", + "\\033[32mINFO Successfully compiled program.\\033[0m\n", + "\\033[32mINFO Connecting to 172.31.11.183 port 3232...\\033[0m\n", + "\\033[32mINFO Connected to 172.31.11.183\\033[0m\n", + "\\033[32mINFO Uploading /data/build/esp-nspanel1masterbdrm/.pioenvs/esp-nspanel1masterbdrm/firmware.bin (1128080 bytes)\\033[0m\n", + "\r", + "Uploading: [ ] 0% \r", + "Uploading: [= ] 1% \r", + "Uploading: [= ] 2% \r", + "Uploading: [== ] 3% \r", + "Uploading: [=== ] 4% \r", + "Uploading: [=== ] 5% \r", + "Uploading: [==== ] 6% \r", + "Uploading: [==== ] 7% \r", + "Uploading: [===== ] 8% \r", + "Uploading: [====== ] 9% \r", + "Uploading: [====== ] 10% \r", + "Uploading: [======= ] 11% \r", + "Uploading: [======= ] 12% \r", + "Uploading: [======== ] 13% \r", + "Uploading: [========= ] 14% \r", + "Uploading: [========= ] 15% \r", + "Uploading: [========== ] 16% \r", + "Uploading: [========== ] 17% \r", + "Uploading: [=========== ] 18% \r", + "Uploading: [============ ] 19% \r", + "Uploading: [============ ] 20% \r", + "Uploading: [============= ] 21% \r", + "Uploading: [============== ] 22% \r", + "Uploading: [============== ] 23% \r", + "Uploading: [=============== ] 24% \r", + "Uploading: [=============== ] 25% \r", + "Uploading: [================ ] 26% \r", + "Uploading: [================= ] 27% \r", + "Uploading: [================= ] 28% \r", + "Uploading: [================= ] 29% \r", + "Uploading: [================== ] 30% \r", + "Uploading: [=================== ] 31% \r", + "Uploading: [==================== ] 32% \r", + "Uploading: [==================== ] 33% \r", + "Uploading: [==================== ] 34% \r", + "Uploading: [===================== ] 35% \r", + "Uploading: [====================== ] 36% \r", + "Uploading: [====================== ] 37% \r", + "Uploading: [======================= ] 38% \r", + "Uploading: [======================== ] 39% \r", + "Uploading: [======================== ] 40% \r", + "Uploading: [========================= ] 41% \r", + "Uploading: [========================= ] 42% \r", + "Uploading: [========================== ] 43% \r", + "Uploading: [=========================== ] 44% \r", + "Uploading: [=========================== ] 45% \r", + "Uploading: [============================ ] 46% \r", + "Uploading: [============================ ] 47% \r", + "Uploading: [============================= ] 48% \r", + "Uploading: [============================== ] 49% \r", + "Uploading: [============================== ] 50% \r", + "Uploading: [=============================== ] 51% \r", + "Uploading: [=============================== ] 52% \r", + "Uploading: [================================ ] 53% \r", + "Uploading: [================================= ] 54% \r", + "Uploading: [================================= ] 55% \r", + "Uploading: [================================== ] 56% \r", + "Uploading: [================================== ] 57% \r", + "Uploading: [=================================== ] 58% \r", + "Uploading: [==================================== ] 59% \r", + "Uploading: [==================================== ] 60% \r", + "Uploading: [===================================== ] 61% \r", + "Uploading: [===================================== ] 62% \r", + "Uploading: [====================================== ] 63% \r", + "Uploading: [======================================= ] 64% \r", + "Uploading: [======================================= ] 65% \r", + "Uploading: [======================================== ] 66% \r", + "Uploading: [========================================= ] 67% \r", + "Uploading: [========================================= ] 68% \r", + "Uploading: [========================================== ] 69% \r", + "Uploading: [========================================== ] 70% \r", + "Uploading: [=========================================== ] 71% \r", + "Uploading: [============================================ ] 72% \r", + "Uploading: [============================================ ] 73% \r", + "Uploading: [============================================ ] 74% \r", + "Uploading: [============================================= ] 75% \r", + "Uploading: [============================================== ] 76% \r", + "Uploading: [=============================================== ] 77% \r", + "Uploading: [=============================================== ] 78% \r", + "Uploading: [=============================================== ] 79% \r", + "Uploading: [================================================ ] 80% \r", + "Uploading: [================================================= ] 81% \r", + "Uploading: [================================================= ] 82% \r", + "Uploading: [================================================== ] 83% \r", + "Uploading: [=================================================== ] 84% \r", + "Uploading: [=================================================== ] 85% \r", + "Uploading: [==================================================== ] 86% \r", + "Uploading: [==================================================== ] 87% \r", + "Uploading: [===================================================== ] 88% \r", + "Uploading: [====================================================== ] 89% \r", + "Uploading: [====================================================== ] 90% \r", + "Uploading: [======================================================= ] 91% \r", + "Uploading: [======================================================= ] 92% \r", + "Uploading: [======================================================== ] 93% \r", + "Uploading: [========================================================= ] 94% \r", + "Uploading: [========================================================= ] 95% \r", + "Uploading: [========================================================== ] 96% \r", + "Uploading: [========================================================== ] 97% \r", + "Uploading: [=========================================================== ] 98% \r", + "Uploading: [============================================================] 99% \r", + "Uploading: [============================================================] 100% Done...\r\n", + "\n", + "\\033[32mINFO Upload took 7.41 seconds, waiting for result...\\033[0m\n", + "\\033[32mINFO OTA successful\\033[0m\n", + "\\033[32mINFO Successfully uploaded program.\\033[0m\n" + ], + "error": null, + "port": "OTA", + "new_name": "", + "progress": 100, + "remote_peer": "", + "remote_job_id": "", + "remote_peer_label": "", + "device_name": "", + "device_friendly_name": "", + "source": "local", + "source_pin_sha256": "", + "source_label": "", + "source_esphome_version": "" + }, + { + "job_id": "0eb2b3a49a5f", + "configuration": "esp-nspanel2lounge.yaml", + "job_type": "compile", + "status": "cancelled", + "created_at": "2026-05-17T08:44:50.516947+00:00", + "started_at": "2026-05-17T08:44:50.517123+00:00", + "completed_at": "2026-05-17T08:47:51.180084+00:00", + "exit_code": -15, + "output": [ + "\\033[32mINFO ESPHome 2026.5.0b1\\033[0m\n", + "\\033[32mINFO Reading configuration /config/esphome/esp-nspanel2lounge.yaml...\\033[0m\n", + "\\033[32mINFO Updating https://github.com/edwardtfn/NSPanel-Easy@v2026.5.5\\033[0m\n", + "\\033[32mINFO Updating https://github.com/edwardtfn/NSPanel-Easy@v2026.5.5\\033[0m\n", + "\\033[32mINFO Detected timezone 'Pacific/Auckland'\\033[0m\n", + "\\033[33mWARNING Found and merged multiple configurations for ota platform esphome port(s) [3232]\\033[0m\n", + "\\033[32mINFO Generating C++ source...\\033[0m\n", + "\\033[32mINFO Setting CONFIG_LWIP_MAX_SOCKETS to 13 (TCP=6 [api=3, captive_portal=3], UDP=3 [captive_portal=1, mdns=2], TCP_LISTEN=4 [api=1, ota=2, web_server_base=1])\\033[0m\n", + "\\033[32mINFO Compiling app... Build path: /data/build/esp-nspanel2lounge\\033[0m\n", + "Processing \u001b[36m\u001b[1mesp-nspanel2lounge\u001b[0m (board: esp32dev; framework: espidf; platform: https://github.com/pioarduino/platform-espressif32/releases/download/55.03.38-1/platform-espressif32.zip)\n", + "\u001b[1m--------------------------------------------------------------------------------\u001b[0m\n", + "\u001b[0mHARDWARE: ESP32 240MHz, 320KB RAM, 4MB Flash\n", + "\u001b[0m - contrib-piohome @ 3.4.4 \n", + "\u001b[0m - framework-espidf @ 3.50504.0 (5.5.4) \n", + "\u001b[0m - tool-cmake @ 4.0.3 \n", + "\u001b[0m - tool-esp-rom-elfs @ 2024.10.11 \n", + "\u001b[0m - tool-esptoolpy @ 5.2.0 \n", + "\u001b[0m - tool-ninja @ 1.13.1 \n", + "\u001b[0m - tool-scons @ 4.40801.0 (4.8.1) \n", + "\u001b[0m - tool-xtensa-esp-elf-gdb @ 16.3.0+20250913 \n", + "\u001b[0m - toolchain-xtensa-esp-elf @ 14.2.0+20260121\n", + "\u001b[33m\u001b[2mUsing Python 3.12.10 environment at: /root/.platformio/penv/.espidf-5.5.4\u001b[0m\n", + "\u001b[0mReading CMake configuration...\n", + "\u001b[0mGenerating assembly for certificate bundle...\n", + "\u001b[0mNo dependencies\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/adc/adc_sensor_common.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/adc/adc_sensor_esp32.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/api_buffer.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/api_connection.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/api_frame_helper.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/api_frame_helper_plaintext.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/api_overflow_buffer.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/api_pb2.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/api_pb2_service.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/api_server.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/list_entities.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/proto.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/subscribe_state.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/api/user_services.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/binary_sensor/automation.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/binary_sensor/binary_sensor.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/binary_sensor/filter.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/button/button.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/captive_portal/captive_portal.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/captive_portal/dns_server_esp32_idf.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/display/display.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/display/display_buffer.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/display/rect.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/esp32/core.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/esp32/crash_handler.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/esp32/gpio.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/esp32/hal.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/esp32/helpers.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/esp32/preferences.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/esp32/printf_stubs.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/esp32/throw_stubs.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/esphome/ota/ota_esphome.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/gpio/switch/gpio_switch.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/homeassistant/time/homeassistant_time.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/ledc/ledc_output.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/logger/logger.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/logger/logger_esp32.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/logger/task_log_buffer_esp32.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/md5/md5.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/mdns/mdns_component.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/mdns/mdns_esp32.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/network/util.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nextion/nextion.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nextion/nextion_commands.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nextion/nextion_component.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nextion/nextion_upload.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nextion/nextion_upload_esp32.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nextion/text_sensor/nextion_textsensor.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/addon_climate.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/addon_display_light.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/addon_upload_tft.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/base.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/chips.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/ha_components.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/hw_display.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/nextion_components.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/page_climate.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/page_entities.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/page_media_player.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/page_notification.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/page_popup_select.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/page_screensaver.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/page_utilities.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/pages.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/text.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/versioning.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/nspanel_easy/weather.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/ntc/ntc.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/number/automation.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/number/number.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/number/number_call.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/number/number_traits.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/ota/ota_backend.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/ota/ota_backend_esp_idf.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/ota/ota_bootloader_esp_idf.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/ota/ota_partitions_esp_idf.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/output/automation.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/output/float_output.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/psram/psram.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/resistance/resistance_sensor.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/restart/button/restart_button.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/rtttl/rtttl.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/safe_mode/safe_mode.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/script/script.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/select/select.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/select/select_call.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/select/select_traits.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/sensor/automation.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/sensor/filter.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/sensor/sensor.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/sha256/sha256.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/socket/bsd_sockets_impl.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/socket/socket.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/switch/automation.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/switch/switch.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/template/binary_sensor/template_binary_sensor.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/template/number/template_number.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/template/select/template_select.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/template/switch/template_switch.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/template/text_sensor/template_text_sensor.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/text_sensor/filter.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/text_sensor/text_sensor.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/time/automation.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/time/posix_tz.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/time/real_time_clock.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/uart/uart.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/uart/uart_component.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/uart/uart_component_esp_idf.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/uart/uart_debugger.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/watchdog/watchdog.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/web_server/ota/ota_web_server.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/web_server_base/web_server_base.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/web_server_idf/multipart.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/web_server_idf/utils.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/web_server_idf/web_server_idf.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/wifi/wifi_component.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/components/wifi/wifi_component_esp_idf.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/alloc_helpers.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/application.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/build_info_data.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/color.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/component.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/component_iterator.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/controller_registry.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/entity_base.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/gpio.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/helpers.cpp.o\n", + "\u001b[0mCompiling .pioenvs/esp-nspanel2lounge/src/esphome/core/log.cpp.o\n" + ], + "error": null, + "port": "", + "new_name": "", + "progress": null, + "remote_peer": "", + "remote_job_id": "", + "remote_peer_label": "", + "device_name": "", + "device_friendly_name": "", + "source": "local", + "source_pin_sha256": "", + "source_label": "", + "source_esphome_version": "" + }, + { + "job_id": "e4433a434baf", + "configuration": "esp-nspanel1masterbdrm.yaml", + "job_type": "clean", + "status": "completed", + "created_at": "2026-05-17T09:55:46.641871+00:00", + "started_at": "2026-05-17T09:55:46.642053+00:00", + "completed_at": "2026-05-17T09:55:49.036615+00:00", + "exit_code": 0, + "output": [ + "\\033[32mINFO ESPHome 2026.5.0b1\\033[0m\n", + "\\033[32mINFO Reading configuration /config/esphome/esp-nspanel1masterbdrm.yaml...\\033[0m\n", + "\\033[32mINFO Detected timezone 'Pacific/Auckland'\\033[0m\n", + "\\033[33mWARNING Found and merged multiple configurations for ota platform esphome port(s) [3232]\\033[0m\n", + "\\033[32mINFO Deleting /data/build/esp-nspanel1masterbdrm/.pioenvs\\033[0m\n", + "\\033[32mINFO Deleting /data/build/esp-nspanel1masterbdrm/.piolibdeps\\033[0m\n", + "\\033[32mINFO Deleting /data/build/esp-nspanel1masterbdrm/dependencies.lock\\033[0m\n", + "\\033[32mINFO Deleting /data/build/esp-nspanel1masterbdrm/managed_components\\033[0m\n", + "\\033[32mINFO Deleting PlatformIO cache /data/cache/platformio/cache\\033[0m\n", + "\\033[32mINFO Done!\\033[0m\n" + ], + "error": null, + "port": "", + "new_name": "", + "progress": null, + "remote_peer": "", + "remote_job_id": "", + "remote_peer_label": "", + "device_name": "", + "device_friendly_name": "", + "source": "local", + "source_pin_sha256": "", + "source_label": "", + "source_esphome_version": "" + }, + { + "job_id": "1cb834d6b311", + "configuration": "esp-nspanel1masterbdrm.yaml", + "job_type": "clean", + "status": "completed", + "created_at": "2026-05-17T09:30:13.803940+00:00", + "started_at": "2026-05-17T09:30:13.804128+00:00", + "completed_at": "2026-05-17T09:30:16.052388+00:00", + "exit_code": 0, + "output": [ + "\\033[32mINFO ESPHome 2026.5.0b1\\033[0m\n", + "\\033[32mINFO Reading configuration /config/esphome/esp-nspanel1masterbdrm.yaml...\\033[0m\n", + "\\033[32mINFO Detected timezone 'Pacific/Auckland'\\033[0m\n", + "\\033[33mWARNING Found and merged multiple configurations for ota platform esphome port(s) [3232]\\033[0m\n", + "\\033[32mINFO Deleting /data/build/esp-nspanel1masterbdrm/.pioenvs\\033[0m\n", + "\\033[32mINFO Deleting /data/build/esp-nspanel1masterbdrm/dependencies.lock\\033[0m\n", + "\\033[32mINFO Deleting /data/build/esp-nspanel1masterbdrm/managed_components\\033[0m\n", + "\\033[32mINFO Deleting PlatformIO cache /data/cache/platformio/cache\\033[0m\n", + "\\033[32mINFO Done!\\033[0m\n" + ], + "error": null, + "port": "", + "new_name": "", + "progress": null, + "remote_peer": "", + "remote_job_id": "", + "remote_peer_label": "", + "device_name": "", + "device_friendly_name": "", + "source": "local", + "source_pin_sha256": "", + "source_label": "", + "source_esphome_version": "" + } + ], + "esp-nspanel2lounge copy.yaml": { + "board_id": "generic-esp32", + "mac_address": "88:13:BF:68:39:2C" + }, + "esp-nspanel1masterbdrm.yaml": { + "board_id": "generic-esp32", + "mac_address": "24:D7:EB:0D:E7:FC" + }, + "esp-ledmatrix1.yaml": { + "board_id": "generic-esp8266", + "mac_address": "5C:CF:7F:A0:36:73" + } +} \ No newline at end of file diff --git a/packages/main_bathroom_actions.yaml b/esphome/.device-builder.json.lock similarity index 100% rename from packages/main_bathroom_actions.yaml rename to esphome/.device-builder.json.lock diff --git a/esphome/.device-builder.lock b/esphome/.device-builder.lock new file mode 100644 index 0000000..af2c826 --- /dev/null +++ b/esphome/.device-builder.lock @@ -0,0 +1 @@ +{"pid": 76431, "lock_format_version": 1, "device_builder_version": "0.1.0b51", "start_ts": 1779063562.6364732} \ No newline at end of file diff --git a/esphome/common/network_common_esp32.yaml b/esphome/common/network_common_esp32.yaml new file mode 100644 index 0000000..e7577b6 --- /dev/null +++ b/esphome/common/network_common_esp32.yaml @@ -0,0 +1,130 @@ +substitutions: + ############################################################################################ + # SPECIFIC DEVICE VARIABLE SUBSTITUTIONS + # If NOT using a secrets file, just replace these with the passwords etc (in quotes) + ########################################################################################### + wifi_ssid: !secret ha_wifi_ssid + wifi_password: !secret ha_wifi_password + fallback_ap_password: !secret fallback_ap_password + + # Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken + wifi_fast_connect: "false" + + # Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs) + dns_domain: ".local" + + # Automatically add the mac address to the name + # eg so you can use a single firmware for all devices + add_mac_suffix: "false" + + # Enable or disable the use of IPv6 networking on the device + ipv6_enable: "false" + + # Add these if we are giving it a static ip, or remove them in the Wifi section + static_ip_subnet: !secret ha_wifi_subnet + static_ip_gateway: !secret ha_wifi_gateway + static_ip_dns1: !secret ha_wifi_dns1 + static_ip_dns2: !secret ha_wifi_dns2 + + # Network reconnect every x hours to ensure best access point + base_interval_hours: "23" # Base interval in hours + random_offset_max_minutes: "59" # Max random offset in minutes + +########################################################################################### +# Common Wifi Settings +# https://esphome.io/components/wifi.html +# +# Power Save mode (can reduce wifi reliability) +# NONE (least power saving, Default for ESP8266) +# LIGHT (Default for ESP32) +# HIGH (most power saving) +########################################################################################### +wifi: + networks: + - ssid: ${wifi_ssid} + password: ${wifi_password} + manual_ip: # optional static IP address + static_ip: ${local_static_ip_address} + gateway: ${static_ip_gateway} + subnet: ${static_ip_subnet} + dns1: ${static_ip_dns1} + dns2: ${static_ip_dns2} + + #enable_rrm: true # (ESP32 only) enable 802.11k Radio Resource Management + #enable_btm: true # (ESP32 only) enable 802.11v BSS Transition Management + power_save_mode: none # https://esphome.io/components/wifi.html#wifi-power-save-mode + use_address: ${local_current_ip_address} + ap: # Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode + ssid: ${local_device_name} + password: ${fallback_ap_password} + ap_timeout: 10min # Time until it brings up fallback AP. default is 1min + + # Allow rapid re-connection to previously connected WiFi SSID, skipping scan of all SSIDs + fast_connect: ${wifi_fast_connect} + + # Define dns domain / suffix to add to hostname + domain: ${dns_domain} + min_auth_mode: WPA2 + + # Reboot eventually when Wi-Fi is down (default is 15min) + reboot_timeout: 56h + +captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails + +########################################################################################### +# Enable Over the Air Update Capability +# https://esphome.io/components/ota.html?highlight=ota +########################################################################################### +ota: + - platform: esphome + password: ${local_ota_pass} + version: 2 + #- platform: web_server # Uncomment if you want to be able to do OTA with the web interface + +########################################################################################### +# Safe Mode +# Safe mode will detect boot loops +# https://esphome.io/components/safe_mode +########################################################################################### +safe_mode: + +########################################################################################### +# Network +# global configuration for all types of networks +# https://esphome.io/components/network.html +########################################################################################### +network: + enable_ipv6: ${ipv6_enable} + +########################################################################################## +# Interval +# Restart Networking every x hours + rand mins +# This ensure that the device is connected to the best AP +########################################################################################## +#script: +# - id: random_reconnect +# mode: restart +# then: +# - lambda: |- +# // Compute total delay: base hours + random offset minutes +# uint32_t extra; +# #if defined(ESP32) +# // ESP32 (both Arduino & IDF builds) uses esp_random() +# extra = esp_random() % (${random_offset_max_minutes} + 1); +# #elif defined(ESP8266) +# // ESP8266 Arduino core +# extra = os_random() % (${random_offset_max_minutes} + 1); +# #else +# // Fallback to esp_random() on other platforms +# extra = esp_random() % (${random_offset_max_minutes} + 1); +# #endif +# uint32_t total_s = ${base_interval_hours} * 3600 + extra * 60; +# ESP_LOGI("random_reconnect", "Next reconnect in %u seconds", total_s); +# // Delay inside lambda (blocks script execution but OK for reconnect timing) +# delay(total_s * 1000); +# - logger.log: "network_check: performing reconnect" +# - wifi.disable: {} +# - delay: 1s +# - wifi.enable: {} +# - script.execute: random_reconnect + diff --git a/esphome/common/sntp_common.yaml b/esphome/common/sntp_common.yaml index af6c25b..eee7d96 100644 --- a/esphome/common/sntp_common.yaml +++ b/esphome/common/sntp_common.yaml @@ -1,104 +1,129 @@ -############################################################################################ -# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS -# If NOT using a secrets file, just replace -# these with the values (in quotes) -########################################################################################### +#:########################################################################################:# +# TITLE: COMMON SNTP TIME SOURCE +# zorruno.com common ESPHome include +#:########################################################################################:# +# FILE: +# common/sntp_common.yaml +#:########################################################################################:# +# VERSIONS: +# V1.1 2026-05-23 Fixed offline fallback clock so it increments from assumed noon boot time +# V1.0 Initial common SNTP time source and diagnostic sensors +#:########################################################################################:# +# PURPOSE: +# - Provides an SNTP time source for ESPHome devices. +# - Stores the current time as minutes since midnight in current_mins. +# - Provides a simple fallback clock if SNTP is not available. +# - Publishes diagnostic text sensors for last restart, current/internal time, and sync status. +#:########################################################################################:# +# OPERATION NOTES: +# - SNTP time is stored in id(sntp_time). +# - Current minutes since midnight are stored in id(current_mins). +# - If SNTP is valid, current_mins follows the real SNTP time. +# - If SNTP is invalid, current_mins increments once per minute from the assumed boot time. +# - The fallback assumes the device was powered on at 12:00 noon. +# - If accurate offline time is needed, power-cycle the device at 12:00 noon. +#:########################################################################################:# + +#:########################################################################################:# +# SUBSTITUTIONS: Specific SNTP variable substitutions +# If NOT using a secrets file, replace these values directly in quotes. +#:########################################################################################:# substitutions: timezone: "Pacific/Auckland" - sntp_update_interval: 6h # Set the duration between the sntp service polling - # Network time servers https://www.ntppool.org/zone/@ + sntp_update_interval: 6h + + # Network time servers + # https://www.ntppool.org/zone/@ sntp_server_1: !secret ntp_server_1 sntp_server_2: !secret ntp_server_2 sntp_server_3: !secret ntp_server_3 -########################################################################################### -# Internal clock fallback support -# If SNTP is invalid, we fall back to a simple internal minute counter -# We assume user powers on the device at 12:00 noon -# => 12 * 60 = 720 minutes from midnight. -# Not restored, so it resets each boot. -# Therefore, if no network and you still need timing functions, switch on ay noon. -########################################################################################### +#:########################################################################################:# +# GLOBALS: Internal clock fallback support +#:########################################################################################:# globals: - id: current_mins type: int - restore_value: no - initial_value: "720" # 720 is 12:00 Noon + restore_value: false + initial_value: "720" # 720 minutes from midnight is 12:00 noon. +#:########################################################################################:# +# INTERVAL: Keep current_mins updated +#:########################################################################################:# interval: - interval: 60s then: - lambda: |- - // If SNTP valid, store minutes since midnight. + // If SNTP is valid, store real minutes since midnight. auto now = id(sntp_time).now(); if (now.is_valid()) { id(current_mins) = (now.hour * 60) + now.minute; } else { - // Fallback: minutes since boot. - id(current_mins) = (int)(millis() / 60000UL); + // Fallback: increment from the assumed startup time. + // Initial value is 720, so offline boot assumes 12:00 noon. + id(current_mins) = (id(current_mins) + 1) % 1440; } -########################################################################################### -# Real time clock time source for ESPHome -# If it's invalid, we fall back to an internal clock +#:########################################################################################:# +# TIME: Real time clock source for ESPHome # https://esphome.io/components/time/index.html # https://esphome.io/components/time/sntp -########################################################################################### +#:########################################################################################:# time: - platform: sntp id: sntp_time - # Define the timezone of the device timezone: "${timezone}" - # Change sync interval from default 5min to 6 hours (or as set in substitutions) update_interval: ${sntp_update_interval} - # Set specific sntp servers to use servers: - "${sntp_server_1}" - "${sntp_server_2}" - "${sntp_server_3}" - # Publish the time the device was last restarted on_time_sync: then: - logger.log: "Synchronised SNTP clock" + - text_sensor.template.publish: id: time_sync - state: "SNTP clock Syncd" - # Update last restart time, but only once. + state: "SNTP clock synced" + + # Update last restart time once only, after the first successful SNTP sync. - if: condition: - lambda: 'return id(device_last_restart).state == "";' + lambda: |- + return id(device_last_restart).state.empty(); then: - text_sensor.template.publish: id: device_last_restart - #state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");' state: !lambda |- - // grab the current SNTP time auto now = id(sntp_time).now(); - // format it as e.g. "Fri 23 May 2025 - 03:45:12 PM" return now.strftime("%a %d %b %Y - %I:%M:%S %p"); +#:########################################################################################:# +# TEXT SENSOR COMPONENT: +# https://esphome.io/components/text_sensor/ +#:########################################################################################:# text_sensor: - # Creates a sensor showing when the device was last restarted + # Shows when the device was last restarted, based on the first successful SNTP sync. - platform: template name: "Time: Last Restart" id: device_last_restart icon: mdi:clock entity_category: diagnostic - #device_class: timestamp + update_interval: never + # Shows the current SNTP time if valid, otherwise the internal fallback time. - platform: template name: "Time: Internal Time" id: time_text - update_interval: "1min" + icon: mdi:clock-outline + update_interval: 1min entity_category: diagnostic lambda: |- - // If SNTP time is valid, show real time/date. if (id(sntp_time).now().is_valid()) { auto t = id(sntp_time).now().strftime("%H:%M:%S - %d-%m-%Y"); return { t }; } - // Fallback: show internal clock based on current_mins (minutes from midnight). int mins = id(current_mins); if (mins < 0) mins = 0; if (mins >= 1440) mins = mins % 1440; @@ -110,8 +135,16 @@ text_sensor: snprintf(buff, sizeof(buff), "%02d:%02d (fallback)", hour, minute); return { std::string(buff) }; + # Shows whether the device is using SNTP or the fallback clock. - platform: template name: "Time: Sync Status" id: time_sync - update_interval: "1min" + icon: mdi:clock-check-outline + update_interval: 1min entity_category: diagnostic + lambda: |- + if (id(sntp_time).now().is_valid()) { + return { std::string("SNTP clock synced") }; + } + + return { std::string("Using fallback internal clock") }; \ No newline at end of file diff --git a/esphome/esp-ledmatrix1.yaml b/esphome/esp-ledmatrix1.yaml new file mode 100644 index 0000000..2ffbcff --- /dev/null +++ b/esphome/esp-ledmatrix1.yaml @@ -0,0 +1,818 @@ +#:########################################################################################:# +# TITLE: LED MATRIX 1 +# zorruno.com layout v1.1 2026 +# ESP32-C3 SuperMini MAX7219 LED matrix clock and scroll display. +# Original Project Reference https://github.com/RealDeco/matrixclock-esphome +#:########################################################################################:# +# REPO: +# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-ledmatrix1.yaml +#:########################################################################################:# +# VERSIONS: +# V1.1 2026-05-23 Updated yaml to zorruno layout V1.1, cleaned comments, fixed SNTP time IDs, +# added current_mins fallback display, removed unused MQTT topic, and fixed +# blank scroll handling. +# V1.0 2026-05-23 Initial Version +#:########################################################################################:# +# HARDWARE: +# - ESP32-C3 SuperMini. +# - MAX7219 4-chip LED matrix display, typically 32 x 8 pixels. +# - SPI wiring configured below: +# - MOSI: GPIO8 +# - CS: GPIO9 +# - SCK: GPIO10 +# - Power note: 4 MAX7219 modules may need a suitable external 5 V supply. +# - Keep ESP32-C3 GND and matrix power supply GND connected together. +#:########################################################################################:# +# FONTS: +# Dragon Font: https://www.freebestfonts.com/eight-bit-dragon-font +# 5x8 Font: https://developer.lasec.com.mx/armando/rpi-rgb-led-matrix/-/blob/master/fonts/5x8.bdf +#:########################################################################################:# +# OPERATION NOTES: +# - Displays the current time from common/sntp_common.yaml using id(sntp_time). +# - If SNTP is invalid, the display falls back to id(current_mins) from common/sntp_common.yaml. +# - 12h/24h display mode is selectable from Home Assistant or MQTT. +# - Display brightness is exposed as a Home Assistant light entity. +# - Brightness accepts values 0..15 where 0 turns the display off and 1..15 sets intensity. +# - Scroll Text is exposed as a Home Assistant text entity and clears itself after use. +# - Before any scroll starts, the clock slides down and off the display. +# - After scroll ends, the selected re-entry effect runs: Slide up, Slide down, or Pixel fall. +# - Pixel fall hides the clock until the effect finishes, avoiding a clock-under-particles look. +# - Scroll speed is adjustable using the Scroll Delay number entity. +#:########################################################################################:# +# MQTT COMMANDS: +# - ${mqtt_command_topic}/scroll : Text payload to scroll once across the display. +# - ${mqtt_command_topic}/mode : Payload 12 or 24 to select clock format. +# - ${mqtt_command_topic}/bright : Payload 0 turns display off, 1-15 sets brightness. +# - ${mqtt_status_topic}/status : Publishes online/offline status. +#:########################################################################################:# +# OFFLINE NOTES: +# a) Home Assistant offline, but WiFi/network and MQTT online: +# - Clock display should continue using SNTP if the NTP servers are reachable. +# - MQTT scroll, brightness, and 12h/24h mode commands should still work. +# +# b) MQTT offline, but WiFi/network and Home Assistant API online: +# - Home Assistant text, light, number, and select entities should still work. +# - Clock display should continue using SNTP if the NTP servers are reachable. +# +# c) Entire WiFi/network offline: +# - SNTP will not be available after boot. +# - Clock display will use the current_mins fallback from common/sntp_common.yaml. +# - Accurate time is needed for the clock, so fallback time will drift. +# - For a rough reset, power-cycle the device at 12:00 noon. +# - Home Assistant and MQTT controls will not be available while offline. +#:########################################################################################:# + +#:########################################################################################:# +# SUBSTITUTIONS: Specific device variable substitutions +# If NOT using a secrets file, just replace these with the passwords etc in quotes. +#:########################################################################################:# +substitutions: + # Device Naming + device_name: "esp-ledmatrix1" + friendly_name: "LED Matrix Display" + description_comment: "LED Matrix Display (Layout V1.1)" + device_area: "Lounge" # Allows ESP device to be automatically linked to an Area in Home Assistant. + + # Project Naming + project_name: "Generic.ESP32 Supermini" + project_version: "v1.1" + + # Passwords & Secrets + api_key: !secret esp-api_key + ota_pass: !secret esp-ota_pass + static_ip_address: !secret esp-ledmatrix1_ip # Substitutions cannot be used inside secret names. + mqtt_command_main_topic: !secret mqtt_command_main_topic + mqtt_status_main_topic: !secret mqtt_status_main_topic + + # If changing IP addresses, update current_ip_address here, otherwise it remains static_ip_address. + # Do not forget to switch it back when the address change is complete. + current_ip_address: ${static_ip_address} + + # MQTT LOCAL Controls + mqtt_device_name: "ledmatrix1" + mqtt_command_topic: "${mqtt_command_main_topic}/${mqtt_device_name}" + mqtt_status_topic: "${mqtt_status_main_topic}/${mqtt_device_name}" + + # Device Settings + log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG, VERBOSE, VERY_VERBOSE + update_interval: "60s" + + # Matrix Display Settings + pin_mosi: GPIO8 + pin_cs: GPIO9 + pin_sck: GPIO10 + num_chips: "4" + scroll_delay_ms: "15" # ms between scroll steps + y_offset: "0" + y_scroll_offset: "0" + +#:########################################################################################:# +# PACKAGES: Included Common Packages +# https://esphome.io/components/packages.html +#:########################################################################################:# +packages: + #### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode #### + common_wifi: !include + file: common/network_common.yaml + vars: + local_device_name: "${device_name}" + local_static_ip_address: "${static_ip_address}" + local_ota_pass: "${ota_pass}" + local_current_ip_address: "${current_ip_address}" + + #### HOME ASSISTANT API (choose encryption or no encryption options) #### + common_api: !include + file: common/api_common.yaml + #file: common/api_common_noencryption.yaml + vars: + local_api_key: "${api_key}" + + #### MQTT #### + common_mqtt: !include + file: common/mqtt_common.yaml + vars: + local_device_name: "${device_name}" + + #### WEB PORTAL #### + common_webportal: !include common/webportal_common.yaml + + #### SNTP (Needed for the clock display) #### + common_sntp: !include common/sntp_common.yaml + + #### DIAGNOSTICS Sensors #### + diag_basic: !include common/include_basic_diag_sensors.yaml + #diag_more: !include common/include_more_diag_sensors.yaml + #diag_debug: !include common/include_debug_diag_sensors.yaml + #diag_resetcount: !include common/include_resetcount_diag_sensors.yaml + +#:########################################################################################:# +# ESPHOME: +# https://esphome.io/components/esphome.html +#:########################################################################################:# +esphome: + name: "${device_name}" + friendly_name: "${friendly_name}" + comment: "${description_comment}" # Appears on the ESPHome page in Home Assistant. + area: "${device_area}" + project: + name: "${project_name}" + version: "${project_version}" + on_boot: + priority: 600 + then: + # Ensure Home Assistant shows the text box as empty rather than unknown. + - delay: 500ms + - lambda: |- + id(ha_scroll_text).publish_state(""); + - component.update: matrix + +#:########################################################################################:# +# ESP PLATFORM AND FRAMEWORK: +# https://esphome.io/components/esp32/ +#:########################################################################################:# +esp32: + variant: esp32c3 + framework: + type: esp-idf + version: recommended + +#:########################################################################################:# +# LOGGER: ESPHome Logging Enable +# https://esphome.io/components/logger.html +#:########################################################################################:# +logger: + level: "${log_level}" # INFO suggested, or DEBUG for testing. + #baud_rate: 0 # Set to 0 for no UART logging if UART is used for another device. + #esp8266_store_log_strings_in_flash: false + #tx_buffer_size: 64 + +#:########################################################################################:# +# GLOBALS: +# https://esphome.io/components/globals.html +#:########################################################################################:# +globals: + # Text currently being scrolled across the matrix. + - id: scroll_text + type: std::string + initial_value: "" + + # Current X position of the scrolling text. + - id: scroll_x + type: int + initial_value: '0' + + # True while a scroll message is active. + - id: scrolling + type: bool + initial_value: 'false' + + # False = 24h mode, true = 12h mode. + - id: use_12h_mode + type: bool + restore_value: true + initial_value: 'false' + + # Runtime scroll delay, controlled by the Scroll Delay number entity. + - id: scroll_delay_runtime + type: int + restore_value: true + initial_value: '${scroll_delay_ms}' + + # MAX7219 intensity: 0=OFF, 1..15=ON. Driven by the HA light entity. + - id: matrix_intensity + type: int + restore_value: false + initial_value: '2' + + # Brightness sync loop protection for HA light <-> MQTT brightness updates. + - id: suppress_mqtt_bright_sync + type: bool + restore_value: false + initial_value: 'false' + + - id: target_mqtt_bright + type: int + restore_value: false + initial_value: '-1' + + - id: last_mqtt_bright + type: int + restore_value: false + initial_value: '-1' + + # Re-entry effect after scrolling: 0=Slide up, 1=Slide down, 2=Pixel fall. + - id: transition_mode + type: int + restore_value: true + initial_value: '0' + + # Slide transition state. + - id: transition_active + type: bool + restore_value: false + initial_value: 'false' + + - id: transition_y + type: int + restore_value: false + initial_value: '0' + + # Pixel-fall transition state. + - id: pixel_fall_active + type: bool + restore_value: false + initial_value: 'false' + + - id: pixel_fall_gen + type: int + restore_value: false + initial_value: '0' + +#:########################################################################################:# +# FONT COMPONENT: +# https://esphome.io/components/font/ +#:########################################################################################:# +font: + # Clock digits. Keep glyphs limited to reduce firmware size. + - file: "fonts/Eight-Bit-Dragon.ttf" + id: digit5 + size: 8 + glyphs: ["0123456789: -"] + + # General 5x8 font for scroll text. + - file: "fonts/5x8.bdf" + id: font5x8 + size: 8 + +#:########################################################################################:# +# SPI COMPONENT: +# https://esphome.io/components/spi.html +#:########################################################################################:# +spi: + clk_pin: ${pin_sck} + mosi_pin: ${pin_mosi} + +#:########################################################################################:# +# MQTT: Device-specific MQTT command triggers +# This adds device-specific MQTT command triggers to the common MQTT configuration. +#:########################################################################################:# +mqtt: + discovery: false + birth_message: + topic: "${mqtt_status_topic}/status" + payload: "online" + qos: 0 + retain: true + will_message: + topic: "${mqtt_status_topic}/status" + payload: "offline" + qos: 0 + retain: true + on_message: + # Scroll a text payload once across the matrix. + - topic: "${mqtt_command_topic}/scroll" + then: + - lambda: |- + std::string s = x.c_str(); + s.erase(std::remove(s.begin(), s.end(), '\r'), s.end()); + s.erase(std::remove(s.begin(), s.end(), '\n'), s.end()); + s.erase(std::remove_if(s.begin(), s.end(), + [](unsigned char c){ return c < 0x20; }), s.end()); + + // Ignore empty MQTT scroll commands completely. + if (s.empty()) return; + + id(scroll_text) = s; + id(scroll_x) = ${num_chips} * 8; + id(start_scroll_sequence).execute(); + + # Set 12h or 24h clock mode from MQTT. + - topic: "${mqtt_command_topic}/mode" + then: + - lambda: |- + if (x == "12") { + id(use_12h_mode) = true; + } else if (x == "24") { + id(use_12h_mode) = false; + } + - component.update: clock_format_select + - component.update: matrix + + # Brightness from MQTT. 0=OFF, 1..15=ON brightness level. + - topic: "${mqtt_command_topic}/bright" + then: + - lambda: |- + std::string s = x.c_str(); + s.erase(std::remove_if(s.begin(), s.end(), + [](unsigned char c){ return c < 0x20 || c == 0x7F; }), s.end()); + s.erase(s.begin(), std::find_if(s.begin(), s.end(), + [](unsigned char ch){ return !std::isspace(ch); })); + s.erase(std::find_if(s.rbegin(), s.rend(), + [](unsigned char ch){ return !std::isspace(ch); }).base(), s.end()); + + if (s.empty() || !std::all_of(s.begin(), s.end(), + [](unsigned char c){ return std::isdigit(c); })) return; + + int v = atoi(s.c_str()); + if (v < 0) v = 0; + if (v > 15) v = 15; + + id(suppress_mqtt_bright_sync) = true; + + if (v == 0) { + id(matrix_light).turn_off().perform(); + } else { + auto call = id(matrix_light).turn_on(); + call.set_brightness((float) v / 15.0f); + call.perform(); + } + + id(last_mqtt_bright) = v; + id(suppress_mqtt_bright_sync) = false; + +#:########################################################################################:# +# TEXT COMPONENT: +# https://esphome.io/components/text/template/ +#:########################################################################################:# +text: + - platform: template + id: ha_scroll_text + name: "${friendly_name} Scroll Text" + icon: "mdi:message-text-outline" + mode: text + max_length: 80 + optimistic: false + set_action: + - lambda: |- + std::string s = x.c_str(); + + s.erase(std::remove(s.begin(), s.end(), '\r'), s.end()); + s.erase(std::remove(s.begin(), s.end(), '\n'), s.end()); + s.erase(std::remove_if(s.begin(), s.end(), + [](unsigned char c){ return c < 0x20; }), s.end()); + + // Publish the cleaned text so the HA UI updates immediately. + id(ha_scroll_text).publish_state(s.c_str()); + + // Empty text only clears the entity. It should not start a scroll. + if (s.empty()) return; + + id(scroll_text) = s; + id(scroll_x) = ${num_chips} * 8; + id(start_scroll_sequence).execute(); + id(clear_scroll_text_box).execute(); + +#:########################################################################################:# +# NUMBER COMPONENT: +# https://esphome.io/components/number/template/ +#:########################################################################################:# +number: + - platform: template + id: scroll_delay_number + name: "${friendly_name} Scroll Delay" + icon: "mdi:timer-outline" + unit_of_measurement: "ms" + min_value: 1 + max_value: 100 + step: 1 + restore_value: true + initial_value: ${scroll_delay_ms} + optimistic: true + set_action: + - lambda: |- + id(scroll_delay_runtime) = (int) x; + +#:########################################################################################:# +# OUTPUT COMPONENT: +# https://esphome.io/components/output/ +#:########################################################################################:# +output: + # Template output lets the HA light slider control MAX7219 intensity. + - platform: template + id: matrix_brightness_out + type: float + write_action: + - lambda: |- + int pub = 0; + + if (state <= 0.001f) { + id(matrix_intensity) = 0; + id(matrix).turn_on_off(false); + pub = 0; + } else { + int v = (int) lroundf(state * 15.0f); + if (v < 1) v = 1; + if (v > 15) v = 15; + + id(matrix_intensity) = v; + id(matrix).turn_on_off(true); + pub = v; + } + + id(target_mqtt_bright) = pub; + + - component.update: matrix + + # Publish brightness back to MQTT unless this update came from MQTT. + - if: + condition: + lambda: |- + return !id(suppress_mqtt_bright_sync) + && (id(target_mqtt_bright) != id(last_mqtt_bright)); + then: + - mqtt.publish: + topic: "${mqtt_command_topic}/bright" + payload: !lambda |- + char buf[6]; + snprintf(buf, sizeof(buf), "%d", id(target_mqtt_bright)); + return std::string(buf); + qos: 0 + retain: true + - lambda: |- + id(last_mqtt_bright) = id(target_mqtt_bright); + +#:########################################################################################:# +# LIGHT COMPONENT: +# https://esphome.io/components/light/monochromatic/ +#:########################################################################################:# +light: + - platform: monochromatic + id: matrix_light + name: "${friendly_name} Display" + icon: "mdi:brightness-6" + output: matrix_brightness_out + gamma_correct: 1.0 + default_transition_length: 0s + restore_mode: RESTORE_DEFAULT_ON + +#:########################################################################################:# +# SELECT COMPONENT: +# https://esphome.io/components/select/template/ +#:########################################################################################:# +select: + - platform: template + id: clock_format_select + name: "${friendly_name} Clock Format" + icon: "mdi:clock-time-three-outline" + options: + - "24" + - "12" + lambda: |- + return esphome::optional(std::string(id(use_12h_mode) ? "12" : "24")); + set_action: + - lambda: |- + id(use_12h_mode) = (x == "12"); + - component.update: clock_format_select + - component.update: matrix + + - platform: template + id: transition_select + name: "${friendly_name} Transition Effect" + icon: "mdi:transition" + options: + - "Slide up" + - "Slide down" + - "Pixel fall" + lambda: |- + if (id(transition_mode) == 2) return std::string("Pixel fall"); + if (id(transition_mode) == 1) return std::string("Slide down"); + return std::string("Slide up"); + set_action: + - lambda: |- + if (x == "Pixel fall") id(transition_mode) = 2; + else if (x == "Slide down") id(transition_mode) = 1; + else id(transition_mode) = 0; + - component.update: transition_select + +#:########################################################################################:# +# SCRIPT COMPONENT: +# https://esphome.io/components/script.html +#:########################################################################################:# +script: + # Clear the HA text box shortly after a scroll request has been accepted. + - id: clear_scroll_text_box + mode: restart + then: + - delay: 400ms + - lambda: |- + id(ha_scroll_text).publish_state(""); + + # Sequence: exit slide-down -> scroll -> selected re-entry. + - id: start_scroll_sequence + mode: restart + then: + - lambda: |- + id(scrolling) = false; + id(pixel_fall_active) = false; + - component.update: matrix + - script.execute: clock_exit_down + - script.wait: clock_exit_down + - lambda: |- + id(scrolling) = true; + - script.execute: do_scroll + + # Scroll the stored text until it has moved fully off the left side. + - id: do_scroll + mode: restart + then: + - while: + condition: + lambda: |- + return id(scrolling); + then: + - lambda: |- + id(scroll_x) -= 1; + const int end_limit = - (int(id(scroll_text).length()) * 6); + if (id(scroll_x) < end_limit) { + id(scrolling) = false; + + if (id(transition_mode) == 1) { + id(clock_slide_down_entry).execute(); + } else if (id(transition_mode) == 2) { + id(clock_pixel_fall_transition).execute(); + } else { + id(clock_slide_up_entry).execute(); + } + } + - component.update: matrix + - delay: !lambda |- + return (uint32_t) id(scroll_delay_runtime); + + # Exit: slide clock down off-screen and hold it there to prevent a one-frame flash. + - id: clock_exit_down + mode: restart + then: + - lambda: |- + id(transition_active) = true; + id(transition_y) = ${y_offset}; + - repeat: + count: 9 + then: + - lambda: |- + id(transition_y) += 1; + - component.update: matrix + - delay: 45ms + - lambda: |- + id(transition_active) = true; + id(transition_y) = 8; + - component.update: matrix + + # Re-entry: clock slides down into place from above. + - id: clock_slide_down_entry + mode: restart + then: + - lambda: |- + id(transition_active) = true; + id(transition_y) = -8; + - repeat: + count: 9 + then: + - lambda: |- + id(transition_y) += 1; + - component.update: matrix + - delay: 60ms + - lambda: |- + id(transition_active) = false; + id(transition_y) = ${y_offset}; + - component.update: matrix + + # Re-entry: clock slides up into place from below. + - id: clock_slide_up_entry + mode: restart + then: + - lambda: |- + id(transition_active) = true; + id(transition_y) = 8; + - repeat: + count: 9 + then: + - lambda: |- + id(transition_y) -= 1; + - component.update: matrix + - delay: 60ms + - lambda: |- + id(transition_active) = false; + id(transition_y) = ${y_offset}; + - component.update: matrix + + # Re-entry: falling pixels effect before the clock redraws. + - id: clock_pixel_fall_transition + mode: restart + then: + - lambda: |- + id(transition_active) = false; + id(transition_y) = ${y_offset}; + id(pixel_fall_active) = true; + id(pixel_fall_gen) += 1; + - repeat: + count: 18 + then: + - component.update: matrix + - delay: 55ms + - lambda: |- + id(pixel_fall_active) = false; + id(transition_active) = false; + id(transition_y) = ${y_offset}; + - component.update: matrix + +#:########################################################################################:# +# DISPLAY COMPONENT: +# https://esphome.io/components/display/max7219digit/ +#:########################################################################################:# +display: + - platform: max7219digit + id: matrix + cs_pin: ${pin_cs} + num_chips: ${num_chips} + intensity: 2 + update_interval: 500ms + rotate_chip: 0 + reverse_enable: false + flip_x: false + + lambda: |- + // Apply brightness only when it changes to reduce flicker. + static int last_brightness = -1; + if (id(matrix_intensity) != last_brightness) { + if (id(matrix_intensity) > 0) { + int v = id(matrix_intensity); + if (v > 15) v = 15; + it.intensity((uint8_t) v); + } + last_brightness = id(matrix_intensity); + } + + if (id(matrix_intensity) == 0) return; + + it.clear(); + + // Scroll text has priority over clock display. + if (id(scrolling)) { + it.print(id(scroll_x), ${y_scroll_offset}, id(font5x8), id(scroll_text).c_str()); + return; + } + + // Pixel fall shows only particles while active. + if (id(pixel_fall_active)) { + static int last_gen = -1; + static int px[24]; + static int py[24]; + static uint32_t rng = 1; + + auto rand_u32 = [&]() -> uint32_t { + rng = rng * 1664525u + 1013904223u; + return rng; + }; + + if (last_gen != id(pixel_fall_gen)) { + last_gen = id(pixel_fall_gen); + rng = 0xA5A5A5A5u ^ (uint32_t) id(pixel_fall_gen); + for (int i = 0; i < 24; i++) { + px[i] = (int)(rand_u32() % 32); + py[i] = - (int)(rand_u32() % 16); + } + } + + for (int i = 0; i < 24; i++) { + int spd = 1 + (int)(rand_u32() % 2); + py[i] += spd; + + if (py[i] > 10) { + px[i] = (int)(rand_u32() % 32); + py[i] = - (int)(rand_u32() % 10); + } + + for (int t = 0; t < 2; t++) { + int yy = py[i] - t; + int xx = px[i]; + if (xx >= 0 && xx <= 31 && yy >= 0 && yy <= 7) { + it.draw_pixel_at(xx, yy, Color::WHITE); + } + } + } + return; + } + + // Prefer real SNTP time. Fall back to current_mins from common/sntp_common.yaml. + auto now = id(sntp_time).now(); + bool time_valid = now.is_valid(); + + int raw_hour = 0; + int minute_to_show = 0; + int second_to_show = 0; + + if (time_valid) { + raw_hour = now.hour; + minute_to_show = now.minute; + second_to_show = now.second; + } else { + int mins = id(current_mins); + if (mins < 0) mins = 0; + if (mins >= 1440) mins = mins % 1440; + + raw_hour = mins / 60; + minute_to_show = mins % 60; + second_to_show = 0; + } + + int hour_to_show = raw_hour; + if (id(use_12h_mode)) { + hour_to_show = raw_hour % 12; + if (hour_to_show == 0) hour_to_show = 12; + } + + char hh[3], mm[3]; + snprintf(hh, sizeof(hh), "%02d", hour_to_show); + snprintf(mm, sizeof(mm), "%02d", minute_to_show); + + const int x_h1 = 3; + const int x_h2 = 9; + const int x_m1 = 17; + const int x_m2 = 23; + + const int y = (id(transition_active) ? id(transition_y) : ${y_offset}); + + char ch[2] = { hh[0], 0 }; + it.print(x_h1, y, id(digit5), ch); + ch[0] = hh[1]; + it.print(x_h2, y, id(digit5), ch); + + ch[0] = mm[0]; + it.print(x_m1, y, id(digit5), ch); + ch[0] = mm[1]; + it.print(x_m2, y, id(digit5), ch); + + // In 12h mode, draw a tiny A or P indicator at the far right. + if (id(use_12h_mode)) { + const bool is_pm = raw_hour >= 12; + + const uint8_t A_rows[4] = { 2, 5, 7, 5 }; + const uint8_t P_rows[4] = { 6, 5, 6, 4 }; + const uint8_t *L = is_pm ? P_rows : A_rows; + + const int x_ampm = 29; + const int y_ampm = y + 4; + + for (int r = 0; r < 4; r++) { + uint8_t row = L[r]; + for (int c = 0; c < 3; c++) { + if (row & (1 << (2 - c))) { + int xx = x_ampm + c; + int yy = y_ampm + r; + if (yy >= 0 && yy <= 7 && xx >= 0 && xx <= 31) { + it.draw_pixel_at(xx, yy, Color::WHITE); + } + } + } + } + } + + // Blink the colon when SNTP is valid. Keep it solid during fallback time. + const bool colon_on = time_valid ? ((second_to_show % 2) == 0) : true; + if (colon_on) { + const int x_col = (x_h2 + x_m1) / 2 + 2; + int y1 = y + 2; + int y2 = y + 5; + if (y1 >= 0 && y1 <= 7) it.draw_pixel_at(x_col, y1, Color::WHITE); + if (y2 >= 0 && y2 <= 7) it.draw_pixel_at(x_col, y2, Color::WHITE); + } diff --git a/esphome/esp-loungemiddleswitch.yaml b/esphome/esp-loungemiddleswitch.yaml.old similarity index 100% rename from esphome/esp-loungemiddleswitch.yaml rename to esphome/esp-loungemiddleswitch.yaml.old diff --git a/esphome/esp-nspanel1masterbdrm.yaml b/esphome/esp-nspanel1masterbdrm.yaml new file mode 100644 index 0000000..ca07bbd --- /dev/null +++ b/esphome/esp-nspanel1masterbdrm.yaml @@ -0,0 +1,154 @@ +#:########################################################################################:# +# TITLE: MASTER BEDROOM NSPANEL 1 +# zorruno.com layout v1.1 2026 +#:########################################################################################:# +# REPO: +# +#:########################################################################################:# +# VERSIONS: +# V1.0 2026-05-16 Initial version +#:########################################################################################:# +# HARDWARE: +# - Sonoff NS Panel +#:########################################################################################:# +# OPERATION NOTES: +# - +#:########################################################################################:# +# OFFLINE NOTES: +# +#:########################################################################################:# + +#:########################################################################################:# +# SUBSTITUTIONS: Specific device variable substitutions +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +#:########################################################################################:# +substitutions: + # Device Naming + device_name: "esp-nspanel1masterbdrm" + friendly_name: "Master Bedroom NSPanel Touchscreen" + description_comment: "Master Bedroom NSPanel Touchscreen" + device_area: "Master Bedroom" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Removed as the remote package will pull this in correctly for the blueprint + # Project Naming + #project_name: "Lounge NSPanel Touchscreen.Sonoff NS Panel" + #project_version: "v1.0" + + # Passwords & Secrets (unfortunately you can't use substitutions inside secrets names) + api_key: !secret esp-api_key + ota_pass: !secret esp-ota_pass + ota_password: "${ota_pass}" #this is for NSPanel Only as a workaround + static_ip_address: !secret esp-nspanel1masterbdrm_ip + + # If we are changing IP addresses, you must update the current IP address here, otherwise it remains + # Don't forget to switch it back when changed. + current_ip_address: ${static_ip_address} + + # Device Settings + log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for general sensors etc + + ## NSPanel Specifics + upload_tft_automatically: true + boot_sound: false + +#:########################################################################################:# +# PACKAGES: Included Common Packages +# https://esphome.io/components/packages.html +#:########################################################################################:# +packages: + #### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode #### + common_wifi: !include + file: common/network_common_esp32.yaml + vars: + local_device_name: "${device_name}" + local_static_ip_address: "${static_ip_address}" + local_ota_pass: "${ota_pass}" + local_current_ip_address: "${current_ip_address}" + + #### HOME ASSISTANT API (choose encryption or no encryption options) #### + #common_api: !include + # file: common/api_common.yaml + # #file: common/api_common_noencryption.yaml + # vars: + # local_api_key: "${api_key}" + + #### MQTT #### + #common_mqtt: !include + # file: common/mqtt_common.yaml + # vars: + # local_device_name: "${device_name}" + + #### WEB PORTAL #### + #common_webportal: !include common/webportal_common.yaml + + #### SNTP (Only use if you want/need accurate timeclocks) #### + #common_sntp: !include common/sntp_common.yaml + + #### DIAGNOSTICS Sensors #### + #diag_basic: !include common/include_basic_diag_sensors.yaml + #diag_more: !include common/include_more_diag_sensors.yaml + #diag_debug: !include common/include_debug_diag_sensors.yaml + #diag_resetcount: !include common/include_resetcount_diag_sensors.yaml + +# Core and optional configurations + remote_package: + url: https://github.com/edwardtfn/NSPanel-Easy + ref: v2026.5.5 + refresh: 300s + files: + - nspanel_esphome.yaml # Basic package + # Optional advanced and add-on configurations + # - esphome/nspanel_esphome_addon_climate_cool.yaml + # - esphome/nspanel_esphome_addon_climate_heat.yaml + # - esphome/nspanel_esphome_addon_climate_dual.yaml + # - esphome/nspanel_esphome_addon_cover.yaml + # - esphome/nspanel_esphome_addon_display_light.yaml # Show the display as a light in Home Assistant + +#:########################################################################################:# +# ESPHOME: +# https://esphome.io/components/esphome.html +#:########################################################################################:# +esphome: + name: "${device_name}" + friendly_name: "${friendly_name}" + comment: "${description_comment}" # appears on the esphome page in HA + area: "${device_area}" + min_version: 2024.6.0 + #project: + # name: "${project_name}" + # version: "${project_version}" + +#:########################################################################################:# +# ESP PLATFORM AND FRAMEWORK: +# https://esphome.io/components/esp32.html +#:########################################################################################:# +esp32: + board: esp32dev + framework: + type: esp-idf # Suggested ESP-IDF framework + version: recommended + advanced: + #minimum_chip_revision: "3.1" # This makes the binary slightly smaller + sram1_as_iram: true + +#:########################################################################################:# +# LOGGING: +# https://esphome.io/components/logger.html +#:########################################################################################:# +logger: + level: "${log_level}" + #baud_rate: 0 # set to 0 for no logging via UART if you want + +#:########################################################################################:# +# BLUETOOTH: +# https://esphome.io/components/bluetooth_proxy.html +# https://esphome.io/components/esp32_ble_tracker.html +#:########################################################################################:# +#bluetooth_proxy: +# active: true + +#esp32_ble_tracker: +# scan_parameters: +# # Defaults used so Bluetooth can co-exist with WiFi +# active: true \ No newline at end of file diff --git a/esphome/esp-nspanel2lounge.yaml b/esphome/esp-nspanel2lounge.yaml new file mode 100644 index 0000000..07d8799 --- /dev/null +++ b/esphome/esp-nspanel2lounge.yaml @@ -0,0 +1,316 @@ +#:########################################################################################:# +# TITLE: LOUNGE NSPANEL 2 +# zorruno.com layout v1.1 2026 +#:########################################################################################:# +# REPO: +# +#:########################################################################################:# +# VERSIONS: +# V1.0 2026-05-16 Initial version +#:########################################################################################:# +# HARDWARE: +# - Sonoff NS Panel +#:########################################################################################:# +# OPERATION NOTES: +# - +#:########################################################################################:# +# OFFLINE NOTES: +# +#:########################################################################################:# + +#:########################################################################################:# +# SUBSTITUTIONS: Specific device variable substitutions +# If NOT using a secrets file, just replace these with the passwords etc (in quotes) +#:########################################################################################:# +substitutions: + # Device Naming + device_name: "esp-nspanel2lounge" + friendly_name: "Lounge NSPanel Touchscreen" + description_comment: "D1 Mini ESP32 outside entranceway with BT Proxy (Layout V1.1)" + device_area: "Lounge" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. + + # Removed as the remote package will pull this in correctly for the blueprint + # Project Naming + #project_name: "Lounge NSPanel Touchscreen.Sonoff NS Panel" + #project_version: "v1.0" + + # Passwords & Secrets (unfortunately you can't use substitutions inside secrets names) + api_key: !secret esp-api_key + ota_pass: !secret esp-ota_pass + ota_password: "${ota_pass}" #this is for NSPanel Only as a workaround + static_ip_address: !secret esp-nspanel2lounge_ip + + # If we are changing IP addresses, you must update the current IP address here, otherwise it remains + # Don't forget to switch it back when changed. + current_ip_address: ${static_ip_address} + + # Device Settings + log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE + update_interval: "60s" # update time for general sensors etc + + ## NSPanel Specifics + upload_tft_automatically: true + boot_sound: false + +#:########################################################################################:# +# PACKAGES: Included Common Packages +# https://esphome.io/components/packages.html +#:########################################################################################:# +packages: + #### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode #### + common_wifi: !include + file: common/network_common_esp32.yaml + vars: + local_device_name: "${device_name}" + local_static_ip_address: "${static_ip_address}" + local_ota_pass: "${ota_pass}" + local_current_ip_address: "${current_ip_address}" + + #### HOME ASSISTANT API (choose encryption or no encryption options) #### + #common_api: !include + # file: common/api_common.yaml + #file: common/api_common_noencryption.yaml + # vars: + # local_api_key: "${api_key}" + + #### MQTT #### + #common_mqtt: !include + # file: common/mqtt_common.yaml + # vars: + # local_device_name: "${device_name}" + + #### WEB PORTAL #### + #common_webportal: !include common/webportal_common.yaml + + #### SNTP (Only use if you want/need accurate timeclocks) #### + #common_sntp: !include common/sntp_common.yaml + + #### DIAGNOSTICS Sensors #### + #diag_basic: !include common/include_basic_diag_sensors.yaml + #diag_more: !include common/include_more_diag_sensors.yaml + #diag_debug: !include common/include_debug_diag_sensors.yaml + #diag_resetcount: !include common/include_resetcount_diag_sensors.yaml + +# Core and optional configurations + remote_package: + url: https://github.com/edwardtfn/NSPanel-Easy + ref: v2026.5.5 + refresh: 300s + files: + - nspanel_esphome.yaml # Basic package + # Optional advanced and add-on configurations + # - esphome/nspanel_esphome_addon_climate_cool.yaml + # - esphome/nspanel_esphome_addon_climate_heat.yaml + # - esphome/nspanel_esphome_addon_climate_dual.yaml + # - esphome/nspanel_esphome_addon_cover.yaml + # - esphome/nspanel_esphome_addon_display_light.yaml # Show the display as a light in Home Assistant + +#:########################################################################################:# +# ESPHOME: +# https://esphome.io/components/esphome.html +#:########################################################################################:# +esphome: + name: "${device_name}" + friendly_name: "${friendly_name}" + comment: "${description_comment}" # appears on the esphome page in HA + area: "${device_area}" + min_version: 2024.6.0 + #project: + # name: "${project_name}" + # version: "${project_version}" + +#:########################################################################################:# +# ESP PLATFORM AND FRAMEWORK: +# https://esphome.io/components/esp32.html +#:########################################################################################:# +esp32: + board: esp32dev + framework: + type: esp-idf # Suggested ESP-IDF framework + version: recommended + advanced: + minimum_chip_revision: "3.1" # This makes the binary slightly smaller + sram1_as_iram: true + + +#uart: +# id: tf_uart +# tx_pin: GPIO16 +# rx_pin: GPIO17 +# baud_rate: 115200 + + +#:########################################################################################:# +# LOGGING: +# https://esphome.io/components/logger.html +#:########################################################################################:# +logger: + level: "${log_level}" + #baud_rate: 0 # set to 0 for no logging via UART if you want + +#:########################################################################################:# +# HOME ASSISTANT STATE IMPORT: +# Import the last remembered lounge scene from Home Assistant. +#:########################################################################################:# +text_sensor: + - platform: homeassistant + id: lounge_last_scene + entity_id: input_text.lounge_last_scene + internal: true + +#:########################################################################################:# +# NSPANEL BUTTON / RELAY LABELS: +# Extend existing NSPanel Easy entities rather than redefining GPIOs. +#:########################################################################################:# +binary_sensor: + - id: !extend right_button + name: "Lounge Stair Downlights Button" + +switch: + - id: !extend relay_2 + name: "Lounge Stair Downlights" + + - platform: template + id: nspanel_ha_loss_test + name: "HA Loss Test" + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + +#:########################################################################################:# +# NSPANEL HARDWARE BUTTON LOCAL / HA ACTIONS: +# +# Behaviour: +# - HA connected and HA Loss Test OFF: +# - Left button toggles lounge scene between Bright and All Off +# - Left relay is forced ON +# - Right button toggles Relay 2 / Lounge Stair Downlights +# +# - HA disconnected OR HA Loss Test ON: +# - Left button toggles Relay 1 +# - Right button toggles Relay 2 / Lounge Stair Downlights +# +# Notes: +# - Uses NSPanel Easy script IDs and relay IDs. +# - Avoid enabling Relay 1 local mode in the NSPanel blueprint, otherwise the package +# may toggle Relay 1 before this custom logic can handle the press. +#:########################################################################################:# +script: + - id: nspanel_lounge_force_left_relay_on_when_ha_connected + mode: restart + then: + - if: + condition: + lambda: |- + const bool actual_ha_online = + wifi_component->is_connected() && + api_server->is_connected_with_state_subscription(); + + const bool effective_ha_online = + actual_ha_online && !id(nspanel_ha_loss_test).state; + + return effective_ha_online; + then: + - switch.turn_on: relay_1 + + - id: nspanel_lounge_left_button_action + mode: restart + then: + - if: + condition: + lambda: |- + const bool actual_ha_online = + wifi_component->is_connected() && + api_server->is_connected_with_state_subscription(); + + const bool effective_ha_online = + actual_ha_online && !id(nspanel_ha_loss_test).state; + + return effective_ha_online; + then: + - script.execute: nspanel_lounge_force_left_relay_on_when_ha_connected + + - if: + condition: + lambda: |- + return id(lounge_last_scene).has_state() && + id(lounge_last_scene).state != "All Off"; + then: + - homeassistant.action: + action: script.lounge_set_scene + data: + scene_name: "All Off" + else: + - homeassistant.action: + action: script.lounge_set_scene + data: + scene_name: "Bright" + + else: + - lambda: |- + const bool actual_ha_online = + wifi_component->is_connected() && + api_server->is_connected_with_state_subscription(); + + // If NSPanel Easy relay local mode already handled the press, do not double toggle. + if (id(hardware_settings).relay1_local) { + return; + } + + // If this is a real HA/API outage and NSPanel Easy fallback already handled it, + // do not double toggle. HA Loss Test still toggles here because HA is actually online. + if (id(hardware_settings).relay1_fallback && !actual_ha_online) { + return; + } + + relay_1->toggle(); + + - id: nspanel_lounge_right_button_action + mode: restart + then: + - lambda: |- + const bool actual_ha_online = + wifi_component->is_connected() && + api_server->is_connected_with_state_subscription(); + + // If NSPanel Easy relay local mode already handled the press, do not double toggle. + if (id(hardware_settings).relay2_local) { + return; + } + + // If this is a real HA/API outage and NSPanel Easy fallback already handled it, + // do not double toggle. HA Loss Test still toggles here because HA is actually online. + if (id(hardware_settings).relay2_fallback && !actual_ha_online) { + return; + } + + relay_2->toggle(); + + - id: !extend button_left_press_short + then: + - script.execute: nspanel_lounge_left_button_action + + - id: !extend button_right_press_short + then: + - script.execute: nspanel_lounge_right_button_action + +#:########################################################################################:# +# INTERVAL: +# Keep Relay 1 ON whenever Home Assistant is effectively connected. +#:########################################################################################:# +interval: + - interval: 5s + then: + - script.execute: nspanel_lounge_force_left_relay_on_when_ha_connected + +#:########################################################################################:# +# BLUETOOTH: +# https://esphome.io/components/bluetooth_proxy.html +# https://esphome.io/components/esp32_ble_tracker.html +#:########################################################################################:# +#bluetooth_proxy: +# active: true + +#esp32_ble_tracker: +# scan_parameters: +# # Defaults used so Bluetooth can co-exist with WiFi +# active: true \ No newline at end of file diff --git a/esphome/fonts/5x8.bdf b/esphome/fonts/5x8.bdf new file mode 100644 index 0000000..939cc05 --- /dev/null +++ b/esphome/fonts/5x8.bdf @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + +Sign in · GitLab + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + diff --git a/esphome/fonts/5x8.bdf.old b/esphome/fonts/5x8.bdf.old new file mode 100644 index 0000000..ecf62d9 --- /dev/null +++ b/esphome/fonts/5x8.bdf.old @@ -0,0 +1,1470 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + matrixclock-esphome/fonts/5x8.bdf at main · RealDeco/matrixclock-esphome · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + +
+ +
+ +
+ +
+ + + + / + + matrixclock-esphome + + + Public +
+ + +
+ +
+ + +
+
+ +
+
+ + + + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + +

Latest commit

 

History

History
21422 lines (21422 loc) · 151 KB

File metadata and controls

21422 lines (21422 loc) · 151 KB
+
+ + + + +
+ +
+ +
+
+ +
+ +
+

Footer

+ + + + +
+
+ + + + + © 2026 GitHub, Inc. + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/esphome/fonts/Eight-Bit-Dragon.ttf b/esphome/fonts/Eight-Bit-Dragon.ttf new file mode 100644 index 0000000..c0bc8fe --- /dev/null +++ b/esphome/fonts/Eight-Bit-Dragon.ttf @@ -0,0 +1,13 @@ + + + Eight Bit Dragon free font + + + + +
PreviewSizeFonts

Eight Bit Dragon - .TTF

Lcd - Modern
13279 Downloads Download

Eight Bit Dragon Lcd - Modern

Eight Bit Dragon font

Eight Bit Dragon fontEight Bit Dragon font bigEight Bit Dragon font textEight Bit Dragon font 1
Font NameDateFile Size
eight_bit_dragon.png 2018-10-10 8 KB
Eight-Bit-Dragon.ttf 2018-10-10 34 KB


Font Details

Concielian Classic version 2 Truetype Font for Windows



2017 Iconian Fonts - Daniel Zadorozny

http://www.iconian.com/



This font comes with the following 5 versions: Regular, Bold, Condensed, Expanded and 3d.


This font may be freely distributed and is free for all non-commercial uses. Use of the fonts are at your own risk.

For commercial use of the font please visit http://iconian.com/commercial.html for additional information.

or e-mail me at iconian@aol.com

Eight Bit Dragonfont download, Eight Bit Dragon font.
; +
+
+ + + diff --git a/group/downstairs_flat_lights.yaml b/group/downstairs_flat_lights.yaml deleted file mode 100644 index 88ef543..0000000 --- a/group/downstairs_flat_lights.yaml +++ /dev/null @@ -1,14 +0,0 @@ -downstairs_flat_lights: - name: Downstairs Flat Lights - # All the lights in the Downstairs flat - # Doesn't include the outdoor switch lights, or bedroom wardrobe light - entities: - - switch.esp_downstloungemain_relay_1_main_lights # Lounge Main Lights - - switch.esp_downstloungemain_relay_2_back_wall_lights # Lounge Back Wall Lights - - switch.esp_downstmasterbedrmlights_relay_1_main_lights # Main Bedroom, Main Lights - - switch.esp_downstbedrm2lights_relay_1_main_lights # Small Bedroom Room Lights - - switch.esp_downstkitchlights_relay_2_kitchen_light # Kitchen Lights - - switch.esp_downstkitchlights_relay_1_dining_light # Dining Lights - - switch.esp_downstbathswitch_relay_1_main_lights # Bathroom main lights - - switch.esp_downstbathswitch_relay_2_cabinet_light # Bathroom Cabinet Lights - diff --git a/packages/16a_ev_charging_controls.yaml b/packages/16a_ev_charging_controls.yaml new file mode 100644 index 0000000..ba7a74b --- /dev/null +++ b/packages/16a_ev_charging_controls.yaml @@ -0,0 +1,389 @@ +# yaml-language-server: $schema=null + +############################################################################### +# PACKAGE: 16A EV Charger Control +############################################################################### +# +# VERSION: +# V1.1 2026-05-15 +# - Reworked 1hr topup logic to use a hard deadline and watchdog. +# - Removed dependency on timer.finished event for relay shutoff. +# - Offpeak until charged now continues past offpeak end until charging stops. +# - 1hr topup now now rolls into Normal Offpeak if the hour ends during +# the offpeak window, without switching the relay off and back on. +# +# V1.0 2026-05-11 +# - Initial package for 16A EV charger offpeak and charge-until-full control. +# +# PACKAGE SETTINGS: +# Car display name: +# - Leaf +# +# Charger relay output: +# - switch.garage_db_control_16a_wallcharger_operation +# +# Vehicle charging sensor: +# - binary_sensor.garage_db_control_16a_wallcharger_charging +# +# Default offpeak period: +# - Start: 21:00:00 +# - End: 00:00:00 +# +############################################################################### + +input_datetime: + ev_16a_offpeak_start: + name: 16A EV Offpeak Start + has_date: false + has_time: true + initial: "21:00:00" + + ev_16a_offpeak_end: + name: 16A EV Offpeak End + has_date: false + has_time: true + initial: "00:00:00" + + ev_16a_1hr_topup_until: + name: 16A EV 1hr topup until + has_date: true + has_time: true + initial: "2000-01-01 00:00:00" + +input_select: + ev_16a_charge_mode: + name: Leaf EV charge mode + icon: mdi:ev-station + options: + - "Normal (Offpeak)" + - "Offpeak until charged" + - "1hr topup now" + - "Charge now until full" + - "Stop charging now" + initial: "Normal (Offpeak)" + +automation: + ############################################################################# + # OFFPEAK START + ############################################################################# + + - id: ev_16a_charger_offpeak_start + alias: "16A EV Charger - Offpeak Start" + mode: single + + triggers: + - trigger: time + at: input_datetime.ev_16a_offpeak_start + + conditions: + - condition: template + value_template: > + {{ states('input_select.ev_16a_charge_mode') in + ['Normal (Offpeak)', 'Offpeak until charged'] }} + + actions: + - action: switch.turn_on + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + - choose: + - conditions: + - condition: state + entity_id: input_select.ev_16a_charge_mode + state: "Offpeak until charged" + sequence: + # If the car never starts charging, or is already full, turn off + # after a 5 minute grace period. + - delay: "00:05:00" + + - condition: state + entity_id: input_select.ev_16a_charge_mode + state: "Offpeak until charged" + + - condition: state + entity_id: binary_sensor.garage_db_control_16a_wallcharger_charging + state: "off" + + - action: switch.turn_off + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + - action: input_select.select_option + target: + entity_id: input_select.ev_16a_charge_mode + data: + option: "Normal (Offpeak)" + + ############################################################################# + # OFFPEAK END + ############################################################################# + + - id: ev_16a_charger_offpeak_end + alias: "16A EV Charger - Offpeak End" + mode: single + + triggers: + - trigger: time + at: input_datetime.ev_16a_offpeak_end + + conditions: + - condition: state + entity_id: input_select.ev_16a_charge_mode + state: "Normal (Offpeak)" + + actions: + - action: switch.turn_off + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + ############################################################################# + # USER CHARGE MODE SELECTIONS + ############################################################################# + + - id: ev_16a_charger_charge_mode_selected + alias: "16A EV Charger - Charge Mode Selected" + mode: restart + + triggers: + - trigger: state + entity_id: input_select.ev_16a_charge_mode + + actions: + - variables: + selected_mode: "{{ trigger.to_state.state }}" + is_offpeak_now: > + {% set start = states('input_datetime.ev_16a_offpeak_start') %} + {% set end = states('input_datetime.ev_16a_offpeak_end') %} + {% set now_time = now().strftime('%H:%M:%S') %} + {% if start == end %} + {{ true }} + {% elif start < end %} + {{ start <= now_time < end }} + {% else %} + {{ now_time >= start or now_time < end }} + {% endif %} + + - choose: + ##################################################################### + # NORMAL OFFPEAK + ##################################################################### + + - conditions: + - condition: template + value_template: "{{ selected_mode == 'Normal (Offpeak)' }}" + sequence: + # No immediate action. + # Normal mode only acts when the offpeak start/end triggers fire. + - stop: "Normal offpeak mode selected." + + ##################################################################### + # OFFPEAK UNTIL CHARGED + ##################################################################### + + - conditions: + - condition: template + value_template: "{{ selected_mode == 'Offpeak until charged' }}" + sequence: + - if: + - condition: template + value_template: "{{ is_offpeak_now }}" + then: + - action: switch.turn_on + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + # If the car never starts charging, or is already full, turn + # off after a 5 minute grace period. + - delay: "00:05:00" + + - condition: state + entity_id: input_select.ev_16a_charge_mode + state: "Offpeak until charged" + + - condition: state + entity_id: binary_sensor.garage_db_control_16a_wallcharger_charging + state: "off" + + - action: switch.turn_off + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + - action: input_select.select_option + target: + entity_id: input_select.ev_16a_charge_mode + data: + option: "Normal (Offpeak)" + + ##################################################################### + # 1 HOUR TOPUP NOW + ##################################################################### + + - conditions: + - condition: template + value_template: "{{ selected_mode == '1hr topup now' }}" + sequence: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.ev_16a_1hr_topup_until + data: + datetime: > + {{ (as_timestamp(now()) + 3600) + | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }} + + - action: switch.turn_on + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + ##################################################################### + # CHARGE NOW UNTIL FULL + ##################################################################### + + - conditions: + - condition: template + value_template: "{{ selected_mode == 'Charge now until full' }}" + sequence: + - action: switch.turn_on + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + # If the car never starts charging, or is already full, turn off + # after a 5 minute grace period. + - delay: "00:05:00" + + - condition: state + entity_id: input_select.ev_16a_charge_mode + state: "Charge now until full" + + - condition: state + entity_id: binary_sensor.garage_db_control_16a_wallcharger_charging + state: "off" + + - action: switch.turn_off + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + - action: input_select.select_option + target: + entity_id: input_select.ev_16a_charge_mode + data: + option: "Normal (Offpeak)" + + ##################################################################### + # STOP CHARGING NOW + ##################################################################### + + - conditions: + - condition: template + value_template: "{{ selected_mode == 'Stop charging now' }}" + sequence: + - action: switch.turn_off + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + - action: input_select.select_option + target: + entity_id: input_select.ev_16a_charge_mode + data: + option: "Normal (Offpeak)" + + ############################################################################# + # 1 HOUR TOPUP WATCHDOG + ############################################################################# + + - id: ev_16a_charger_1hr_topup_watchdog + alias: "16A EV Charger - 1hr Topup Watchdog" + mode: single + + triggers: + - trigger: time_pattern + minutes: "/1" + + - trigger: homeassistant + event: start + + conditions: + - condition: state + entity_id: input_select.ev_16a_charge_mode + state: "1hr topup now" + + - condition: template + value_template: > + {{ as_timestamp(now()) >= + as_timestamp(states('input_datetime.ev_16a_1hr_topup_until')) }} + + actions: + - variables: + is_offpeak_now: > + {% set start = states('input_datetime.ev_16a_offpeak_start') %} + {% set end = states('input_datetime.ev_16a_offpeak_end') %} + {% set now_time = now().strftime('%H:%M:%S') %} + {% if start == end %} + {{ true }} + {% elif start < end %} + {{ start <= now_time < end }} + {% else %} + {{ now_time >= start or now_time < end }} + {% endif %} + + - action: input_select.select_option + target: + entity_id: input_select.ev_16a_charge_mode + data: + option: "Normal (Offpeak)" + + - choose: + # If the 1 hour topup finishes during offpeak, do not flick the relay. + # Leave it on and let the Normal Offpeak end automation turn it off. + - conditions: + - condition: template + value_template: "{{ is_offpeak_now }}" + sequence: + - if: + - condition: state + entity_id: switch.garage_db_control_16a_wallcharger_operation + state: "off" + then: + - action: switch.turn_on + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + default: + - if: + - condition: state + entity_id: switch.garage_db_control_16a_wallcharger_operation + state: "on" + then: + - action: switch.turn_off + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + ############################################################################# + # CHARGING COMPLETE MONITOR + ############################################################################# + + - id: ev_16a_charger_stop_when_charging_complete + alias: "16A EV Charger - Stop When Charging Complete" + mode: single + + triggers: + - trigger: state + entity_id: binary_sensor.garage_db_control_16a_wallcharger_charging + to: "off" + for: "00:05:00" + + conditions: + - condition: template + value_template: > + {{ states('input_select.ev_16a_charge_mode') in + ['Offpeak until charged', 'Charge now until full'] }} + + actions: + - action: switch.turn_off + target: + entity_id: switch.garage_db_control_16a_wallcharger_operation + + - action: input_select.select_option + target: + entity_id: input_select.ev_16a_charge_mode + data: + option: "Normal (Offpeak)" diff --git a/packages/announcement_test.yaml b/packages/announcement_test.yaml index 300c0f9..7602697 100644 --- a/packages/announcement_test.yaml +++ b/packages/announcement_test.yaml @@ -17,14 +17,14 @@ automation: entity_id: input_button.announcement_test action: - - action: script.view_road_announcement - data: - channels: "sony_tv_lounge" - title: "View Road Test" - short_announcement: "Test announcement" - long_announcement: "This is a test announcement from the package test button." - payload: "On" - indicator: "" + # - action: script.view_road_announcement + # data: + # channels: "sony_tv_lounge" + # title: "View Road Test" + # short_announcement: "Test announcement" + # long_announcement: "This is a test announcement from the package test button." + # payload: "On" + # indicator: "" #- action: script.view_road_announcement # data: @@ -34,6 +34,7 @@ automation: - action: script.view_road_announcement data: - channels: "lounge_google_home_voice" - title: "View Road Test" - long_announcement: "This is a test announcement on the lounge Google Home." + channels: "lounge_google_home_voice, sony_tv_lounge" + title: "View Road " + short_announcement: "9pm, Free Power time" + long_announcement: "It is 9pm." diff --git a/packages/auto_lights_underhouse_entrance.yaml b/packages/auto_lights_underhouse_entrance.yaml new file mode 100644 index 0000000..d18638e --- /dev/null +++ b/packages/auto_lights_underhouse_entrance.yaml @@ -0,0 +1,64 @@ +################################################################################ +# PACKAGE: Underhouse Entrance Auto Lights +################################################################################ +# +# Version: +# 1.2 - 2026-05-12 +# - Added fast Zigbee2MQTT FP1-style event triggers: +# sensor.underhouse_mmwave_sensor_x23ms_presence_event = enter +# sensor.underhouse_mmwave_sensor_x23ms_presence_event = approach +# - Keeps binary presence as a backup trigger and as the clear/off condition. +# - This should behave closer to the previous Node-RED automation. +# +# Notes: +# - The UI may show the binary sensor as "Detected", but the YAML state is +# normally "on". +# - The presence_event sensor can react earlier than the binary presence sensor. +# - The event sensor is used to turn lights on quickly, but not to decide when +# the area is clear, because it may remain on its last event value. +# +################################################################################ + +automation: + - id: underhouse_entrance_auto_lights + alias: "Underhouse Entrance - Auto Lights" + description: "Turns underhouse entrance lights on from Zigbee2MQTT mmWave enter/approach/presence, then off 400 seconds after presence clears." + mode: restart + max_exceeded: silent + + trigger: + - platform: state + entity_id: sensor.underhouse_mmwave_sensor_x23ms_presence_event + to: "enter" + + - platform: state + entity_id: sensor.underhouse_mmwave_sensor_x23ms_presence_event + to: "approach" + + - platform: state + entity_id: binary_sensor.underhouse_mmwave_sensor_x23ms_presence + to: "on" + + action: + - alias: "Turn on the underhouse entrance lights" + action: switch.turn_on + target: + entity_id: switch.esp_underhouselights_underhouse_entrance_lights + + - alias: "Wait until the mmWave presence sensor is clear" + wait_template: > + {{ is_state('binary_sensor.underhouse_mmwave_sensor_x23ms_presence', 'off') }} + + - alias: "Keep lights on for 400 seconds after presence clears" + delay: + seconds: 400 + + - alias: "Only continue if presence is still clear" + condition: state + entity_id: binary_sensor.underhouse_mmwave_sensor_x23ms_presence + state: "off" + + - alias: "Turn off the underhouse entrance lights" + action: switch.turn_off + target: + entity_id: switch.esp_underhouselights_underhouse_entrance_lights diff --git a/packages/auto_lights_underhouse_storage.yaml b/packages/auto_lights_underhouse_storage.yaml new file mode 100644 index 0000000..e66f1ec --- /dev/null +++ b/packages/auto_lights_underhouse_storage.yaml @@ -0,0 +1,276 @@ +#:########################################################################################:# +# TITLE: UNDERHOUSE STORAGE LIGHTS AUTO LIGHTS PACKAGE +#:########################################################################################:# +# DESCRIPTION: +# Replaces the Node-RED automation for the underhouse storage lights. +# Doors, PIR sensors, a remote switch, and local relay changes can start or +# extend a 1 hour lighting timer. +# +# VERSION: +# V1.0 2026-05-12 +# - Initial Home Assistant package version. +# +# EDIT NOTES: +# - Add future door contacts in BOTH places marked FUTURE DOOR. +# - Add future PIR sensors in the PIR group marked FUTURE PIR. +# - The generated group entities used below should be: +# binary_sensor.underhouse_storage_door_contacts +# binary_sensor.underhouse_storage_pir_presence +#:########################################################################################:# + +binary_sensor: + - platform: group + name: Underhouse Storage Door Contacts + unique_id: underhouse_storage_door_contacts + device_class: door + all: false + entities: + - binary_sensor.underhouse_stealth_door_x07rs_contact + - binary_sensor.underhouse_north_door_door + # FUTURE DOOR: add the next underhouse storage door contact here. + # - binary_sensor.underhouse_future_door_contact + + - platform: group + name: Underhouse Storage PIR Presence + unique_id: underhouse_storage_pir_presence + device_class: occupancy + all: false + entities: + - binary_sensor.underhouse_pir_sensor_south_occupancy + # FUTURE PIR: uncomment this when the north PIR exists. + # - binary_sensor.underhouse_pir_sensor_north_occupancy + +timer: + underhouse_storage_lights_runtime: + name: Underhouse Storage Lights Runtime + duration: "01:00:00" + restore: true + icon: mdi:timer-outline + +script: + underhouse_storage_lights_start_runtime: + alias: Underhouse Storage Lights - Start Or Extend Runtime + mode: restart + sequence: + - choose: + - conditions: + - condition: state + entity_id: switch.esp_underhouselights_underhouse_storage_lights + state: "off" + sequence: + - action: switch.turn_on + target: + entity_id: switch.esp_underhouselights_underhouse_storage_lights + + - action: timer.start + target: + entity_id: timer.underhouse_storage_lights_runtime + data: + duration: "0:20:00" + + underhouse_storage_lights_stop_runtime: + alias: Underhouse Storage Lights - Stop Runtime + mode: restart + sequence: + - choose: + - conditions: + - condition: state + entity_id: switch.esp_underhouselights_underhouse_storage_lights + state: "on" + sequence: + - action: switch.turn_off + target: + entity_id: switch.esp_underhouselights_underhouse_storage_lights + + - choose: + - conditions: + - condition: template + value_template: >- + {{ not is_state('timer.underhouse_storage_lights_runtime', 'idle') }} + sequence: + - action: timer.cancel + target: + entity_id: timer.underhouse_storage_lights_runtime + +automation: + - id: underhouse_storage_lights_start_extend_runtime + alias: Underhouse Storage Lights - Start Or Extend Runtime + description: >- + Turns the storage lights on and starts or restarts the 1 hour timer when + a door opens, PIR detects occupancy, or the remote switch is pressed. + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.underhouse_storage_door_contacts + to: "on" + - platform: state + entity_id: binary_sensor.underhouse_storage_pir_presence + to: "on" + - platform: state + entity_id: binary_sensor.esp_underhouselights_underhouse_storage_lights_remote_switch + to: "on" + action: + - action: script.underhouse_storage_lights_start_runtime + + - id: underhouse_storage_lights_start_timer_when_relay_turned_on + alias: Underhouse Storage Lights - Start Timer When Relay Turned On + description: >- + Starts the timer if the relay is turned on locally by ESPHome or + by another Home Assistant automation. + mode: restart + trigger: + - platform: state + entity_id: switch.esp_underhouselights_underhouse_storage_lights + to: "on" + action: + - action: timer.start + target: + entity_id: timer.underhouse_storage_lights_runtime + data: + duration: "00:20:00" + + - id: underhouse_storage_lights_timer_finished + alias: Underhouse Storage Lights - Timer Finished + description: >- + Turns the storage lights off after the runtime expires, even if a + door has been left open. + mode: single + trigger: + - platform: event + event_type: timer.finished + event_data: + entity_id: timer.underhouse_storage_lights_runtime + action: + - choose: + - conditions: + - condition: state + entity_id: switch.esp_underhouselights_underhouse_storage_lights + state: "on" + sequence: + - action: switch.turn_off + target: + entity_id: switch.esp_underhouselights_underhouse_storage_lights + + - id: underhouse_storage_lights_cancel_timer_when_relay_off + alias: Underhouse Storage Lights - Cancel Timer When Relay Off + description: >- + Keeps the timer helper tidy if the relay is turned off manually, locally, + or by another automation. + mode: restart + trigger: + - platform: state + entity_id: switch.esp_underhouselights_underhouse_storage_lights + to: "off" + condition: + - condition: template + value_template: >- + {{ not is_state('timer.underhouse_storage_lights_runtime', 'idle') }} + action: + - action: timer.cancel + target: + entity_id: timer.underhouse_storage_lights_runtime + + - id: underhouse_storage_lights_all_doors_closed + alias: Underhouse Storage Lights - All Doors Closed + description: >- + When all door contacts are closed, turn the lights off immediately if no + PIR occupancy is active. If PIR is still active, the PIR clear automation + below will turn them off after 60 seconds without occupancy. + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.underhouse_storage_door_contacts + from: "on" + to: "off" + condition: + - condition: state + entity_id: binary_sensor.underhouse_storage_pir_presence + state: "off" + action: + - action: script.underhouse_storage_lights_stop_runtime + + - id: underhouse_storage_lights_individual_door_closed_check + alias: Underhouse Storage Lights - Individual Door Closed No Motion Check + description: >- + When a real door contact closes, wait 60 seconds and then turn the relay + off only if all doors are closed and there is no PIR occupancy. + mode: restart + trigger: + - platform: state + entity_id: + - binary_sensor.underhouse_stealth_door_x07rs_contact + - binary_sensor.underhouse_north_door_door + # FUTURE DOOR: add the next underhouse storage door contact here too. + # - binary_sensor.underhouse_future_door_contact + from: "on" + to: "off" + action: + - delay: "00:01:00" + - condition: state + entity_id: binary_sensor.underhouse_storage_door_contacts + state: "off" + - condition: state + entity_id: binary_sensor.underhouse_storage_pir_presence + state: "off" + - condition: state + entity_id: switch.esp_underhouselights_underhouse_storage_lights + state: "on" + - action: script.underhouse_storage_lights_stop_runtime + + - id: underhouse_storage_lights_pir_clear_after_doors_closed + alias: Underhouse Storage Lights - PIR Clear After Doors Closed + description: >- + If the doors are closed and PIR occupancy clears for 60 seconds, turn the + storage lights off. + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.underhouse_storage_pir_presence + to: "off" + for: "00:01:00" + condition: + - condition: state + entity_id: binary_sensor.underhouse_storage_door_contacts + state: "off" + - condition: state + entity_id: switch.esp_underhouselights_underhouse_storage_lights + state: "on" + action: + - action: script.underhouse_storage_lights_stop_runtime + + - id: underhouse_storage_lights_startup_sync + alias: Underhouse Storage Lights - Startup Sync + description: >- + On Home Assistant startup, re-sync the package state so an already-open + door or already-active PIR still controls the lights after a restart. + mode: single + trigger: + - platform: homeassistant + event: start + action: + - delay: "00:00:20" + - choose: + - conditions: + - condition: template + value_template: >- + {{ + is_state('binary_sensor.underhouse_storage_door_contacts', 'on') + or is_state('binary_sensor.underhouse_storage_pir_presence', 'on') + or is_state('binary_sensor.esp_underhouselights_underhouse_storage_lights_remote_switch', 'on') + }} + sequence: + - action: script.underhouse_storage_lights_start_runtime + + - conditions: + - condition: state + entity_id: switch.esp_underhouselights_underhouse_storage_lights + state: "on" + - condition: state + entity_id: timer.underhouse_storage_lights_runtime + state: "idle" + sequence: + - action: timer.start + target: + entity_id: timer.underhouse_storage_lights_runtime + data: + duration: "00:20:00" diff --git a/packages/bedroom_1_ikea_air_filter.yaml b/packages/bedroom_1_ikea_air_filter.yaml index e78cf22..8caae44 100644 --- a/packages/bedroom_1_ikea_air_filter.yaml +++ b/packages/bedroom_1_ikea_air_filter.yaml @@ -1,20 +1,26 @@ ############################################################################### # Bedroom 1 Ikea Air Filter Package # File: /config/packages/bedroom_1_ikea_air_filter.yaml -# Version: 1.0 -# Date: 2026-04-29 +# Version: 1.1 +# Date: 2026-05-08 # # Purpose: # - Creates a dropdown helper called "Bedroom 1 Ikea Fan". -# - Auto mode controls the Bedroom 1 Ikea air filter speed by priority. +# - Auto mode controls the Bedroom 1 Ikea air filter speed by event and timing. # - Manual dropdown modes set a fixed fan speed and disable auto control. # -# Auto priority order: -# 1. Quiet time on: fan speed 2 -# 2. Bedside light on: fan speed 2 -# 3. Free power time, 21:00 to midnight: fan speed 5 -# 4. Daytime, 09:00 to 15:00: fan speed 4 -# 5. Default: fan speed 2 +# Behaviour notes: +# - Quiet time turning on sets fan speed to 2. +# - Bedside lamp turning on sets fan speed to 2. +# - Bedside lamp turning off does not change the fan speed. +# - There is no 10-minute re-check, so the fan will not ramp back up just +# because the bedside lamp was switched off. +# - Time boundaries can still change the fan speed later. +# +# Auto timing: +# - 21:00 to midnight: fan speed 5 +# - 09:00 to 15:00: fan speed 4 +# - Default: fan speed 2 ############################################################################### input_select: @@ -68,13 +74,17 @@ automation: - trigger: homeassistant event: start - # Highest priority condition. + # Quiet time only forces low when it turns on. + # Turning quiet time off does not automatically ramp the fan back up. - trigger: state entity_id: input_boolean.quiet_time + to: "on" - # Bedside lamp should force low speed while on. + # Bedside lamp only forces low when it turns on. + # Turning the lamp off does not change the fan speed. - trigger: state entity_id: light.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb + to: "on" # Main timing boundaries. - trigger: time @@ -84,10 +94,6 @@ automation: - "15:00:00" - "21:00:00" - # Safety/re-check trigger, especially for the bedside lamp rule. - - trigger: time_pattern - minutes: "/10" - conditions: # Manual dropdown modes disable automatic control. - condition: state @@ -96,10 +102,26 @@ automation: actions: - variables: + trigger_entity: "{{ trigger.entity_id | default('') }}" + bedside_lamp_entity: light.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb + bedside_lamp_on_trigger: >- + {{ + trigger_entity == bedside_lamp_entity + and trigger.to_state is defined + and trigger.to_state.state == 'on' + }} + quiet_time_on_trigger: >- + {{ + trigger_entity == 'input_boolean.quiet_time' + and trigger.to_state is defined + and trigger.to_state.state == 'on' + }} auto_target_speed: >- - {% if is_state('input_boolean.quiet_time', 'on') %} + {% if bedside_lamp_on_trigger %} 2 - {% elif is_state('light.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb', 'on') %} + {% elif quiet_time_on_trigger %} + 2 + {% elif is_state('input_boolean.quiet_time', 'on') %} 2 {% elif now().hour >= 21 %} 5 @@ -142,7 +164,7 @@ automation: entity_id: automation.bedroom_1_ikea_air_filter_auto_control # Auto starts at speed 2, then immediately evaluates the - # current priority rules. + # current timing rules. - action: script.bedroom_1_ikea_air_filter_set_speed_if_needed data: target_speed: 2 diff --git a/packages/bedtime_mode_actions.yaml b/packages/bedtime_mode_actions.yaml index 6d9a4a4..5c2ea76 100644 --- a/packages/bedtime_mode_actions.yaml +++ b/packages/bedtime_mode_actions.yaml @@ -3,6 +3,9 @@ ############################################################################### # # VERSION: +# 1.4 - 2026-05-21 +# - Added delayed lounge NSPanel touchscreen brightness reduction to 5. +# # 1.3 - 2026-05-07 # - Added downstairs lights all off script. # - Added main hallway nightlights 60 minute bedtime timer. @@ -29,6 +32,7 @@ # - climate entities are turned off if they are not already "off". # - Main hallway nightlights are turned on for 60 minutes when bedtime mode runs. # - If the main hallway nightlights are turned off manually, the timer is cancelled. +# - Lounge NSPanel touchscreen brightness is set to 5 after 20 seconds. # ############################################################################### @@ -94,6 +98,18 @@ automation: delay_seconds: 0 action: + # Turn hallway nightlights on immediately before any other bedtime actions. + - service: switch.turn_on + target: + entity_id: switch.main_hallway_nightlights_1_relay_1_main_hallway_nightlights + + # Start or restart the 60 minute bedtime nightlight timer. + - service: timer.start + target: + entity_id: timer.main_hallway_nightlights_bedtime + data: + duration: "01:00:00" + - service: script.downstairs_lights_all_off - service: script.lounge_set_scene @@ -104,15 +120,10 @@ automation: target: entity_id: input_boolean.quiet_time - - service: switch.turn_on + # Reduce the lounge NSPanel screen brightness after 20 seconds. + - service: script.turn_on target: - entity_id: switch.main_hallway_nightlights_1_relay_1_main_hallway_nightlights - - - service: timer.start - target: - entity_id: timer.main_hallway_nightlights_bedtime - data: - duration: "01:00:00" + entity_id: script.bedtime_mode_set_lounge_nspanel_brightness_after_delay - repeat: for_each: "{{ bedtime_delayed_off_targets }}" @@ -232,3 +243,21 @@ script: mode: parallel max: 50 + + bedtime_mode_set_lounge_nspanel_brightness_after_delay: + alias: "Bedtime Mode - Set Lounge NSPanel Brightness After Delay" + description: > + Waits 20 seconds after bedtime mode starts, then sets the lounge NSPanel + touchscreen display brightness to 5. + + sequence: + - delay: + seconds: 20 + + - service: number.set_value + target: + entity_id: number.lounge_nspanel_touchscreen_display_brightness + data: + value: 1 + + mode: restart diff --git a/packages/blind_control.yaml b/packages/blind_control.yaml deleted file mode 100644 index 3c1dcf2..0000000 --- a/packages/blind_control.yaml +++ /dev/null @@ -1,24 +0,0 @@ -mqtt: - switch: - - unique_id: lounge_blinds_control - name: "Lounge Blinds Control" - state_topic: "viewroad-status/blinds/lounge-verticalblinds" - command_topic: "viewroad-commands/blinds/lounge-verticalblinds" - payload_off: "off" - payload_on: "on" - state_on: "on" - state_off: "off" - icon: mdi:blinds - retain: false - -automation: - - id: lounge_blinds_control_switch - alias: Publish lounge blind control to mqtt - trigger: - platform: state - entity_id: input_boolean.lounge_blinds - action: - service: mqtt.publish - data_template: - payload: "{{trigger.to_state.state}}" - topic: viewroad-status/blinds/lounge-verticalblinds diff --git a/packages/downstairs_flat_controls.yaml b/packages/downstairs_flat_controls.yaml index feb328a..9abc46a 100644 --- a/packages/downstairs_flat_controls.yaml +++ b/packages/downstairs_flat_controls.yaml @@ -4,6 +4,12 @@ # # VERSION HISTORY # +# 2026-05-07 - v1.0.2 +# - Added binary_sensor.downstairs_flat_any_lights_on. +# - Updated all-on/all-off script to use the new binary sensor. +# - Kept direct entity lists instead of using a Home Assistant group, to avoid +# package merge errors from duplicate group/name keys. +# # 2026-05-07 - v1.0.1 # - Removed group.downstairs_flat_lights from this package to avoid duplicate # group/name package merge errors. @@ -30,6 +36,11 @@ # - downstairs_light_toggle_all_group turns all listed lights OFF if any are ON. # - downstairs_light_toggle_all_group turns all listed lights ON if they are all OFF. # +# - binary_sensor.downstairs_flat_any_lights_on: +# - ON when any listed downstairs light is ON. +# - OFF when all listed downstairs lights are OFF. +# - This replaces the need for a group.downstairs_flat_lights entity. +# ############################################################################### ############################################################################### @@ -41,6 +52,27 @@ input_boolean: name: Downstairs Guest Occupied icon: mdi:home-account +############################################################################### +# TEMPLATE SENSORS +############################################################################### + +template: + - binary_sensor: + - name: Downstairs Flat Any Lights On + unique_id: downstairs_flat_any_lights_on + icon: mdi:lightbulb-group + state: > + {{ + is_state('switch.esp_downstloungemain_relay_1_main_lights', 'on') + or is_state('switch.esp_downstloungemain_relay_2_back_wall_lights', 'on') + or is_state('switch.esp_downstmasterbedrmlights_relay_1_main_lights', 'on') + or is_state('switch.esp_downstbedrm2lights_relay_1_main_lights', 'on') + or is_state('switch.esp_downstkitchlights_relay_2_kitchen_light', 'on') + or is_state('switch.esp_downstkitchlights_relay_1_dining_light', 'on') + or is_state('switch.esp_downstbathswitch_relay_1_main_lights', 'on') + or is_state('switch.esp_downstbathswitch_relay_2_cabinet_light', 'on') + }} + ############################################################################### # SCRIPTS ############################################################################### @@ -169,18 +201,9 @@ script: - choose: - conditions: - - condition: template - value_template: > - {{ - is_state('switch.esp_downstloungemain_relay_1_main_lights', 'on') - or is_state('switch.esp_downstloungemain_relay_2_back_wall_lights', 'on') - or is_state('switch.esp_downstmasterbedrmlights_relay_1_main_lights', 'on') - or is_state('switch.esp_downstbedrm2lights_relay_1_main_lights', 'on') - or is_state('switch.esp_downstkitchlights_relay_2_kitchen_light', 'on') - or is_state('switch.esp_downstkitchlights_relay_1_dining_light', 'on') - or is_state('switch.esp_downstbathswitch_relay_1_main_lights', 'on') - or is_state('switch.esp_downstbathswitch_relay_2_cabinet_light', 'on') - }} + - condition: state + entity_id: binary_sensor.downstairs_flat_any_lights_on + state: "on" sequence: - action: homeassistant.turn_off target: diff --git a/packages/energy_readings.yaml b/packages/energy_readings.yaml deleted file mode 100644 index af9db82..0000000 --- a/packages/energy_readings.yaml +++ /dev/null @@ -1,35 +0,0 @@ -mqtt: - sensor: - - unique_id: view_road_power_now - name: "Power Now" - state_topic: "viewroad-status/energy/PowerTotals" - unit_of_measurement: "kW" - - unique_id: view_road_power_now_red - name: "Power Now (Red)" - state_topic: "tele/tasmo-wemosd1-7280-powermon-1/SENSOR" - unit_of_measurement: "kW" - value_template: "{{ value_json.ENERGY.Power[0] }}" - - unique_id: view_road_power_now_white - name: "Power Now (White)" - state_topic: "tele/tasmo-wemosd1-7280-powermon-1/SENSOR" - unit_of_measurement: "kW" - value_template: "{{ value_json.ENERGY.Power[1] }}" - - unique_id: view_road_power_now_blue - name: "Power Now (Blue)" - state_topic: "tele/tasmo-wemosd1-7280-powermon-1/SENSOR" - unit_of_measurement: "kW" - value_template: "{{ value_json.ENERGY.Power[2] }}" - -template: - - sensor: - - name: "Electricity Power Total" - unique_id: electricity_power_total - unit_of_measurement: "W" - device_class: power - state_class: measurement - state: > - {{ - states('sensor.tasmo_wemosd1_7280_powermon_1_energy_power_0') | float(0) - + states('sensor.tasmo_wemosd1_7280_powermon_1_energy_power_1') | float(0) - + states('sensor.tasmo_wemosd1_7280_powermon_1_energy_power_2') | float(0) - }} diff --git a/packages/ev_charging.yaml b/packages/ev_charging.yaml deleted file mode 100644 index 208ad77..0000000 --- a/packages/ev_charging.yaml +++ /dev/null @@ -1,105 +0,0 @@ -#mqtt: -# sensor: -# - unique_id: 32a_ev_wallcharger_power -# name: "32A EV Wallcharger Power" -# device_class: power -# unit_of_measurement: kW -# state_topic: "stat/tasmo-wemosd1-7280-powermon-1/EnergyMeterCount" -# value_template: "{{ value_json.EVChargerWhCount * 2 * 60 / 1000 }}" -# - unique_id: 16a_ev_wallcharger_power -# name: "16A EV Wallcharger Power" -# device_class: power -# unit_of_measurement: kW -# state_topic: "stat/tasmo-s4chan-7594-garage-1/EnergyMeterCount" -# value_template: "{{ value_json.EVChargerWhCount * 2 * 60 / 1000 }}" - -# Use the Riemann sum integral to calculate energy in kWh -# from the contimuous power measuring sensors -sensor: - - platform: integration - unique_id: 32a_ev_wallcharger_power_total - name: "EV 32A Wallcharger Power Total (Integral)" - source: sensor.main_house_3_phase_power_monitor_32a_wallcharger_power - #device_class: energy - #unit_of_measurement: Wh - #unit_time: h - #unit_prefix: k # Show in kWh - round: 0 # Only need 0 decimals accuracy - - platform: integration - unique_id: 16a_ev_wallcharger_power_total - name: "EV 16A EV Wallcharger Power Total (Integral)" - source: sensor.garage_db_control_16a_wallcharger_power - #device_class: energy - #unit_of_measurement: Wh - #unit_time: h - #unit_prefix: k - round: 0 - -# - platform: filter -# name: "32A EV Charger Power 1m Avg" -# unique_id: ev_charger_32a_power_1m_avg -# entity_id: sensor.main_house_3_phase_power_monitor_ev_wallcharger_power -# filters: -# - filter: time_simple_moving_average -# window_size: "00:01" -# precision: 3 - -input_select: - leaf_ev_charging_mode: - icon: mdi:ev-plug-type1 - name: Leaf EV Charge Mode - initial: "Normal (Offpeak)" - options: - - "Normal (Offpeak)" - - "Offpeak until charged" - - "1hr top-up now" - - "Charge now until full" - - "Stop charging now" - -automation: - - id: set_the_leaf_ev_charging_mode - alias: Set the Leaf EV Charging Mode - trigger: - entity_id: input_select.leaf_ev_charging_mode - platform: state - action: - service: mqtt.publish - data: - topic: viewroad-status/evcharging/leaf-chargemode - payload: - '{% set mapping = { "Normal (Offpeak)":"normal", "Offpeak until charged":"offpeak-full", - "1hr top-up now":"boost", "Charge now until full":"now-full", "Stop charging now":"stop" } %} {% set dta = trigger.to_state.state %} - {{ mapping[dta] }} - ' -#template: -# - trigger: -# - platform: time_pattern -# seconds: "/10" -# - platform: homeassistant -# event: start -# binary_sensor: -# - name: "32A EV Charger Charging" -# unique_id: ev_charger_32a_charging -# icon: mdi:car-electric -# state: >- -# {{ states('sensor.32a_ev_charger_power_1m_avg') | float(0) > 0.1 }} -# delay_on: -# seconds: 60 -# delay_off: -# seconds: 60 -# -# - name: "16A EV Charger Charging" -# unique_id: ev_charger_16a_charging -# icon: mdi:car-electric -# state: >- -# {{ (states('sensor.16a_ev_wallcharger_power') | float(0)) > 0.1 }} -# delay_on: -# seconds: 60 -# delay_off: -# seconds: 60 -# -# - binary_sensor: -# - name: 32A EV Charger Charging UI -# unique_id: ev_charger_charging_ui -# state: "{{ is_state('sensor.32a_ev_charger_charging', 'on') }}" -# icon: mdi:car-electric diff --git a/packages/free_power_time_actions.yaml b/packages/free_power_time_actions.yaml new file mode 100644 index 0000000..b55ec7a --- /dev/null +++ b/packages/free_power_time_actions.yaml @@ -0,0 +1,299 @@ +############################################################################### +# PACKAGE: Free Power Time Automation +############################################################################### +# VERSION: +# 1.0.0 - 2026-05-08 +# - Initial package. +# - Adds editable free power start/end times. +# - Adds master enable switch and per-action enable switches. +# - Adds announcement, gas-to-heatpump changeover, tool charger control, +# and underhouse dehumidifier control. +# +# NOTES: +# - Free power start/end times can be changed in Home Assistant. +# - Start time default is 9:00pm. +# - End time default is midnight. +# - YAML-only gas heater settings are in script.free_power_gas_heater_changeover: +# gas_heater_shutdown_delay_seconds +# heatpump_setpoint_offset_c +# +# IMPORTANT: +# - input_datetime initial values are used to provide defaults. +# - If you later want UI changes to survive every HA restart, remove the +# initial lines after the helpers have been created and set once. +############################################################################### + +############################################################################### +# INPUT DATETIME +############################################################################### + +input_datetime: + free_power_start_time: + name: Free Power - Start Time + has_date: false + has_time: true + initial: "21:00:00" + icon: mdi:clock-start + + free_power_end_time: + name: Free Power - End Time + has_date: false + has_time: true + initial: "00:00:00" + icon: mdi:clock-end + +############################################################################### +# ENABLE SWITCHES +############################################################################### + +input_boolean: + free_power_automation_enabled: + name: Free Power - Master Automation Enable + icon: mdi:transmission-tower + + free_power_announcement_enabled: + name: Free Power - Announcement Enable + icon: mdi:bullhorn + + free_power_gas_heater_changeover_enabled: + name: Free Power - Gas Heater Changeover Enable + icon: mdi:heat-pump-outline + + free_power_tool_battery_chargers_enabled: + name: Free Power - Tool Battery Chargers Enable + icon: mdi:battery-charging + + free_power_underhouse_dehumidifier_enabled: + name: Free Power - Underhouse Dehumidifier Enable + icon: mdi:air-humidifier + +############################################################################### +# TEMPLATE SENSORS +############################################################################### + +template: + - binary_sensor: + - name: Free Power Time Active + unique_id: free_power_time_active + icon: mdi:transmission-tower + state: >- + {% set start = states('input_datetime.free_power_start_time') %} + {% set end = states('input_datetime.free_power_end_time') %} + {% set now_mins = now().hour * 60 + now().minute %} + {% set start_mins = (start[0:2] | int(0)) * 60 + (start[3:5] | int(0)) %} + {% set end_mins = (end[0:2] | int(0)) * 60 + (end[3:5] | int(0)) %} + + {% if start_mins == end_mins %} + false + {% elif start_mins < end_mins %} + {{ start_mins <= now_mins < end_mins }} + {% else %} + {{ now_mins >= start_mins or now_mins < end_mins }} + {% endif %} + +############################################################################### +# SCRIPTS +############################################################################### + +script: + free_power_start_actions: + alias: Free Power - Start Actions + mode: single + sequence: + - condition: state + entity_id: input_boolean.free_power_automation_enabled + state: "on" + + - variables: + free_power_time_label: >- + {%- set raw_time = states('input_datetime.free_power_start_time') -%} + {%- set hour = raw_time[0:2] | int(0) -%} + {%- set minute = raw_time[3:5] | int(0) -%} + {%- set suffix = 'am' if hour < 12 else 'pm' -%} + {%- set hour12 = hour % 12 -%} + {%- set hour12 = 12 if hour12 == 0 else hour12 -%} + {{- hour12 ~ suffix if minute == 0 else hour12 ~ ':' ~ '%02d' | format(minute) ~ suffix -}} + + ########################################################################### + # 1) Free Power Time Announcement + ########################################################################### + + - if: + - condition: state + entity_id: input_boolean.free_power_announcement_enabled + state: "on" + then: + - action: script.view_road_announcement + data: + channels: "lounge_google_home_voice, sony_tv_lounge" + title: "View Road " + short_announcement: "{{ free_power_time_label }}: Free power time" + long_announcement: "It is {{ free_power_time_label }}." + + ########################################################################### + # 2) Gas Heater Changeover + ########################################################################### + + - if: + - condition: state + entity_id: input_boolean.free_power_gas_heater_changeover_enabled + state: "on" + then: + - action: script.free_power_gas_heater_changeover + + ########################################################################### + # 3) Tool Battery Chargers On + ########################################################################### + + - if: + - condition: state + entity_id: input_boolean.free_power_tool_battery_chargers_enabled + state: "on" + then: + - action: switch.turn_on + target: + entity_id: + - switch.ryobi_charger_left_power_monitor_x17pp + - switch.ryobi_charger_right_power_monitor_x16pp + + ########################################################################### + # 4) Underhouse Dehumidifier On + ########################################################################### + + - if: + - condition: state + entity_id: input_boolean.free_power_underhouse_dehumidifier_enabled + state: "on" + then: + - action: switch.turn_on + target: + entity_id: + - switch.underhouse_dehumidifier_power_plug_x10pp + + free_power_end_actions: + alias: Free Power - End Actions + mode: single + sequence: + - condition: state + entity_id: input_boolean.free_power_automation_enabled + state: "on" + + ########################################################################### + # 3) Tool Battery Chargers Off + # + # If this action is enabled, the chargers are switched off at free power + # end even if something else turned them on. + ########################################################################### + + - if: + - condition: state + entity_id: input_boolean.free_power_tool_battery_chargers_enabled + state: "on" + then: + - action: switch.turn_off + target: + entity_id: + - switch.ryobi_charger_left_power_monitor_x17pp + - switch.ryobi_charger_right_power_monitor_x16pp + + ########################################################################### + # 4) Underhouse Dehumidifier Off + # + # If this action is enabled, the dehumidifier is switched off at free power + # end even if something else turned it on. + ########################################################################### + + - if: + - condition: state + entity_id: input_boolean.free_power_underhouse_dehumidifier_enabled + state: "on" + then: + - action: switch.turn_off + target: + entity_id: + - switch.underhouse_dehumidifier_power_plug_x10pp + + free_power_gas_heater_changeover: + alias: Free Power - Gas Heater Changeover + mode: single + sequence: + ########################################################################### + # YAML-only settings + ########################################################################### + + - variables: + gas_heater_operation_entity: switch.rinnai_neo_gas_heater_heater_operation + gas_heater_setpoint_entity: number.rinnai_neo_gas_heater_rinnai_neo_gas_heater_setpoint + heatpump_climate_entity: climate.lounge + + gas_heater_shutdown_delay_seconds: 60 + heatpump_setpoint_offset_c: 0 + + heatpump_setpoint: >- + {{ + ( + (states(gas_heater_setpoint_entity) | float(0)) + + (heatpump_setpoint_offset_c | float(0)) + ) | round(1) + }} + + ########################################################################### + # Only change over if the gas heater is currently on. + ########################################################################### + + - condition: template + value_template: "{{ is_state(gas_heater_operation_entity, 'on') }}" + + ########################################################################### + # Only continue if the gas setpoint can be read. + ########################################################################### + + - condition: template + value_template: >- + {{ states(gas_heater_setpoint_entity) not in ['unknown', 'unavailable', 'none', 'None', ''] }} + + ########################################################################### + # Set the Panasonic heatpump to heat mode and match the gas heater setpoint, + # with the YAML-only offset applied. + ########################################################################### + + - action: climate.set_temperature + target: + entity_id: climate.lounge + data: + hvac_mode: heat + temperature: "{{ heatpump_setpoint }}" + + ########################################################################### + # Give the heatpump time to start before turning the gas heater off. + ########################################################################### + + - delay: + seconds: "{{ gas_heater_shutdown_delay_seconds | int(60) }}" + + - action: switch.turn_off + target: + entity_id: switch.rinnai_neo_gas_heater_heater_operation + +############################################################################### +# AUTOMATIONS +############################################################################### + +automation: + - id: free_power_time_start_actions + alias: Free Power Time - Start Actions + mode: single + triggers: + - trigger: time + at: input_datetime.free_power_start_time + actions: + - action: script.free_power_start_actions + + - id: free_power_time_end_actions + alias: Free Power Time - End Actions + mode: single + triggers: + - trigger: time + at: input_datetime.free_power_end_time + actions: + - action: script.free_power_end_actions diff --git a/packages/gas_heater_operation.yaml.old2026 b/packages/gas_heater_operation.yaml.old2026 deleted file mode 100644 index 092555e..0000000 --- a/packages/gas_heater_operation.yaml.old2026 +++ /dev/null @@ -1,19 +0,0 @@ -mqtt: - sensor: - - unique_id: gas_heater_operating - name: "Gas Heater Operating" - state_topic: "viewroad-status/gasheater/operating" - switch: - - unique_id: gasheater_switch - name: "Gas Heater" - state_topic: "viewroad-status/gasheater/operating" - command_topic: "viewroad-commands/gasheater/onoff" - #availability: - # - topic: "home/bedroom/switch1/available" - payload_on: "ON" - payload_off: "OFF" - state_on: "ON" - state_off: "OFF" - optimistic: false - qos: 0 - retain: true # need this, or fire starts on HA reboot. Could fix in Node red too. diff --git a/packages/lounge_blind_control.yaml b/packages/lounge_blind_control.yaml new file mode 100644 index 0000000..16eef02 --- /dev/null +++ b/packages/lounge_blind_control.yaml @@ -0,0 +1,368 @@ +############################################################################### +# PACKAGE: Lounge Blinds - Sunrise / Sunset Automation +############################################################################### +# +# VERSION: +# v1.2 - 2026-05-08 +# - Updated input_boolean.lounge_blinds control logic. +# - input_boolean.lounge_blinds ON now closes the blinds. +# - input_boolean.lounge_blinds OFF now opens the blinds. +# - Removed the sync guard helper. +# - Added state checks so cover feedback does not create command loops. +# +# v1.1 - 2026-05-08 +# - Added input_boolean.lounge_blinds as a manual control/status helper. +# - Added cover state sync back to input_boolean.lounge_blinds. +# +# v1.0 - 2026-05-08 +# - Initial package. +# - Opens lounge blinds at sunrise. +# - Closes lounge blinds at sunset. +# - Includes sunrise/sunset minute offsets. +# - Includes repeated command attempts for Zigbee reliability. +# - Opens blinds in list order: A, B, C. +# - Closes blinds in reverse list order: C, B, A. +# +# NOTES: +# - input_boolean.lounge_blinds is treated as: +# ON = blinds closed +# OFF = blinds open +# +# - Turning input_boolean.lounge_blinds ON will close the blinds. +# - Turning input_boolean.lounge_blinds OFF will open the blinds. +# - If all blinds are closed by another method, the boolean will turn ON. +# - If all blinds are opened by another method, the boolean will turn OFF. +# - If blinds are in a mixed/partial state, the boolean is left unchanged. +# +# - The blind entity list is inside: +# script.lounge_blinds_run_sequence +# script.lounge_blinds_sync_status +# automation.lounge_blinds_control_from_input_boolean +# +# - If you add more blinds, update the blind list in those three places. +# +############################################################################### + +input_boolean: + lounge_blinds: + name: Lounge Blinds Closed + icon: mdi:blinds + +input_number: + lounge_blinds_sunrise_offset_minutes: + name: Lounge Blinds Sunrise Offset + icon: mdi:weather-sunset-up + min: -180 + max: 180 + step: 1 + mode: box + unit_of_measurement: min + initial: 0 + + lounge_blinds_sunset_offset_minutes: + name: Lounge Blinds Sunset Offset + icon: mdi:weather-sunset-down + min: -180 + max: 180 + step: 1 + mode: box + unit_of_measurement: min + initial: 0 + + lounge_blinds_per_blind_delay_seconds: + name: Lounge Blinds Per-Blind Delay + icon: mdi:timer-outline + min: 0 + max: 60 + step: 1 + mode: box + unit_of_measurement: sec + initial: 2 + + lounge_blinds_command_repeats: + name: Lounge Blinds Command Repeats + icon: mdi:repeat + min: 1 + max: 5 + step: 1 + mode: box + initial: 2 + + lounge_blinds_retry_delay_seconds: + name: Lounge Blinds Retry Delay + icon: mdi:timer-refresh-outline + min: 0 + max: 120 + step: 1 + mode: box + unit_of_measurement: sec + initial: 10 + +input_datetime: + lounge_blinds_next_open_time: + name: Lounge Blinds Next Open Time + has_date: true + has_time: true + + lounge_blinds_next_close_time: + name: Lounge Blinds Next Close Time + has_date: true + has_time: true + +script: + lounge_blinds_update_sun_times: + alias: Lounge Blinds - Update Sun Times + mode: single + sequence: + - variables: + sunrise_offset_seconds: > + {{ states('input_number.lounge_blinds_sunrise_offset_minutes') | int(0) * 60 }} + sunset_offset_seconds: > + {{ states('input_number.lounge_blinds_sunset_offset_minutes') | int(0) * 60 }} + next_sunrise_timestamp: > + {{ as_timestamp(state_attr('sun.sun', 'next_rising')) + sunrise_offset_seconds }} + next_sunset_timestamp: > + {{ as_timestamp(state_attr('sun.sun', 'next_setting')) + sunset_offset_seconds }} + + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.lounge_blinds_next_open_time + data: + datetime: > + {{ next_sunrise_timestamp | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }} + + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.lounge_blinds_next_close_time + data: + datetime: > + {{ next_sunset_timestamp | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }} + + lounge_blinds_run_sequence: + alias: Lounge Blinds - Run Open/Close Sequence + mode: queued + fields: + blind_action: + name: Blind Action + description: Use open or close. + required: true + selector: + select: + options: + - open + - close + + sequence: + - variables: + blind_list: + - cover.lounge_blind_a_xbl11 + - cover.lounge_blind_b_xbl12 + - cover.lounge_blind_c_xbl13 + + command_repeats: > + {{ states('input_number.lounge_blinds_command_repeats') | int(2) }} + + retry_delay_seconds: > + {{ states('input_number.lounge_blinds_retry_delay_seconds') | int(10) }} + + per_blind_delay_seconds: > + {{ states('input_number.lounge_blinds_per_blind_delay_seconds') | int(2) }} + + - repeat: + count: "{{ command_repeats }}" + sequence: + - choose: + - conditions: + - condition: template + value_template: "{{ blind_action == 'open' }}" + sequence: + - repeat: + for_each: "{{ blind_list }}" + sequence: + - action: cover.open_cover + target: + entity_id: "{{ repeat.item }}" + + - delay: + seconds: "{{ per_blind_delay_seconds }}" + + - conditions: + - condition: template + value_template: "{{ blind_action == 'close' }}" + sequence: + - repeat: + for_each: "{{ blind_list | reverse | list }}" + sequence: + - action: cover.close_cover + target: + entity_id: "{{ repeat.item }}" + + - delay: + seconds: "{{ per_blind_delay_seconds }}" + + - if: + - condition: template + value_template: "{{ repeat.index < command_repeats }}" + then: + - delay: + seconds: "{{ retry_delay_seconds }}" + + - action: script.lounge_blinds_sync_status + + lounge_blinds_sync_status: + alias: Lounge Blinds - Sync Status + mode: queued + sequence: + - variables: + blind_list: + - cover.lounge_blind_a_xbl11 + - cover.lounge_blind_b_xbl12 + - cover.lounge_blind_c_xbl13 + + blind_states: > + {{ expand(blind_list) | map(attribute='state') | list }} + + all_blinds_closed: > + {{ blind_states | select('eq', 'closed') | list | count == blind_list | count }} + + all_blinds_open: > + {{ blind_states | select('eq', 'open') | list | count == blind_list | count }} + + - choose: + - conditions: + - condition: template + value_template: > + {{ all_blinds_closed and is_state('input_boolean.lounge_blinds', 'off') }} + sequence: + - action: input_boolean.turn_on + target: + entity_id: input_boolean.lounge_blinds + + - conditions: + - condition: template + value_template: > + {{ all_blinds_open and is_state('input_boolean.lounge_blinds', 'on') }} + sequence: + - action: input_boolean.turn_off + target: + entity_id: input_boolean.lounge_blinds + +automation: + - id: lounge_blinds_update_sun_times + alias: Lounge Blinds - Update Sun Times + mode: restart + trigger: + - platform: homeassistant + event: start + + - platform: time + at: "00:05:00" + + - platform: state + entity_id: + - input_number.lounge_blinds_sunrise_offset_minutes + - input_number.lounge_blinds_sunset_offset_minutes + + action: + - action: script.lounge_blinds_update_sun_times + + - id: lounge_blinds_open_at_sunrise + alias: Lounge Blinds - Open At Sunrise + mode: single + trigger: + - platform: time + at: input_datetime.lounge_blinds_next_open_time + + action: + - action: script.lounge_blinds_run_sequence + data: + blind_action: open + + - id: lounge_blinds_close_at_sunset + alias: Lounge Blinds - Close At Sunset + mode: single + trigger: + - platform: time + at: input_datetime.lounge_blinds_next_close_time + + action: + - action: script.lounge_blinds_run_sequence + data: + blind_action: close + + - id: lounge_blinds_control_from_input_boolean + alias: Lounge Blinds - Control From Input Boolean + mode: restart + trigger: + - platform: state + entity_id: input_boolean.lounge_blinds + to: "on" + + - platform: state + entity_id: input_boolean.lounge_blinds + to: "off" + + action: + - variables: + blind_list: + - cover.lounge_blind_a_xbl11 + - cover.lounge_blind_b_xbl12 + - cover.lounge_blind_c_xbl13 + + blind_states: > + {{ expand(blind_list) | map(attribute='state') | list }} + + all_blinds_closed: > + {{ blind_states | select('eq', 'closed') | list | count == blind_list | count }} + + all_blinds_open: > + {{ blind_states | select('eq', 'open') | list | count == blind_list | count }} + + - choose: + - conditions: + - condition: state + entity_id: input_boolean.lounge_blinds + state: "on" + + - condition: template + value_template: "{{ not all_blinds_closed }}" + sequence: + - action: script.lounge_blinds_run_sequence + data: + blind_action: close + + - conditions: + - condition: state + entity_id: input_boolean.lounge_blinds + state: "off" + + - condition: template + value_template: "{{ not all_blinds_open }}" + sequence: + - action: script.lounge_blinds_run_sequence + data: + blind_action: open + + - id: lounge_blinds_sync_input_boolean_from_cover_states + alias: Lounge Blinds - Sync Input Boolean From Cover States + mode: restart + trigger: + - platform: homeassistant + event: start + + - platform: state + entity_id: + - cover.lounge_blind_a_xbl11 + - cover.lounge_blind_b_xbl12 + - cover.lounge_blind_c_xbl13 + to: "open" + + - platform: state + entity_id: + - cover.lounge_blind_a_xbl11 + - cover.lounge_blind_b_xbl12 + - cover.lounge_blind_c_xbl13 + to: "closed" + + action: + - action: script.lounge_blinds_sync_status diff --git a/packages/lounge_lighting_scenes.yaml b/packages/lounge_lighting_scenes.yaml index 2e06baa..38f843e 100644 --- a/packages/lounge_lighting_scenes.yaml +++ b/packages/lounge_lighting_scenes.yaml @@ -46,8 +46,10 @@ input_select: - "Select Scene:" - Bright - TV General + - Balanced - Movie - Reading On/Off + - Dining On/Off - Night Light - Warm Cozy - All Off @@ -106,6 +108,7 @@ script: excluded_scenes: - "Select Scene:" - "Reading On/Off" + - "Dining On/Off" #- "Night Light" ##################################################################### @@ -120,18 +123,20 @@ script: light.esp_lounge6chdimmer_lounge_downlights_centre: 100 light.esp_lounge6chdimmer_lounge_downlights_south: 100 light.esp_lounge6chdimmer_lounge_rafter_buttons: 100 - switch.esp_loungemiddleswitch_relay_3_downlights_north: "on" + #switch.esp_loungemiddleswitch_relay_3_downlights_north: "on" + switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "on" # non dimmable pair light.esp_breakfastbarleds_breakfast_bar_leds: 90 light.esp_loungecabinetleds2_lounge_cabinet_leds: 90 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 90 switch.lounge_cupboard_dual_usb_xus09_l2: "on" switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "on" switch.esp_loungesouthrightswitch_relay_2_wall_washer_right: "on" - switch.tasmo_ks811t_0702_lounge_3c: "on" + #switch.tasmo_ks811t_0702_lounge_3c: "on" #switch.esp_loungenorthswitch_relay_1_main_lights: "on" number.esp_loungepelmetleds2_lounge_pelmet_leds_a_output_set_0_100: 100 number.esp_loungepelmetleds2_lounge_pelmet_leds_b_output_set_0_100: 100 input_number.openhasp_1_backlight_level: 255 + number.lounge_nspanel_touchscreen_display_brightness: 100 TV General: light.esp_lounge6chdimmer_couch_spots_left: 60 @@ -143,7 +148,8 @@ script: light.esp_breakfastbarleds_breakfast_bar_leds: 60 light.esp_loungecabinetleds2_lounge_cabinet_leds: 60 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 60 - switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" # non-dimmable pair + #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" # non-dimmable pair + switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair switch.lounge_cupboard_dual_usb_xus09_l2: "on" switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "off" switch.esp_loungesouthrightswitch_relay_2_wall_washer_right: "off" @@ -151,6 +157,28 @@ script: number.esp_loungepelmetleds2_lounge_pelmet_leds_a_output_set_0_100: 60 number.esp_loungepelmetleds2_lounge_pelmet_leds_b_output_set_0_100: 60 input_number.openhasp_1_backlight_level: 120 + number.lounge_nspanel_touchscreen_display_brightness: 80 + + Balanced: + light.esp_lounge6chdimmer_couch_spots_left: 40 + light.esp_lounge6chdimmer_couch_spots_right: 40 + #light.esp_lounge6chdimmer_dining_table_light: 100 + light.esp_lounge6chdimmer_lounge_downlights_centre: 40 + light.esp_lounge6chdimmer_lounge_downlights_south: 40 + light.esp_lounge6chdimmer_lounge_rafter_buttons: 30 + light.esp_breakfastbarleds_breakfast_bar_leds: 50 + light.esp_loungecabinetleds2_lounge_cabinet_leds: 50 + light.esp_loungebookshelfleds_lounge_bookshelf_leds: 50 + #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" # non-dimmable pair + switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair + switch.lounge_cupboard_dual_usb_xus09_l2: "on" + switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "off" + switch.esp_loungesouthrightswitch_relay_2_wall_washer_right: "off" + #switch.esp_loungenorthswitch_relay_1_main_lights: "on" + number.esp_loungepelmetleds2_lounge_pelmet_leds_a_output_set_0_100: 50 + number.esp_loungepelmetleds2_lounge_pelmet_leds_b_output_set_0_100: 50 + input_number.openhasp_1_backlight_level: 100 + number.lounge_nspanel_touchscreen_display_brightness: 80 Movie: light.esp_lounge6chdimmer_couch_spots_left: 20 @@ -162,7 +190,8 @@ script: light.esp_breakfastbarleds_breakfast_bar_leds: 40 light.esp_loungecabinetleds2_lounge_cabinet_leds: 40 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 40 - switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" + #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" + switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair switch.lounge_cupboard_dual_usb_xus09_l2: "on" switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "off" switch.esp_loungesouthrightswitch_relay_2_wall_washer_right: "off" @@ -170,6 +199,17 @@ script: number.esp_loungepelmetleds2_lounge_pelmet_leds_a_output_set_0_100: 40 number.esp_loungepelmetleds2_lounge_pelmet_leds_b_output_set_0_100: 40 input_number.openhasp_1_backlight_level: 15 + switch.esp_mainkitchenlights_relay_1_main_lights: "off" + switch.esp_mainkitchenlights_relay_2_benchtop_lights: "off" + number.lounge_nspanel_touchscreen_display_brightness: 40 + + Dining On/Off: + light.esp_lounge6chdimmer_dining_table_light: "toggle_100" + #light.esp_lounge6chdimmer_dining_table_light: 60 + #light.esp_lounge6chdimmer_lounge_downlights_centre: 45 + #light.esp_lounge6chdimmer_lounge_downlights_south: 35 + #light.esp_lounge6chdimmer_lounge_rafter_buttons: 20 + #switch.tasmo_ks811t_0702_lounge_3c: "on" Reading On/Off: light.esp_lounge6chdimmer_couch_spots_left: "toggle_75" @@ -190,14 +230,16 @@ script: light.esp_breakfastbarleds_breakfast_bar_leds: 5 light.esp_loungecabinetleds2_lounge_cabinet_leds: 5 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 5 - switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" + #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" + switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair switch.lounge_cupboard_dual_usb_xus09_l2: "off" switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "off" switch.esp_loungesouthrightswitch_relay_2_wall_washer_right: "off" #switch.esp_loungenorthswitch_relay_1_main_lights: "on" number.esp_loungepelmetleds2_lounge_pelmet_leds_a_output_set_0_100: 5 number.esp_loungepelmetleds2_lounge_pelmet_leds_b_output_set_0_100: 5 - input_number.openhasp_1_backlight_level: 8 + input_number.openhasp_1_backlight_level: 10 + number.lounge_nspanel_touchscreen_display_brightness: 30 Warm Cozy: light.esp_lounge6chdimmer_couch_spots_left: 20 @@ -206,8 +248,9 @@ script: light.esp_breakfastbarleds_breakfast_bar_leds: 25 light.esp_loungecabinetleds2_lounge_cabinet_leds: 30 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 30 - light.tasmo_h801_loungeled1_6180_a: 25 - light.tasmo_h801_loungeled1_6180_b: 25 + #light.tasmo_h801_loungeled1_6180_a: 25 + #light.tasmo_h801_loungeled1_6180_b: 25 + switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair switch.lounge_cupboard_dual_usb_xus09_l2: "on" switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "on" switch.esp_loungesouthrightswitch_relay_2_wall_washer_right: "on" @@ -215,6 +258,7 @@ script: number.esp_loungepelmetleds2_lounge_pelmet_leds_a_output_set_0_100: 20 number.esp_loungepelmetleds2_lounge_pelmet_leds_b_output_set_0_100: 20 input_number.openhasp_1_backlight_level: 12 + number.lounge_nspanel_touchscreen_display_brightness: 40 All Off: light.esp_lounge6chdimmer_couch_spots_left: 0 @@ -223,7 +267,8 @@ script: light.esp_lounge6chdimmer_lounge_downlights_centre: 0 light.esp_lounge6chdimmer_lounge_downlights_south: 0 light.esp_lounge6chdimmer_lounge_rafter_buttons: 0 - switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" + #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" + switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair light.esp_breakfastbarleds_breakfast_bar_leds: 0 light.esp_loungecabinetleds2_lounge_cabinet_leds: 0 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 0 @@ -234,6 +279,9 @@ script: number.esp_loungepelmetleds2_lounge_pelmet_leds_a_output_set_0_100: 0 number.esp_loungepelmetleds2_lounge_pelmet_leds_b_output_set_0_100: 0 #input_number.openhasp_1_backlight_level: 100 + #switch.esp_mainkitchenlights_relay_1_main_lights: "off" + #switch.esp_mainkitchenlights_relay_2_benchtop_lights: "off" + number.lounge_nspanel_touchscreen_display_brightness: 80 ######################################################################### # REMEMBER LAST SCENE diff --git a/packages/ms_teams_status.yaml.old b/packages/ms_teams_status.yaml.old deleted file mode 100644 index 4c3b4ba..0000000 --- a/packages/ms_teams_status.yaml.old +++ /dev/null @@ -1,20 +0,0 @@ -input_text: - teams_status: - name: Microsoft Teams status - icon: mdi:microsoft-teams - teams_activity: - name: Microsoft Teams activity - icon: mdi:phone-off - -sensor: - - platform: template - sensors: - teams_status: - friendly_name: "Microsoft Teams status" - value_template: "{{states('input_text.teams_status')}}" - icon_template: "{{state_attr('input_text.teams_status','icon')}}" - unique_id: sensor.teams_status - teams_activity: - friendly_name: "Microsoft Teams activity" - value_template: "{{states('input_text.teams_activity')}}" - unique_id: sensor.teams_activity diff --git a/packages/nspanel_keepawake.yaml b/packages/nspanel_keepawake.yaml deleted file mode 100644 index 7770edc..0000000 --- a/packages/nspanel_keepawake.yaml +++ /dev/null @@ -1,23 +0,0 @@ -template: - - light: - - name: "Inverted Quiet time as light" - unique_id: inverted_quiet_time_as_light - state: "{{ is_state('input_boolean.quiet_time', 'off') }}" - turn_on: - - service: light.turn_on - target: - entity_id: light.inverted_quiet_time_light - turn_off: - - service: light.turn_off - target: - entity_id: light.inverted_quiet_time_light - -light: - - platform: group - name: "Entities to keep NSPanel Awake" - unique_id: entities_to_keep_nspanel_awake - entities: - - light.inverted_quiet_time_as_light_2 - - light.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb - - light.tasmo_ifan02_3793_bedrm1_1 - - light.tasmo_ks811t_3647_bedrm1_1b diff --git a/packages/nspanel_lounge_controller.yaml b/packages/nspanel_lounge_controller.yaml new file mode 100644 index 0000000..2bde278 --- /dev/null +++ b/packages/nspanel_lounge_controller.yaml @@ -0,0 +1,732 @@ +# yaml-language-server: $schema=null +#:########################################################################################:# +# TITLE: NSPANEL LOUNGE CONTROLLER +# zorruno.com Home Assistant package +#:########################################################################################:# +# VERSIONS: +# V1.5 2026-05-19 +# - Added Reading button brightness display. +# - Reading brightness is shown as the highest brightness percentage of either +# couch spot light. +# - Changed Balanced button icon to mdi:scale / \uE472. +# - Added startup panel page refresh before full button state refresh. +# +# V1.4 2026-05-19 +# - Changed button highlighting to targeted updates. +# - Remembered scene changes update only the old scene button and new scene button. +# - Reading/Dining changes update only the Reading and Dining buttons. +# - Full refresh is only used at HA start or when manually called. +# - ON icon colour is black [0, 0, 0]. +# - OFF icon colour is white [255, 255, 255]. +# +# V1.3 2026-05-19 +# - Tested page refresh method, but it did not reliably refresh remembered scenes. +# +# V1.2 2026-05-19 +# - Added explicit NSPanel button refresh actions using esphome.esp_nspanel2lounge_button. +# - Button states were pushed to the NSPanel when lounge_last_scene or the +# Reading/Dining light states changed. +# +# V1.1 2026-05-18 +# - Added template switches so remembered lighting scenes can stay highlighted +# on the NSPanel button page. +# - Updated Blueprint button entities from script.* to switch.*. +# +# V1.0 2026-05-18 +# - Initial NSPanel Lounge Controller package using NSPanel Easy Blueprint. +#:########################################################################################:# +# PURPOSE: +# - Configures the Lounge NSPanel Easy button page. +# - Provides Home Assistant script wrappers for lounge lighting scene actions. +# - Provides template switches for NSPanel button state/highlighting. +# - Pushes targeted NSPanel button highlight updates when relevant states change. +#:########################################################################################:# +# OPERATION NOTES: +# - The NSPanel display is configured by the NSPanel Easy Blueprint automation. +# - The screen buttons point to switch.nspanel_lounge_scene_* entities. +# - Remembered scenes are highlighted from input_text.lounge_last_scene. +# - Reading On/Off is highlighted from either couch spot light being on. +# - Reading On/Off shows the highest couch spot brightness percentage. +# - Dining On/Off is highlighted from the dining table light being on. +# - Pressing any NSPanel touch button activates the assigned scene/action. +# - Normal remembered scene transitions update only: +# a) the previous remembered scene button to OFF +# b) the new remembered scene button to ON +#:########################################################################################:# +# DEPENDENCIES: +# - script.lounge_set_scene +# - input_text.lounge_last_scene +# - light.esp_lounge6chdimmer_couch_spots_left +# - light.esp_lounge6chdimmer_couch_spots_right +# - light.esp_lounge6chdimmer_dining_table_light +# - NSPanel Easy Blueprint: +# edwardtfn/nspanel_easy_blueprint.yaml +# - ESPHome actions: +# esphome.esp_nspanel2lounge_button +# esphome.esp_nspanel2lounge_command +#:########################################################################################:# +# NOTES: +# - This is Home Assistant package YAML, not ESPHome YAML. +# - The nspanel_name value is the Home Assistant device ID selected by the +# Blueprint dropdown. Do not replace it with an entity_id or ESPHome hostname. +# - The icon values used by esphome.esp_nspanel2lounge_button are HASP/Nextion +# icon codepoints, not mdi: icon names. +#:########################################################################################:# + +#:########################################################################################:# +# AUTOMATIONS +#:########################################################################################:# + +automation: + #:######################################################################################:# + # AUTOMATION: NSPanel Easy Blueprint Instance + #:######################################################################################:# + - id: nspanel_lounge_controller + alias: "NSPanel Lounge Controller" + description: "NSPanel Easy configuration for the lounge lighting scene panel." + use_blueprint: + path: edwardtfn/nspanel_easy_blueprint.yaml + input: + nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f + + ######################################################################## + # BUTTON PAGE 01: Lounge Lighting Scenes + ######################################################################## + button_page01_label: Lounge Lighting Scenes + button_pages_icon_size: "10" + + entity01: switch.nspanel_lounge_scene_bright + entity01_name: "Bright" + entity01_icon: mdi:brightness-7 + entity01_confirm: false + + entity02: switch.nspanel_lounge_scene_tv_general + entity02_name: "TV General" + entity02_icon: mdi:television + entity02_confirm: false + + entity03: switch.nspanel_lounge_scene_movie + entity03_name: "Movie" + entity03_icon: mdi:movie-open + entity03_confirm: false + + entity04: switch.nspanel_lounge_scene_balanced + entity04_name: "Balance" + entity04_icon: mdi:scale + entity04_confirm: false + + entity05: switch.nspanel_lounge_scene_warm_cozy + entity05_name: "Warm Cozy" + entity05_icon: mdi:fireplace + entity05_confirm: false + + entity06: switch.nspanel_lounge_scene_reading + entity06_name: "Reading On/Off" + entity06_icon: mdi:book-open-page-variant + entity06_confirm: false + + entity07: switch.nspanel_lounge_scene_dining + entity07_name: "Dining On/Off" + entity07_icon: mdi:silverware-fork-knife + entity07_confirm: false + + entity08: switch.nspanel_lounge_scene_all_off + entity08_name: "All Off" + entity08_icon: mdi:power + entity08_confirm: false + mode: single + + #:######################################################################################:# + # AUTOMATION: Refresh Remembered Scene Button Highlights + # + # Only updates the previous remembered scene button and the new remembered + # scene button when input_text.lounge_last_scene changes. + #:######################################################################################:# + - id: nspanel_lounge_refresh_remembered_scene_buttons + alias: "NSPanel Lounge - Refresh Remembered Scene Buttons" + description: "Update only the old and new remembered scene buttons on the NSPanel." + mode: restart + trigger: + - platform: state + entity_id: input_text.lounge_last_scene + + action: + - delay: + milliseconds: 500 + + - variables: + old_scene: "{{ trigger.from_state.state if trigger.from_state is not none else '' }}" + new_scene: "{{ trigger.to_state.state if trigger.to_state is not none else states('input_text.lounge_last_scene') }}" + remembered_scenes: + - "Bright" + - "TV General" + - "Balanced" + - "Movie" + - "Warm Cozy" + - "All Off" + + - choose: + - conditions: "{{ old_scene in remembered_scenes and old_scene != new_scene }}" + sequence: + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "{{ old_scene }}" + button_state: false + + - delay: + milliseconds: 100 + + - choose: + - conditions: "{{ new_scene in remembered_scenes }}" + sequence: + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "{{ new_scene }}" + button_state: true + + #:######################################################################################:# + # AUTOMATION: Refresh Reading And Dining Button Highlights + # + # Reading/Dining are toggle/action scenes, so they are updated from the real + # light states rather than input_text.lounge_last_scene. + #:######################################################################################:# + - id: nspanel_lounge_refresh_toggle_scene_buttons + alias: "NSPanel Lounge - Refresh Toggle Scene Buttons" + description: "Update Reading and Dining buttons when their lights change." + mode: restart + trigger: + - platform: state + entity_id: + - light.esp_lounge6chdimmer_couch_spots_left + - light.esp_lounge6chdimmer_couch_spots_right + - light.esp_lounge6chdimmer_dining_table_light + + - platform: state + entity_id: + - light.esp_lounge6chdimmer_couch_spots_left + - light.esp_lounge6chdimmer_couch_spots_right + - light.esp_lounge6chdimmer_dining_table_light + attribute: brightness + + action: + - delay: + milliseconds: 500 + + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "Reading On/Off" + button_state: >- + {{ + ( + is_state('light.esp_lounge6chdimmer_couch_spots_left', 'on') + and (state_attr('light.esp_lounge6chdimmer_couch_spots_left', 'brightness') | int(0)) > 0 + ) + or + ( + is_state('light.esp_lounge6chdimmer_couch_spots_right', 'on') + and (state_attr('light.esp_lounge6chdimmer_couch_spots_right', 'brightness') | int(0)) > 0 + ) + }} + + - delay: + milliseconds: 100 + + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "Dining On/Off" + button_state: >- + {{ + is_state('light.esp_lounge6chdimmer_dining_table_light', 'on') + and (state_attr('light.esp_lounge6chdimmer_dining_table_light', 'brightness') | int(0)) > 0 + }} + + #:######################################################################################:# + # AUTOMATION: Initial NSPanel Button Refresh + # + # Full refresh happens when HA starts, or when the NSPanel comes back online. + # Normal scene changes update only the required buttons. + #:######################################################################################:# + - id: nspanel_lounge_refresh_all_buttons_on_start + alias: "NSPanel Lounge - Refresh All Buttons On Start" + description: "Refresh all NSPanel lounge buttons when HA starts or the panel reconnects." + mode: restart + trigger: + - platform: homeassistant + event: start + + - platform: state + entity_id: number.lounge_nspanel_touchscreen_display_brightness + from: "unavailable" + + - platform: state + entity_id: number.lounge_nspanel_touchscreen_display_brightness + from: "unknown" + + action: + - delay: + seconds: 20 + + - action: esphome.esp_nspanel2lounge_command + data: + cmd: "page buttonpage01" + + - delay: + seconds: 1 + + - action: script.nspanel_lounge_refresh_all_button_states + +#:########################################################################################:# +# SCRIPTS +#:########################################################################################:# + +script: + #:######################################################################################:# + # SCRIPTS: NSPanel Lounge Scene Button Actions + #:######################################################################################:# + nspanel_lounge_scene_bright: + alias: "NSPanel Lounge Scene - Bright" + mode: single + sequence: + - action: script.lounge_set_scene + data: + scene_name: "Bright" + + nspanel_lounge_scene_tv_general: + alias: "NSPanel Lounge Scene - TV General" + mode: single + sequence: + - action: script.lounge_set_scene + data: + scene_name: "TV General" + + nspanel_lounge_scene_movie: + alias: "NSPanel Lounge Scene - Movie" + mode: single + sequence: + - action: script.lounge_set_scene + data: + scene_name: "Movie" + + nspanel_lounge_scene_balanced: + alias: "NSPanel Lounge Scene - Balanced" + mode: single + sequence: + - action: script.lounge_set_scene + data: + scene_name: "Balanced" + + nspanel_lounge_scene_warm_cozy: + alias: "NSPanel Lounge Scene - Warm Cozy" + mode: single + sequence: + - action: script.lounge_set_scene + data: + scene_name: "Warm Cozy" + + nspanel_lounge_scene_reading: + alias: "NSPanel Lounge Scene - Reading" + mode: single + sequence: + - action: script.lounge_set_scene + data: + scene_name: "Reading On/Off" + + nspanel_lounge_scene_dining: + alias: "NSPanel Lounge Scene - Dining" + mode: single + sequence: + - action: script.lounge_set_scene + data: + scene_name: "Dining On/Off" + + nspanel_lounge_scene_all_off: + alias: "NSPanel Lounge Scene - All Off" + mode: single + sequence: + - action: script.lounge_set_scene + data: + scene_name: "All Off" + + #:######################################################################################:# + # SCRIPT: Push One NSPanel Button State + # + # ON state: + # - Button highlighted + # - Icon colour black [0, 0, 0] + # + # OFF state: + # - Button not highlighted + # - Icon colour white [255, 255, 255] + #:######################################################################################:# + nspanel_lounge_push_button_state: + alias: "NSPanel Lounge - Push Button State" + mode: queued + max: 20 + fields: + button_id: + description: "NSPanel button id, for example button01" + button_state: + description: "True for highlighted, false for normal" + icon_code: + description: "HASwitchPlate/Nextion icon codepoint" + button_label: + description: "Button label text" + button_bri: + description: "Optional brightness/value text shown on the NSPanel button" + sequence: + - variables: + button_is_on: >- + {{ + button_state in [true, 'true', 'True', 'on', 'On', '1', 1] + }} + button_bri_text: "{{ button_bri if button_bri is defined else '' }}" + + - choose: + - conditions: "{{ button_is_on }}" + sequence: + - action: esphome.esp_nspanel2lounge_button + data: + page: buttonpage01 + id: "{{ button_id }}" + state: true + icon: "{{ icon_code }}" + icon_color: + - 0 + - 0 + - 0 + icon_font: 10 + bri: "{{ button_bri_text }}" + label: "{{ button_label }}" + is_page_render: false + + default: + - action: esphome.esp_nspanel2lounge_button + data: + page: buttonpage01 + id: "{{ button_id }}" + state: false + icon: "{{ icon_code }}" + icon_color: + - 255 + - 255 + - 255 + icon_font: 10 + bri: "{{ button_bri_text }}" + label: "{{ button_label }}" + is_page_render: false + + #:######################################################################################:# + # SCRIPT: Push One Scene Button State + # + # Maps scene/action names to NSPanel button ids, labels and icon codepoints. + #:######################################################################################:# + nspanel_lounge_push_scene_button_state: + alias: "NSPanel Lounge - Push Scene Button State" + mode: queued + max: 20 + fields: + scene_name: + description: "Lounge scene/action name" + button_state: + description: "True for highlighted, false for normal" + sequence: + - choose: + - conditions: "{{ scene_name == 'Bright' }}" + sequence: + - action: script.nspanel_lounge_push_button_state + data: + button_id: button01 + button_state: "{{ button_state }}" + icon_code: "\uE0E0" + button_label: "Bright" + button_bri: "" + + - conditions: "{{ scene_name == 'TV General' }}" + sequence: + - action: script.nspanel_lounge_push_button_state + data: + button_id: button02 + button_state: "{{ button_state }}" + icon_code: "\uE502" + button_label: "TV General" + button_bri: "" + + - conditions: "{{ scene_name == 'Movie' }}" + sequence: + - action: script.nspanel_lounge_push_button_state + data: + button_id: button03 + button_state: "{{ button_state }}" + icon_code: "\uEFCE" + button_label: "Movie" + button_bri: "" + + - conditions: "{{ scene_name == 'Balanced' }}" + sequence: + - action: script.nspanel_lounge_push_button_state + data: + button_id: button04 + button_state: "{{ button_state }}" + icon_code: "\uE472" + button_label: "Balance" + button_bri: "" + + - conditions: "{{ scene_name == 'Warm Cozy' }}" + sequence: + - action: script.nspanel_lounge_push_button_state + data: + button_id: button05 + button_state: "{{ button_state }}" + icon_code: "\uEE2E" + button_label: "Warm Cozy" + button_bri: "" + + - conditions: "{{ scene_name == 'Reading On/Off' }}" + sequence: + - action: script.nspanel_lounge_push_button_state + data: + button_id: button06 + button_state: "{{ button_state }}" + icon_code: "\uE5DA" + button_label: "Reading On/Off" + button_bri: >- + {% set left_on = is_state('light.esp_lounge6chdimmer_couch_spots_left', 'on') %} + {% set right_on = is_state('light.esp_lounge6chdimmer_couch_spots_right', 'on') %} + {% set left_raw = state_attr('light.esp_lounge6chdimmer_couch_spots_left', 'brightness') | int(0) if left_on else 0 %} + {% set right_raw = state_attr('light.esp_lounge6chdimmer_couch_spots_right', 'brightness') | int(0) if right_on else 0 %} + {% set left_pct = ((left_raw / 255) * 100) | round(0) | int %} + {% set right_pct = ((right_raw / 255) * 100) | round(0) | int %} + {% set reading_pct = [left_pct, right_pct] | max %} + {% if reading_pct > 0 %}{{ reading_pct }}%{% else %}{% endif %} + + - conditions: "{{ scene_name == 'Dining On/Off' }}" + sequence: + - action: script.nspanel_lounge_push_button_state + data: + button_id: button07 + button_state: "{{ button_state }}" + icon_code: "\uEA70" + button_label: "Dining On/Off" + button_bri: "" + + - conditions: "{{ scene_name == 'All Off' }}" + sequence: + - action: script.nspanel_lounge_push_button_state + data: + button_id: button08 + button_state: "{{ button_state }}" + icon_code: "\uE425" + button_label: "All Off" + button_bri: "" + + #:######################################################################################:# + # SCRIPT: Refresh All NSPanel Lounge Button States + # + # Use this manually after a panel restart, or automatically once at HA start. + # Normal scene changes do not call this script. + #:######################################################################################:# + nspanel_lounge_refresh_all_button_states: + alias: "NSPanel Lounge - Refresh All Button States" + mode: restart + sequence: + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "Bright" + button_state: "{{ is_state('switch.nspanel_lounge_scene_bright', 'on') }}" + + - delay: + milliseconds: 100 + + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "TV General" + button_state: "{{ is_state('switch.nspanel_lounge_scene_tv_general', 'on') }}" + + - delay: + milliseconds: 100 + + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "Movie" + button_state: "{{ is_state('switch.nspanel_lounge_scene_movie', 'on') }}" + + - delay: + milliseconds: 100 + + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "Balanced" + button_state: "{{ is_state('switch.nspanel_lounge_scene_balanced', 'on') }}" + + - delay: + milliseconds: 100 + + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "Warm Cozy" + button_state: "{{ is_state('switch.nspanel_lounge_scene_warm_cozy', 'on') }}" + + - delay: + milliseconds: 100 + + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "Reading On/Off" + button_state: "{{ is_state('switch.nspanel_lounge_scene_reading', 'on') }}" + + - delay: + milliseconds: 100 + + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "Dining On/Off" + button_state: "{{ is_state('switch.nspanel_lounge_scene_dining', 'on') }}" + + - delay: + milliseconds: 100 + + - action: script.nspanel_lounge_push_scene_button_state + data: + scene_name: "All Off" + button_state: "{{ is_state('switch.nspanel_lounge_scene_all_off', 'on') }}" + + #:######################################################################################:# + # SCRIPT: Compatibility Wrapper + # + # Older versions used this script name. Keep it as a manual full refresh. + #:######################################################################################:# + nspanel_lounge_refresh_button_states: + alias: "NSPanel Lounge - Refresh Button States" + mode: restart + sequence: + - action: script.nspanel_lounge_refresh_all_button_states + +#:########################################################################################:# +# TEMPLATE SWITCHES: NSPANEL BUTTON STATE AND HIGHLIGHTING +#:########################################################################################:# + +template: + - switch: + ########################################################################## + # Remembered scenes + # These switches stay on when input_text.lounge_last_scene matches the + # last remembered scene name. This allows the NSPanel button to remain + # highlighted for the selected remembered scene. + ########################################################################## + + - name: "NSPanel Lounge Scene Bright" + unique_id: nspanel_lounge_scene_switch_bright + state: "{{ is_state('input_text.lounge_last_scene', 'Bright') }}" + turn_on: + - action: script.lounge_set_scene + data: + scene_name: "Bright" + turn_off: + - action: script.lounge_set_scene + data: + scene_name: "Bright" + + - name: "NSPanel Lounge Scene TV General" + unique_id: nspanel_lounge_scene_switch_tv_general + state: "{{ is_state('input_text.lounge_last_scene', 'TV General') }}" + turn_on: + - action: script.lounge_set_scene + data: + scene_name: "TV General" + turn_off: + - action: script.lounge_set_scene + data: + scene_name: "TV General" + + - name: "NSPanel Lounge Scene Balanced" + unique_id: nspanel_lounge_scene_switch_balanced + state: "{{ is_state('input_text.lounge_last_scene', 'Balanced') }}" + turn_on: + - action: script.lounge_set_scene + data: + scene_name: "Balanced" + turn_off: + - action: script.lounge_set_scene + data: + scene_name: "Balanced" + + - name: "NSPanel Lounge Scene Movie" + unique_id: nspanel_lounge_scene_switch_movie + state: "{{ is_state('input_text.lounge_last_scene', 'Movie') }}" + turn_on: + - action: script.lounge_set_scene + data: + scene_name: "Movie" + turn_off: + - action: script.lounge_set_scene + data: + scene_name: "Movie" + + - name: "NSPanel Lounge Scene Warm Cozy" + unique_id: nspanel_lounge_scene_switch_warm_cozy + state: "{{ is_state('input_text.lounge_last_scene', 'Warm Cozy') }}" + turn_on: + - action: script.lounge_set_scene + data: + scene_name: "Warm Cozy" + turn_off: + - action: script.lounge_set_scene + data: + scene_name: "Warm Cozy" + + - name: "NSPanel Lounge Scene All Off" + unique_id: nspanel_lounge_scene_switch_all_off + state: "{{ is_state('input_text.lounge_last_scene', 'All Off') }}" + turn_on: + - action: script.lounge_set_scene + data: + scene_name: "All Off" + turn_off: + - action: script.lounge_set_scene + data: + scene_name: "All Off" + + ########################################################################## + # Toggle/action scenes + # These are excluded from input_text.lounge_last_scene, so their displayed + # state comes from the actual light entities they control. + ########################################################################## + + - name: "NSPanel Lounge Scene Reading" + unique_id: nspanel_lounge_scene_switch_reading + state: >- + {{ + ( + is_state('light.esp_lounge6chdimmer_couch_spots_left', 'on') + and (state_attr('light.esp_lounge6chdimmer_couch_spots_left', 'brightness') | int(0)) > 0 + ) + or + ( + is_state('light.esp_lounge6chdimmer_couch_spots_right', 'on') + and (state_attr('light.esp_lounge6chdimmer_couch_spots_right', 'brightness') | int(0)) > 0 + ) + }} + turn_on: + - action: script.lounge_set_scene + data: + scene_name: "Reading On/Off" + turn_off: + - action: script.lounge_set_scene + data: + scene_name: "Reading On/Off" + + - name: "NSPanel Lounge Scene Dining" + unique_id: nspanel_lounge_scene_switch_dining + state: >- + {{ + is_state('light.esp_lounge6chdimmer_dining_table_light', 'on') + and (state_attr('light.esp_lounge6chdimmer_dining_table_light', 'brightness') | int(0)) > 0 + }} + turn_on: + - action: script.lounge_set_scene + data: + scene_name: "Dining On/Off" + turn_off: + - action: script.lounge_set_scene + data: + scene_name: "Dining On/Off" diff --git a/packages/nspanel_masterbdrm_controller.yaml.old b/packages/nspanel_masterbdrm_controller.yaml.old new file mode 100644 index 0000000..4e61afa --- /dev/null +++ b/packages/nspanel_masterbdrm_controller.yaml.old @@ -0,0 +1,52 @@ +automation: + - id: nspanel_lounge_controller + alias: "NSPanel Master Bedroom Controller" + description: "" + use_blueprint: + path: edwardtfn/nspanel_easy_blueprint.yaml + input: + nspanel_name: 084fb5b4a94e636d01244b1838156446 + + button_page01_label: Lounge Lighting Scenes + button_pages_icon_size: "10" + + entity01: script.nspanel_lounge_scene_bright + entity01_name: "Bright" + entity01_icon: mdi:brightness-7 + entity01_confirm: false + + entity02: script.nspanel_lounge_scene_tv_general + entity02_name: "TV General" + entity02_icon: mdi:television + entity02_confirm: false + + entity03: script.nspanel_lounge_scene_movie + entity03_name: "Movie" + entity03_icon: mdi:movie-open + entity03_confirm: false + + entity04: script.nspanel_lounge_scene_balanced + entity04_name: "Balance" + entity04_icon: mdi:scale-balance + entity04_confirm: false + + entity05: script.nspanel_lounge_scene_warm_cozy + entity05_name: "Warm Cozy" + entity05_icon: mdi:fireplace + entity05_confirm: false + + entity06: script.nspanel_lounge_scene_reading + entity06_name: "Reading On/Off" + entity06_icon: mdi:book-open-page-variant + entity06_confirm: false + + entity07: script.nspanel_lounge_scene_dining + entity07_name: "Dining On/Off" + entity07_icon: mdi:silverware-fork-knife + entity07_confirm: false + + entity08: script.nspanel_lounge_scene_all_off + entity08_name: "All Off" + entity08_icon: mdi:power + entity08_confirm: false + mode: single diff --git a/packages/outdoor_lighting_control.yaml b/packages/outdoor_lighting_control.yaml new file mode 100644 index 0000000..8bda922 --- /dev/null +++ b/packages/outdoor_lighting_control.yaml @@ -0,0 +1,1242 @@ +############################################################################### +# PACKAGE: Outdoor lighting controls +############################################################################### +# +# Version: +# 2026-05-09 v0.2 +# - Refactored scheduled control to use editable schedule lists. +# - Switched lights now use explicit ON/OFF schedule entries instead of +# ON plus duration. +# - Added dimmer schedule event tracking so long ramps are not restarted by +# the 5 minute scheduler. +# - Added manual-on start tracking for switched lights so scheduled events +# can override a temporary manual-on period. +# +# 2026-05-09 v0.1 +# - Initial package for outdoor switched and dimmable lighting. +# +# Notes: +# - Edit schedules inside the per-light reconcile/apply scripts below. +# - The schedule rows are deliberately near the top of each script. +# - Dimmable schedule format: +# trigger: sunset, offset_minutes, target_brightness, ramp_minutes +# trigger: time, at, target_brightness, ramp_minutes +# - Switched schedule format: +# trigger: sunset, offset_minutes, target_state +# trigger: time, at, target_state +# - For dimmable schedules, fixed time entries override sun based entries if +# the real sunset offset would occur after the following fixed time. +# - The scheduler runs every 5 minutes. Dimming ramps also step every 5 minutes +# by default. +# - Manual-off suppression lasts until the next day's first scheduled ON. +# +############################################################################### + +input_boolean: + outdoor_lighting_automation_enable: + name: Outdoor Lighting Automation - Master Enable + icon: mdi:outdoor-lamp + initial: true + + outdoor_lighting_deck_coloured_automation_enable: + name: Outdoor Lighting Automation - Deck Coloured Lights + icon: mdi:string-lights + initial: true + + outdoor_lighting_patio_automation_enable: + name: Outdoor Lighting Automation - Patio Lights + icon: mdi:coach-lamp + initial: true + + outdoor_lighting_number_16_automation_enable: + name: Outdoor Lighting Automation - View Road Number 16 + icon: mdi:numeric-16-box + initial: true + + outdoor_lighting_main_entrance_automation_enable: + name: Outdoor Lighting Automation - Main Entrance Outside LEDs + icon: mdi:led-strip-variant + initial: true + +input_datetime: + outdoor_lighting_deck_suppress_until: + name: Outdoor Lighting Deck Suppress Until + has_date: true + has_time: true + + outdoor_lighting_deck_manual_on_started: + name: Outdoor Lighting Deck Manual On Started + has_date: true + has_time: true + + outdoor_lighting_patio_suppress_until: + name: Outdoor Lighting Patio Suppress Until + has_date: true + has_time: true + + outdoor_lighting_patio_manual_on_started: + name: Outdoor Lighting Patio Manual On Started + has_date: true + has_time: true + + outdoor_lighting_number_16_last_schedule_event: + name: Outdoor Lighting Number 16 Last Schedule Event + has_date: true + has_time: true + + outdoor_lighting_main_entrance_suppress_until: + name: Outdoor Lighting Main Entrance Suppress Until + has_date: true + has_time: true + + outdoor_lighting_main_entrance_last_schedule_event: + name: Outdoor Lighting Main Entrance Last Schedule Event + has_date: true + has_time: true + + outdoor_lighting_main_entrance_last_override_press: + name: Outdoor Lighting Main Entrance Last Override Press + has_date: true + has_time: true + +input_select: + outdoor_lighting_main_entrance_override_mode: + name: Outdoor Lighting Main Entrance Override Mode + options: + - none + - return_to_schedule + - turn_off + initial: none + icon: mdi:gesture-tap-button + +timer: + outdoor_lighting_deck_manual_on: + name: Outdoor Lighting Deck Manual On Timer + duration: "01:00:00" + + outdoor_lighting_patio_manual_on: + name: Outdoor Lighting Patio Manual On Timer + duration: "01:00:00" + + outdoor_lighting_main_entrance_override: + name: Outdoor Lighting Main Entrance Override Timer + duration: "01:00:00" + +script: + ############################################################################# + # Shared dimmable ramp scripts + ############################################################################# + + outdoor_lighting_ramp_number_16: + alias: Outdoor Lighting - Ramp View Road Number 16 + mode: restart + fields: + target_brightness: + description: Target brightness from 0 to 100. Zero means off. + ramp_seconds: + description: Ramp duration in seconds. + sequence: + - variables: + brightness_entity: number.front_entranceway_led_light_controls_number_16_led_light_brightness + switch_entity: switch.front_entranceway_led_light_controls_number_16_led_light + ramp_update_seconds: 300 + target: > + {% set raw = target_brightness | int(0) %} + {{ [[raw, 0] | max, 100] | min }} + ramp_s: "{{ ramp_seconds | int(0) }}" + current: > + {% if is_state(switch_entity, 'on') %} + {{ states(brightness_entity) | float(0) }} + {% else %} + 0 + {% endif %} + raw_steps: > + {% if ramp_s | int(0) <= 0 %} + 0 + {% else %} + {{ ((ramp_s | int(0)) / (ramp_update_seconds | int(300))) | round(0, 'ceil') | int }} + {% endif %} + steps: > + {% if ramp_s | int(0) <= 0 %} + 1 + {% else %} + {{ (raw_steps | int(0)) + 1 }} + {% endif %} + step_delay_seconds: > + {% if (steps | int(1)) <= 1 %} + 0 + {% else %} + {{ ((ramp_s | int(0)) / ((steps | int(1)) - 1)) | round(0, 'ceil') | int }} + {% endif %} + + - repeat: + count: "{{ steps | int(1) }}" + sequence: + - variables: + next_value: > + {% set val = (current | float(0)) + (((target | float(0)) - (current | float(0))) * (repeat.index | int) / (steps | int(1))) %} + {{ [[val | round(0) | int, 0] | max, 100] | min }} + + - action: number.set_value + target: + entity_id: number.front_entranceway_led_light_controls_number_16_led_light_brightness + data: + value: "{{ next_value | int(0) }}" + + - if: + - condition: template + value_template: "{{ (target | int(0)) > 0 and is_state('switch.front_entranceway_led_light_controls_number_16_led_light', 'off') }}" + then: + - action: switch.turn_on + target: + entity_id: switch.front_entranceway_led_light_controls_number_16_led_light + + - if: + - condition: template + value_template: "{{ repeat.index < (steps | int(1)) }}" + then: + - delay: + seconds: "{{ step_delay_seconds | int(0) }}" + + - if: + - condition: template + value_template: "{{ (target | int(0)) <= 0 }}" + then: + - action: switch.turn_off + target: + entity_id: switch.front_entranceway_led_light_controls_number_16_led_light + + outdoor_lighting_ramp_main_entrance: + alias: Outdoor Lighting - Ramp Main Entrance Outside LEDs + mode: restart + fields: + target_brightness: + description: Target brightness from 0 to 100. Zero means off. + ramp_seconds: + description: Ramp duration in seconds. + sequence: + - variables: + brightness_entity: number.front_entranceway_led_light_controls_main_entrance_outside_led_lights_brightness + switch_entity: switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights + ramp_update_seconds: 300 + target: > + {% set raw = target_brightness | int(0) %} + {{ [[raw, 0] | max, 100] | min }} + ramp_s: "{{ ramp_seconds | int(0) }}" + current: > + {% if is_state(switch_entity, 'on') %} + {{ states(brightness_entity) | float(0) }} + {% else %} + 0 + {% endif %} + raw_steps: > + {% if ramp_s | int(0) <= 0 %} + 0 + {% else %} + {{ ((ramp_s | int(0)) / (ramp_update_seconds | int(300))) | round(0, 'ceil') | int }} + {% endif %} + steps: > + {% if ramp_s | int(0) <= 0 %} + 1 + {% else %} + {{ (raw_steps | int(0)) + 1 }} + {% endif %} + step_delay_seconds: > + {% if (steps | int(1)) <= 1 %} + 0 + {% else %} + {{ ((ramp_s | int(0)) / ((steps | int(1)) - 1)) | round(0, 'ceil') | int }} + {% endif %} + + - repeat: + count: "{{ steps | int(1) }}" + sequence: + - variables: + next_value: > + {% set val = (current | float(0)) + (((target | float(0)) - (current | float(0))) * (repeat.index | int) / (steps | int(1))) %} + {{ [[val | round(0) | int, 0] | max, 100] | min }} + + - action: number.set_value + target: + entity_id: number.front_entranceway_led_light_controls_main_entrance_outside_led_lights_brightness + data: + value: "{{ next_value | int(0) }}" + + - if: + - condition: template + value_template: "{{ (target | int(0)) > 0 and is_state('switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights', 'off') }}" + then: + - action: switch.turn_on + target: + entity_id: switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights + + - if: + - condition: template + value_template: "{{ repeat.index < (steps | int(1)) }}" + then: + - delay: + seconds: "{{ step_delay_seconds | int(0) }}" + + - if: + - condition: template + value_template: "{{ (target | int(0)) <= 0 }}" + then: + - action: switch.turn_off + target: + entity_id: switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights + + ############################################################################# + # 1) Outdoor Deck Coloured Lights + # Switch: switch.esp_downstloungeoutside_relay_1_outside_lights + # Override: binary_sensor.esp_downstloungeoutside_button_1_outside_lights + ############################################################################# + + outdoor_lighting_reconcile_deck_coloured: + alias: Outdoor Lighting - Reconcile Deck Coloured Lights + mode: restart + sequence: + - condition: state + entity_id: input_boolean.outdoor_lighting_automation_enable + state: "on" + - condition: state + entity_id: input_boolean.outdoor_lighting_deck_coloured_automation_enable + state: "on" + + ########################################################################### + # EDIT SCHEDULE HERE + # Format: trigger, offset_minutes, target_state + # Current schedule: + # - sunset +60, ON + # - sunset +240, OFF + ########################################################################### + - variables: + schedule: + - trigger: sunset + offset_minutes: 60 + target_state: "on" + - trigger: sunset + offset_minutes: 240 + target_state: "off" + + - variables: + selected_schedule: > + {% set now_ts = as_timestamp(now()) %} + {% set next_setting_raw = state_attr('sun.sun', 'next_setting') %} + {% if next_setting_raw %} + {% set next_setting = as_datetime(next_setting_raw) | as_local %} + {% if next_setting.date() == now().date() %} + {% set today_sunset = next_setting %} + {% else %} + {% set today_sunset = next_setting - timedelta(days=1) %} + {% endif %} + {% else %} + {% set today_sunset = none %} + {% endif %} + {% set ns = namespace(best_ts=0, best_state='off') %} + {% for day_offset in [-1, 0] %} + {% for item in schedule %} + {% if item.trigger == 'sunset' and today_sunset %} + {% set event_dt = today_sunset + timedelta(days=day_offset) + timedelta(minutes=item.offset_minutes | int(0)) %} + {% elif item.trigger == 'time' %} + {% set event_dt = today_at(item.at) + timedelta(days=day_offset) %} + {% else %} + {% set event_dt = none %} + {% endif %} + {% if event_dt %} + {% set event_ts = as_timestamp(event_dt, 0) %} + {% if event_ts <= now_ts and event_ts > ns.best_ts %} + {% set ns.best_ts = event_ts %} + {% set ns.best_state = item.target_state %} + {% endif %} + {% endif %} + {% endfor %} + {% endfor %} + {{ ns.best_state }}|{{ ns.best_ts | int(0) }} + selected_target_state: "{{ selected_schedule.split('|')[0] }}" + selected_event_ts: "{{ selected_schedule.split('|')[1] | int(0) }}" + suppress_active: > + {% set ts = as_timestamp(states('input_datetime.outdoor_lighting_deck_suppress_until'), 0) %} + {{ ts > as_timestamp(now()) }} + manual_on_active: "{{ is_state('timer.outdoor_lighting_deck_manual_on', 'active') }}" + manual_on_started_ts: "{{ as_timestamp(states('input_datetime.outdoor_lighting_deck_manual_on_started'), 0) }}" + manual_on_still_valid: > + {{ manual_on_active | bool and (selected_event_ts | int(0)) <= (manual_on_started_ts | int(0)) }} + should_be_on: > + {% if suppress_active | bool %} + false + {% elif manual_on_still_valid | bool %} + true + {% else %} + {{ selected_target_state == 'on' }} + {% endif %} + schedule_has_overridden_manual: > + {{ manual_on_active | bool and not (manual_on_still_valid | bool) }} + + - if: + - condition: template + value_template: "{{ schedule_has_overridden_manual | bool }}" + then: + - action: timer.cancel + target: + entity_id: timer.outdoor_lighting_deck_manual_on + + - choose: + - conditions: + - condition: template + value_template: "{{ should_be_on | bool }}" + sequence: + - action: switch.turn_on + target: + entity_id: switch.esp_downstloungeoutside_relay_1_outside_lights + default: + - action: switch.turn_off + target: + entity_id: switch.esp_downstloungeoutside_relay_1_outside_lights + + ############################################################################# + # 2) Patio Lights + # Switch: switch.esp_garageentrylights_relay_2_patio_lights + # Override: binary_sensor.esp_garageentrylights_button_2_patio_lights + ############################################################################# + + outdoor_lighting_reconcile_patio: + alias: Outdoor Lighting - Reconcile Patio Lights + mode: restart + sequence: + - condition: state + entity_id: input_boolean.outdoor_lighting_automation_enable + state: "on" + - condition: state + entity_id: input_boolean.outdoor_lighting_patio_automation_enable + state: "on" + + ########################################################################### + # EDIT SCHEDULE HERE + # Format: trigger, offset_minutes, target_state + # Current schedule: + # - sunset -30, ON + # - sunset +150, OFF + ########################################################################### + - variables: + schedule: + - trigger: sunset + offset_minutes: -30 + target_state: "on" + - trigger: sunset + offset_minutes: 150 + target_state: "off" + + - variables: + selected_schedule: > + {% set now_ts = as_timestamp(now()) %} + {% set next_setting_raw = state_attr('sun.sun', 'next_setting') %} + {% if next_setting_raw %} + {% set next_setting = as_datetime(next_setting_raw) | as_local %} + {% if next_setting.date() == now().date() %} + {% set today_sunset = next_setting %} + {% else %} + {% set today_sunset = next_setting - timedelta(days=1) %} + {% endif %} + {% else %} + {% set today_sunset = none %} + {% endif %} + {% set ns = namespace(best_ts=0, best_state='off') %} + {% for day_offset in [-1, 0] %} + {% for item in schedule %} + {% if item.trigger == 'sunset' and today_sunset %} + {% set event_dt = today_sunset + timedelta(days=day_offset) + timedelta(minutes=item.offset_minutes | int(0)) %} + {% elif item.trigger == 'time' %} + {% set event_dt = today_at(item.at) + timedelta(days=day_offset) %} + {% else %} + {% set event_dt = none %} + {% endif %} + {% if event_dt %} + {% set event_ts = as_timestamp(event_dt, 0) %} + {% if event_ts <= now_ts and event_ts > ns.best_ts %} + {% set ns.best_ts = event_ts %} + {% set ns.best_state = item.target_state %} + {% endif %} + {% endif %} + {% endfor %} + {% endfor %} + {{ ns.best_state }}|{{ ns.best_ts | int(0) }} + selected_target_state: "{{ selected_schedule.split('|')[0] }}" + selected_event_ts: "{{ selected_schedule.split('|')[1] | int(0) }}" + suppress_active: > + {% set ts = as_timestamp(states('input_datetime.outdoor_lighting_patio_suppress_until'), 0) %} + {{ ts > as_timestamp(now()) }} + manual_on_active: "{{ is_state('timer.outdoor_lighting_patio_manual_on', 'active') }}" + manual_on_started_ts: "{{ as_timestamp(states('input_datetime.outdoor_lighting_patio_manual_on_started'), 0) }}" + manual_on_still_valid: > + {{ manual_on_active | bool and (selected_event_ts | int(0)) <= (manual_on_started_ts | int(0)) }} + should_be_on: > + {% if suppress_active | bool %} + false + {% elif manual_on_still_valid | bool %} + true + {% else %} + {{ selected_target_state == 'on' }} + {% endif %} + schedule_has_overridden_manual: > + {{ manual_on_active | bool and not (manual_on_still_valid | bool) }} + + - if: + - condition: template + value_template: "{{ schedule_has_overridden_manual | bool }}" + then: + - action: timer.cancel + target: + entity_id: timer.outdoor_lighting_patio_manual_on + + - choose: + - conditions: + - condition: template + value_template: "{{ should_be_on | bool }}" + sequence: + - action: switch.turn_on + target: + entity_id: switch.esp_garageentrylights_relay_2_patio_lights + default: + - action: switch.turn_off + target: + entity_id: switch.esp_garageentrylights_relay_2_patio_lights + + ############################################################################# + # 3) View Road Street Number 16 + # Brightness: number.front_entranceway_led_light_controls_number_16_led_light_brightness + # Switch: switch.front_entranceway_led_light_controls_number_16_led_light + ############################################################################# + + outdoor_lighting_apply_number_16_current_target: + alias: Outdoor Lighting - Apply Current Target - View Road Number 16 + mode: restart + fields: + force_reconcile: + description: Force immediate reconciliation even if the schedule event has already been applied. + ramp_seconds: + description: Optional ramp seconds override. Use -1 or omit to use the schedule ramp. + sequence: + - condition: state + entity_id: input_boolean.outdoor_lighting_automation_enable + state: "on" + - condition: state + entity_id: input_boolean.outdoor_lighting_number_16_automation_enable + state: "on" + + ########################################################################### + # EDIT SCHEDULE HERE + # Format: trigger, offset_minutes/at, target_brightness, ramp_minutes + # Current schedule: + # - sunset -30, 80, 30 + # - 20:00, 50, 30 + # - 01:00, 20, 120 + # - 06:00, 0, 120 + ########################################################################### + - variables: + schedule: + - trigger: sunset + offset_minutes: -30 + target_brightness: 60 + ramp_minutes: 30 + - trigger: time + at: "20:00:00" + target_brightness: 50 + ramp_minutes: 120 + - trigger: time + at: "01:00:00" + target_brightness: 20 + ramp_minutes: 120 + - trigger: time + at: "06:00:00" + target_brightness: 0 + ramp_minutes: 120 + + - variables: + selected_schedule: > + {% set now_ts = as_timestamp(now()) %} + {% set next_setting_raw = state_attr('sun.sun', 'next_setting') %} + {% if next_setting_raw %} + {% set next_setting = as_datetime(next_setting_raw) | as_local %} + {% if next_setting.date() == now().date() %} + {% set today_sunset = next_setting %} + {% else %} + {% set today_sunset = next_setting - timedelta(days=1) %} + {% endif %} + {% else %} + {% set today_sunset = none %} + {% endif %} + {% set ns = namespace(best_ts=0, best_target=0, best_ramp=0) %} + {% for day_offset in [-1, 0] %} + {% for item in schedule %} + {% if item.trigger == 'sunset' and today_sunset %} + {% set event_dt = today_sunset + timedelta(days=day_offset) + timedelta(minutes=item.offset_minutes | int(0)) %} + {% elif item.trigger == 'time' %} + {% set event_dt = today_at(item.at) + timedelta(days=day_offset) %} + {% else %} + {% set event_dt = none %} + {% endif %} + {% if event_dt %} + {% set valid = namespace(ok=true) %} + {% if item.trigger == 'sunset' %} + {% set idx = loop.index0 %} + {% set nxt = namespace(found=false, dt=none) %} + {% for step in range(1, schedule | length) %} + {% set j = (idx + step) % (schedule | length) %} + {% set next_item = schedule[j] %} + {% if not nxt.found and next_item.trigger == 'time' %} + {% set next_dt = today_at(next_item.at) + timedelta(days=day_offset) %} + {% if next_dt <= event_dt %} + {% set next_dt = next_dt + timedelta(days=1) %} + {% endif %} + {% set nxt.dt = next_dt %} + {% set nxt.found = true %} + {% endif %} + {% endfor %} + {% if nxt.found and event_dt >= nxt.dt %} + {% set valid.ok = false %} + {% endif %} + {% endif %} + {% set event_ts = as_timestamp(event_dt, 0) %} + {% if valid.ok and event_ts <= now_ts and event_ts > ns.best_ts %} + {% set ns.best_ts = event_ts %} + {% set ns.best_target = item.target_brightness | int(0) %} + {% set ns.best_ramp = (item.ramp_minutes | int(0) * 60) %} + {% endif %} + {% endif %} + {% endfor %} + {% endfor %} + {{ ns.best_target }}|{{ ns.best_ramp }}|{{ ns.best_ts | int(0) }} + selected_target: "{{ selected_schedule.split('|')[0] | int(0) }}" + selected_ramp_seconds: "{{ selected_schedule.split('|')[1] | int(0) }}" + selected_event_ts: "{{ selected_schedule.split('|')[2] | int(0) }}" + last_event_ts: "{{ as_timestamp(states('input_datetime.outdoor_lighting_number_16_last_schedule_event'), 0) | int(0) }}" + do_force: "{{ force_reconcile | default(false) | bool }}" + requested_ramp_seconds: "{{ ramp_seconds | default(-1) | int(-1) }}" + ramp_to_use: > + {% if requested_ramp_seconds | int(-1) >= 0 %} + {{ requested_ramp_seconds | int(0) }} + {% elif do_force | bool %} + 0 + {% else %} + {{ selected_ramp_seconds | int(0) }} + {% endif %} + should_apply: > + {{ do_force | bool or (selected_event_ts | int(0)) != (last_event_ts | int(0)) }} + + - if: + - condition: template + value_template: "{{ should_apply | bool }}" + then: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_number_16_last_schedule_event + data: + datetime: "{{ (selected_event_ts | int(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }}" + - action: script.outdoor_lighting_ramp_number_16 + data: + target_brightness: "{{ selected_target | int(0) }}" + ramp_seconds: "{{ ramp_to_use | int(0) }}" + + ############################################################################# + # 4) Main Entrance Outside LED Lights + # Brightness: number.front_entranceway_led_light_controls_main_entrance_outside_led_lights_brightness + # Switch: switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights + # Override: binary_sensor.esp_entrancelightswitch_button_2_external_light_controller + ############################################################################# + + outdoor_lighting_apply_main_entrance_current_target: + alias: Outdoor Lighting - Apply Current Target - Main Entrance Outside LEDs + mode: restart + fields: + force_reconcile: + description: Force immediate reconciliation even if the schedule event has already been applied. + ramp_seconds: + description: Optional ramp seconds override. Use -1 or omit to use the schedule ramp. + sequence: + - condition: state + entity_id: input_boolean.outdoor_lighting_automation_enable + state: "on" + - condition: state + entity_id: input_boolean.outdoor_lighting_main_entrance_automation_enable + state: "on" + - condition: state + entity_id: input_select.outdoor_lighting_main_entrance_override_mode + state: "none" + + ########################################################################### + # EDIT SCHEDULE HERE + # Format: trigger, offset_minutes/at, target_brightness, ramp_minutes + # Current schedule: + # - sunset -30, 30, 30 + # - 20:00, 20, 30 + # - 23:00, 0, 30 + ########################################################################### + - variables: + schedule: + - trigger: sunset + offset_minutes: -30 + target_brightness: 30 + ramp_minutes: 30 + - trigger: time + at: "20:00:00" + target_brightness: 20 + ramp_minutes: 30 + - trigger: time + at: "23:00:00" + target_brightness: 0 + ramp_minutes: 30 + + - variables: + suppress_active: > + {% set ts = as_timestamp(states('input_datetime.outdoor_lighting_main_entrance_suppress_until'), 0) %} + {{ ts > as_timestamp(now()) }} + selected_schedule: > + {% set now_ts = as_timestamp(now()) %} + {% set next_setting_raw = state_attr('sun.sun', 'next_setting') %} + {% if next_setting_raw %} + {% set next_setting = as_datetime(next_setting_raw) | as_local %} + {% if next_setting.date() == now().date() %} + {% set today_sunset = next_setting %} + {% else %} + {% set today_sunset = next_setting - timedelta(days=1) %} + {% endif %} + {% else %} + {% set today_sunset = none %} + {% endif %} + {% set ns = namespace(best_ts=0, best_target=0, best_ramp=0) %} + {% for day_offset in [-1, 0] %} + {% for item in schedule %} + {% if item.trigger == 'sunset' and today_sunset %} + {% set event_dt = today_sunset + timedelta(days=day_offset) + timedelta(minutes=item.offset_minutes | int(0)) %} + {% elif item.trigger == 'time' %} + {% set event_dt = today_at(item.at) + timedelta(days=day_offset) %} + {% else %} + {% set event_dt = none %} + {% endif %} + {% if event_dt %} + {% set valid = namespace(ok=true) %} + {% if item.trigger == 'sunset' %} + {% set idx = loop.index0 %} + {% set nxt = namespace(found=false, dt=none) %} + {% for step in range(1, schedule | length) %} + {% set j = (idx + step) % (schedule | length) %} + {% set next_item = schedule[j] %} + {% if not nxt.found and next_item.trigger == 'time' %} + {% set next_dt = today_at(next_item.at) + timedelta(days=day_offset) %} + {% if next_dt <= event_dt %} + {% set next_dt = next_dt + timedelta(days=1) %} + {% endif %} + {% set nxt.dt = next_dt %} + {% set nxt.found = true %} + {% endif %} + {% endfor %} + {% if nxt.found and event_dt >= nxt.dt %} + {% set valid.ok = false %} + {% endif %} + {% endif %} + {% set event_ts = as_timestamp(event_dt, 0) %} + {% if valid.ok and event_ts <= now_ts and event_ts > ns.best_ts %} + {% set ns.best_ts = event_ts %} + {% set ns.best_target = item.target_brightness | int(0) %} + {% set ns.best_ramp = (item.ramp_minutes | int(0) * 60) %} + {% endif %} + {% endif %} + {% endfor %} + {% endfor %} + {{ ns.best_target }}|{{ ns.best_ramp }}|{{ ns.best_ts | int(0) }} + selected_target: > + {% if suppress_active | bool %} + 0 + {% else %} + {{ selected_schedule.split('|')[0] | int(0) }} + {% endif %} + selected_ramp_seconds: "{{ selected_schedule.split('|')[1] | int(0) }}" + selected_event_ts: "{{ selected_schedule.split('|')[2] | int(0) }}" + last_event_ts: "{{ as_timestamp(states('input_datetime.outdoor_lighting_main_entrance_last_schedule_event'), 0) | int(0) }}" + do_force: "{{ force_reconcile | default(false) | bool }}" + requested_ramp_seconds: "{{ ramp_seconds | default(-1) | int(-1) }}" + ramp_to_use: > + {% if requested_ramp_seconds | int(-1) >= 0 %} + {{ requested_ramp_seconds | int(0) }} + {% elif do_force | bool %} + 0 + {% else %} + {{ selected_ramp_seconds | int(0) }} + {% endif %} + ramp_script_active: "{{ is_state('script.outdoor_lighting_ramp_main_entrance', 'on') }}" + should_apply: > + {% if suppress_active | bool and ramp_script_active | bool %} + false + {% else %} + {{ do_force | bool or suppress_active | bool or (selected_event_ts | int(0)) != (last_event_ts | int(0)) }} + {% endif %} + + - if: + - condition: template + value_template: "{{ should_apply | bool }}" + then: + - if: + - condition: template + value_template: "{{ not (suppress_active | bool) }}" + then: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_main_entrance_last_schedule_event + data: + datetime: "{{ (selected_event_ts | int(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }}" + - action: script.outdoor_lighting_ramp_main_entrance + data: + target_brightness: "{{ selected_target | int(0) }}" + ramp_seconds: "{{ ramp_to_use | int(0) }}" + + ############################################################################# + # Reconcile all + ############################################################################# + + outdoor_lighting_reconcile_all: + alias: Outdoor Lighting - Reconcile All Outdoor Lights + mode: restart + fields: + force_reconcile: + description: Force dimmable lights to reconcile immediately. + sequence: + - action: script.outdoor_lighting_reconcile_deck_coloured + - action: script.outdoor_lighting_reconcile_patio + - action: script.outdoor_lighting_apply_number_16_current_target + data: + force_reconcile: "{{ force_reconcile | default(false) | bool }}" + ramp_seconds: > + {% if force_reconcile | default(false) | bool %} + 0 + {% else %} + -1 + {% endif %} + - action: script.outdoor_lighting_apply_main_entrance_current_target + data: + force_reconcile: "{{ force_reconcile | default(false) | bool }}" + ramp_seconds: > + {% if force_reconcile | default(false) | bool %} + 0 + {% else %} + -1 + {% endif %} + +automation: + ############################################################################# + # Startup, scheduler, and enable switch cleanup + ############################################################################# + + - id: outdoor_lighting_reconcile_on_start + alias: Outdoor Lighting - Reconcile On Home Assistant Start + mode: single + trigger: + - platform: homeassistant + event: start + action: + - delay: "00:00:30" + - action: input_select.select_option + target: + entity_id: input_select.outdoor_lighting_main_entrance_override_mode + data: + option: none + - action: timer.cancel + target: + entity_id: + - timer.outdoor_lighting_deck_manual_on + - timer.outdoor_lighting_patio_manual_on + - timer.outdoor_lighting_main_entrance_override + - action: script.outdoor_lighting_reconcile_all + data: + force_reconcile: true + + - id: outdoor_lighting_scheduler_tick + alias: Outdoor Lighting - Scheduler Tick + mode: single + trigger: + - platform: time_pattern + minutes: "/5" + condition: + - condition: state + entity_id: input_boolean.outdoor_lighting_automation_enable + state: "on" + action: + - action: script.outdoor_lighting_reconcile_all + data: + force_reconcile: false + + - id: outdoor_lighting_reconcile_when_enable_changes + alias: Outdoor Lighting - Reconcile When Automation Enable Changes + mode: queued + trigger: + - platform: state + entity_id: + - input_boolean.outdoor_lighting_automation_enable + - input_boolean.outdoor_lighting_deck_coloured_automation_enable + - input_boolean.outdoor_lighting_patio_automation_enable + - input_boolean.outdoor_lighting_number_16_automation_enable + - input_boolean.outdoor_lighting_main_entrance_automation_enable + to: "on" + action: + - action: script.outdoor_lighting_reconcile_all + data: + force_reconcile: true + + - id: outdoor_lighting_cleanup_when_master_disabled + alias: Outdoor Lighting - Cleanup When Master Automation Disabled + mode: single + trigger: + - platform: state + entity_id: input_boolean.outdoor_lighting_automation_enable + to: "off" + action: + - action: script.turn_off + target: + entity_id: + - script.outdoor_lighting_ramp_number_16 + - script.outdoor_lighting_ramp_main_entrance + - script.outdoor_lighting_apply_number_16_current_target + - script.outdoor_lighting_apply_main_entrance_current_target + - action: timer.cancel + target: + entity_id: + - timer.outdoor_lighting_deck_manual_on + - timer.outdoor_lighting_patio_manual_on + - timer.outdoor_lighting_main_entrance_override + - action: input_select.select_option + target: + entity_id: input_select.outdoor_lighting_main_entrance_override_mode + data: + option: none + + ############################################################################# + # Switched light override buttons + ############################################################################# + + - id: outdoor_lighting_deck_coloured_override_button + alias: Outdoor Lighting - Deck Coloured Lights - Override Button + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.esp_downstloungeoutside_button_1_outside_lights + to: "on" + condition: + - condition: state + entity_id: input_boolean.outdoor_lighting_automation_enable + state: "on" + - condition: state + entity_id: input_boolean.outdoor_lighting_deck_coloured_automation_enable + state: "on" + action: + - variables: + was_on_at_press: "{{ is_state('switch.esp_downstloungeoutside_relay_1_outside_lights', 'on') }}" + - delay: "00:00:01" + - choose: + - conditions: + - condition: template + value_template: "{{ was_on_at_press | bool }}" + sequence: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_deck_suppress_until + data: + datetime: >- + {%- set next_setting_raw = state_attr('sun.sun', 'next_setting') -%} + {%- set next_setting = as_datetime(next_setting_raw) | as_local -%} + {%- if next_setting.date() == now().date() -%} + {%- set today_sunset = next_setting -%} + {%- else -%} + {%- set today_sunset = next_setting - timedelta(days=1) -%} + {%- endif -%} + {{ (today_sunset + timedelta(days=1, minutes=60)).strftime('%Y-%m-%d %H:%M:%S') }} + - action: timer.cancel + target: + entity_id: timer.outdoor_lighting_deck_manual_on + - action: switch.turn_off + target: + entity_id: switch.esp_downstloungeoutside_relay_1_outside_lights + default: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_deck_suppress_until + data: + datetime: "1970-01-01 00:00:00" + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_deck_manual_on_started + data: + datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}" + - action: switch.turn_on + target: + entity_id: switch.esp_downstloungeoutside_relay_1_outside_lights + - action: timer.start + target: + entity_id: timer.outdoor_lighting_deck_manual_on + data: + duration: "01:00:00" + + - id: outdoor_lighting_patio_override_button + alias: Outdoor Lighting - Patio Lights - Override Button + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.esp_garageentrylights_button_2_patio_lights + to: "on" + condition: + - condition: state + entity_id: input_boolean.outdoor_lighting_automation_enable + state: "on" + - condition: state + entity_id: input_boolean.outdoor_lighting_patio_automation_enable + state: "on" + action: + - variables: + was_on_at_press: "{{ is_state('switch.esp_garageentrylights_relay_2_patio_lights', 'on') }}" + - delay: "00:00:01" + - choose: + - conditions: + - condition: template + value_template: "{{ was_on_at_press | bool }}" + sequence: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_patio_suppress_until + data: + datetime: >- + {%- set next_setting_raw = state_attr('sun.sun', 'next_setting') -%} + {%- set next_setting = as_datetime(next_setting_raw) | as_local -%} + {%- if next_setting.date() == now().date() -%} + {%- set today_sunset = next_setting -%} + {%- else -%} + {%- set today_sunset = next_setting - timedelta(days=1) -%} + {%- endif -%} + {{ (today_sunset + timedelta(days=1, minutes=-30)).strftime('%Y-%m-%d %H:%M:%S') }} + - action: timer.cancel + target: + entity_id: timer.outdoor_lighting_patio_manual_on + - action: switch.turn_off + target: + entity_id: switch.esp_garageentrylights_relay_2_patio_lights + default: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_patio_suppress_until + data: + datetime: "1970-01-01 00:00:00" + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_patio_manual_on_started + data: + datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}" + - action: switch.turn_on + target: + entity_id: switch.esp_garageentrylights_relay_2_patio_lights + - action: timer.start + target: + entity_id: timer.outdoor_lighting_patio_manual_on + data: + duration: "01:00:00" + + - id: outdoor_lighting_deck_coloured_manual_on_timer_finished + alias: Outdoor Lighting - Deck Coloured Lights - Manual On Timer Finished + mode: single + trigger: + - platform: event + event_type: timer.finished + event_data: + entity_id: timer.outdoor_lighting_deck_manual_on + action: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_deck_manual_on_started + data: + datetime: "1970-01-01 00:00:00" + - action: script.outdoor_lighting_reconcile_deck_coloured + + - id: outdoor_lighting_patio_manual_on_timer_finished + alias: Outdoor Lighting - Patio Lights - Manual On Timer Finished + mode: single + trigger: + - platform: event + event_type: timer.finished + event_data: + entity_id: timer.outdoor_lighting_patio_manual_on + action: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_patio_manual_on_started + data: + datetime: "1970-01-01 00:00:00" + - action: script.outdoor_lighting_reconcile_patio + + ############################################################################# + # Main entrance dimmable override button + ############################################################################# + + - id: outdoor_lighting_main_entrance_override_button + alias: Outdoor Lighting - Main Entrance Outside LEDs - Override Button + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.esp_entrancelightswitch_button_2_external_light_controller + to: "on" + condition: + - condition: state + entity_id: input_boolean.outdoor_lighting_automation_enable + state: "on" + - condition: state + entity_id: input_boolean.outdoor_lighting_main_entrance_automation_enable + state: "on" + action: + - variables: + override_ramp_seconds: 60 + was_on_at_press: "{{ is_state('switch.front_entranceway_led_light_controls_main_entrance_outside_led_lights', 'on') }}" + + - delay: "00:00:01" + + - variables: + last_press_age_seconds: > + {% set last_ts = as_timestamp(states('input_datetime.outdoor_lighting_main_entrance_last_override_press'), 0) %} + {% if last_ts <= 0 %} + 999999 + {% else %} + {{ (as_timestamp(now()) - last_ts) | int(999999) }} + {% endif %} + + - choose: + - conditions: + - condition: template + value_template: > + {{ not is_state('input_select.outdoor_lighting_main_entrance_override_mode', 'none') + and (last_press_age_seconds | int(999999)) <= 60 }} + sequence: + - action: timer.cancel + target: + entity_id: timer.outdoor_lighting_main_entrance_override + - action: input_select.select_option + target: + entity_id: input_select.outdoor_lighting_main_entrance_override_mode + data: + option: none + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_main_entrance_suppress_until + data: + datetime: >- + {%- set next_setting_raw = state_attr('sun.sun', 'next_setting') -%} + {%- set next_setting = as_datetime(next_setting_raw) | as_local -%} + {%- if next_setting.date() == now().date() -%} + {%- set today_sunset = next_setting -%} + {%- else -%} + {%- set today_sunset = next_setting - timedelta(days=1) -%} + {%- endif -%} + {%- set tomorrow_sun_on = today_sunset + timedelta(days=1, minutes=-30) -%} + {%- set tomorrow_2000 = today_at('20:00:00') + timedelta(days=1) -%} + {%- if tomorrow_sun_on < tomorrow_2000 -%} + {{ tomorrow_sun_on.strftime('%Y-%m-%d %H:%M:%S') }} + {%- else -%} + {{ tomorrow_2000.strftime('%Y-%m-%d %H:%M:%S') }} + {%- endif -%} + - action: script.outdoor_lighting_ramp_main_entrance + data: + target_brightness: 0 + ramp_seconds: "{{ override_ramp_seconds }}" + + - conditions: + - condition: template + value_template: > + {{ not is_state('input_select.outdoor_lighting_main_entrance_override_mode', 'none') + and (last_press_age_seconds | int(999999)) > 60 + and (last_press_age_seconds | int(999999)) <= 3600 }} + sequence: + - action: timer.cancel + target: + entity_id: timer.outdoor_lighting_main_entrance_override + - action: input_select.select_option + target: + entity_id: input_select.outdoor_lighting_main_entrance_override_mode + data: + option: none + - action: script.outdoor_lighting_apply_main_entrance_current_target + data: + force_reconcile: true + ramp_seconds: "{{ override_ramp_seconds }}" + + default: + - action: input_datetime.set_datetime + target: + entity_id: input_datetime.outdoor_lighting_main_entrance_last_override_press + data: + datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}" + + - choose: + - conditions: + - condition: template + value_template: "{{ was_on_at_press | bool }}" + sequence: + - action: input_select.select_option + target: + entity_id: input_select.outdoor_lighting_main_entrance_override_mode + data: + option: return_to_schedule + default: + - action: input_select.select_option + target: + entity_id: input_select.outdoor_lighting_main_entrance_override_mode + data: + option: turn_off + + - action: script.outdoor_lighting_ramp_main_entrance + data: + target_brightness: 100 + ramp_seconds: "{{ override_ramp_seconds }}" + + - action: timer.start + target: + entity_id: timer.outdoor_lighting_main_entrance_override + data: + duration: "01:00:00" + + - id: outdoor_lighting_main_entrance_override_timer_finished + alias: Outdoor Lighting - Main Entrance Outside LEDs - Override Timer Finished + mode: single + trigger: + - platform: event + event_type: timer.finished + event_data: + entity_id: timer.outdoor_lighting_main_entrance_override + condition: + - condition: state + entity_id: input_boolean.outdoor_lighting_automation_enable + state: "on" + - condition: state + entity_id: input_boolean.outdoor_lighting_main_entrance_automation_enable + state: "on" + action: + - variables: + override_ramp_seconds: 60 + mode_at_finish: "{{ states('input_select.outdoor_lighting_main_entrance_override_mode') }}" + + - action: input_select.select_option + target: + entity_id: input_select.outdoor_lighting_main_entrance_override_mode + data: + option: none + + - choose: + - conditions: + - condition: template + value_template: "{{ mode_at_finish == 'return_to_schedule' }}" + sequence: + - action: script.outdoor_lighting_apply_main_entrance_current_target + data: + force_reconcile: true + ramp_seconds: "{{ override_ramp_seconds }}" + + - conditions: + - condition: template + value_template: "{{ mode_at_finish == 'turn_off' }}" + sequence: + - action: script.outdoor_lighting_ramp_main_entrance + data: + target_brightness: 0 + ramp_seconds: "{{ override_ramp_seconds }}" diff --git a/packages/quiet_time.yaml b/packages/quiet_time.yaml new file mode 100644 index 0000000..25dd8d7 --- /dev/null +++ b/packages/quiet_time.yaml @@ -0,0 +1,123 @@ +################################################################################ +# Quiet Time Package +################################################################################ +# +# TITLE: +# Quiet Time +# +# VERSIONS: +# V1.2 2026-05-21 +# - Added MQTT publishing when input_boolean.quiet_time changes. +# - Publishes quiet_time state to viewroad-status/homeassistant/quiet-time. +# +# V1.1 2026-05-21 +# - Reduced daily reset check frequency from every minute to every 30 minutes. +# - Reduced reset dropdown options to 00:30 through 10:00 only. +# +# V1.0 2026-05-21 +# - Initial package. +# - Adds input_boolean.quiet_time. +# - Adds input_select.quiet_time_daily_reset. +# - Adds daily automation to turn quiet_time OFF at the selected reset time. +# +# OPERATION NOTES: +# - input_boolean.quiet_time is the master quiet time toggle. +# - Other packages can check input_boolean.quiet_time before sending +# announcements, notifications, TTS messages, or similar. +# - input_select.quiet_time_daily_reset selects the daily time that quiet_time +# will automatically be turned OFF. +# - Default reset time is 07:00. +# - The quiet_time state is published to MQTT whenever it changes. +# +# MQTT STATUS: +# - Topic: +# viewroad-status/homeassistant/quiet-time +# - Payload: +# on +# off +# +################################################################################ + +################################################################################ +# INPUT BOOLEAN +################################################################################ + +input_boolean: + quiet_time: + name: Quiet Time + icon: mdi:bell-off + +################################################################################ +# INPUT SELECT +################################################################################ + +input_select: + quiet_time_daily_reset: + name: Quiet time daily reset + icon: mdi:clock-end + options: + - "00:30" + - "01:00" + - "01:30" + - "02:00" + - "02:30" + - "03:00" + - "03:30" + - "04:00" + - "04:30" + - "05:00" + - "05:30" + - "06:00" + - "06:30" + - "07:00" + - "07:30" + - "08:00" + - "08:30" + - "09:00" + - "09:30" + - "10:00" + initial: "07:00" + +################################################################################ +# AUTOMATIONS +################################################################################ + +automation: + - id: quiet_time_daily_reset_turn_off + alias: "Quiet Time - Daily Reset Off" + description: "Turns Quiet Time off once per day at the selected reset time." + mode: single + + triggers: + - trigger: time_pattern + minutes: "/30" + seconds: "0" + + conditions: + - condition: state + entity_id: input_boolean.quiet_time + state: "on" + + - condition: template + value_template: > + {{ states('input_select.quiet_time_daily_reset') == now().strftime('%H:%M') }} + + actions: + - action: input_boolean.turn_off + target: + entity_id: input_boolean.quiet_time + + - id: quiet_time_settings_to_mqtt + alias: "Publish quiet time value to MQTT" + description: "Publishes the Quiet Time boolean state to MQTT whenever it changes." + mode: single + + triggers: + - trigger: state + entity_id: input_boolean.quiet_time + + actions: + - action: mqtt.publish + data: + topic: viewroad-status/homeassistant/quiet-time + payload: "{{ trigger.to_state.state }}" diff --git a/packages/tts.yaml b/packages/tts.yaml deleted file mode 100644 index 810ebe6..0000000 --- a/packages/tts.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Text to speech -tts: - platform: google_translate - service_name: google_say diff --git a/packages/view_road_announcement_router.yaml b/packages/view_road_announcement_router.yaml index 5893889..90cc35e 100644 --- a/packages/view_road_announcement_router.yaml +++ b/packages/view_road_announcement_router.yaml @@ -343,6 +343,31 @@ script: - condition: template value_template: "{{ ann_voice_message | string | trim | length > 0 }}" then: + - choose: + - conditions: + - condition: template + value_template: "{{ lounge_google_home_voice_suppress_cast_chime | bool(false) }}" + - condition: template + value_template: "{{ states(lounge_google_home_voice_media_player) in ['off', 'unavailable', 'unknown'] }}" + sequence: + - action: media_player.volume_set + data: + entity_id: "{{ lounge_google_home_voice_media_player }}" + volume_level: 0 + + - action: media_player.turn_on + target: + entity_id: "{{ lounge_google_home_voice_media_player }}" + + - wait_template: >- + {{ states(lounge_google_home_voice_media_player) in ['idle', 'paused', 'playing', 'on'] }} + timeout: + seconds: 5 + continue_on_timeout: true + + - delay: + seconds: 1 + - action: media_player.volume_set data: entity_id: "{{ lounge_google_home_voice_media_player }}"