From c9451eeeb8a7838c51567621172d46f1f3924682 Mon Sep 17 00:00:00 2001 From: Justin Oro Date: Mon, 17 Aug 2026 12:04:41 -0700 Subject: [PATCH] Add {clock} field for notification timestamps; fix offline preview path and validator allowlist --- solixauto/engine.py | 7 +++++++ solixauto/rules.py | 12 ++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/solixauto/engine.py b/solixauto/engine.py index 2f21948..a0e5823 100644 --- a/solixauto/engine.py +++ b/solixauto/engine.py @@ -166,6 +166,12 @@ def stamp(): return datetime.now().strftime("%Y-%m-%d %H:%M:%S") +def clock_stamp(): + now = datetime.now() + hour12 = now.hour % 12 or 12 + return f"{hour12}:{now.minute:02d} {now.strftime('%p')}" + + class Reporter: def __init__(self, log_path=None, quiet=False): self.quiet = quiet @@ -355,6 +361,7 @@ class Engine: "target_host": self.target.host, "target_channel": self.target.channel, "time": stamp(), + "clock": clock_stamp(), } ) return context diff --git a/solixauto/rules.py b/solixauto/rules.py index 17b98c7..3c58121 100644 --- a/solixauto/rules.py +++ b/solixauto/rules.py @@ -43,7 +43,7 @@ ACTIONS = {"target.on", "target.off", "none"} DURATION_PATTERN = re.compile(r"^\s*(\d+(?:\.\d+)?)\s*([smh]?)\s*$", re.IGNORECASE) DEFAULT_NOTIFY_TEMPLATE = ( - "{source_name} battery {battery_soc}%, solar {pv_total}W. " + "{clock} {source_name} battery {battery_soc}%, solar {pv_total}W. " "{target_name} turned {action}." ) DEFAULT_NOTIFY_THROTTLE = 300 @@ -197,14 +197,14 @@ class NotificationSettings: self.stale_template = str( raw.get("stale_template") or ( - "{source_name}: telemetry lost ({reason}). Last seen battery " - "{battery_soc}% at {last_seen}. {target_name} was turned {action} " - "as a precaution." + "{clock} {source_name}: telemetry lost ({reason}). Last seen " + "battery {battery_soc}% at {last_seen}. {target_name} was " + "turned {action} as a precaution." ) ) self.recovered_template = str( raw.get("recovered_template") - or "{source_name}: telemetry is back after {outage}. Rules resumed." + or "{clock} {source_name}: telemetry is back after {outage}. Rules resumed." ) self.title = str(raw.get("title") or "{profile}") self.throttle = parse_duration( @@ -537,7 +537,7 @@ def validate(profile): context_names = { "profile", "rule", "action", "action_word", "source_name", "source_model", "source_serial", "target_name", "target_model", "target_host", - "target_channel", "condition", "time", "reason", "event", + "target_channel", "condition", "time", "clock", "reason", "event", } for template in templates_to_check: for field in notify_module.template_fields(template):