openhasp mods and file shuffling

This commit is contained in:
zorruno
2026-06-22 21:38:18 +12:00
parent e7b0677810
commit 0db246aefa
11 changed files with 1604 additions and 93 deletions
@@ -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
}}
@@ -1,41 +1,51 @@
###############################################################################
#:########################################################################################:#
# PACKAGE: OpenHASP Thermostat / Gas Heater Control
# VERSION: v1.3 (2026-06-20)
# VERSION: v1.5 (2026-06-22)
# NODE/TOPIC ROOT: hasp/openhasp_1/
#
# PAGE 1: Gas heater / lounge environment
# - p1b1 = main temperature display
# - p1b2 = gas heater setpoint display
# - p1b1 = gas heater setpoint large display
# - p1b2 = actual lounge room temperature display
# - p1b3 = gas heater setpoint down
# - p1b4 = gas heater setpoint up
# - p1b5 = gas heater status text
# - p1b6 = gas heater operation / flame button
# - p1b7 = humidity display
#
# PAGE 2: Lounge climate control
# - p2b1 = main temperature display
# - p2b2 = climate.lounge setpoint display
# PAGE 2: Lounge climate heating control
# - p2b1 = climate.lounge setpoint large display
# - p2b2 = actual lounge room temperature display
# - p2b3 = climate.lounge setpoint down 0.5°C
# - p2b4 = climate.lounge setpoint up 0.5°C
# - p2b5 = climate.lounge status text
# - p2b6 = climate.lounge heat mode / sun button
# - 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:
# - p1b2/p1b7 and p2b2/p2b7 include their icons in the MQTT text payload so
# HA updates do not overwrite icons loaded from pages.jsonl.
# - The large top display is the setpoint for that device/page.
# - The middle-left temperature display is always the actual lounge room temp.
# - p1b3/p1b4 are yellow.
# - p1b6 is grey when OFF/BYPASS, yellow in AUTO, and red flame when firing.
# - p2b3/p2b4 are pink.
# - p2b6 turns climate.lounge ON and sets HVAC mode to heat.
# - 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:
sensor:
@@ -70,16 +80,16 @@ mqtt:
{{ raw }}
{% endif %}
###############################################################################
#:########################################################################################:#
# AUTOMATIONS
###############################################################################
#:########################################################################################:#
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
alias: "HASP: update lounge temperature displays"
alias: "HASP: update lounge actual temperature displays"
mode: queued
trigger:
- platform: state
@@ -104,10 +114,10 @@ automation:
- 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
alias: "HASP: update gas heater setpoint (p1b2.text)"
alias: "HASP: update gas heater setpoint (p1b1.text)"
mode: queued
trigger:
- platform: state
@@ -116,7 +126,7 @@ automation:
- 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
alias: "HASP: update humidity displays"
@@ -191,10 +201,10 @@ automation:
- 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
alias: "HASP: update lounge climate page 2"
alias: "HASP: update lounge climate pages 2 and 3"
mode: queued
trigger:
- platform: state
@@ -202,6 +212,8 @@ automation:
action:
- 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
# -------------------------------------------------------------------------
# Page 2 climate.lounge setpoint down
@@ -225,6 +237,7 @@ automation:
{{ (current - 0.5) | round(1) }}
- delay: "00:00:01"
- service: script.hasp_push_page2_climate_texts
- service: script.hasp_push_page3_climate_texts
# -------------------------------------------------------------------------
# Page 2 climate.lounge setpoint up
@@ -248,6 +261,7 @@ automation:
{{ (current + 0.5) | round(1) }}
- delay: "00:00:01"
- service: script.hasp_push_page2_climate_texts
- service: script.hasp_push_page3_climate_texts
# -------------------------------------------------------------------------
# Page 2 climate.lounge heat mode button
@@ -273,6 +287,97 @@ automation:
- 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
# -------------------------------------------------------------------------
# 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
@@ -300,9 +405,9 @@ automation:
- delay: "00:00:05"
- service: script.hasp_push_static_texts
###############################################################################
#:########################################################################################:#
# SCRIPTS
###############################################################################
#:########################################################################################:#
script:
# -------------------------------------------------------------------------
@@ -315,6 +420,7 @@ script:
- service: script.hasp_style_center_info_buttons
- service: script.hasp_style_page1_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_heater_status_text
- service: script.hasp_publish_heater_setpoint
@@ -322,61 +428,62 @@ script:
- service: script.hasp_apply_heat_button_state
- 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
# -------------------------------------------------------------------------
# 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:
alias: "HASP: publish lounge 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"
alias: "HASP: publish lounge actual temperature displays"
mode: queued
sequence:
- service: mqtt.publish
data:
topic: "hasp/openhasp_1/command/p1b2.text"
payload: >-
{% set raw = states('sensor.rinnai_neo_gas_heater_setpoint_text') %}
{% 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/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', '')
| trim %}
{% if cleaned not in ['unknown', 'unavailable', '', 'none', 'None'] %}
@@ -386,7 +493,32 @@ script:
{% 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:
alias: "HASP: publish humidity displays"
@@ -416,6 +548,18 @@ script:
{{ '\uE58C' }} {{ cleaned }}%
{% 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
#
@@ -423,10 +567,7 @@ script:
# - OFF -> Off
# - BYPASS (Remote Only) -> Bypass (Remote Only)
# - Auto but flame OFF -> Auto (Up to Temp)
# - Auto (Level 1) -> Auto + 1 flame icon
# - Auto (Level 2) -> Auto + 2 flame icons
# - Auto (Level 3) -> Auto + 3 flame icons
# - Auto (Level 4) -> Auto + 4 flame icons
# - Auto while firing -> Auto (Level 1), Auto (Level 2), etc.
# -------------------------------------------------------------------------
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 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 flame = '\uE238' %}
{% if mode == 'OFF' %}
Off
@@ -449,17 +589,7 @@ script:
{% elif op == 'on' and not flame_on %}
Auto (Up to Temp)
{% 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 }}
{% endif %}
{{ s }}
{% elif mode not in ['unknown', 'unavailable', '', 'none', 'None'] %}
{{ mode }}
{% else %}
@@ -604,13 +734,13 @@ script:
sequence:
- service: mqtt.publish
data:
topic: "hasp/openhasp_1/command/p2b2.text"
topic: "hasp/openhasp_1/command/p2b1.text"
payload: >-
{% set target = state_attr('climate.lounge', 'temperature') %}
{% if target is not none %}
{{ '\uE50F' }} {{ target | float(0) | round(1) }}°C
{{ '\uE5A8' }}{{ target | float(0) | round(1) }}°C
{% else %}
{{ '\uE50F' }} --°C
{{ '\uE5A8' }}--°C
{% endif %}
- service: mqtt.publish
@@ -663,6 +793,74 @@ script:
{{ mode_text }}
{% 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
# -------------------------------------------------------------------------
@@ -673,7 +871,7 @@ script:
- service: mqtt.publish
data:
topic: "hasp/openhasp_1/command/p2b6.text"
payload: "\uE599"
payload: "\uE5A8"
- choose:
# climate.lounge is ON and in HEAT mode -> pink sun
@@ -718,7 +916,61 @@ script:
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:
alias: "HASP: style centre info buttons"
@@ -792,6 +1044,40 @@ script:
topic: "hasp/openhasp_1/command/p2b7.border_color"
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
# p1b6 is state-driven by hasp_apply_heat_button_state.
@@ -875,3 +1161,45 @@ script:
data:
topic: "hasp/openhasp_1/command/p2b4.border_color"
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"