various packages
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
################################################################################
|
||||
# Jobs Tracker Processor Package
|
||||
################################################################################
|
||||
#:########################################################################################:#
|
||||
# Jobs Tracker Processor Package #
|
||||
#:########################################################################################:#
|
||||
#
|
||||
# TITLE:
|
||||
# Jobs Tracker Processor
|
||||
@@ -9,20 +9,122 @@
|
||||
# jobs_tracker_processor.yaml
|
||||
#
|
||||
# VERSION:
|
||||
# V1.3.1 2026-06-20
|
||||
# V1.6 2026-07-10
|
||||
#
|
||||
# CHANGELOG:
|
||||
# V1.3.1:
|
||||
# PURPOSE:
|
||||
# Generic processing engine for the Jobs Tracker system.
|
||||
#
|
||||
# OPERATION:
|
||||
# This package is the generic processing engine for the Jobs Tracker system.
|
||||
# It is not normally edited when adding, removing or changing household jobs.
|
||||
# Jobs are defined in the separate job-list package, which calls this processor
|
||||
# through script.jobs_tracker_update_job. The processor calculates whether each
|
||||
# job is Not Due, Missed, Due, Overdue or Complete, then publishes the
|
||||
# result as retained MQTT JSON to that job's configured MQTT topic. The retained
|
||||
# MQTT payload stores the last completion timestamp, so after a Home Assistant
|
||||
# restart the processor can recalculate the correct state from the last physical
|
||||
# button/action press.
|
||||
#
|
||||
# Normal operation is automatic. The job-list package refreshes all jobs on a
|
||||
# regular timer and after Home Assistant starts, and it calls this processor with
|
||||
# mark_complete: true when a physical button, event, sensor, MQTT button or other
|
||||
# completion action confirms a job has been completed. A job is considered
|
||||
# Complete when its stored last_completed_ts is within the current reset period.
|
||||
#
|
||||
# Daily jobs reset at 00:00 but only become Due at their configured due_time.
|
||||
# Weekly jobs reset at the week rollover, currently Monday 00:00. Monthly jobs
|
||||
# reset at the month rollover, currently the 1st day of the month at 00:00.
|
||||
#
|
||||
# If the previous reset period was missed, jobs without a completion window are
|
||||
# marked Missed after the reset and before the current due time. Jobs with a
|
||||
# completion window are marked Missed after the current completion window ends.
|
||||
# This lets jobs such as morning dog feeding show Missed after the morning
|
||||
# window, but then reset to Not Due/Due again the next morning.
|
||||
#
|
||||
# Once the current due time arrives, a job changes to Due, then Overdue after
|
||||
# the configured threshold unless the job has reached the end of its completion
|
||||
# window, in which case it changes to Missed. Completing a job early is allowed
|
||||
# once the current reset period has started.
|
||||
#
|
||||
# SETTINGS:
|
||||
# The job-list package passes these settings into this processor for each job:
|
||||
#
|
||||
# job_id:
|
||||
# Machine-safe ID used for matching and MQTT topic naming.
|
||||
# Example: dog_feeding_morning
|
||||
#
|
||||
# job_name:
|
||||
# Human-readable job name used in the retained MQTT JSON.
|
||||
# Example: Dog Feeding Morning
|
||||
#
|
||||
# repeat_type:
|
||||
# Schedule type. Valid values are daily, weekly or monthly.
|
||||
#
|
||||
# due_time:
|
||||
# Time the job is due, in HH:MM 24-hour format.
|
||||
# Example: "06:00"
|
||||
#
|
||||
# due_weekday:
|
||||
# Weekly jobs only. Monday=0, Tuesday=1, Wednesday=2, Thursday=3,
|
||||
# Friday=4, Saturday=5, Sunday=6.
|
||||
#
|
||||
# due_day:
|
||||
# Monthly jobs only. Use 1-28 for best reliability across all months.
|
||||
#
|
||||
# mqtt_topic:
|
||||
# Retained MQTT state topic for this job.
|
||||
# Example: viewroad-status/jobs_tracker/dog_feeding_morning
|
||||
#
|
||||
# state_entity:
|
||||
# MQTT sensor entity that reads this job's retained JSON payload.
|
||||
# Example: sensor.jobs_tracker_dog_feeding_morning
|
||||
#
|
||||
# mark_complete:
|
||||
# false when refreshing/calculating the job state.
|
||||
# true when a physical/manual completion action has just occurred.
|
||||
#
|
||||
# completion_window_start / completion_window_end:
|
||||
# Optional HH:MM window used for jobs that share a physical button or have
|
||||
# a clear daily completion window. If the current window ends before the
|
||||
# job is completed, the job is marked Missed instead of remaining Overdue.
|
||||
# Example: "00:00" to "11:59" for morning dog feeding.
|
||||
#
|
||||
# Current built-in overdue thresholds are:
|
||||
# - Daily jobs: 2 hours after due_time
|
||||
# - Weekly jobs: 2 days after due_time
|
||||
# - Monthly jobs: 2 weeks after due_time
|
||||
#
|
||||
# VERSION HISTORY:
|
||||
# V1.6 2026-07-10
|
||||
# - Renamed displayed state from Missed to Missed.
|
||||
# - Added optional completion_window_start and completion_window_end fields.
|
||||
# - Windowed jobs now become Missed when the completion window has ended.
|
||||
# - Windowed jobs reset normally into Not Due / Due in the next period.
|
||||
#
|
||||
# V1.5 2026-07-07
|
||||
# - Documentation update for the refactored registry-driven job-list package.
|
||||
# - No functional processing change from V1.4.
|
||||
#
|
||||
# V1.4 2026-07-06
|
||||
# - Added Missed state.
|
||||
# - If the previous reset period was missed, the job now shows Previous
|
||||
# Missed until the current period's due time.
|
||||
# - Missed applies to daily, weekly and monthly jobs.
|
||||
# - Due now starts at the configured due time for daily, weekly and monthly
|
||||
# jobs.
|
||||
# - Early completion remains supported after the current reset period starts.
|
||||
#
|
||||
# V1.3.1 2026-06-20
|
||||
# - Corrected script field definitions so Home Assistant reliably creates
|
||||
# script.jobs_tracker_update_job.
|
||||
#
|
||||
# V1.3:
|
||||
# V1.3 2026-06-20
|
||||
# - Daily jobs reset at 00:00 but do not become Due until their configured
|
||||
# due time.
|
||||
# - Added "Not Due" state.
|
||||
# - Added Not Due state.
|
||||
# - Completion is recalculated from retained last_completed_ts after restart.
|
||||
#
|
||||
################################################################################
|
||||
#:########################################################################################:#
|
||||
|
||||
script:
|
||||
jobs_tracker_update_job:
|
||||
@@ -59,6 +161,12 @@ script:
|
||||
mark_complete:
|
||||
description: "Set true when a human has completed the job."
|
||||
example: true
|
||||
completion_window_start:
|
||||
description: "Optional HH:MM completion window start."
|
||||
example: "00:00"
|
||||
completion_window_end:
|
||||
description: "Optional HH:MM completion window end."
|
||||
example: "11:59"
|
||||
|
||||
sequence:
|
||||
- variables:
|
||||
@@ -73,6 +181,17 @@ script:
|
||||
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) }}"
|
||||
completion_window_start_safe: "{{ completion_window_start | default('') }}"
|
||||
completion_window_end_safe: "{{ completion_window_end | default('') }}"
|
||||
has_completion_window: >-
|
||||
{{
|
||||
completion_window_start_safe not in ['', none, 'unknown', 'unavailable']
|
||||
and completion_window_end_safe not in ['', none, 'unknown', 'unavailable']
|
||||
}}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Current and next reset periods #
|
||||
#:####################################################################################:#
|
||||
|
||||
- variables:
|
||||
current_period_start_ts: >-
|
||||
@@ -98,6 +217,28 @@ script:
|
||||
{{ (today_midnight_ts | float(0)) + 86400 }}
|
||||
{% endif %}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Previous reset period #
|
||||
#:####################################################################################:#
|
||||
|
||||
- variables:
|
||||
previous_period_start_ts: >-
|
||||
{% if repeat_type_safe == 'daily' %}
|
||||
{{ (current_period_start_ts | float(0)) - 86400 }}
|
||||
{% elif repeat_type_safe == 'weekly' %}
|
||||
{{ (current_period_start_ts | float(0)) - 604800 }}
|
||||
{% elif repeat_type_safe == 'monthly' %}
|
||||
{% set first_day_this_month = now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) %}
|
||||
{% set last_day_previous_month = first_day_this_month - timedelta(days=1) %}
|
||||
{{ as_timestamp(last_day_previous_month.replace(day=1, hour=0, minute=0, second=0, microsecond=0)) }}
|
||||
{% else %}
|
||||
{{ (current_period_start_ts | float(0)) - 86400 }}
|
||||
{% endif %}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Due times #
|
||||
#:####################################################################################:#
|
||||
|
||||
- variables:
|
||||
current_due_ts: >-
|
||||
{% if repeat_type_safe == 'daily' %}
|
||||
@@ -110,6 +251,19 @@ script:
|
||||
{{ (current_period_start_ts | float(0)) + (due_seconds | int) }}
|
||||
{% endif %}
|
||||
|
||||
previous_due_ts: >-
|
||||
{% if repeat_type_safe == 'daily' %}
|
||||
{{ (previous_period_start_ts | float(0)) + (due_seconds | int) }}
|
||||
{% elif repeat_type_safe == 'weekly' %}
|
||||
{{ (previous_period_start_ts | float(0)) + ((due_weekday_safe | int) * 86400) + (due_seconds | int) }}
|
||||
{% elif repeat_type_safe == 'monthly' %}
|
||||
{% set first_day_this_month = now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) %}
|
||||
{% set last_day_previous_month = first_day_this_month - timedelta(days=1) %}
|
||||
{{ as_timestamp(last_day_previous_month.replace(day=(due_day_safe | int), hour=(due_hour | int), minute=(due_minute | int), second=0, microsecond=0)) }}
|
||||
{% else %}
|
||||
{{ (previous_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) }}
|
||||
@@ -133,6 +287,60 @@ script:
|
||||
120
|
||||
{% endif %}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Completion window timing #
|
||||
#:####################################################################################:#
|
||||
#
|
||||
# For windowed jobs, Missed begins when the completion window has ended.
|
||||
# The window is anchored to the current due date. End times are treated as
|
||||
# inclusive through the final minute, so "11:59" ends at 11:59:59.
|
||||
|
||||
- variables:
|
||||
completion_window_start_seconds: >-
|
||||
{% if has_completion_window | bool %}
|
||||
{% set t = completion_window_start_safe.split(':') %}
|
||||
{{ ((t[0] | int(0)) * 3600) + ((t[1] | int(0)) * 60) }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
|
||||
completion_window_end_seconds: >-
|
||||
{% if has_completion_window | bool %}
|
||||
{% set t = completion_window_end_safe.split(':') %}
|
||||
{{ ((t[0] | int(23)) * 3600) + ((t[1] | int(59)) * 60) + 59 }}
|
||||
{% else %}
|
||||
86399
|
||||
{% endif %}
|
||||
|
||||
current_due_day_start_ts: >-
|
||||
{% set d = (current_due_ts | float(0)) | timestamp_custom('%Y-%m-%d', true) %}
|
||||
{{ as_timestamp(strptime(d ~ ' 00:00:00', '%Y-%m-%d %H:%M:%S')) }}
|
||||
|
||||
current_completion_window_start_ts: >-
|
||||
{% if has_completion_window | bool %}
|
||||
{{ (current_due_day_start_ts | float(0)) + (completion_window_start_seconds | int(0)) }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
|
||||
current_completion_window_end_ts: >-
|
||||
{% if has_completion_window | bool %}
|
||||
{% set start_seconds = completion_window_start_seconds | int(0) %}
|
||||
{% set end_seconds = completion_window_end_seconds | int(86399) %}
|
||||
{% set base_end = (current_due_day_start_ts | float(0)) + end_seconds %}
|
||||
{% if end_seconds < start_seconds %}
|
||||
{{ base_end + 86400 }}
|
||||
{% else %}
|
||||
{{ base_end }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Restore retained completion timestamp #
|
||||
#:####################################################################################:#
|
||||
|
||||
- variables:
|
||||
stored_last_completed_ts: >-
|
||||
{% set v = state_attr(state_entity, 'last_completed_ts') %}
|
||||
@@ -141,20 +349,52 @@ script:
|
||||
last_completed_ts: >-
|
||||
{{ now_ts if mark_complete_bool else stored_last_completed_ts }}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Completion and timing state #
|
||||
#:####################################################################################:#
|
||||
|
||||
- variables:
|
||||
complete_bool: >-
|
||||
{{ (last_completed_ts | float(0)) >= (current_period_start_ts | float(0)) }}
|
||||
|
||||
previous_completed_bool: >-
|
||||
{{
|
||||
(last_completed_ts | float(0)) >= (previous_period_start_ts | float(0))
|
||||
and (last_completed_ts | float(0)) < (current_period_start_ts | float(0))
|
||||
}}
|
||||
|
||||
previous_missed_bool: >-
|
||||
{{ not (previous_completed_bool | bool) }}
|
||||
|
||||
due_started_bool: >-
|
||||
{% if repeat_type_safe == 'daily' %}
|
||||
{{ (now_ts | float(0)) >= (current_due_ts | float(0)) }}
|
||||
{% else %}
|
||||
true
|
||||
{% endif %}
|
||||
{{ (now_ts | float(0)) >= (current_due_ts | float(0)) }}
|
||||
|
||||
overdue_started_bool: >-
|
||||
{{ (now_ts | float(0)) >= ((current_due_ts | float(0)) + ((overdue_after_minutes | int(0)) * 60)) }}
|
||||
|
||||
previous_missed_active_bool: >-
|
||||
{{
|
||||
previous_missed_bool | bool
|
||||
and not (complete_bool | bool)
|
||||
and not (due_started_bool | bool)
|
||||
}}
|
||||
|
||||
completion_window_missed_bool: >-
|
||||
{{
|
||||
has_completion_window | bool
|
||||
and not (complete_bool | bool)
|
||||
and (now_ts | float(0)) > (current_completion_window_end_ts | float(0))
|
||||
}}
|
||||
|
||||
missed_bool: >-
|
||||
{{
|
||||
completion_window_missed_bool | bool
|
||||
or (
|
||||
previous_missed_active_bool | bool
|
||||
and not (has_completion_window | bool)
|
||||
)
|
||||
}}
|
||||
|
||||
minutes_since_required: >-
|
||||
{% if (now_ts | float(0)) <= (current_due_ts | float(0)) %}
|
||||
0
|
||||
@@ -169,10 +409,16 @@ script:
|
||||
{{ (((now_ts | float(0)) - (current_due_ts | float(0))) / 3600) | round(0) | int }}
|
||||
{% endif %}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Display state #
|
||||
#:####################################################################################:#
|
||||
|
||||
- variables:
|
||||
job_status: >-
|
||||
{% if complete_bool | bool %}
|
||||
Complete
|
||||
{% elif missed_bool | bool %}
|
||||
Missed
|
||||
{% elif not (due_started_bool | bool) %}
|
||||
Not Due
|
||||
{% elif overdue_started_bool | bool %}
|
||||
@@ -265,52 +511,64 @@ script:
|
||||
{{ ts | timestamp_custom('%Y-%m-%d @ %H:%M', true) }}
|
||||
{% endif %}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Publish retained MQTT JSON #
|
||||
#:####################################################################################:#
|
||||
|
||||
- variables:
|
||||
completed_for_period_ts_safe: >-
|
||||
{% if complete_bool | bool %}
|
||||
{{ current_period_start_ts | float(0) }}
|
||||
{% else %}
|
||||
0
|
||||
{% 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
|
||||
}}
|
||||
{
|
||||
"job_id": {{ job_id | tojson }},
|
||||
"job_name": {{ job_name | tojson }},
|
||||
"repeat": {{ repeat_type_safe | tojson }},
|
||||
"due_time": {{ due_time_safe | tojson }},
|
||||
"due_weekday": {{ due_weekday_safe | int }},
|
||||
"due_day": {{ due_day_safe | int }},
|
||||
"state": {{ job_status | trim | tojson }},
|
||||
"complete": {{ complete_bool | bool | tojson }},
|
||||
"last_completed_ts": {{ last_completed_ts | float(0) }},
|
||||
"last_completed_time": {{ last_completed_time | tojson }},
|
||||
"last_completed_human": {{ last_completed_human | tojson }},
|
||||
"previous_period_start_ts": {{ previous_period_start_ts | float(0) }},
|
||||
"previous_due_ts": {{ previous_due_ts | float(0) }},
|
||||
"previous_completed": {{ previous_completed_bool | bool | tojson }},
|
||||
"previous_missed": {{ previous_missed_bool | bool | tojson }},
|
||||
"previous_missed_active": {{ previous_missed_active_bool | bool | tojson }},
|
||||
"missed": {{ missed_bool | bool | tojson }},
|
||||
"completion_window_start": {{ completion_window_start_safe | tojson }},
|
||||
"completion_window_end": {{ completion_window_end_safe | tojson }},
|
||||
"has_completion_window": {{ has_completion_window | bool | tojson }},
|
||||
"completion_window_missed": {{ completion_window_missed_bool | bool | tojson }},
|
||||
"current_completion_window_start_ts": {{ current_completion_window_start_ts | float(0) }},
|
||||
"current_completion_window_end_ts": {{ current_completion_window_end_ts | float(0) }},
|
||||
"completed_for_period_ts": {{ completed_for_period_ts_safe | float(0) }},
|
||||
"completed_for_due_ts": {{ completed_for_period_ts_safe | float(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 | tojson }},
|
||||
"due_started": {{ due_started_bool | bool | tojson }},
|
||||
"overdue_started": {{ overdue_started_bool | bool | tojson }},
|
||||
"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 | tojson }},
|
||||
"last_updated_local": {{ ((now_ts | float(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true)) | tojson }},
|
||||
"mqtt_topic": {{ topic_safe | tojson }}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user