Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0db246aefa | |||
| e7b0677810 | |||
| 593366b65a | |||
| 9a28eb8c0f | |||
| c9ac6e80b9 | |||
| 0c6260e67a |
@@ -111,7 +111,8 @@
|
|||||||
"mac_address": "8C:AA:B5:DF:B7:DE"
|
"mac_address": "8C:AA:B5:DF:B7:DE"
|
||||||
},
|
},
|
||||||
"esp-loungepelmetleds2.yaml": {
|
"esp-loungepelmetleds2.yaml": {
|
||||||
"board_id": "generic-esp8266"
|
"board_id": "generic-esp8266",
|
||||||
|
"mac_address": "48:E7:29:48:A4:F1"
|
||||||
},
|
},
|
||||||
"esp-entmulti.yaml": {
|
"esp-entmulti.yaml": {
|
||||||
"board_id": "generic-esp32",
|
"board_id": "generic-esp32",
|
||||||
@@ -161,7 +162,8 @@
|
|||||||
"mac_address": "5C:CF:7F:92:35:96"
|
"mac_address": "5C:CF:7F:92:35:96"
|
||||||
},
|
},
|
||||||
"esp-loungecabinetleds.yaml": {
|
"esp-loungecabinetleds.yaml": {
|
||||||
"board_id": "generic-esp8266"
|
"board_id": "generic-esp8266",
|
||||||
|
"mac_address": "E8:DB:84:5C:4D:34"
|
||||||
},
|
},
|
||||||
"esp-6buttontest-pmb.yaml": {
|
"esp-6buttontest-pmb.yaml": {
|
||||||
"board_id": "generic-esp32"
|
"board_id": "generic-esp32"
|
||||||
@@ -174,7 +176,8 @@
|
|||||||
"mac_address": "5C:CF:7F:B4:A7:A5"
|
"mac_address": "5C:CF:7F:B4:A7:A5"
|
||||||
},
|
},
|
||||||
"esp-breakfastbarleds.yaml": {
|
"esp-breakfastbarleds.yaml": {
|
||||||
"board_id": "generic-esp8266"
|
"board_id": "generic-esp8266",
|
||||||
|
"mac_address": "24:62:AB:6E:D3:94"
|
||||||
},
|
},
|
||||||
"esp-laundryenv.yaml": {
|
"esp-laundryenv.yaml": {
|
||||||
"board_id": "generic-esp8266",
|
"board_id": "generic-esp8266",
|
||||||
@@ -257,7 +260,8 @@
|
|||||||
"mac_address": "2C:F4:32:4B:BC:70"
|
"mac_address": "2C:F4:32:4B:BC:70"
|
||||||
},
|
},
|
||||||
"esp-loungebookshelfleds.yaml": {
|
"esp-loungebookshelfleds.yaml": {
|
||||||
"board_id": "generic-esp8266"
|
"board_id": "generic-esp8266",
|
||||||
|
"mac_address": "24:62:AB:6E:D5:3C"
|
||||||
},
|
},
|
||||||
"esp-officeduallights.yaml": {
|
"esp-officeduallights.yaml": {
|
||||||
"board_id": "generic-esp8266"
|
"board_id": "generic-esp8266"
|
||||||
@@ -282,7 +286,8 @@
|
|||||||
"board_id": "d1-mini"
|
"board_id": "d1-mini"
|
||||||
},
|
},
|
||||||
"esp-downstairskitchleds.yaml": {
|
"esp-downstairskitchleds.yaml": {
|
||||||
"board_id": "generic-esp8266"
|
"board_id": "generic-esp8266",
|
||||||
|
"mac_address": "E8:DB:84:49:4E:99"
|
||||||
},
|
},
|
||||||
"esp-entrancelightswitch.yaml": {
|
"esp-entrancelightswitch.yaml": {
|
||||||
"board_id": "generic-esp8266",
|
"board_id": "generic-esp8266",
|
||||||
@@ -303,6 +308,56 @@
|
|||||||
"mac_address": "88:13:BF:68:39:2C"
|
"mac_address": "88:13:BF:68:39:2C"
|
||||||
},
|
},
|
||||||
"_firmware_jobs": [
|
"_firmware_jobs": [
|
||||||
|
{
|
||||||
|
"job_id": "1a468db814e3",
|
||||||
|
"configuration": "esp-astroclockstepper1.yaml",
|
||||||
|
"job_type": "upload",
|
||||||
|
"status": "completed",
|
||||||
|
"created_at": "2026-06-22T03:29:56.540327+00:00",
|
||||||
|
"started_at": "2026-06-22T03:30:10.489873+00:00",
|
||||||
|
"completed_at": "2026-06-22T03:30:21.965677+00:00",
|
||||||
|
"exit_code": 0,
|
||||||
|
"error": null,
|
||||||
|
"port": "OTA",
|
||||||
|
"new_name": "",
|
||||||
|
"depends_on": "46b32677aa36",
|
||||||
|
"dependency_released": true,
|
||||||
|
"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": "46b32677aa36",
|
||||||
|
"configuration": "esp-astroclockstepper1.yaml",
|
||||||
|
"job_type": "compile",
|
||||||
|
"status": "completed",
|
||||||
|
"created_at": "2026-06-22T03:29:56.540282+00:00",
|
||||||
|
"started_at": "2026-06-22T03:29:56.541217+00:00",
|
||||||
|
"completed_at": "2026-06-22T03:30:10.487553+00:00",
|
||||||
|
"exit_code": 0,
|
||||||
|
"error": null,
|
||||||
|
"port": "",
|
||||||
|
"new_name": "",
|
||||||
|
"depends_on": "",
|
||||||
|
"dependency_released": false,
|
||||||
|
"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": "dfabed27adf5",
|
"job_id": "dfabed27adf5",
|
||||||
"configuration": "esp-masterbdrmwest.yaml",
|
"configuration": "esp-masterbdrmwest.yaml",
|
||||||
@@ -2982,5 +3037,8 @@
|
|||||||
"_board_id_user_set_migrated": true,
|
"_board_id_user_set_migrated": true,
|
||||||
"_dashboard_identity": {
|
"_dashboard_identity": {
|
||||||
"dashboard_id": "vhamVDnWEnx-FvAa24uVv49Wsddurh84"
|
"dashboard_id": "vhamVDnWEnx-FvAa24uVv49Wsddurh84"
|
||||||
|
},
|
||||||
|
"esp-astroclockstepper1.yaml": {
|
||||||
|
"mac_address": "24:D7:EB:D0:16:68"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,865 @@
|
|||||||
|
#:########################################################################################:#
|
||||||
|
# TITLE: ASTRONOMICAL CLOCK STEPPER
|
||||||
|
# zorruno.com layout v1.1 2026
|
||||||
|
#:########################################################################################:#
|
||||||
|
# REPO:
|
||||||
|
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-astroclockstepper.yaml
|
||||||
|
#:########################################################################################:#
|
||||||
|
# VERSIONS:
|
||||||
|
# V1.1 2026-06-22 Added onboard D1 Mini LED flash on each motor step
|
||||||
|
# V1.0 2026-06-22 Initial Version
|
||||||
|
#:########################################################################################:#
|
||||||
|
# HARDWARE:
|
||||||
|
# ESP8266 D1 Mini Clone
|
||||||
|
# 5V 28BYJ-48 Stepper Motor
|
||||||
|
# Keyestudio / ULN2003 style 5V stepper driver board
|
||||||
|
#
|
||||||
|
# Stepper Driver:
|
||||||
|
# - IN1: GPIO14 / D5
|
||||||
|
# - IN2: GPIO12 / D6
|
||||||
|
# - IN3: GPIO13 / D7
|
||||||
|
# - IN4: GPIO15 / D8
|
||||||
|
# - VCC: 5V motor supply
|
||||||
|
# - GND: Common GND with ESP8266
|
||||||
|
#
|
||||||
|
# Physical Buttons:
|
||||||
|
# - Fast Forward: GPIO5 / D1
|
||||||
|
# - Slow Forward: GPIO4 / D2
|
||||||
|
# - Fast Reverse: GPIO0 / D3
|
||||||
|
# - Slow Reverse: GPIO16 / D0
|
||||||
|
#
|
||||||
|
# Button board:
|
||||||
|
# - Uses built-in 103 pull-up resistors, 10k ohm
|
||||||
|
# - Buttons should pull the GPIO pin to GND when pressed
|
||||||
|
# - Pull-ups must go to 3.3V, not 5V
|
||||||
|
#
|
||||||
|
# Onboard Step LED:
|
||||||
|
# - GPIO2 / D4
|
||||||
|
# - Active-low on most D1 Mini boards
|
||||||
|
#:########################################################################################:#
|
||||||
|
# OPERATION NOTES:
|
||||||
|
# - Normal rotation starts automatically after boot.
|
||||||
|
# - Default rotation is one full revolution every 365.2422 days.
|
||||||
|
# - Default step count is 4096 steps per full revolution.
|
||||||
|
# - Normal movement is extremely slow, about one step every 2.14 hours.
|
||||||
|
# - Motor coils are powered only while stepping, then released.
|
||||||
|
# - No holding torque is used.
|
||||||
|
# - Web template switches mimic held physical buttons.
|
||||||
|
# - Manual movement only applies while a physical button or web switch is held/on.
|
||||||
|
# - Position counter wraps back to zero after one full rotation.
|
||||||
|
# - Onboard D1 Mini LED flashes briefly every time a motor step is issued.
|
||||||
|
#:########################################################################################:#
|
||||||
|
# OFFLINE NOTES:
|
||||||
|
# a) Home Assistant OFFLINE, but Network and MQTT ONLINE
|
||||||
|
# - Device remains controllable via local web portal and MQTT commands
|
||||||
|
# - HA entities unavailable until HA returns
|
||||||
|
# b) MQTT OFFLINE, but WiFi/Network and HA API ONLINE
|
||||||
|
# - Device remains controllable from Home Assistant via API
|
||||||
|
# - Local web portal remains available
|
||||||
|
# c) Entire WiFi/Network OFFLINE
|
||||||
|
# - No HA API, MQTT, or web control available
|
||||||
|
# - Physical buttons continue to work
|
||||||
|
# - Normal motor timing continues from device uptime
|
||||||
|
# d) Device power loss / reboot
|
||||||
|
# - V1 does not restore exact astronomical position after reboot
|
||||||
|
# - Re-align manually using the web or physical button controls
|
||||||
|
#:########################################################################################:#
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# 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-astroclockstepper1"
|
||||||
|
friendly_name: "Astronomical Clock Stepper"
|
||||||
|
description_comment: "Astronomical Clock Stepper :: ESP8266 D1 Mini with 28BYJ-48 Stepper Motor (Layout V1.1)"
|
||||||
|
device_area: "Standalone"
|
||||||
|
|
||||||
|
# Project Naming
|
||||||
|
project_name: "zorruno.Astronomical Clock Stepper"
|
||||||
|
project_version: "v1.1"
|
||||||
|
|
||||||
|
# Passwords & Secrets (Unfortunately, you cannot use substitutions inside secret names)
|
||||||
|
api_key: !secret esp-api_key
|
||||||
|
ota_pass: !secret esp-ota_pass
|
||||||
|
static_ip_address: !secret esp-astroclockstepper1_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}
|
||||||
|
|
||||||
|
# MQTT Topics
|
||||||
|
mqtt_command_main_topic: !secret mqtt_command_main_topic
|
||||||
|
mqtt_status_main_topic: !secret mqtt_status_main_topic
|
||||||
|
mqtt_device_name: "astro-clock-stepper1"
|
||||||
|
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"
|
||||||
|
sensor_update_interval: "1s"
|
||||||
|
calculated_sensor_update_interval: "60s"
|
||||||
|
|
||||||
|
# Astronomical Clock Settings
|
||||||
|
rotation_days: "365.2422"
|
||||||
|
steps_per_rotation: "4096"
|
||||||
|
normal_direction_name: "Clockwise"
|
||||||
|
|
||||||
|
# Use 1 for normal direction.
|
||||||
|
# Use -1 if the motor rotates the wrong way.
|
||||||
|
normal_direction_multiplier: "1"
|
||||||
|
|
||||||
|
# Manual Movement Speeds
|
||||||
|
manual_fast_rotation_seconds: "30"
|
||||||
|
manual_slow_rotation_seconds: "300"
|
||||||
|
|
||||||
|
# Coil Release
|
||||||
|
coil_release_ms: "500"
|
||||||
|
|
||||||
|
# Step LED
|
||||||
|
onboard_step_led_pin: GPIO2
|
||||||
|
step_led_flash_ms: "25"
|
||||||
|
|
||||||
|
# Stepper Output Pins
|
||||||
|
stepper_in1_pin: GPIO14
|
||||||
|
stepper_in2_pin: GPIO12
|
||||||
|
stepper_in3_pin: GPIO13
|
||||||
|
stepper_in4_pin: GPIO15
|
||||||
|
|
||||||
|
# Physical Button Input Pins
|
||||||
|
button_fast_forward_pin: GPIO5
|
||||||
|
button_slow_forward_pin: GPIO4
|
||||||
|
button_fast_reverse_pin: GPIO0
|
||||||
|
button_slow_reverse_pin: GPIO16
|
||||||
|
|
||||||
|
# Entity Naming
|
||||||
|
web_fast_forward_name: "Web Fast Forward"
|
||||||
|
web_slow_forward_name: "Web Slow Forward"
|
||||||
|
web_fast_reverse_name: "Web Fast Reverse"
|
||||||
|
web_slow_reverse_name: "Web Slow Reverse"
|
||||||
|
|
||||||
|
physical_fast_forward_name: "Physical Fast Forward"
|
||||||
|
physical_slow_forward_name: "Physical Slow Forward"
|
||||||
|
physical_fast_reverse_name: "Physical Fast Reverse"
|
||||||
|
physical_slow_reverse_name: "Physical Slow Reverse"
|
||||||
|
|
||||||
|
zero_position_name: "Zero Position Counter"
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# 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 (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
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# ESPHOME:
|
||||||
|
# https://esphome.io/components/esphome.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
esphome:
|
||||||
|
name: "${device_name}"
|
||||||
|
friendly_name: "${friendly_name}"
|
||||||
|
comment: "${description_comment}"
|
||||||
|
area: "${device_area}"
|
||||||
|
project:
|
||||||
|
name: "${project_name}"
|
||||||
|
version: "${project_version}"
|
||||||
|
|
||||||
|
on_boot:
|
||||||
|
priority: -100
|
||||||
|
then:
|
||||||
|
- output.turn_off: onboard_step_led
|
||||||
|
- logger.log:
|
||||||
|
level: INFO
|
||||||
|
format: "Astronomical Clock Stepper started. Normal rotation is %s days per full revolution."
|
||||||
|
args:
|
||||||
|
- '"${rotation_days}"'
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# ESP PLATFORM AND FRAMEWORK:
|
||||||
|
# https://esphome.io/components/esp8266.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
esp8266:
|
||||||
|
board: d1_mini
|
||||||
|
restore_from_flash: true
|
||||||
|
|
||||||
|
preferences:
|
||||||
|
flash_write_interval: 5min
|
||||||
|
|
||||||
|
mdns:
|
||||||
|
disabled: false
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# LOGGING:
|
||||||
|
# https://esphome.io/components/logger.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
logger:
|
||||||
|
level: "${log_level}"
|
||||||
|
baud_rate: 0
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# GLOBALS:
|
||||||
|
# Internal position, timing, mode, and held-button state
|
||||||
|
# https://esphome.io/components/globals.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
globals:
|
||||||
|
- id: current_position_step
|
||||||
|
type: int
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "0"
|
||||||
|
|
||||||
|
- id: current_phase_index
|
||||||
|
type: int
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "0"
|
||||||
|
|
||||||
|
- id: last_loop_ms
|
||||||
|
type: uint32_t
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "0"
|
||||||
|
|
||||||
|
- id: last_step_ms
|
||||||
|
type: uint32_t
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "0"
|
||||||
|
|
||||||
|
- id: step_accumulator_ms
|
||||||
|
type: double
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "0.0"
|
||||||
|
|
||||||
|
- id: coils_are_on
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
- id: step_led_is_on
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
- id: step_led_started_ms
|
||||||
|
type: uint32_t
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "0"
|
||||||
|
|
||||||
|
# 0 = Normal
|
||||||
|
# 1 = Fast Forward
|
||||||
|
# 2 = Slow Forward
|
||||||
|
# 3 = Fast Reverse
|
||||||
|
# 4 = Slow Reverse
|
||||||
|
- id: current_motion_mode
|
||||||
|
type: int
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "0"
|
||||||
|
|
||||||
|
# Physical Button States
|
||||||
|
- id: physical_fast_forward_active
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
- id: physical_slow_forward_active
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
- id: physical_fast_reverse_active
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
- id: physical_slow_reverse_active
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
# Web Button States
|
||||||
|
- id: web_fast_forward_active
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
- id: web_slow_forward_active
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
- id: web_fast_reverse_active
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
- id: web_slow_reverse_active
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# OUTPUT:
|
||||||
|
# Stepper driver GPIO outputs and onboard step LED
|
||||||
|
# https://esphome.io/components/output/gpio.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
output:
|
||||||
|
- platform: gpio
|
||||||
|
id: stepper_in1
|
||||||
|
pin: ${stepper_in1_pin}
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
id: stepper_in2
|
||||||
|
pin: ${stepper_in2_pin}
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
id: stepper_in3
|
||||||
|
pin: ${stepper_in3_pin}
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
id: stepper_in4
|
||||||
|
pin: ${stepper_in4_pin}
|
||||||
|
|
||||||
|
# D1 Mini onboard LED is usually GPIO2 / D4 and active-low
|
||||||
|
- platform: gpio
|
||||||
|
id: onboard_step_led
|
||||||
|
pin:
|
||||||
|
number: ${onboard_step_led_pin}
|
||||||
|
inverted: true
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# SWITCH:
|
||||||
|
# Web held-button controls
|
||||||
|
# https://esphome.io/components/switch/template.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
switch:
|
||||||
|
- platform: template
|
||||||
|
name: "${web_fast_forward_name}"
|
||||||
|
id: web_fast_forward_switch
|
||||||
|
icon: mdi:fast-forward
|
||||||
|
optimistic: true
|
||||||
|
restore_mode: ALWAYS_OFF
|
||||||
|
turn_on_action:
|
||||||
|
- switch.turn_off: web_slow_forward_switch
|
||||||
|
- switch.turn_off: web_fast_reverse_switch
|
||||||
|
- switch.turn_off: web_slow_reverse_switch
|
||||||
|
- globals.set:
|
||||||
|
id: web_fast_forward_active
|
||||||
|
value: "true"
|
||||||
|
turn_off_action:
|
||||||
|
- globals.set:
|
||||||
|
id: web_fast_forward_active
|
||||||
|
value: "false"
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "${web_slow_forward_name}"
|
||||||
|
id: web_slow_forward_switch
|
||||||
|
icon: mdi:play-speed
|
||||||
|
optimistic: true
|
||||||
|
restore_mode: ALWAYS_OFF
|
||||||
|
turn_on_action:
|
||||||
|
- switch.turn_off: web_fast_forward_switch
|
||||||
|
- switch.turn_off: web_fast_reverse_switch
|
||||||
|
- switch.turn_off: web_slow_reverse_switch
|
||||||
|
- globals.set:
|
||||||
|
id: web_slow_forward_active
|
||||||
|
value: "true"
|
||||||
|
turn_off_action:
|
||||||
|
- globals.set:
|
||||||
|
id: web_slow_forward_active
|
||||||
|
value: "false"
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "${web_fast_reverse_name}"
|
||||||
|
id: web_fast_reverse_switch
|
||||||
|
icon: mdi:rewind
|
||||||
|
optimistic: true
|
||||||
|
restore_mode: ALWAYS_OFF
|
||||||
|
turn_on_action:
|
||||||
|
- switch.turn_off: web_fast_forward_switch
|
||||||
|
- switch.turn_off: web_slow_forward_switch
|
||||||
|
- switch.turn_off: web_slow_reverse_switch
|
||||||
|
- globals.set:
|
||||||
|
id: web_fast_reverse_active
|
||||||
|
value: "true"
|
||||||
|
turn_off_action:
|
||||||
|
- globals.set:
|
||||||
|
id: web_fast_reverse_active
|
||||||
|
value: "false"
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "${web_slow_reverse_name}"
|
||||||
|
id: web_slow_reverse_switch
|
||||||
|
icon: mdi:rewind-outline
|
||||||
|
optimistic: true
|
||||||
|
restore_mode: ALWAYS_OFF
|
||||||
|
turn_on_action:
|
||||||
|
- switch.turn_off: web_fast_forward_switch
|
||||||
|
- switch.turn_off: web_slow_forward_switch
|
||||||
|
- switch.turn_off: web_fast_reverse_switch
|
||||||
|
- globals.set:
|
||||||
|
id: web_slow_reverse_active
|
||||||
|
value: "true"
|
||||||
|
turn_off_action:
|
||||||
|
- globals.set:
|
||||||
|
id: web_slow_reverse_active
|
||||||
|
value: "false"
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# BINARY SENSORS:
|
||||||
|
# Physical button inputs using external 103 / 10k pull-ups
|
||||||
|
# https://esphome.io/components/binary_sensor/gpio.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
binary_sensor:
|
||||||
|
- platform: gpio
|
||||||
|
name: "${physical_fast_forward_name}"
|
||||||
|
id: physical_fast_forward_button
|
||||||
|
pin:
|
||||||
|
number: ${button_fast_forward_pin}
|
||||||
|
mode: INPUT
|
||||||
|
inverted: true
|
||||||
|
filters:
|
||||||
|
- delayed_on: 20ms
|
||||||
|
- delayed_off: 20ms
|
||||||
|
on_press:
|
||||||
|
- globals.set:
|
||||||
|
id: physical_fast_forward_active
|
||||||
|
value: "true"
|
||||||
|
on_release:
|
||||||
|
- globals.set:
|
||||||
|
id: physical_fast_forward_active
|
||||||
|
value: "false"
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
name: "${physical_slow_forward_name}"
|
||||||
|
id: physical_slow_forward_button
|
||||||
|
pin:
|
||||||
|
number: ${button_slow_forward_pin}
|
||||||
|
mode: INPUT
|
||||||
|
inverted: true
|
||||||
|
filters:
|
||||||
|
- delayed_on: 20ms
|
||||||
|
- delayed_off: 20ms
|
||||||
|
on_press:
|
||||||
|
- globals.set:
|
||||||
|
id: physical_slow_forward_active
|
||||||
|
value: "true"
|
||||||
|
on_release:
|
||||||
|
- globals.set:
|
||||||
|
id: physical_slow_forward_active
|
||||||
|
value: "false"
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
name: "${physical_fast_reverse_name}"
|
||||||
|
id: physical_fast_reverse_button
|
||||||
|
pin:
|
||||||
|
number: ${button_fast_reverse_pin}
|
||||||
|
mode: INPUT
|
||||||
|
inverted: true
|
||||||
|
filters:
|
||||||
|
- delayed_on: 20ms
|
||||||
|
- delayed_off: 20ms
|
||||||
|
on_press:
|
||||||
|
- globals.set:
|
||||||
|
id: physical_fast_reverse_active
|
||||||
|
value: "true"
|
||||||
|
on_release:
|
||||||
|
- globals.set:
|
||||||
|
id: physical_fast_reverse_active
|
||||||
|
value: "false"
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
name: "${physical_slow_reverse_name}"
|
||||||
|
id: physical_slow_reverse_button
|
||||||
|
pin:
|
||||||
|
number: ${button_slow_reverse_pin}
|
||||||
|
mode: INPUT
|
||||||
|
inverted: true
|
||||||
|
filters:
|
||||||
|
- delayed_on: 20ms
|
||||||
|
- delayed_off: 20ms
|
||||||
|
on_press:
|
||||||
|
- globals.set:
|
||||||
|
id: physical_slow_reverse_active
|
||||||
|
value: "true"
|
||||||
|
on_release:
|
||||||
|
- globals.set:
|
||||||
|
id: physical_slow_reverse_active
|
||||||
|
value: "false"
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# BUTTON:
|
||||||
|
# Utility buttons
|
||||||
|
# https://esphome.io/components/button/template.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
button:
|
||||||
|
- platform: template
|
||||||
|
name: "${zero_position_name}"
|
||||||
|
id: zero_position_counter_button
|
||||||
|
icon: mdi:counter
|
||||||
|
on_press:
|
||||||
|
- globals.set:
|
||||||
|
id: current_position_step
|
||||||
|
value: "0"
|
||||||
|
- globals.set:
|
||||||
|
id: current_phase_index
|
||||||
|
value: "0"
|
||||||
|
- globals.set:
|
||||||
|
id: step_accumulator_ms
|
||||||
|
value: "0.0"
|
||||||
|
- logger.log:
|
||||||
|
level: INFO
|
||||||
|
format: "Position counter zeroed."
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# SENSOR:
|
||||||
|
# Position and calculated movement sensors
|
||||||
|
# https://esphome.io/components/sensor/template.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
sensor:
|
||||||
|
- platform: template
|
||||||
|
name: "Position Days"
|
||||||
|
id: position_days_sensor
|
||||||
|
icon: mdi:calendar-clock
|
||||||
|
unit_of_measurement: "days"
|
||||||
|
accuracy_decimals: 4
|
||||||
|
update_interval: "${sensor_update_interval}"
|
||||||
|
lambda: |-
|
||||||
|
return ((double) id(current_position_step) * ${rotation_days}) / ${steps_per_rotation};
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "Position Step"
|
||||||
|
id: position_step_sensor
|
||||||
|
icon: mdi:counter
|
||||||
|
accuracy_decimals: 0
|
||||||
|
update_interval: "${sensor_update_interval}"
|
||||||
|
lambda: |-
|
||||||
|
return id(current_position_step);
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "Days Per Step"
|
||||||
|
id: days_per_step_sensor
|
||||||
|
icon: mdi:calendar-arrow-right
|
||||||
|
unit_of_measurement: "days/step"
|
||||||
|
accuracy_decimals: 8
|
||||||
|
update_interval: "${calculated_sensor_update_interval}"
|
||||||
|
lambda: |-
|
||||||
|
return ${rotation_days} / ${steps_per_rotation};
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "Hours Per Step"
|
||||||
|
id: hours_per_step_sensor
|
||||||
|
icon: mdi:timer-outline
|
||||||
|
unit_of_measurement: "h/step"
|
||||||
|
accuracy_decimals: 8
|
||||||
|
update_interval: "${calculated_sensor_update_interval}"
|
||||||
|
lambda: |-
|
||||||
|
return (${rotation_days} * 24.0) / ${steps_per_rotation};
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "Normal Step Interval"
|
||||||
|
id: normal_step_interval_sensor
|
||||||
|
icon: mdi:timer-cog-outline
|
||||||
|
unit_of_measurement: "s/step"
|
||||||
|
accuracy_decimals: 4
|
||||||
|
update_interval: "${calculated_sensor_update_interval}"
|
||||||
|
lambda: |-
|
||||||
|
return (${rotation_days} * 24.0 * 60.0 * 60.0) / ${steps_per_rotation};
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# TEXT SENSOR:
|
||||||
|
# Human-readable status sensors
|
||||||
|
# https://esphome.io/components/text_sensor/template.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
text_sensor:
|
||||||
|
- platform: template
|
||||||
|
name: "Motion Mode"
|
||||||
|
id: motion_mode_text_sensor
|
||||||
|
icon: mdi:rotate-3d-variant
|
||||||
|
update_interval: "${sensor_update_interval}"
|
||||||
|
lambda: |-
|
||||||
|
switch (id(current_motion_mode)) {
|
||||||
|
case 1:
|
||||||
|
return {"Fast Forward"};
|
||||||
|
case 2:
|
||||||
|
return {"Slow Forward"};
|
||||||
|
case 3:
|
||||||
|
return {"Fast Reverse"};
|
||||||
|
case 4:
|
||||||
|
return {"Slow Reverse"};
|
||||||
|
case 0:
|
||||||
|
default:
|
||||||
|
return {"Normal"};
|
||||||
|
}
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: "Normal Direction"
|
||||||
|
id: normal_direction_text_sensor
|
||||||
|
icon: mdi:rotate-right
|
||||||
|
update_interval: "${calculated_sensor_update_interval}"
|
||||||
|
lambda: |-
|
||||||
|
return {"${normal_direction_name}"};
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# INTERVAL:
|
||||||
|
# Stepper scheduler
|
||||||
|
# https://esphome.io/components/interval.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
interval:
|
||||||
|
- interval: 5ms
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
const int STEPS_PER_ROTATION = ${steps_per_rotation};
|
||||||
|
const int NORMAL_DIRECTION_MULTIPLIER = ${normal_direction_multiplier};
|
||||||
|
|
||||||
|
const double NORMAL_STEP_INTERVAL_MS =
|
||||||
|
(${rotation_days} * 24.0 * 60.0 * 60.0 * 1000.0) / ${steps_per_rotation};
|
||||||
|
|
||||||
|
const double FAST_STEP_INTERVAL_MS =
|
||||||
|
(${manual_fast_rotation_seconds} * 1000.0) / ${steps_per_rotation};
|
||||||
|
|
||||||
|
const double SLOW_STEP_INTERVAL_MS =
|
||||||
|
(${manual_slow_rotation_seconds} * 1000.0) / ${steps_per_rotation};
|
||||||
|
|
||||||
|
const uint32_t COIL_RELEASE_MS = ${coil_release_ms};
|
||||||
|
const uint32_t STEP_LED_FLASH_MS = ${step_led_flash_ms};
|
||||||
|
|
||||||
|
auto all_coils_off = [&]() {
|
||||||
|
id(stepper_in1).turn_off();
|
||||||
|
id(stepper_in2).turn_off();
|
||||||
|
id(stepper_in3).turn_off();
|
||||||
|
id(stepper_in4).turn_off();
|
||||||
|
id(coils_are_on) = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto flash_step_led = [&](uint32_t now_ms) {
|
||||||
|
id(onboard_step_led).turn_on();
|
||||||
|
id(step_led_started_ms) = now_ms;
|
||||||
|
id(step_led_is_on) = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto set_coils_for_phase = [&](int phase) {
|
||||||
|
const bool sequence[8][4] = {
|
||||||
|
{true, false, false, false},
|
||||||
|
{true, true, false, false},
|
||||||
|
{false, true, false, false},
|
||||||
|
{false, true, true, false},
|
||||||
|
{false, false, true, false},
|
||||||
|
{false, false, true, true },
|
||||||
|
{false, false, false, true },
|
||||||
|
{true, false, false, true }
|
||||||
|
};
|
||||||
|
|
||||||
|
phase = ((phase % 8) + 8) % 8;
|
||||||
|
|
||||||
|
if (sequence[phase][0]) {
|
||||||
|
id(stepper_in1).turn_on();
|
||||||
|
} else {
|
||||||
|
id(stepper_in1).turn_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequence[phase][1]) {
|
||||||
|
id(stepper_in2).turn_on();
|
||||||
|
} else {
|
||||||
|
id(stepper_in2).turn_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequence[phase][2]) {
|
||||||
|
id(stepper_in3).turn_on();
|
||||||
|
} else {
|
||||||
|
id(stepper_in3).turn_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequence[phase][3]) {
|
||||||
|
id(stepper_in4).turn_on();
|
||||||
|
} else {
|
||||||
|
id(stepper_in4).turn_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
id(coils_are_on) = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto do_one_step = [&](int direction, uint32_t now_ms) {
|
||||||
|
if (direction >= 0) {
|
||||||
|
id(current_phase_index) = (id(current_phase_index) + 1) % 8;
|
||||||
|
id(current_position_step) = (id(current_position_step) + 1) % STEPS_PER_ROTATION;
|
||||||
|
} else {
|
||||||
|
id(current_phase_index) = (id(current_phase_index) + 7) % 8;
|
||||||
|
id(current_position_step) = (id(current_position_step) + STEPS_PER_ROTATION - 1) % STEPS_PER_ROTATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_coils_for_phase(id(current_phase_index));
|
||||||
|
id(last_step_ms) = now_ms;
|
||||||
|
flash_step_led(now_ms);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool fast_forward_active =
|
||||||
|
id(physical_fast_forward_active) || id(web_fast_forward_active);
|
||||||
|
|
||||||
|
const bool slow_forward_active =
|
||||||
|
id(physical_slow_forward_active) || id(web_slow_forward_active);
|
||||||
|
|
||||||
|
const bool fast_reverse_active =
|
||||||
|
id(physical_fast_reverse_active) || id(web_fast_reverse_active);
|
||||||
|
|
||||||
|
const bool slow_reverse_active =
|
||||||
|
id(physical_slow_reverse_active) || id(web_slow_reverse_active);
|
||||||
|
|
||||||
|
int selected_mode = 0;
|
||||||
|
int selected_direction = NORMAL_DIRECTION_MULTIPLIER;
|
||||||
|
double selected_step_interval_ms = NORMAL_STEP_INTERVAL_MS;
|
||||||
|
|
||||||
|
if (fast_forward_active) {
|
||||||
|
selected_mode = 1;
|
||||||
|
selected_direction = NORMAL_DIRECTION_MULTIPLIER;
|
||||||
|
selected_step_interval_ms = FAST_STEP_INTERVAL_MS;
|
||||||
|
} else if (slow_forward_active) {
|
||||||
|
selected_mode = 2;
|
||||||
|
selected_direction = NORMAL_DIRECTION_MULTIPLIER;
|
||||||
|
selected_step_interval_ms = SLOW_STEP_INTERVAL_MS;
|
||||||
|
} else if (fast_reverse_active) {
|
||||||
|
selected_mode = 3;
|
||||||
|
selected_direction = -NORMAL_DIRECTION_MULTIPLIER;
|
||||||
|
selected_step_interval_ms = FAST_STEP_INTERVAL_MS;
|
||||||
|
} else if (slow_reverse_active) {
|
||||||
|
selected_mode = 4;
|
||||||
|
selected_direction = -NORMAL_DIRECTION_MULTIPLIER;
|
||||||
|
selected_step_interval_ms = SLOW_STEP_INTERVAL_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t now_ms = millis();
|
||||||
|
|
||||||
|
if (id(last_loop_ms) == 0) {
|
||||||
|
id(last_loop_ms) = now_ms;
|
||||||
|
id(current_motion_mode) = selected_mode;
|
||||||
|
id(step_accumulator_ms) = 0.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t elapsed_ms = now_ms - id(last_loop_ms);
|
||||||
|
id(last_loop_ms) = now_ms;
|
||||||
|
|
||||||
|
if (selected_mode != id(current_motion_mode)) {
|
||||||
|
id(current_motion_mode) = selected_mode;
|
||||||
|
id(step_accumulator_ms) = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
id(step_accumulator_ms) += (double) elapsed_ms;
|
||||||
|
|
||||||
|
if (id(step_accumulator_ms) >= selected_step_interval_ms) {
|
||||||
|
do_one_step(selected_direction, now_ms);
|
||||||
|
id(step_accumulator_ms) -= selected_step_interval_ms;
|
||||||
|
|
||||||
|
// If the loop was badly delayed, avoid trying to catch up with a burst
|
||||||
|
// of very fast steps that the motor may not physically follow.
|
||||||
|
if (id(step_accumulator_ms) > selected_step_interval_ms) {
|
||||||
|
id(step_accumulator_ms) = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
id(coils_are_on) &&
|
||||||
|
selected_mode == 0 &&
|
||||||
|
((uint32_t) (now_ms - id(last_step_ms)) >= COIL_RELEASE_MS)
|
||||||
|
) {
|
||||||
|
all_coils_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
id(step_led_is_on) &&
|
||||||
|
((uint32_t) (now_ms - id(step_led_started_ms)) >= STEP_LED_FLASH_MS)
|
||||||
|
) {
|
||||||
|
id(onboard_step_led).turn_off();
|
||||||
|
id(step_led_is_on) = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# MQTT:
|
||||||
|
# Device-specific MQTT command triggers, in addition to common MQTT config
|
||||||
|
# https://esphome.io/components/mqtt.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
mqtt:
|
||||||
|
on_message:
|
||||||
|
- topic: "${mqtt_command_topic}/fast_forward/set"
|
||||||
|
payload: "ON"
|
||||||
|
then:
|
||||||
|
- switch.turn_on: web_fast_forward_switch
|
||||||
|
|
||||||
|
- topic: "${mqtt_command_topic}/fast_forward/set"
|
||||||
|
payload: "OFF"
|
||||||
|
then:
|
||||||
|
- switch.turn_off: web_fast_forward_switch
|
||||||
|
|
||||||
|
- topic: "${mqtt_command_topic}/slow_forward/set"
|
||||||
|
payload: "ON"
|
||||||
|
then:
|
||||||
|
- switch.turn_on: web_slow_forward_switch
|
||||||
|
|
||||||
|
- topic: "${mqtt_command_topic}/slow_forward/set"
|
||||||
|
payload: "OFF"
|
||||||
|
then:
|
||||||
|
- switch.turn_off: web_slow_forward_switch
|
||||||
|
|
||||||
|
- topic: "${mqtt_command_topic}/fast_reverse/set"
|
||||||
|
payload: "ON"
|
||||||
|
then:
|
||||||
|
- switch.turn_on: web_fast_reverse_switch
|
||||||
|
|
||||||
|
- topic: "${mqtt_command_topic}/fast_reverse/set"
|
||||||
|
payload: "OFF"
|
||||||
|
then:
|
||||||
|
- switch.turn_off: web_fast_reverse_switch
|
||||||
|
|
||||||
|
- topic: "${mqtt_command_topic}/slow_reverse/set"
|
||||||
|
payload: "ON"
|
||||||
|
then:
|
||||||
|
- switch.turn_on: web_slow_reverse_switch
|
||||||
|
|
||||||
|
- topic: "${mqtt_command_topic}/slow_reverse/set"
|
||||||
|
payload: "OFF"
|
||||||
|
then:
|
||||||
|
- switch.turn_off: web_slow_reverse_switch
|
||||||
@@ -0,0 +1,809 @@
|
|||||||
|
#:########################################################################################:#
|
||||||
|
# Jobs Tracker Job List Package #
|
||||||
|
#:########################################################################################:#
|
||||||
|
#
|
||||||
|
# TITLE:
|
||||||
|
# Jobs Tracker Job List
|
||||||
|
#
|
||||||
|
# FILE:
|
||||||
|
# jobs_tracker_jobs.yaml
|
||||||
|
#
|
||||||
|
# VERSION:
|
||||||
|
# V1.4 2026-06-20
|
||||||
|
#
|
||||||
|
# PURPOSE:
|
||||||
|
# Defines the job list, MQTT entities, completion triggers and summary output
|
||||||
|
# for the Jobs Tracker system.
|
||||||
|
#
|
||||||
|
# DEPENDS ON:
|
||||||
|
# jobs_tracker_processor.yaml V1.3.1 or newer
|
||||||
|
#
|
||||||
|
# MQTT TOPICS:
|
||||||
|
# viewroad-status/jobs_tracker/summary
|
||||||
|
# viewroad-status/jobs_tracker/dog_feeding_morning
|
||||||
|
# viewroad-status/jobs_tracker/dog_feeding_evening
|
||||||
|
# viewroad-status/jobs_tracker/dog_depooping
|
||||||
|
# viewroad-status/jobs_tracker/dishwasher_unpacked
|
||||||
|
# viewroad-status/jobs_tracker/rubbish_removed
|
||||||
|
#
|
||||||
|
# JOB STATES:
|
||||||
|
# Not Due
|
||||||
|
# Due
|
||||||
|
# Overdue
|
||||||
|
# Complete
|
||||||
|
#
|
||||||
|
# VERSION HISTORY:
|
||||||
|
# V1.4 2026-06-20
|
||||||
|
# - Updated shared dog-fed button behaviour.
|
||||||
|
# - If either dog-feeding job is Due or Overdue, the shared physical button
|
||||||
|
# completes every dog-feeding job that is currently Due or Overdue.
|
||||||
|
# - If neither dog-feeding job is Due or Overdue, the shared physical button
|
||||||
|
# falls back to time-based early completion.
|
||||||
|
#
|
||||||
|
# V1.3 2026-06-20
|
||||||
|
# - Added Not Due support for daily jobs before configured due time.
|
||||||
|
# - Added grouped MQTT Jobs Tracker device.
|
||||||
|
#
|
||||||
|
# NOTES:
|
||||||
|
# - due_weekday uses Python numbering:
|
||||||
|
# Monday=0, Tuesday=1, Wednesday=2, Thursday=3,
|
||||||
|
# Friday=4, Saturday=5, Sunday=6
|
||||||
|
#
|
||||||
|
# - event.dog_fed_button_xbt14_action is shared by:
|
||||||
|
# Dog Feeding Morning
|
||||||
|
# Dog Feeding Evening
|
||||||
|
#
|
||||||
|
# - sensor.jobs_done marks both:
|
||||||
|
# Dishwasher Unpacked
|
||||||
|
# Rubbish Removed
|
||||||
|
#
|
||||||
|
#:########################################################################################:#
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# MANUAL TEST BUTTONS #
|
||||||
|
#:########################################################################################:#
|
||||||
|
|
||||||
|
input_button:
|
||||||
|
jobs_tracker_complete_dog_feeding_morning:
|
||||||
|
name: "Jobs Tracker Complete Dog Feeding Morning"
|
||||||
|
icon: mdi:dog-side
|
||||||
|
|
||||||
|
jobs_tracker_complete_dog_feeding_evening:
|
||||||
|
name: "Jobs Tracker Complete Dog Feeding Evening"
|
||||||
|
icon: mdi:dog-side
|
||||||
|
|
||||||
|
jobs_tracker_complete_dog_depooping:
|
||||||
|
name: "Jobs Tracker Complete Dog Depooping"
|
||||||
|
icon: mdi:emoticon-poop
|
||||||
|
|
||||||
|
jobs_tracker_complete_dishwasher_unpacked:
|
||||||
|
name: "Jobs Tracker Complete Dishwasher Unpacked"
|
||||||
|
icon: mdi:dishwasher
|
||||||
|
|
||||||
|
jobs_tracker_complete_rubbish_removed:
|
||||||
|
name: "Jobs Tracker Complete Rubbish Removed"
|
||||||
|
icon: mdi:trash-can
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# MQTT SENSORS #
|
||||||
|
#:########################################################################################:#
|
||||||
|
|
||||||
|
mqtt:
|
||||||
|
sensor:
|
||||||
|
#:####################################################################################:#
|
||||||
|
# Summary sensor #
|
||||||
|
#:####################################################################################:#
|
||||||
|
|
||||||
|
- name: "Jobs Tracker - Summary"
|
||||||
|
unique_id: jobs_tracker_summary
|
||||||
|
state_topic: "viewroad-status/jobs_tracker/summary"
|
||||||
|
value_template: "{{ value_json.state | default('unknown') }}"
|
||||||
|
json_attributes_topic: "viewroad-status/jobs_tracker/summary"
|
||||||
|
icon: mdi:clipboard-list
|
||||||
|
device:
|
||||||
|
identifiers:
|
||||||
|
- viewroad_jobs_tracker
|
||||||
|
name: "Jobs Tracker"
|
||||||
|
manufacturer: "View Road Home Assistant"
|
||||||
|
model: "Shared household jobs tracker"
|
||||||
|
sw_version: "1.4"
|
||||||
|
|
||||||
|
#:####################################################################################:#
|
||||||
|
# Individual job sensors #
|
||||||
|
#:####################################################################################:#
|
||||||
|
|
||||||
|
- name: "Jobs Tracker - Dog Feeding Morning"
|
||||||
|
unique_id: jobs_tracker_dog_feeding_morning
|
||||||
|
state_topic: "viewroad-status/jobs_tracker/dog_feeding_morning"
|
||||||
|
value_template: "{{ value_json.state | default('unknown') }}"
|
||||||
|
json_attributes_topic: "viewroad-status/jobs_tracker/dog_feeding_morning"
|
||||||
|
icon: mdi:dog-side
|
||||||
|
device:
|
||||||
|
identifiers:
|
||||||
|
- viewroad_jobs_tracker
|
||||||
|
name: "Jobs Tracker"
|
||||||
|
manufacturer: "View Road Home Assistant"
|
||||||
|
model: "Shared household jobs tracker"
|
||||||
|
sw_version: "1.4"
|
||||||
|
|
||||||
|
- name: "Jobs Tracker - Dog Feeding Evening"
|
||||||
|
unique_id: jobs_tracker_dog_feeding_evening
|
||||||
|
state_topic: "viewroad-status/jobs_tracker/dog_feeding_evening"
|
||||||
|
value_template: "{{ value_json.state | default('unknown') }}"
|
||||||
|
json_attributes_topic: "viewroad-status/jobs_tracker/dog_feeding_evening"
|
||||||
|
icon: mdi:dog-side
|
||||||
|
device:
|
||||||
|
identifiers:
|
||||||
|
- viewroad_jobs_tracker
|
||||||
|
name: "Jobs Tracker"
|
||||||
|
manufacturer: "View Road Home Assistant"
|
||||||
|
model: "Shared household jobs tracker"
|
||||||
|
sw_version: "1.4"
|
||||||
|
|
||||||
|
- name: "Jobs Tracker - Dog Depooping"
|
||||||
|
unique_id: jobs_tracker_dog_depooping
|
||||||
|
state_topic: "viewroad-status/jobs_tracker/dog_depooping"
|
||||||
|
value_template: "{{ value_json.state | default('unknown') }}"
|
||||||
|
json_attributes_topic: "viewroad-status/jobs_tracker/dog_depooping"
|
||||||
|
icon: mdi:emoticon-poop
|
||||||
|
device:
|
||||||
|
identifiers:
|
||||||
|
- viewroad_jobs_tracker
|
||||||
|
name: "Jobs Tracker"
|
||||||
|
manufacturer: "View Road Home Assistant"
|
||||||
|
model: "Shared household jobs tracker"
|
||||||
|
sw_version: "1.4"
|
||||||
|
|
||||||
|
- name: "Jobs Tracker - Dishwasher Unpacked"
|
||||||
|
unique_id: jobs_tracker_dishwasher_unpacked
|
||||||
|
state_topic: "viewroad-status/jobs_tracker/dishwasher_unpacked"
|
||||||
|
value_template: "{{ value_json.state | default('unknown') }}"
|
||||||
|
json_attributes_topic: "viewroad-status/jobs_tracker/dishwasher_unpacked"
|
||||||
|
icon: mdi:dishwasher
|
||||||
|
device:
|
||||||
|
identifiers:
|
||||||
|
- viewroad_jobs_tracker
|
||||||
|
name: "Jobs Tracker"
|
||||||
|
manufacturer: "View Road Home Assistant"
|
||||||
|
model: "Shared household jobs tracker"
|
||||||
|
sw_version: "1.4"
|
||||||
|
|
||||||
|
- name: "Jobs Tracker - Rubbish Removed"
|
||||||
|
unique_id: jobs_tracker_rubbish_removed
|
||||||
|
state_topic: "viewroad-status/jobs_tracker/rubbish_removed"
|
||||||
|
value_template: "{{ value_json.state | default('unknown') }}"
|
||||||
|
json_attributes_topic: "viewroad-status/jobs_tracker/rubbish_removed"
|
||||||
|
icon: mdi:trash-can
|
||||||
|
device:
|
||||||
|
identifiers:
|
||||||
|
- viewroad_jobs_tracker
|
||||||
|
name: "Jobs Tracker"
|
||||||
|
manufacturer: "View Road Home Assistant"
|
||||||
|
model: "Shared household jobs tracker"
|
||||||
|
sw_version: "1.4"
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# TEMPLATE SENSORS #
|
||||||
|
#:########################################################################################:#
|
||||||
|
|
||||||
|
template:
|
||||||
|
- binary_sensor:
|
||||||
|
- name: "Job Complete - Dog Feeding Morning"
|
||||||
|
unique_id: job_complete_dog_feeding_morning
|
||||||
|
icon: mdi:dog-side
|
||||||
|
state: "{{ is_state('sensor.jobs_tracker_dog_feeding_morning', 'Complete') }}"
|
||||||
|
|
||||||
|
- name: "Job Complete - Dog Feeding Evening"
|
||||||
|
unique_id: job_complete_dog_feeding_evening
|
||||||
|
icon: mdi:dog-side
|
||||||
|
state: "{{ is_state('sensor.jobs_tracker_dog_feeding_evening', 'Complete') }}"
|
||||||
|
|
||||||
|
- name: "Job Complete - Dog Depooping"
|
||||||
|
unique_id: job_complete_dog_depooping
|
||||||
|
icon: mdi:emoticon-poop
|
||||||
|
state: "{{ is_state('sensor.jobs_tracker_dog_depooping', 'Complete') }}"
|
||||||
|
|
||||||
|
- name: "Job Complete - Dishwasher Unpacked"
|
||||||
|
unique_id: job_complete_dishwasher_unpacked
|
||||||
|
icon: mdi:dishwasher
|
||||||
|
state: "{{ is_state('sensor.jobs_tracker_dishwasher_unpacked', 'Complete') }}"
|
||||||
|
|
||||||
|
- name: "Job Complete - Rubbish Removed"
|
||||||
|
unique_id: job_complete_rubbish_removed
|
||||||
|
icon: mdi:trash-can
|
||||||
|
state: "{{ is_state('sensor.jobs_tracker_rubbish_removed', 'Complete') }}"
|
||||||
|
|
||||||
|
- sensor:
|
||||||
|
- name: "Jobs Tracker - Complete Count"
|
||||||
|
unique_id: jobs_tracker_complete_count
|
||||||
|
icon: mdi:clipboard-check
|
||||||
|
state: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
states('sensor.jobs_tracker_rubbish_removed')
|
||||||
|
] | select('eq', 'Complete') | list | count
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: "Jobs Tracker - Not Due Count"
|
||||||
|
unique_id: jobs_tracker_not_due_count
|
||||||
|
icon: mdi:clipboard-clock-outline
|
||||||
|
state: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
states('sensor.jobs_tracker_rubbish_removed')
|
||||||
|
] | select('eq', 'Not Due') | list | count
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: "Jobs Tracker - Due Count"
|
||||||
|
unique_id: jobs_tracker_due_count
|
||||||
|
icon: mdi:clipboard-alert
|
||||||
|
state: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
states('sensor.jobs_tracker_rubbish_removed')
|
||||||
|
] | select('eq', 'Due') | list | count
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: "Jobs Tracker - Overdue Count"
|
||||||
|
unique_id: jobs_tracker_overdue_count
|
||||||
|
icon: mdi:clipboard-alert-outline
|
||||||
|
state: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
states('sensor.jobs_tracker_rubbish_removed')
|
||||||
|
] | select('eq', 'Overdue') | list | count
|
||||||
|
}}
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# SCRIPTS #
|
||||||
|
#:########################################################################################:#
|
||||||
|
|
||||||
|
script:
|
||||||
|
jobs_tracker_dispatch:
|
||||||
|
alias: "Jobs Tracker - Dispatch"
|
||||||
|
description: "Runs the generic processor against either all jobs or one completed job."
|
||||||
|
mode: queued
|
||||||
|
max: 20
|
||||||
|
|
||||||
|
fields:
|
||||||
|
update_all:
|
||||||
|
description: "True to refresh all jobs."
|
||||||
|
example: true
|
||||||
|
|
||||||
|
job_id:
|
||||||
|
description: "Job ID to mark complete."
|
||||||
|
example: "dog_feeding_morning"
|
||||||
|
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
jobs_tracker_jobs:
|
||||||
|
- job_id: dog_feeding_morning
|
||||||
|
job_name: "Dog Feeding Morning"
|
||||||
|
repeat_type: daily
|
||||||
|
due_time: "06:00"
|
||||||
|
due_weekday: 0
|
||||||
|
due_day: 1
|
||||||
|
mqtt_topic: "viewroad-status/jobs_tracker/dog_feeding_morning"
|
||||||
|
state_entity: "sensor.jobs_tracker_dog_feeding_morning"
|
||||||
|
|
||||||
|
- job_id: dog_feeding_evening
|
||||||
|
job_name: "Dog Feeding Evening"
|
||||||
|
repeat_type: daily
|
||||||
|
due_time: "17:00"
|
||||||
|
due_weekday: 0
|
||||||
|
due_day: 1
|
||||||
|
mqtt_topic: "viewroad-status/jobs_tracker/dog_feeding_evening"
|
||||||
|
state_entity: "sensor.jobs_tracker_dog_feeding_evening"
|
||||||
|
|
||||||
|
- job_id: dog_depooping
|
||||||
|
job_name: "Dog De-pooping"
|
||||||
|
repeat_type: weekly
|
||||||
|
due_time: "08:00"
|
||||||
|
due_weekday: 4
|
||||||
|
due_day: 1
|
||||||
|
mqtt_topic: "viewroad-status/jobs_tracker/dog_depooping"
|
||||||
|
state_entity: "sensor.jobs_tracker_dog_depooping"
|
||||||
|
|
||||||
|
- job_id: dishwasher_unpacked
|
||||||
|
job_name: "Dishwasher Unpacked"
|
||||||
|
repeat_type: daily
|
||||||
|
due_time: "15:30"
|
||||||
|
due_weekday: 0
|
||||||
|
due_day: 1
|
||||||
|
mqtt_topic: "viewroad-status/jobs_tracker/dishwasher_unpacked"
|
||||||
|
state_entity: "sensor.jobs_tracker_dishwasher_unpacked"
|
||||||
|
|
||||||
|
- job_id: rubbish_removed
|
||||||
|
job_name: "Rubbish Removed"
|
||||||
|
repeat_type: daily
|
||||||
|
due_time: "15:30"
|
||||||
|
due_weekday: 0
|
||||||
|
due_day: 1
|
||||||
|
mqtt_topic: "viewroad-status/jobs_tracker/rubbish_removed"
|
||||||
|
state_entity: "sensor.jobs_tracker_rubbish_removed"
|
||||||
|
|
||||||
|
- choose:
|
||||||
|
#:################################################################################:#
|
||||||
|
# Refresh all jobs #
|
||||||
|
#:################################################################################:#
|
||||||
|
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ update_all | default(false) | bool }}"
|
||||||
|
sequence:
|
||||||
|
- repeat:
|
||||||
|
for_each: "{{ jobs_tracker_jobs }}"
|
||||||
|
sequence:
|
||||||
|
- action: script.jobs_tracker_update_job
|
||||||
|
data:
|
||||||
|
job_id: "{{ repeat.item.job_id }}"
|
||||||
|
job_name: "{{ repeat.item.job_name }}"
|
||||||
|
repeat_type: "{{ repeat.item.repeat_type }}"
|
||||||
|
due_time: "{{ repeat.item.due_time }}"
|
||||||
|
due_weekday: "{{ repeat.item.due_weekday }}"
|
||||||
|
due_day: "{{ repeat.item.due_day }}"
|
||||||
|
mqtt_topic: "{{ repeat.item.mqtt_topic }}"
|
||||||
|
state_entity: "{{ repeat.item.state_entity }}"
|
||||||
|
mark_complete: false
|
||||||
|
|
||||||
|
#:##################################################################################:#
|
||||||
|
# Mark one job complete #
|
||||||
|
#:##################################################################################:#
|
||||||
|
|
||||||
|
default:
|
||||||
|
- repeat:
|
||||||
|
for_each: "{{ jobs_tracker_jobs }}"
|
||||||
|
sequence:
|
||||||
|
- if:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ repeat.item.job_id == job_id }}"
|
||||||
|
then:
|
||||||
|
- action: script.jobs_tracker_update_job
|
||||||
|
data:
|
||||||
|
job_id: "{{ repeat.item.job_id }}"
|
||||||
|
job_name: "{{ repeat.item.job_name }}"
|
||||||
|
repeat_type: "{{ repeat.item.repeat_type }}"
|
||||||
|
due_time: "{{ repeat.item.due_time }}"
|
||||||
|
due_weekday: "{{ repeat.item.due_weekday }}"
|
||||||
|
due_day: "{{ repeat.item.due_day }}"
|
||||||
|
mqtt_topic: "{{ repeat.item.mqtt_topic }}"
|
||||||
|
state_entity: "{{ repeat.item.state_entity }}"
|
||||||
|
mark_complete: true
|
||||||
|
|
||||||
|
# Give the MQTT sensors a moment to update from the retained job payloads
|
||||||
|
# before building the summary payload.
|
||||||
|
- delay: "00:00:01"
|
||||||
|
|
||||||
|
- action: script.jobs_tracker_publish_summary
|
||||||
|
|
||||||
|
jobs_tracker_publish_summary:
|
||||||
|
alias: "Jobs Tracker - Publish Summary"
|
||||||
|
description: "Publishes retained MQTT JSON summary for all tracked jobs."
|
||||||
|
mode: queued
|
||||||
|
max: 5
|
||||||
|
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
complete_count: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
states('sensor.jobs_tracker_rubbish_removed')
|
||||||
|
] | select('eq', 'Complete') | list | count
|
||||||
|
}}
|
||||||
|
|
||||||
|
not_due_count: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
states('sensor.jobs_tracker_rubbish_removed')
|
||||||
|
] | select('eq', 'Not Due') | list | count
|
||||||
|
}}
|
||||||
|
|
||||||
|
due_count: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
states('sensor.jobs_tracker_rubbish_removed')
|
||||||
|
] | select('eq', 'Due') | list | count
|
||||||
|
}}
|
||||||
|
|
||||||
|
overdue_count: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
states('sensor.jobs_tracker_rubbish_removed')
|
||||||
|
] | select('eq', 'Overdue') | list | count
|
||||||
|
}}
|
||||||
|
|
||||||
|
unknown_count: >-
|
||||||
|
{{
|
||||||
|
[
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
states('sensor.jobs_tracker_rubbish_removed')
|
||||||
|
]
|
||||||
|
| reject('in', ['Complete', 'Not Due', 'Due', 'Overdue'])
|
||||||
|
| list
|
||||||
|
| count
|
||||||
|
}}
|
||||||
|
|
||||||
|
summary_state: >-
|
||||||
|
{% if overdue_count | int(0) > 0 and due_count | int(0) > 0 %}
|
||||||
|
{{ overdue_count }} Overdue, {{ due_count }} Due
|
||||||
|
{% elif overdue_count | int(0) > 0 %}
|
||||||
|
{{ overdue_count }} Overdue
|
||||||
|
{% elif due_count | int(0) > 0 %}
|
||||||
|
{{ due_count }} Due
|
||||||
|
{% elif not_due_count | int(0) > 0 and complete_count | int(0) > 0 %}
|
||||||
|
{{ complete_count }} Complete, {{ not_due_count }} Not Due
|
||||||
|
{% elif not_due_count | int(0) > 0 %}
|
||||||
|
{{ not_due_count }} Not Due
|
||||||
|
{% elif complete_count | int(0) == 5 %}
|
||||||
|
All Complete
|
||||||
|
{% elif unknown_count | int(0) > 0 %}
|
||||||
|
{{ unknown_count }} Unknown
|
||||||
|
{% else %}
|
||||||
|
Unknown
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "viewroad-status/jobs_tracker/summary"
|
||||||
|
retain: true
|
||||||
|
qos: 1
|
||||||
|
payload: >-
|
||||||
|
{{
|
||||||
|
{
|
||||||
|
"state": summary_state | trim,
|
||||||
|
"complete_count": complete_count | int(0),
|
||||||
|
"not_due_count": not_due_count | int(0),
|
||||||
|
"due_count": due_count | int(0),
|
||||||
|
"overdue_count": overdue_count | int(0),
|
||||||
|
"unknown_count": unknown_count | int(0),
|
||||||
|
"total_count": 5,
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"job_id": "dog_feeding_morning",
|
||||||
|
"job_name": "Dog Feeding Morning",
|
||||||
|
"state": states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
"next_due_human": state_attr('sensor.jobs_tracker_dog_feeding_morning', 'next_due_human'),
|
||||||
|
"last_completed_human": state_attr('sensor.jobs_tracker_dog_feeding_morning', 'last_completed_human'),
|
||||||
|
"minutes_since_required": state_attr('sensor.jobs_tracker_dog_feeding_morning', 'minutes_since_required'),
|
||||||
|
"hours_since_required": state_attr('sensor.jobs_tracker_dog_feeding_morning', 'hours_since_required'),
|
||||||
|
"hours_since_completed": state_attr('sensor.jobs_tracker_dog_feeding_morning', 'hours_since_completed')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"job_id": "dog_feeding_evening",
|
||||||
|
"job_name": "Dog Feeding Evening",
|
||||||
|
"state": states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
"next_due_human": state_attr('sensor.jobs_tracker_dog_feeding_evening', 'next_due_human'),
|
||||||
|
"last_completed_human": state_attr('sensor.jobs_tracker_dog_feeding_evening', 'last_completed_human'),
|
||||||
|
"minutes_since_required": state_attr('sensor.jobs_tracker_dog_feeding_evening', 'minutes_since_required'),
|
||||||
|
"hours_since_required": state_attr('sensor.jobs_tracker_dog_feeding_evening', 'hours_since_required'),
|
||||||
|
"hours_since_completed": state_attr('sensor.jobs_tracker_dog_feeding_evening', 'hours_since_completed')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"job_id": "dog_depooping",
|
||||||
|
"job_name": "Dog De-pooping",
|
||||||
|
"state": states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
"next_due_human": state_attr('sensor.jobs_tracker_dog_depooping', 'next_due_human'),
|
||||||
|
"last_completed_human": state_attr('sensor.jobs_tracker_dog_depooping', 'last_completed_human'),
|
||||||
|
"minutes_since_required": state_attr('sensor.jobs_tracker_dog_depooping', 'minutes_since_required'),
|
||||||
|
"hours_since_required": state_attr('sensor.jobs_tracker_dog_depooping', 'hours_since_required'),
|
||||||
|
"hours_since_completed": state_attr('sensor.jobs_tracker_dog_depooping', 'hours_since_completed')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"job_id": "dishwasher_unpacked",
|
||||||
|
"job_name": "Dishwasher Unpacked",
|
||||||
|
"state": states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
"next_due_human": state_attr('sensor.jobs_tracker_dishwasher_unpacked', 'next_due_human'),
|
||||||
|
"last_completed_human": state_attr('sensor.jobs_tracker_dishwasher_unpacked', 'last_completed_human'),
|
||||||
|
"minutes_since_required": state_attr('sensor.jobs_tracker_dishwasher_unpacked', 'minutes_since_required'),
|
||||||
|
"hours_since_required": state_attr('sensor.jobs_tracker_dishwasher_unpacked', 'hours_since_required'),
|
||||||
|
"hours_since_completed": state_attr('sensor.jobs_tracker_dishwasher_unpacked', 'hours_since_completed')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"job_id": "rubbish_removed",
|
||||||
|
"job_name": "Rubbish Removed",
|
||||||
|
"state": states('sensor.jobs_tracker_rubbish_removed'),
|
||||||
|
"next_due_human": state_attr('sensor.jobs_tracker_rubbish_removed', 'next_due_human'),
|
||||||
|
"last_completed_human": state_attr('sensor.jobs_tracker_rubbish_removed', 'last_completed_human'),
|
||||||
|
"minutes_since_required": state_attr('sensor.jobs_tracker_rubbish_removed', 'minutes_since_required'),
|
||||||
|
"hours_since_required": state_attr('sensor.jobs_tracker_rubbish_removed', 'hours_since_required'),
|
||||||
|
"hours_since_completed": state_attr('sensor.jobs_tracker_rubbish_removed', 'hours_since_completed')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dog_feeding_morning": states('sensor.jobs_tracker_dog_feeding_morning'),
|
||||||
|
"dog_feeding_evening": states('sensor.jobs_tracker_dog_feeding_evening'),
|
||||||
|
"dog_depooping": states('sensor.jobs_tracker_dog_depooping'),
|
||||||
|
"dishwasher_unpacked": states('sensor.jobs_tracker_dishwasher_unpacked'),
|
||||||
|
"rubbish_removed": states('sensor.jobs_tracker_rubbish_removed'),
|
||||||
|
"last_updated_local": now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
} | tojson
|
||||||
|
}}
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# AUTOMATIONS #
|
||||||
|
#:########################################################################################:#
|
||||||
|
|
||||||
|
automation:
|
||||||
|
- id: jobs_tracker_refresh_all_jobs
|
||||||
|
alias: "Jobs Tracker - Refresh All Jobs"
|
||||||
|
description: "Refreshes derived due/overdue values for every job."
|
||||||
|
mode: single
|
||||||
|
|
||||||
|
triggers:
|
||||||
|
- trigger: homeassistant
|
||||||
|
event: start
|
||||||
|
id: ha_start
|
||||||
|
|
||||||
|
- trigger: time_pattern
|
||||||
|
minutes: "/1"
|
||||||
|
id: tick
|
||||||
|
|
||||||
|
actions:
|
||||||
|
# Let retained MQTT job messages restore before the first refresh.
|
||||||
|
- if:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.id == 'ha_start' }}"
|
||||||
|
then:
|
||||||
|
- delay: "00:01:00"
|
||||||
|
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
update_all: true
|
||||||
|
|
||||||
|
- id: jobs_tracker_completion_actions
|
||||||
|
alias: "Jobs Tracker - Completion Actions"
|
||||||
|
description: "Marks jobs complete when physical/action/manual buttons are pressed."
|
||||||
|
mode: queued
|
||||||
|
max: 20
|
||||||
|
|
||||||
|
triggers:
|
||||||
|
#:####################################################################################:#
|
||||||
|
# Physical / action entities #
|
||||||
|
#:####################################################################################:#
|
||||||
|
|
||||||
|
- trigger: state
|
||||||
|
entity_id: event.dog_fed_button_xbt14_action
|
||||||
|
id: dog_fed_button
|
||||||
|
|
||||||
|
- trigger: state
|
||||||
|
entity_id: event.dog_depooped_button_xbt15_action
|
||||||
|
id: dog_depooped_button
|
||||||
|
|
||||||
|
- trigger: state
|
||||||
|
entity_id: sensor.jobs_done
|
||||||
|
id: jobs_done_sensor
|
||||||
|
|
||||||
|
#:####################################################################################:#
|
||||||
|
# Manual test buttons #
|
||||||
|
#:####################################################################################:#
|
||||||
|
|
||||||
|
- trigger: state
|
||||||
|
entity_id: input_button.jobs_tracker_complete_dog_feeding_morning
|
||||||
|
id: manual_dog_feeding_morning
|
||||||
|
|
||||||
|
- trigger: state
|
||||||
|
entity_id: input_button.jobs_tracker_complete_dog_feeding_evening
|
||||||
|
id: manual_dog_feeding_evening
|
||||||
|
|
||||||
|
- trigger: state
|
||||||
|
entity_id: input_button.jobs_tracker_complete_dog_depooping
|
||||||
|
id: manual_dog_depooping
|
||||||
|
|
||||||
|
- trigger: state
|
||||||
|
entity_id: input_button.jobs_tracker_complete_dishwasher_unpacked
|
||||||
|
id: manual_dishwasher_unpacked
|
||||||
|
|
||||||
|
- trigger: state
|
||||||
|
entity_id: input_button.jobs_tracker_complete_rubbish_removed
|
||||||
|
id: manual_rubbish_removed
|
||||||
|
|
||||||
|
conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: >-
|
||||||
|
{{
|
||||||
|
trigger.to_state is not none
|
||||||
|
and trigger.to_state.state not in ['unknown', 'unavailable']
|
||||||
|
}}
|
||||||
|
|
||||||
|
actions:
|
||||||
|
- choose:
|
||||||
|
#:################################################################################:#
|
||||||
|
# Dog fed physical button #
|
||||||
|
#:################################################################################:#
|
||||||
|
#
|
||||||
|
# Shared button behaviour:
|
||||||
|
#
|
||||||
|
# If any dog feeding jobs are already Due or Overdue:
|
||||||
|
# - Complete every linked dog-feeding job that is currently Due or Overdue.
|
||||||
|
#
|
||||||
|
# If no dog feeding jobs are currently Due or Overdue:
|
||||||
|
# - Fall back to time-based behaviour so early completion still works.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# 07:00, morning Due, evening Not Due:
|
||||||
|
# - Completes morning only.
|
||||||
|
#
|
||||||
|
# 23:30, morning Overdue, evening Overdue:
|
||||||
|
# - Completes morning and evening.
|
||||||
|
#
|
||||||
|
# 16:30, morning Complete, evening Not Due:
|
||||||
|
# - Completes evening early.
|
||||||
|
#
|
||||||
|
#:################################################################################:#
|
||||||
|
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.id == 'dog_fed_button' }}"
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
dog_feeding_morning_state: "{{ states('sensor.jobs_tracker_dog_feeding_morning') }}"
|
||||||
|
dog_feeding_evening_state: "{{ states('sensor.jobs_tracker_dog_feeding_evening') }}"
|
||||||
|
|
||||||
|
dog_feeding_any_due_or_overdue: >-
|
||||||
|
{{
|
||||||
|
dog_feeding_morning_state in ['Due', 'Overdue']
|
||||||
|
or dog_feeding_evening_state in ['Due', 'Overdue']
|
||||||
|
}}
|
||||||
|
|
||||||
|
#:############################################################################:#
|
||||||
|
# Complete all linked dog feeding jobs that are already Due or Overdue #
|
||||||
|
#:############################################################################:#
|
||||||
|
|
||||||
|
- if:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ dog_feeding_morning_state in ['Due', 'Overdue'] }}"
|
||||||
|
then:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dog_feeding_morning
|
||||||
|
|
||||||
|
- if:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ dog_feeding_evening_state in ['Due', 'Overdue'] }}"
|
||||||
|
then:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dog_feeding_evening
|
||||||
|
|
||||||
|
#:############################################################################:#
|
||||||
|
# Fallback for early completion #
|
||||||
|
#:############################################################################:#
|
||||||
|
#
|
||||||
|
# Only used when neither dog feeding job is currently Due or Overdue.
|
||||||
|
# This keeps useful behaviour such as feeding early before the due time.
|
||||||
|
# It will not overwrite an already Complete job.
|
||||||
|
|
||||||
|
- if:
|
||||||
|
- condition: template
|
||||||
|
value_template: >-
|
||||||
|
{{
|
||||||
|
not (dog_feeding_any_due_or_overdue | bool)
|
||||||
|
and now().hour < 12
|
||||||
|
and dog_feeding_morning_state != 'Complete'
|
||||||
|
}}
|
||||||
|
then:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dog_feeding_morning
|
||||||
|
|
||||||
|
- if:
|
||||||
|
- condition: template
|
||||||
|
value_template: >-
|
||||||
|
{{
|
||||||
|
not (dog_feeding_any_due_or_overdue | bool)
|
||||||
|
and now().hour >= 12
|
||||||
|
and dog_feeding_evening_state != 'Complete'
|
||||||
|
}}
|
||||||
|
then:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dog_feeding_evening
|
||||||
|
|
||||||
|
#:################################################################################:#
|
||||||
|
# Dog de-pooped physical button #
|
||||||
|
#:################################################################################:#
|
||||||
|
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.id == 'dog_depooped_button' }}"
|
||||||
|
sequence:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dog_depooping
|
||||||
|
|
||||||
|
#:################################################################################:#
|
||||||
|
# Shared jobs_done sensor #
|
||||||
|
#:################################################################################:#
|
||||||
|
#
|
||||||
|
# One state change marks both jobs complete.
|
||||||
|
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.id == 'jobs_done_sensor' }}"
|
||||||
|
sequence:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dishwasher_unpacked
|
||||||
|
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: rubbish_removed
|
||||||
|
|
||||||
|
#:################################################################################:#
|
||||||
|
# Manual test buttons #
|
||||||
|
#:################################################################################:#
|
||||||
|
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.id == 'manual_dog_feeding_morning' }}"
|
||||||
|
sequence:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dog_feeding_morning
|
||||||
|
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.id == 'manual_dog_feeding_evening' }}"
|
||||||
|
sequence:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dog_feeding_evening
|
||||||
|
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.id == 'manual_dog_depooping' }}"
|
||||||
|
sequence:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dog_depooping
|
||||||
|
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.id == 'manual_dishwasher_unpacked' }}"
|
||||||
|
sequence:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: dishwasher_unpacked
|
||||||
|
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.id == 'manual_rubbish_removed' }}"
|
||||||
|
sequence:
|
||||||
|
- action: script.jobs_tracker_dispatch
|
||||||
|
data:
|
||||||
|
job_id: rubbish_removed
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
################################################################################
|
||||||
|
# Jobs Tracker Processor Package
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# TITLE:
|
||||||
|
# Jobs Tracker Processor
|
||||||
|
#
|
||||||
|
# FILE:
|
||||||
|
# jobs_tracker_processor.yaml
|
||||||
|
#
|
||||||
|
# VERSION:
|
||||||
|
# V1.3.1 2026-06-20
|
||||||
|
#
|
||||||
|
# CHANGELOG:
|
||||||
|
# V1.3.1:
|
||||||
|
# - Corrected script field definitions so Home Assistant reliably creates
|
||||||
|
# script.jobs_tracker_update_job.
|
||||||
|
#
|
||||||
|
# V1.3:
|
||||||
|
# - Daily jobs reset at 00:00 but do not become Due until their configured
|
||||||
|
# due time.
|
||||||
|
# - Added "Not Due" state.
|
||||||
|
# - Completion is recalculated from retained last_completed_ts after restart.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
script:
|
||||||
|
jobs_tracker_update_job:
|
||||||
|
alias: "Jobs Tracker - Update Job"
|
||||||
|
description: "Updates one job's retained MQTT JSON state."
|
||||||
|
mode: queued
|
||||||
|
max: 20
|
||||||
|
|
||||||
|
fields:
|
||||||
|
job_id:
|
||||||
|
description: "Machine-safe job ID."
|
||||||
|
example: "dog_feeding_morning"
|
||||||
|
job_name:
|
||||||
|
description: "Human-readable job name."
|
||||||
|
example: "Dog Feeding Morning"
|
||||||
|
repeat_type:
|
||||||
|
description: "daily, weekly or monthly."
|
||||||
|
example: "daily"
|
||||||
|
due_time:
|
||||||
|
description: "Time the job is due, HH:MM."
|
||||||
|
example: "06:00"
|
||||||
|
due_weekday:
|
||||||
|
description: "For weekly jobs only. Monday=0, Friday=4, Sunday=6."
|
||||||
|
example: 4
|
||||||
|
due_day:
|
||||||
|
description: "For monthly jobs only. Use 1-28."
|
||||||
|
example: 1
|
||||||
|
mqtt_topic:
|
||||||
|
description: "MQTT topic used for retained job state."
|
||||||
|
example: "viewroad-status/jobs_tracker/dog_feeding_morning"
|
||||||
|
state_entity:
|
||||||
|
description: "MQTT sensor entity that reads this job's retained JSON."
|
||||||
|
example: "sensor.jobs_tracker_dog_feeding_morning"
|
||||||
|
mark_complete:
|
||||||
|
description: "Set true when a human has completed the job."
|
||||||
|
example: true
|
||||||
|
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
mark_complete_bool: "{{ mark_complete | default(false) | bool }}"
|
||||||
|
repeat_type_safe: "{{ repeat_type | default('daily') | lower }}"
|
||||||
|
due_time_safe: "{{ due_time | default('00:00') }}"
|
||||||
|
due_hour: "{{ due_time_safe.split(':')[0] | int(0) }}"
|
||||||
|
due_minute: "{{ due_time_safe.split(':')[1] | int(0) }}"
|
||||||
|
due_seconds: "{{ ((due_hour | int) * 3600) + ((due_minute | int) * 60) }}"
|
||||||
|
due_weekday_safe: "{{ due_weekday | default(0) | int(0) }}"
|
||||||
|
due_day_safe: "{{ due_day | default(1) | int(1) }}"
|
||||||
|
now_ts: "{{ as_timestamp(now()) }}"
|
||||||
|
today_midnight_ts: "{{ as_timestamp(today_at('00:00')) }}"
|
||||||
|
topic_safe: "{{ mqtt_topic | default('viewroad-status/jobs_tracker/' ~ job_id) }}"
|
||||||
|
|
||||||
|
- variables:
|
||||||
|
current_period_start_ts: >-
|
||||||
|
{% if repeat_type_safe == 'daily' %}
|
||||||
|
{{ today_midnight_ts | float(0) }}
|
||||||
|
{% elif repeat_type_safe == 'weekly' %}
|
||||||
|
{{ (today_midnight_ts | float(0)) - ((now().weekday() | int) * 86400) }}
|
||||||
|
{% elif repeat_type_safe == 'monthly' %}
|
||||||
|
{{ as_timestamp(now().replace(day=1, hour=0, minute=0, second=0, microsecond=0)) }}
|
||||||
|
{% else %}
|
||||||
|
{{ today_midnight_ts | float(0) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
next_period_start_ts: >-
|
||||||
|
{% if repeat_type_safe == 'daily' %}
|
||||||
|
{{ (today_midnight_ts | float(0)) + 86400 }}
|
||||||
|
{% elif repeat_type_safe == 'weekly' %}
|
||||||
|
{{ ((today_midnight_ts | float(0)) - ((now().weekday() | int) * 86400)) + 604800 }}
|
||||||
|
{% elif repeat_type_safe == 'monthly' %}
|
||||||
|
{% set next_month = (now().replace(day=28) + timedelta(days=4)).replace(day=1, hour=0, minute=0, second=0, microsecond=0) %}
|
||||||
|
{{ as_timestamp(next_month) }}
|
||||||
|
{% else %}
|
||||||
|
{{ (today_midnight_ts | float(0)) + 86400 }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- variables:
|
||||||
|
current_due_ts: >-
|
||||||
|
{% if repeat_type_safe == 'daily' %}
|
||||||
|
{{ (current_period_start_ts | float(0)) + (due_seconds | int) }}
|
||||||
|
{% elif repeat_type_safe == 'weekly' %}
|
||||||
|
{{ (current_period_start_ts | float(0)) + ((due_weekday_safe | int) * 86400) + (due_seconds | int) }}
|
||||||
|
{% elif repeat_type_safe == 'monthly' %}
|
||||||
|
{{ as_timestamp(now().replace(day=(due_day_safe | int), hour=(due_hour | int), minute=(due_minute | int), second=0, microsecond=0)) }}
|
||||||
|
{% else %}
|
||||||
|
{{ (current_period_start_ts | float(0)) + (due_seconds | int) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
next_due_ts: >-
|
||||||
|
{% if repeat_type_safe == 'daily' %}
|
||||||
|
{{ (next_period_start_ts | float(0)) + (due_seconds | int) }}
|
||||||
|
{% elif repeat_type_safe == 'weekly' %}
|
||||||
|
{{ (next_period_start_ts | float(0)) + ((due_weekday_safe | int) * 86400) + (due_seconds | int) }}
|
||||||
|
{% elif repeat_type_safe == 'monthly' %}
|
||||||
|
{% set next_month = (now().replace(day=28) + timedelta(days=4)).replace(day=1, hour=0, minute=0, second=0, microsecond=0) %}
|
||||||
|
{{ as_timestamp(next_month.replace(day=(due_day_safe | int), hour=(due_hour | int), minute=(due_minute | int), second=0, microsecond=0)) }}
|
||||||
|
{% else %}
|
||||||
|
{{ (next_period_start_ts | float(0)) + (due_seconds | int) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
overdue_after_minutes: >-
|
||||||
|
{% if repeat_type_safe == 'daily' %}
|
||||||
|
120
|
||||||
|
{% elif repeat_type_safe == 'weekly' %}
|
||||||
|
2880
|
||||||
|
{% elif repeat_type_safe == 'monthly' %}
|
||||||
|
20160
|
||||||
|
{% else %}
|
||||||
|
120
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- variables:
|
||||||
|
stored_last_completed_ts: >-
|
||||||
|
{% set v = state_attr(state_entity, 'last_completed_ts') %}
|
||||||
|
{{ 0 if v in [none, '', 'unknown', 'unavailable'] else v | float(0) }}
|
||||||
|
|
||||||
|
last_completed_ts: >-
|
||||||
|
{{ now_ts if mark_complete_bool else stored_last_completed_ts }}
|
||||||
|
|
||||||
|
- variables:
|
||||||
|
complete_bool: >-
|
||||||
|
{{ (last_completed_ts | float(0)) >= (current_period_start_ts | float(0)) }}
|
||||||
|
|
||||||
|
due_started_bool: >-
|
||||||
|
{% if repeat_type_safe == 'daily' %}
|
||||||
|
{{ (now_ts | float(0)) >= (current_due_ts | float(0)) }}
|
||||||
|
{% else %}
|
||||||
|
true
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
overdue_started_bool: >-
|
||||||
|
{{ (now_ts | float(0)) >= ((current_due_ts | float(0)) + ((overdue_after_minutes | int(0)) * 60)) }}
|
||||||
|
|
||||||
|
minutes_since_required: >-
|
||||||
|
{% if (now_ts | float(0)) <= (current_due_ts | float(0)) %}
|
||||||
|
0
|
||||||
|
{% else %}
|
||||||
|
{{ (((now_ts | float(0)) - (current_due_ts | float(0))) / 60) | round(0) | int }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
hours_since_required: >-
|
||||||
|
{% if (now_ts | float(0)) <= (current_due_ts | float(0)) %}
|
||||||
|
0
|
||||||
|
{% else %}
|
||||||
|
{{ (((now_ts | float(0)) - (current_due_ts | float(0))) / 3600) | round(0) | int }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- variables:
|
||||||
|
job_status: >-
|
||||||
|
{% if complete_bool | bool %}
|
||||||
|
Complete
|
||||||
|
{% elif not (due_started_bool | bool) %}
|
||||||
|
Not Due
|
||||||
|
{% elif overdue_started_bool | bool %}
|
||||||
|
Overdue
|
||||||
|
{% else %}
|
||||||
|
Due
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
display_due_ts: >-
|
||||||
|
{% if complete_bool | bool %}
|
||||||
|
{{ next_due_ts | float(0) }}
|
||||||
|
{% else %}
|
||||||
|
{{ current_due_ts | float(0) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
overdue_minutes: >-
|
||||||
|
{% if job_status | trim == 'Overdue' %}
|
||||||
|
{{ minutes_since_required | int(0) }}
|
||||||
|
{% else %}
|
||||||
|
0
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
overdue_hours: >-
|
||||||
|
{% if job_status | trim == 'Overdue' %}
|
||||||
|
{{ hours_since_required | int(0) }}
|
||||||
|
{% else %}
|
||||||
|
0
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
hours_since_completed: >-
|
||||||
|
{% if (last_completed_ts | float(0)) > 0 %}
|
||||||
|
{{ (((now_ts | float(0)) - (last_completed_ts | float(0))) / 3600) | round(0) | int }}
|
||||||
|
{% else %}
|
||||||
|
unknown
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
last_completed_time: >-
|
||||||
|
{% if (last_completed_ts | float(0)) > 0 %}
|
||||||
|
{{ (last_completed_ts | float(0)) | timestamp_custom('%H:%M', true) }}
|
||||||
|
{% else %}
|
||||||
|
Never
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
last_completed_human: >-
|
||||||
|
{% set ts = last_completed_ts | float(0) %}
|
||||||
|
{% if ts <= 0 %}
|
||||||
|
Never
|
||||||
|
{% else %}
|
||||||
|
{% set d = ts | timestamp_custom('%Y-%m-%d', true) %}
|
||||||
|
{% set t = ts | timestamp_custom('%H:%M', true) %}
|
||||||
|
{% set today = now().strftime('%Y-%m-%d') %}
|
||||||
|
{% set yesterday = (as_timestamp(today_at('00:00')) - 86400) | timestamp_custom('%Y-%m-%d', true) %}
|
||||||
|
{% set completed_midnight = as_timestamp(strptime(d ~ ' 00:00:00', '%Y-%m-%d %H:%M:%S')) %}
|
||||||
|
{% set days_ago = ((as_timestamp(today_at('00:00')) - completed_midnight) / 86400) | round(0) | int %}
|
||||||
|
{% if d == today %}
|
||||||
|
Today @ {{ t }}
|
||||||
|
{% elif d == yesterday %}
|
||||||
|
Yesterday @ {{ t }}
|
||||||
|
{% elif days_ago < 7 %}
|
||||||
|
Last {{ ts | timestamp_custom('%A', true) }} @ {{ t }}
|
||||||
|
{% else %}
|
||||||
|
{{ days_ago }} days ago @ {{ t }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
next_due_human: >-
|
||||||
|
{% set ts = display_due_ts | float(0) %}
|
||||||
|
{% set d = ts | timestamp_custom('%Y-%m-%d', true) %}
|
||||||
|
{% set t = ts | timestamp_custom('%H:%M', true) %}
|
||||||
|
{% set today = now().strftime('%Y-%m-%d') %}
|
||||||
|
{% set tomorrow = (as_timestamp(today_at('00:00')) + 86400) | timestamp_custom('%Y-%m-%d', true) %}
|
||||||
|
{% set yesterday = (as_timestamp(today_at('00:00')) - 86400) | timestamp_custom('%Y-%m-%d', true) %}
|
||||||
|
{% set due_midnight = as_timestamp(strptime(d ~ ' 00:00:00', '%Y-%m-%d %H:%M:%S')) %}
|
||||||
|
{% set days_diff = ((due_midnight - as_timestamp(today_at('00:00'))) / 86400) | round(0) | int %}
|
||||||
|
{% if d == today %}
|
||||||
|
Today @ {{ t }}
|
||||||
|
{% elif d == tomorrow %}
|
||||||
|
Tomorrow @ {{ t }}
|
||||||
|
{% elif d == yesterday %}
|
||||||
|
Yesterday @ {{ t }}
|
||||||
|
{% elif days_diff > 1 and days_diff <= 7 %}
|
||||||
|
Next {{ ts | timestamp_custom('%A', true) }} @ {{ t }}
|
||||||
|
{% elif days_diff < 0 and days_diff > -7 %}
|
||||||
|
Last {{ ts | timestamp_custom('%A', true) }} @ {{ t }}
|
||||||
|
{% elif days_diff > 7 %}
|
||||||
|
In {{ days_diff }} days @ {{ t }}
|
||||||
|
{% elif days_diff < -7 %}
|
||||||
|
{{ 0 - days_diff }} days ago @ {{ t }}
|
||||||
|
{% else %}
|
||||||
|
{{ ts | timestamp_custom('%Y-%m-%d @ %H:%M', true) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "{{ topic_safe }}"
|
||||||
|
retain: true
|
||||||
|
qos: 1
|
||||||
|
payload: >-
|
||||||
|
{{
|
||||||
|
{
|
||||||
|
"job_id": job_id,
|
||||||
|
"job_name": job_name,
|
||||||
|
"repeat": repeat_type_safe,
|
||||||
|
"due_time": due_time_safe,
|
||||||
|
"due_weekday": due_weekday_safe | int,
|
||||||
|
"due_day": due_day_safe | int,
|
||||||
|
|
||||||
|
"state": job_status | trim,
|
||||||
|
"complete": complete_bool | bool,
|
||||||
|
|
||||||
|
"last_completed_ts": last_completed_ts | float(0),
|
||||||
|
"last_completed_time": last_completed_time,
|
||||||
|
"last_completed_human": last_completed_human,
|
||||||
|
|
||||||
|
"completed_for_period_ts": current_period_start_ts | float(0) if complete_bool | bool else 0,
|
||||||
|
"completed_for_due_ts": current_period_start_ts | float(0) if complete_bool | bool else 0,
|
||||||
|
|
||||||
|
"current_period_start_ts": current_period_start_ts | float(0),
|
||||||
|
"next_period_start_ts": next_period_start_ts | float(0),
|
||||||
|
|
||||||
|
"current_due_ts": current_due_ts | float(0),
|
||||||
|
"next_due_ts": next_due_ts | float(0),
|
||||||
|
"display_due_ts": display_due_ts | float(0),
|
||||||
|
"next_due_human": next_due_human,
|
||||||
|
|
||||||
|
"due_started": due_started_bool | bool,
|
||||||
|
"overdue_started": overdue_started_bool | bool,
|
||||||
|
|
||||||
|
"minutes_since_required": minutes_since_required | int(0),
|
||||||
|
"hours_since_required": hours_since_required | int(0),
|
||||||
|
|
||||||
|
"overdue_after_minutes": overdue_after_minutes | int(0),
|
||||||
|
"overdue_minutes": overdue_minutes | int(0),
|
||||||
|
"overdue_hours": overdue_hours | int(0),
|
||||||
|
|
||||||
|
"hours_since_completed": hours_since_completed,
|
||||||
|
|
||||||
|
"last_updated_local": (now_ts | float(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true),
|
||||||
|
"mqtt_topic": topic_safe
|
||||||
|
} | tojson
|
||||||
|
}}
|
||||||
+415
-87
@@ -1,41 +1,51 @@
|
|||||||
###############################################################################
|
#:########################################################################################:#
|
||||||
# PACKAGE: OpenHASP Thermostat / Gas Heater Control
|
# PACKAGE: OpenHASP Thermostat / Gas Heater Control
|
||||||
# VERSION: v1.3 (2026-06-20)
|
# VERSION: v1.5 (2026-06-22)
|
||||||
# NODE/TOPIC ROOT: hasp/openhasp_1/
|
# NODE/TOPIC ROOT: hasp/openhasp_1/
|
||||||
#
|
#
|
||||||
# PAGE 1: Gas heater / lounge environment
|
# PAGE 1: Gas heater / lounge environment
|
||||||
# - p1b1 = main temperature display
|
# - p1b1 = gas heater setpoint large display
|
||||||
# - p1b2 = gas heater setpoint display
|
# - p1b2 = actual lounge room temperature display
|
||||||
# - p1b3 = gas heater setpoint down
|
# - p1b3 = gas heater setpoint down
|
||||||
# - p1b4 = gas heater setpoint up
|
# - p1b4 = gas heater setpoint up
|
||||||
# - p1b5 = gas heater status text
|
# - p1b5 = gas heater status text
|
||||||
# - p1b6 = gas heater operation / flame button
|
# - p1b6 = gas heater operation / flame button
|
||||||
# - p1b7 = humidity display
|
# - p1b7 = humidity display
|
||||||
#
|
#
|
||||||
# PAGE 2: Lounge climate control
|
# PAGE 2: Lounge climate heating control
|
||||||
# - p2b1 = main temperature display
|
# - p2b1 = climate.lounge setpoint large display
|
||||||
# - p2b2 = climate.lounge setpoint display
|
# - p2b2 = actual lounge room temperature display
|
||||||
# - p2b3 = climate.lounge setpoint down 0.5°C
|
# - p2b3 = climate.lounge setpoint down 0.5°C
|
||||||
# - p2b4 = climate.lounge setpoint up 0.5°C
|
# - p2b4 = climate.lounge setpoint up 0.5°C
|
||||||
# - p2b5 = climate.lounge status text
|
# - p2b5 = climate.lounge status text
|
||||||
# - p2b6 = climate.lounge heat mode / sun button
|
# - p2b6 = climate.lounge heat mode / sun button
|
||||||
# - p2b7 = humidity display
|
# - p2b7 = humidity display
|
||||||
#
|
#
|
||||||
# PAGE 3: Blank placeholder
|
# PAGE 3: Lounge climate cooling control
|
||||||
|
# - p3b1 = climate.lounge setpoint large display
|
||||||
|
# - p3b2 = actual lounge room temperature display
|
||||||
|
# - p3b3 = climate.lounge setpoint down 0.5°C
|
||||||
|
# - p3b4 = climate.lounge setpoint up 0.5°C
|
||||||
|
# - p3b5 = climate.lounge status text
|
||||||
|
# - p3b6 = climate.lounge cool mode / snowflake button
|
||||||
|
# - p3b7 = humidity display
|
||||||
#
|
#
|
||||||
# NOTES:
|
# NOTES:
|
||||||
# - p1b2/p1b7 and p2b2/p2b7 include their icons in the MQTT text payload so
|
# - The large top display is the setpoint for that device/page.
|
||||||
# HA updates do not overwrite icons loaded from pages.jsonl.
|
# - The middle-left temperature display is always the actual lounge room temp.
|
||||||
# - p1b3/p1b4 are yellow.
|
# - p1b3/p1b4 are yellow.
|
||||||
# - p1b6 is grey when OFF/BYPASS, yellow in AUTO, and red flame when firing.
|
# - p1b6 is grey when OFF/BYPASS, yellow in AUTO, and red flame when firing.
|
||||||
# - p2b3/p2b4 are pink.
|
# - p2b3/p2b4 are pink.
|
||||||
# - p2b6 turns climate.lounge ON and sets HVAC mode to heat.
|
# - p2b6 turns climate.lounge ON and sets HVAC mode to heat.
|
||||||
# - p2b6 is pink only when climate.lounge is in heat mode.
|
# - p2b6 is pink only when climate.lounge is in heat mode.
|
||||||
###############################################################################
|
# - p3b3/p3b4 are light blue.
|
||||||
|
# - p3b6 toggles climate.lounge OFF if already cooling; otherwise sets COOL.
|
||||||
|
# - p3b6 is light blue only when climate.lounge is in cool mode.
|
||||||
|
#:########################################################################################:#
|
||||||
|
|
||||||
###############################################################################
|
#:########################################################################################:#
|
||||||
# MQTT ENTITIES
|
# MQTT ENTITIES
|
||||||
###############################################################################
|
#:########################################################################################:#
|
||||||
|
|
||||||
mqtt:
|
mqtt:
|
||||||
sensor:
|
sensor:
|
||||||
@@ -70,16 +80,16 @@ mqtt:
|
|||||||
{{ raw }}
|
{{ raw }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
###############################################################################
|
#:########################################################################################:#
|
||||||
# AUTOMATIONS
|
# AUTOMATIONS
|
||||||
###############################################################################
|
#:########################################################################################:#
|
||||||
|
|
||||||
automation:
|
automation:
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Lounge temperature display on page 1 and page 2
|
# Actual lounge room temperature display on page 1, page 2, and page 3
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
- id: hasp_lounge_temp_to_page_text
|
- id: hasp_lounge_temp_to_page_text
|
||||||
alias: "HASP: update lounge temperature displays"
|
alias: "HASP: update lounge actual temperature displays"
|
||||||
mode: queued
|
mode: queued
|
||||||
trigger:
|
trigger:
|
||||||
- platform: state
|
- platform: state
|
||||||
@@ -104,10 +114,10 @@ automation:
|
|||||||
- service: script.hasp_publish_heater_status_text
|
- service: script.hasp_publish_heater_status_text
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Gas heater setpoint display on page 1
|
# Gas heater setpoint large display on page 1
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
- id: hasp_setpoint_to_btn_text
|
- id: hasp_setpoint_to_btn_text
|
||||||
alias: "HASP: update gas heater setpoint (p1b2.text)"
|
alias: "HASP: update gas heater setpoint (p1b1.text)"
|
||||||
mode: queued
|
mode: queued
|
||||||
trigger:
|
trigger:
|
||||||
- platform: state
|
- platform: state
|
||||||
@@ -116,7 +126,7 @@ automation:
|
|||||||
- service: script.hasp_publish_heater_setpoint
|
- service: script.hasp_publish_heater_setpoint
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Humidity display on page 1 and page 2
|
# Humidity display on page 1, page 2, and page 3
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
- id: hasp_humidity_to_btn_text
|
- id: hasp_humidity_to_btn_text
|
||||||
alias: "HASP: update humidity displays"
|
alias: "HASP: update humidity displays"
|
||||||
@@ -191,10 +201,10 @@ automation:
|
|||||||
- service: script.hasp_apply_heat_button_state
|
- service: script.hasp_apply_heat_button_state
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Page 2 climate.lounge text and button state updates
|
# Page 2 and page 3 climate.lounge text and button state updates
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
- id: hasp_lounge_climate_to_page2
|
- id: hasp_lounge_climate_to_page2
|
||||||
alias: "HASP: update lounge climate page 2"
|
alias: "HASP: update lounge climate pages 2 and 3"
|
||||||
mode: queued
|
mode: queued
|
||||||
trigger:
|
trigger:
|
||||||
- platform: state
|
- platform: state
|
||||||
@@ -202,6 +212,8 @@ automation:
|
|||||||
action:
|
action:
|
||||||
- service: script.hasp_push_page2_climate_texts
|
- service: script.hasp_push_page2_climate_texts
|
||||||
- service: script.hasp_apply_climate_heat_button_state
|
- service: script.hasp_apply_climate_heat_button_state
|
||||||
|
- service: script.hasp_push_page3_climate_texts
|
||||||
|
- service: script.hasp_apply_climate_cool_button_state
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Page 2 climate.lounge setpoint down
|
# Page 2 climate.lounge setpoint down
|
||||||
@@ -225,6 +237,7 @@ automation:
|
|||||||
{{ (current - 0.5) | round(1) }}
|
{{ (current - 0.5) | round(1) }}
|
||||||
- delay: "00:00:01"
|
- delay: "00:00:01"
|
||||||
- service: script.hasp_push_page2_climate_texts
|
- service: script.hasp_push_page2_climate_texts
|
||||||
|
- service: script.hasp_push_page3_climate_texts
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Page 2 climate.lounge setpoint up
|
# Page 2 climate.lounge setpoint up
|
||||||
@@ -248,6 +261,7 @@ automation:
|
|||||||
{{ (current + 0.5) | round(1) }}
|
{{ (current + 0.5) | round(1) }}
|
||||||
- delay: "00:00:01"
|
- delay: "00:00:01"
|
||||||
- service: script.hasp_push_page2_climate_texts
|
- service: script.hasp_push_page2_climate_texts
|
||||||
|
- service: script.hasp_push_page3_climate_texts
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Page 2 climate.lounge heat mode button
|
# Page 2 climate.lounge heat mode button
|
||||||
@@ -273,6 +287,97 @@ automation:
|
|||||||
- delay: "00:00:01"
|
- delay: "00:00:01"
|
||||||
- service: script.hasp_push_page2_climate_texts
|
- service: script.hasp_push_page2_climate_texts
|
||||||
- service: script.hasp_apply_climate_heat_button_state
|
- service: script.hasp_apply_climate_heat_button_state
|
||||||
|
- service: script.hasp_push_page3_climate_texts
|
||||||
|
- service: script.hasp_apply_climate_cool_button_state
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Page 3 climate.lounge setpoint down
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- id: hasp_page3_climate_down_press
|
||||||
|
alias: "HASP: p3b3 up -> lounge climate setpoint DOWN 0.5"
|
||||||
|
mode: queued
|
||||||
|
trigger:
|
||||||
|
- platform: mqtt
|
||||||
|
topic: "hasp/openhasp_1/state/p3b3"
|
||||||
|
condition:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.payload_json.event | default('') == 'up' }}"
|
||||||
|
action:
|
||||||
|
- service: climate.set_temperature
|
||||||
|
target:
|
||||||
|
entity_id: climate.lounge
|
||||||
|
data:
|
||||||
|
temperature: >
|
||||||
|
{% set current = state_attr('climate.lounge', 'temperature') | float(20) %}
|
||||||
|
{{ (current - 0.5) | round(1) }}
|
||||||
|
- delay: "00:00:01"
|
||||||
|
- service: script.hasp_push_page2_climate_texts
|
||||||
|
- service: script.hasp_push_page3_climate_texts
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Page 3 climate.lounge setpoint up
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- id: hasp_page3_climate_up_press
|
||||||
|
alias: "HASP: p3b4 up -> lounge climate setpoint UP 0.5"
|
||||||
|
mode: queued
|
||||||
|
trigger:
|
||||||
|
- platform: mqtt
|
||||||
|
topic: "hasp/openhasp_1/state/p3b4"
|
||||||
|
condition:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.payload_json.event | default('') == 'up' }}"
|
||||||
|
action:
|
||||||
|
- service: climate.set_temperature
|
||||||
|
target:
|
||||||
|
entity_id: climate.lounge
|
||||||
|
data:
|
||||||
|
temperature: >
|
||||||
|
{% set current = state_attr('climate.lounge', 'temperature') | float(20) %}
|
||||||
|
{{ (current + 0.5) | round(1) }}
|
||||||
|
- delay: "00:00:01"
|
||||||
|
- service: script.hasp_push_page2_climate_texts
|
||||||
|
- service: script.hasp_push_page3_climate_texts
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Page 3 climate.lounge cool mode button
|
||||||
|
# - If already cooling, turn OFF
|
||||||
|
# - Otherwise turn ON and set COOL
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- id: hasp_page3_climate_cool_press
|
||||||
|
alias: "HASP: p3b6 up -> lounge climate cool toggle"
|
||||||
|
mode: queued
|
||||||
|
trigger:
|
||||||
|
- platform: mqtt
|
||||||
|
topic: "hasp/openhasp_1/state/p3b6"
|
||||||
|
condition:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ trigger.payload_json.event | default('') == 'up' }}"
|
||||||
|
action:
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: state
|
||||||
|
entity_id: climate.lounge
|
||||||
|
state: "cool"
|
||||||
|
sequence:
|
||||||
|
- service: climate.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: climate.lounge
|
||||||
|
|
||||||
|
default:
|
||||||
|
- service: climate.turn_on
|
||||||
|
target:
|
||||||
|
entity_id: climate.lounge
|
||||||
|
- service: climate.set_hvac_mode
|
||||||
|
target:
|
||||||
|
entity_id: climate.lounge
|
||||||
|
data:
|
||||||
|
hvac_mode: cool
|
||||||
|
|
||||||
|
- delay: "00:00:01"
|
||||||
|
- service: script.hasp_push_page2_climate_texts
|
||||||
|
- service: script.hasp_apply_climate_heat_button_state
|
||||||
|
- service: script.hasp_push_page3_climate_texts
|
||||||
|
- service: script.hasp_apply_climate_cool_button_state
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Push all screen state when Home Assistant starts
|
# Push all screen state when Home Assistant starts
|
||||||
@@ -300,9 +405,9 @@ automation:
|
|||||||
- delay: "00:00:05"
|
- delay: "00:00:05"
|
||||||
- service: script.hasp_push_static_texts
|
- service: script.hasp_push_static_texts
|
||||||
|
|
||||||
###############################################################################
|
#:########################################################################################:#
|
||||||
# SCRIPTS
|
# SCRIPTS
|
||||||
###############################################################################
|
#:########################################################################################:#
|
||||||
|
|
||||||
script:
|
script:
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -315,6 +420,7 @@ script:
|
|||||||
- service: script.hasp_style_center_info_buttons
|
- service: script.hasp_style_center_info_buttons
|
||||||
- service: script.hasp_style_page1_control_buttons
|
- service: script.hasp_style_page1_control_buttons
|
||||||
- service: script.hasp_style_page2_control_buttons
|
- service: script.hasp_style_page2_control_buttons
|
||||||
|
- service: script.hasp_style_page3_control_buttons
|
||||||
- service: script.hasp_publish_lounge_temp_displays
|
- service: script.hasp_publish_lounge_temp_displays
|
||||||
- service: script.hasp_publish_heater_status_text
|
- service: script.hasp_publish_heater_status_text
|
||||||
- service: script.hasp_publish_heater_setpoint
|
- service: script.hasp_publish_heater_setpoint
|
||||||
@@ -322,61 +428,62 @@ script:
|
|||||||
- service: script.hasp_apply_heat_button_state
|
- service: script.hasp_apply_heat_button_state
|
||||||
- service: script.hasp_push_page2_climate_texts
|
- service: script.hasp_push_page2_climate_texts
|
||||||
- service: script.hasp_apply_climate_heat_button_state
|
- service: script.hasp_apply_climate_heat_button_state
|
||||||
|
- service: script.hasp_push_page3_climate_texts
|
||||||
|
- service: script.hasp_apply_climate_cool_button_state
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Publish lounge temperature to page 1 and page 2
|
# Publish actual lounge room temperature to all middle-left displays
|
||||||
|
# p1b2 = actual room temperature
|
||||||
|
# p2b2 = actual room temperature
|
||||||
|
# p3b2 = actual room temperature
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
hasp_publish_lounge_temp_displays:
|
hasp_publish_lounge_temp_displays:
|
||||||
alias: "HASP: publish lounge temperature displays"
|
alias: "HASP: publish lounge actual temperature displays"
|
||||||
mode: queued
|
|
||||||
sequence:
|
|
||||||
- service: mqtt.publish
|
|
||||||
data:
|
|
||||||
topic: "hasp/openhasp_1/command/p1b1.text"
|
|
||||||
payload: >-
|
|
||||||
{% set raw = states('sensor.lounge_environment_lounge_environment_temperature') %}
|
|
||||||
{% set cleaned = raw
|
|
||||||
| replace('°C', '')
|
|
||||||
| replace('° C', '')
|
|
||||||
| replace('C', '')
|
|
||||||
| trim %}
|
|
||||||
{% if cleaned not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
|
||||||
{{ cleaned | float(0) | round(1) }}°C
|
|
||||||
{% else %}
|
|
||||||
--°C
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
- service: mqtt.publish
|
|
||||||
data:
|
|
||||||
topic: "hasp/openhasp_1/command/p2b1.text"
|
|
||||||
payload: >-
|
|
||||||
{% set raw = states('sensor.lounge_environment_lounge_environment_temperature') %}
|
|
||||||
{% set cleaned = raw
|
|
||||||
| replace('°C', '')
|
|
||||||
| replace('° C', '')
|
|
||||||
| replace('C', '')
|
|
||||||
| trim %}
|
|
||||||
{% if cleaned not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
|
||||||
{{ cleaned | float(0) | round(1) }}°C
|
|
||||||
{% else %}
|
|
||||||
--°C
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Publish gas heater setpoint to page 1
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
hasp_publish_heater_setpoint:
|
|
||||||
alias: "HASP: publish gas heater setpoint"
|
|
||||||
mode: queued
|
mode: queued
|
||||||
sequence:
|
sequence:
|
||||||
- service: mqtt.publish
|
- service: mqtt.publish
|
||||||
data:
|
data:
|
||||||
topic: "hasp/openhasp_1/command/p1b2.text"
|
topic: "hasp/openhasp_1/command/p1b2.text"
|
||||||
payload: >-
|
payload: >-
|
||||||
{% set raw = states('sensor.rinnai_neo_gas_heater_setpoint_text') %}
|
{% set raw = states('sensor.lounge_environment_lounge_environment_temperature') %}
|
||||||
{% set cleaned = raw
|
{% set cleaned = raw
|
||||||
| replace('°C', '')
|
| replace('°C', '')
|
||||||
| replace('° C', '')
|
| replace('° C', '')
|
||||||
|
| replace('℃', '')
|
||||||
|
| replace('C', '')
|
||||||
|
| trim %}
|
||||||
|
{% if cleaned not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
||||||
|
{{ '\uE50F' }} {{ cleaned | float(0) | round(1) }}°C
|
||||||
|
{% else %}
|
||||||
|
{{ '\uE50F' }} --°C
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p2b2.text"
|
||||||
|
payload: >-
|
||||||
|
{% set raw = states('sensor.lounge_environment_lounge_environment_temperature') %}
|
||||||
|
{% set cleaned = raw
|
||||||
|
| replace('°C', '')
|
||||||
|
| replace('° C', '')
|
||||||
|
| replace('℃', '')
|
||||||
|
| replace('C', '')
|
||||||
|
| trim %}
|
||||||
|
{% if cleaned not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
||||||
|
{{ '\uE50F' }} {{ cleaned | float(0) | round(1) }}°C
|
||||||
|
{% else %}
|
||||||
|
{{ '\uE50F' }} --°C
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b2.text"
|
||||||
|
payload: >-
|
||||||
|
{% set raw = states('sensor.lounge_environment_lounge_environment_temperature') %}
|
||||||
|
{% set cleaned = raw
|
||||||
|
| replace('°C', '')
|
||||||
|
| replace('° C', '')
|
||||||
|
| replace('℃', '')
|
||||||
| replace('C', '')
|
| replace('C', '')
|
||||||
| trim %}
|
| trim %}
|
||||||
{% if cleaned not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
{% if cleaned not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
||||||
@@ -386,7 +493,32 @@ script:
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Publish humidity to page 1 and page 2
|
# Publish gas heater setpoint to page 1 large top display
|
||||||
|
# p1b1 = gas heater setpoint
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
hasp_publish_heater_setpoint:
|
||||||
|
alias: "HASP: publish gas heater setpoint"
|
||||||
|
mode: queued
|
||||||
|
sequence:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p1b1.text"
|
||||||
|
payload: >-
|
||||||
|
{% set raw = states('sensor.rinnai_neo_gas_heater_setpoint_text') %}
|
||||||
|
{% set cleaned = raw
|
||||||
|
| replace('°C', '')
|
||||||
|
| replace('° C', '')
|
||||||
|
| replace('℃', '')
|
||||||
|
| replace('C', '')
|
||||||
|
| trim %}
|
||||||
|
{% if cleaned not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
||||||
|
{{ '\uE238' }}{{ cleaned | float(0) | round(1) }}°C
|
||||||
|
{% else %}
|
||||||
|
{{ '\uE238' }}--°C
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Publish humidity to page 1, page 2, and page 3
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
hasp_publish_humidity_displays:
|
hasp_publish_humidity_displays:
|
||||||
alias: "HASP: publish humidity displays"
|
alias: "HASP: publish humidity displays"
|
||||||
@@ -416,6 +548,18 @@ script:
|
|||||||
{{ '\uE58C' }} {{ cleaned }}%
|
{{ '\uE58C' }} {{ cleaned }}%
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b7.text"
|
||||||
|
payload: >-
|
||||||
|
{% set raw = states('sensor.lounge_environment_humidity_mqtt') %}
|
||||||
|
{% set cleaned = raw | replace('%', '') | trim %}
|
||||||
|
{% if cleaned in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
||||||
|
{{ '\uE58C' }} --%
|
||||||
|
{% else %}
|
||||||
|
{{ '\uE58C' }} {{ cleaned }}%
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Publish gas heater status text to page 1 bottom label
|
# Publish gas heater status text to page 1 bottom label
|
||||||
#
|
#
|
||||||
@@ -423,10 +567,7 @@ script:
|
|||||||
# - OFF -> Off
|
# - OFF -> Off
|
||||||
# - BYPASS (Remote Only) -> Bypass (Remote Only)
|
# - BYPASS (Remote Only) -> Bypass (Remote Only)
|
||||||
# - Auto but flame OFF -> Auto (Up to Temp)
|
# - Auto but flame OFF -> Auto (Up to Temp)
|
||||||
# - Auto (Level 1) -> Auto + 1 flame icon
|
# - Auto while firing -> Auto (Level 1), Auto (Level 2), etc.
|
||||||
# - Auto (Level 2) -> Auto + 2 flame icons
|
|
||||||
# - Auto (Level 3) -> Auto + 3 flame icons
|
|
||||||
# - Auto (Level 4) -> Auto + 4 flame icons
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
hasp_publish_heater_status_text:
|
hasp_publish_heater_status_text:
|
||||||
alias: "HASP: publish heater status text"
|
alias: "HASP: publish heater status text"
|
||||||
@@ -440,7 +581,6 @@ script:
|
|||||||
{% set op = states('switch.rinnai_neo_gas_heater_heater_operation') %}
|
{% set op = states('switch.rinnai_neo_gas_heater_heater_operation') %}
|
||||||
{% set flame_on = is_state('switch.rinnai_neo_gas_heater_rinnai_neo_gas_heater_activate_flame', 'on') %}
|
{% set flame_on = is_state('switch.rinnai_neo_gas_heater_rinnai_neo_gas_heater_activate_flame', 'on') %}
|
||||||
{% set s = states('sensor.rinnai_neo_gas_heater_rinnai_neo_gas_heater_status_text') %}
|
{% set s = states('sensor.rinnai_neo_gas_heater_rinnai_neo_gas_heater_status_text') %}
|
||||||
{% set flame = '\uE238' %}
|
|
||||||
|
|
||||||
{% if mode == 'OFF' %}
|
{% if mode == 'OFF' %}
|
||||||
Off
|
Off
|
||||||
@@ -449,17 +589,7 @@ script:
|
|||||||
{% elif op == 'on' and not flame_on %}
|
{% elif op == 'on' and not flame_on %}
|
||||||
Auto (Up to Temp)
|
Auto (Up to Temp)
|
||||||
{% elif s not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
{% elif s not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
||||||
{% set matches = s | regex_findall('Level ([0-9]+)') %}
|
|
||||||
{% if matches | length > 0 %}
|
|
||||||
{% set level = matches[0] | int(0) %}
|
|
||||||
{% else %}
|
|
||||||
{% set level = 0 %}
|
|
||||||
{% endif %}
|
|
||||||
{% if level > 0 %}
|
|
||||||
Auto {{ flame * level }}
|
|
||||||
{% else %}
|
|
||||||
{{ s }}
|
{{ s }}
|
||||||
{% endif %}
|
|
||||||
{% elif mode not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
{% elif mode not in ['unknown', 'unavailable', '', 'none', 'None'] %}
|
||||||
{{ mode }}
|
{{ mode }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -604,13 +734,13 @@ script:
|
|||||||
sequence:
|
sequence:
|
||||||
- service: mqtt.publish
|
- service: mqtt.publish
|
||||||
data:
|
data:
|
||||||
topic: "hasp/openhasp_1/command/p2b2.text"
|
topic: "hasp/openhasp_1/command/p2b1.text"
|
||||||
payload: >-
|
payload: >-
|
||||||
{% set target = state_attr('climate.lounge', 'temperature') %}
|
{% set target = state_attr('climate.lounge', 'temperature') %}
|
||||||
{% if target is not none %}
|
{% if target is not none %}
|
||||||
{{ '\uE50F' }} {{ target | float(0) | round(1) }}°C
|
{{ '\uE5A8' }}{{ target | float(0) | round(1) }}°C
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ '\uE50F' }} --°C
|
{{ '\uE5A8' }}--°C
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
- service: mqtt.publish
|
- service: mqtt.publish
|
||||||
@@ -663,6 +793,74 @@ script:
|
|||||||
{{ mode_text }}
|
{{ mode_text }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Push page 3 climate.lounge setpoint and status text
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
hasp_push_page3_climate_texts:
|
||||||
|
alias: "HASP: push page 3 climate texts"
|
||||||
|
mode: queued
|
||||||
|
sequence:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b1.text"
|
||||||
|
payload: >-
|
||||||
|
{% set target = state_attr('climate.lounge', 'temperature') %}
|
||||||
|
{% if target is not none %}
|
||||||
|
{{ '\uE717' }}{{ target | float(0) | round(1) }}°C
|
||||||
|
{% else %}
|
||||||
|
{{ '\uE717' }}--°C
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b5.text"
|
||||||
|
payload: >-
|
||||||
|
{% set mode = states('climate.lounge') %}
|
||||||
|
{% set mode_map = {
|
||||||
|
'off': 'Off',
|
||||||
|
'heat': 'Heating',
|
||||||
|
'cool': 'Cooling',
|
||||||
|
'heat_cool': 'Auto',
|
||||||
|
'auto': 'Auto',
|
||||||
|
'dry': 'Dry',
|
||||||
|
'fan_only': 'Fan'
|
||||||
|
} %}
|
||||||
|
{% set mode_text = mode_map.get(mode, mode | replace('_', ' ') | title) %}
|
||||||
|
|
||||||
|
{% set preset = state_attr('climate.lounge', 'preset_mode') | default('', true) | string | lower %}
|
||||||
|
{% set fan = state_attr('climate.lounge', 'fan_mode') | default('', true) | string | lower %}
|
||||||
|
{% set swing = state_attr('climate.lounge', 'swing_mode') | default('', true) | string | lower %}
|
||||||
|
|
||||||
|
{% set eco_attr = state_attr('climate.lounge', 'eco') | default('', true) | string | lower %}
|
||||||
|
{% set powerful_attr = state_attr('climate.lounge', 'powerful') | default('', true) | string | lower %}
|
||||||
|
{% set quiet_attr = state_attr('climate.lounge', 'quiet') | default('', true) | string | lower %}
|
||||||
|
{% set nanoe_attr = state_attr('climate.lounge', 'nanoe') | default('', true) | string | lower %}
|
||||||
|
{% set nanoe_x_attr = state_attr('climate.lounge', 'nanoe_x') | default('', true) | string | lower %}
|
||||||
|
|
||||||
|
{% set ns = namespace(extras=[]) %}
|
||||||
|
|
||||||
|
{% if 'eco' in preset or eco_attr in ['on', 'true', '1', 'yes'] %}
|
||||||
|
{% set ns.extras = ns.extras + ['Eco'] %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if 'powerful' in preset or 'boost' in preset or powerful_attr in ['on', 'true', '1', 'yes'] %}
|
||||||
|
{% set ns.extras = ns.extras + ['Powerful'] %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if 'quiet' in preset or 'quiet' in fan or quiet_attr in ['on', 'true', '1', 'yes'] %}
|
||||||
|
{% set ns.extras = ns.extras + ['Quiet'] %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if 'nanoe' in preset or 'nanoe' in swing or nanoe_attr in ['on', 'true', '1', 'yes'] or nanoe_x_attr in ['on', 'true', '1', 'yes'] %}
|
||||||
|
{% set ns.extras = ns.extras + ['nanoe-x'] %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ns.extras | length > 0 %}
|
||||||
|
{{ mode_text }} ({{ ns.extras | join(', ') }})
|
||||||
|
{% else %}
|
||||||
|
{{ mode_text }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Apply page 2 climate.lounge heat button state
|
# Apply page 2 climate.lounge heat button state
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -673,7 +871,7 @@ script:
|
|||||||
- service: mqtt.publish
|
- service: mqtt.publish
|
||||||
data:
|
data:
|
||||||
topic: "hasp/openhasp_1/command/p2b6.text"
|
topic: "hasp/openhasp_1/command/p2b6.text"
|
||||||
payload: "\uE599"
|
payload: "\uE5A8"
|
||||||
|
|
||||||
- choose:
|
- choose:
|
||||||
# climate.lounge is ON and in HEAT mode -> pink sun
|
# climate.lounge is ON and in HEAT mode -> pink sun
|
||||||
@@ -718,7 +916,61 @@ script:
|
|||||||
payload: "#2F3240"
|
payload: "#2F3240"
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Style page 1 and page 2 setpoint/humidity display buttons as black panels
|
# Apply page 3 climate.lounge cool button state
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
hasp_apply_climate_cool_button_state:
|
||||||
|
alias: "HASP: apply lounge climate cool button state"
|
||||||
|
mode: queued
|
||||||
|
sequence:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b6.text"
|
||||||
|
payload: "\uE717"
|
||||||
|
|
||||||
|
- choose:
|
||||||
|
# climate.lounge is ON and in COOL mode -> light blue snowflake
|
||||||
|
- conditions:
|
||||||
|
- condition: state
|
||||||
|
entity_id: climate.lounge
|
||||||
|
state: "cool"
|
||||||
|
sequence:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b6.text_color"
|
||||||
|
payload: "#FFFFFF"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b6.bg_color"
|
||||||
|
payload: "#0288D1"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b6.bg_grad_color"
|
||||||
|
payload: "#4FC3F7"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b6.border_color"
|
||||||
|
payload: "#0288D1"
|
||||||
|
|
||||||
|
default:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b6.text_color"
|
||||||
|
payload: "#C0C0C0"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b6.bg_color"
|
||||||
|
payload: "#2F3240"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b6.bg_grad_color"
|
||||||
|
payload: "#2F3240"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b6.border_color"
|
||||||
|
payload: "#2F3240"
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Style page 1, page 2, and page 3 actual temp/humidity displays
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
hasp_style_center_info_buttons:
|
hasp_style_center_info_buttons:
|
||||||
alias: "HASP: style centre info buttons"
|
alias: "HASP: style centre info buttons"
|
||||||
@@ -792,6 +1044,40 @@ script:
|
|||||||
topic: "hasp/openhasp_1/command/p2b7.border_color"
|
topic: "hasp/openhasp_1/command/p2b7.border_color"
|
||||||
payload: "#000000"
|
payload: "#000000"
|
||||||
|
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b2.text_color"
|
||||||
|
payload: "#FFFFFF"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b2.bg_color"
|
||||||
|
payload: "#000000"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b2.bg_grad_color"
|
||||||
|
payload: "#000000"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b2.border_color"
|
||||||
|
payload: "#000000"
|
||||||
|
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b7.text_color"
|
||||||
|
payload: "#FFFFFF"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b7.bg_color"
|
||||||
|
payload: "#000000"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b7.bg_grad_color"
|
||||||
|
payload: "#000000"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b7.border_color"
|
||||||
|
payload: "#000000"
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Style page 1 gas heater up/down buttons yellow
|
# Style page 1 gas heater up/down buttons yellow
|
||||||
# p1b6 is state-driven by hasp_apply_heat_button_state.
|
# p1b6 is state-driven by hasp_apply_heat_button_state.
|
||||||
@@ -875,3 +1161,45 @@ script:
|
|||||||
data:
|
data:
|
||||||
topic: "hasp/openhasp_1/command/p2b4.border_color"
|
topic: "hasp/openhasp_1/command/p2b4.border_color"
|
||||||
payload: "#C2185B"
|
payload: "#C2185B"
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Style page 3 climate up/down buttons light blue
|
||||||
|
# p3b6 is state-driven by hasp_apply_climate_cool_button_state.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
hasp_style_page3_control_buttons:
|
||||||
|
alias: "HASP: style page 3 control buttons"
|
||||||
|
mode: queued
|
||||||
|
sequence:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b3.text_color"
|
||||||
|
payload: "#FFFFFF"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b3.bg_color"
|
||||||
|
payload: "#0288D1"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b3.bg_grad_color"
|
||||||
|
payload: "#4FC3F7"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b3.border_color"
|
||||||
|
payload: "#0288D1"
|
||||||
|
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b4.text_color"
|
||||||
|
payload: "#FFFFFF"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b4.bg_color"
|
||||||
|
payload: "#0288D1"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b4.bg_grad_color"
|
||||||
|
payload: "#4FC3F7"
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "hasp/openhasp_1/command/p3b4.border_color"
|
||||||
|
payload: "#0288D1"
|
||||||
Reference in New Issue
Block a user