Send OctoEverywhere alerts to Google Chat instead of Notify.Events
Replace the Notify.Events delivery with a Google Chat space webhook. The automation builds a cardsV2 card (printer/event header, print details, camera snapshot, Open printer button) and falls back to plain text if Google rejects the card or the request fails. Adds rest_command.google_chat_octoeverywhere, which needs google_chat_octoeverywhere_webhook in secrets.yaml on the host. The notify_event_octoeverywhere_token secret is no longer used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7831b35af2
commit
54eaa88616
@@ -0,0 +1,124 @@
|
||||
### OctoEverywhere -> Google Chat ###
|
||||
# OctoEverywhere (LAN mode) POSTs fixed JSON to this webhook. Google Chat needs a
|
||||
# `text` or `cardsV2` body, so this automation builds a card and sends it through
|
||||
# rest_command.google_chat_octoeverywhere (helpers/rest_command.yaml).
|
||||
# Requires `google_chat_octoeverywhere_webhook` in secrets.yaml.
|
||||
#
|
||||
# Card: header (printer + event), details (file, progress, layer, time, error),
|
||||
# camera snapshot, and an "Open printer" button to OctoEverywhere Quick View.
|
||||
# If Google rejects the card or the request fails, a plain-text message is sent.
|
||||
#
|
||||
# Event types: https://docs.octoeverywhere.com/webhook-notifications/event-types/
|
||||
# Forwarded ("important only"): 1-5, 7-12, 16. Skipped: 6 Progress, 13, 14, 15.
|
||||
|
||||
- id: '1793812953197'
|
||||
alias: OctoEverywhere to Google Chat
|
||||
description: Turns OctoEverywhere webhook JSON into Google Chat cards
|
||||
mode: queued
|
||||
max: 10
|
||||
triggers:
|
||||
- trigger: webhook
|
||||
webhook_id: octoeverywhere-7301a739d954078c5a5c5fb3
|
||||
allowed_methods:
|
||||
- POST
|
||||
local_only: true
|
||||
actions:
|
||||
# HA only fills trigger.json when the request's Content-Type contains "json".
|
||||
# Otherwise the body is dropped and p is empty, which the safety net catches.
|
||||
- variables:
|
||||
p: "{{ trigger.json | default({}, true) }}"
|
||||
ev: "{{ p.EventType | default(0) | int(0) }}"
|
||||
wanted: [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 16]
|
||||
names:
|
||||
"1": Print started
|
||||
"2": Print complete
|
||||
"3": Print failed
|
||||
"4": Print paused
|
||||
"5": Print resumed
|
||||
"6": Print progress
|
||||
"7": Gadget possible failure
|
||||
"8": Gadget paused the print
|
||||
"9": Printer error
|
||||
"10": First layer complete
|
||||
"11": Filament change required
|
||||
"12": Needs your attention
|
||||
"13": OctoEverywhere daily notification limit reached
|
||||
"14": Third layer complete
|
||||
"15": Bed cool down complete
|
||||
"16": Test notification
|
||||
|
||||
# Safety net: body wasn't readable JSON, so say so instead of failing silently
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ not p }}"
|
||||
then:
|
||||
- action: rest_command.google_chat_octoeverywhere
|
||||
data:
|
||||
payload:
|
||||
text: "⚠️ *OctoEverywhere webhook problem:* Home Assistant got a webhook it could not read as JSON. Check the trace for the \"OctoEverywhere to Google Chat\" automation."
|
||||
- stop: Body was not JSON
|
||||
|
||||
# Drop events we don't want
|
||||
- condition: template
|
||||
value_template: "{{ ev in wanted }}"
|
||||
|
||||
# Build the message
|
||||
- variables:
|
||||
printer: "{{ p.PrinterName | default('Printer', true) | string }}"
|
||||
event_name: "{{ names[ev | string] | default('Event ' ~ ev, true) }}"
|
||||
emoji: "{{ '🛑' if ev in [3, 8, 9] else '⚠️' if ev in [4, 7, 11, 12] else '✅' if ev == 2 else '🖨️' }}"
|
||||
# [label, value] rows, shared by the card and the plain-text fallback
|
||||
details: >-
|
||||
{%- set rows = [] -%}
|
||||
{%- if p.FileName %}{% set rows = rows + [['File', p.FileName | string]] %}{% endif -%}
|
||||
{%- if p.Progress is defined and ev not in [1, 2] %}{% set rows = rows + [['Progress', (p.Progress | int(0)) ~ '%']] %}{% endif -%}
|
||||
{%- if (p.TotalLayers | int(0)) > 0 and ev not in [1, 2] %}{% set rows = rows + [['Layer', (p.CurrentLayer | int(0)) ~ ' of ' ~ (p.TotalLayers | int(0))]] %}{% endif -%}
|
||||
{%- if (p.TimeRemainingSec | int(0)) > 0 and ev not in [2, 3] -%}
|
||||
{%- set s = p.TimeRemainingSec | int(0) -%}
|
||||
{%- set rows = rows + [['Time left', (s // 3600) ~ 'h ' ~ ((s % 3600) // 60) ~ 'm']] -%}
|
||||
{%- endif -%}
|
||||
{%- if p.DurationSec is defined and ev in [2, 3] -%}
|
||||
{%- set d = p.DurationSec | int(0) -%}
|
||||
{%- set rows = rows + [['Ran for', (d // 3600) ~ 'h ' ~ ((d % 3600) // 60) ~ 'm']] -%}
|
||||
{%- endif -%}
|
||||
{%- if p.Error %}{% set rows = rows + [['Error', p.Error | string]] %}{% endif -%}
|
||||
{{ rows }}
|
||||
# Google Chat Message body: short text (shows in phone notifications) + card
|
||||
card: >-
|
||||
{%- set ns = namespace(w=[]) -%}
|
||||
{%- for r in details -%}
|
||||
{%- set ns.w = ns.w + [{'decoratedText': {'topLabel': r[0], 'text': r[1] | replace('&', '&') | replace('<', '<') | replace('>', '>'), 'wrapText': true}}] -%}
|
||||
{%- endfor -%}
|
||||
{%- set sections = [{'widgets': ns.w if ns.w else [{'decoratedText': {'text': event_name, 'wrapText': true}}]}] -%}
|
||||
{%- if p.SnapshotUrl %}{% set sections = sections + [{'widgets': [{'image': {'imageUrl': p.SnapshotUrl | string, 'altText': printer ~ ' camera snapshot'}}]}] %}{% endif -%}
|
||||
{%- if p.QuickViewUrl %}{% set sections = sections + [{'widgets': [{'buttonList': {'buttons': [{'text': 'Open printer', 'onClick': {'openLink': {'url': p.QuickViewUrl | string}}}]}}]}] %}{% endif -%}
|
||||
{{ {'text': emoji ~ ' *' ~ printer ~ ':* ' ~ event_name,
|
||||
'cardsV2': [{'cardId': 'octoeverywhere-' ~ ev,
|
||||
'card': {'header': {'title': printer, 'subtitle': emoji ~ ' ' ~ event_name},
|
||||
'sections': sections}}]} }}
|
||||
# Plain-text fallback in Google Chat markup (*bold*, <url|label>)
|
||||
plain: >-
|
||||
{%- set ns = namespace(lines=[emoji ~ ' *' ~ printer ~ ':* ' ~ event_name]) -%}
|
||||
{%- for r in details %}{% set ns.lines = ns.lines + ['*' ~ r[0] ~ ':* ' ~ r[1]] %}{% endfor -%}
|
||||
{%- if p.SnapshotUrl %}{% set ns.lines = ns.lines + ['<' ~ p.SnapshotUrl ~ '|Snapshot>'] %}{% endif -%}
|
||||
{%- if p.QuickViewUrl %}{% set ns.lines = ns.lines + ['<' ~ p.QuickViewUrl ~ '|Open printer>'] %}{% endif -%}
|
||||
{{ ns.lines | join('\n') }}
|
||||
|
||||
# Send the card
|
||||
- action: rest_command.google_chat_octoeverywhere
|
||||
data:
|
||||
payload: "{{ card }}"
|
||||
response_variable: chat_resp
|
||||
continue_on_error: true
|
||||
|
||||
# rest_command doesn't raise on HTTP 4xx/5xx, so check the status. If the card
|
||||
# was rejected (or the request failed), send plain text so the alert still lands.
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ chat_resp is not defined or (chat_resp.status | int(0)) != 200 }}"
|
||||
then:
|
||||
- delay: 2
|
||||
- action: rest_command.google_chat_octoeverywhere
|
||||
data:
|
||||
payload:
|
||||
text: "{{ plain }}"
|
||||
@@ -1,100 +0,0 @@
|
||||
### OctoEverywhere -> Notify.Events ###
|
||||
# OctoEverywhere (LAN mode) POSTs fixed JSON to this webhook. Notify.Events can't
|
||||
# parse that JSON itself, so this automation turns it into a readable alert.
|
||||
#
|
||||
# Token: the notify_events platform ignores per-platform api_key and always uses
|
||||
# the global notify_events token (Cats). Passing `token` inside `data` overrides
|
||||
# it per message, same pattern as script.notify_events_alerts.
|
||||
# Requires `notify_event_octoeverywhere_token` in secrets.yaml (the Home Assistant
|
||||
# source token from the Notify.Events "OctoEverywhere" channel).
|
||||
#
|
||||
# Event types: https://docs.octoeverywhere.com/webhook-notifications/event-types/
|
||||
# Forwarded ("important only"): 1-5, 7-12, 16. Skipped: 6 Progress, 13, 14, 15.
|
||||
|
||||
- id: '1793812953197'
|
||||
alias: OctoEverywhere to Notify.Events
|
||||
description: Turns OctoEverywhere webhook JSON into readable Notify.Events alerts
|
||||
mode: queued
|
||||
max: 10
|
||||
triggers:
|
||||
- trigger: webhook
|
||||
webhook_id: octoeverywhere-7301a739d954078c5a5c5fb3
|
||||
allowed_methods:
|
||||
- POST
|
||||
local_only: true
|
||||
actions:
|
||||
# HA only fills trigger.json when the request's Content-Type contains "json".
|
||||
# Otherwise the body is dropped and p is empty, which the safety net catches.
|
||||
- variables:
|
||||
p: "{{ trigger.json | default({}, true) }}"
|
||||
ev: "{{ p.EventType | default(0) | int(0) }}"
|
||||
wanted: [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 16]
|
||||
names:
|
||||
"1": Print started
|
||||
"2": Print complete
|
||||
"3": Print failed
|
||||
"4": Print paused
|
||||
"5": Print resumed
|
||||
"6": Print progress
|
||||
"7": Gadget possible failure
|
||||
"8": Gadget paused the print
|
||||
"9": Printer error
|
||||
"10": First layer complete
|
||||
"11": Filament change required
|
||||
"12": Needs your attention
|
||||
"13": OctoEverywhere daily notification limit reached
|
||||
"14": Third layer complete
|
||||
"15": Bed cool down complete
|
||||
"16": Test notification
|
||||
|
||||
# Safety net: body wasn't readable JSON, so say so instead of failing silently
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ not p }}"
|
||||
then:
|
||||
- action: notify.notify_events_alerts
|
||||
data:
|
||||
message: OctoEverywhere sent a webhook that Home Assistant could not read as JSON. Check this automation's trace.
|
||||
data:
|
||||
token: !secret notify_event_octoeverywhere_token
|
||||
title: OctoEverywhere webhook problem
|
||||
level: warning
|
||||
- stop: Body was not JSON
|
||||
|
||||
# Drop events we don't want
|
||||
- condition: template
|
||||
value_template: "{{ ev in wanted }}"
|
||||
|
||||
# Build the message
|
||||
- variables:
|
||||
printer: "{{ p.PrinterName | default('Printer', true) }}"
|
||||
event_name: "{{ names[ev | string] | default('Event ' ~ ev, true) }}"
|
||||
level: "{{ 'error' if ev in [3, 8, 9] else 'warning' if ev in [4, 7, 11, 12] else 'success' if ev == 2 else 'info' }}"
|
||||
priority: "{{ 'high' if ev in [3, 4, 7, 8, 9, 11, 12] else 'normal' }}"
|
||||
images: "{{ [{'url': p.SnapshotUrl, 'name': 'snapshot.jpg'}] if p.SnapshotUrl else [] }}"
|
||||
body: >-
|
||||
{%- set parts = [] -%}
|
||||
{%- if p.FileName %}{% set parts = parts + ['<b>File:</b> ' ~ p.FileName] %}{% endif -%}
|
||||
{%- if p.Progress is defined and ev not in [1, 2] %}{% set parts = parts + ['<b>Progress:</b> ' ~ p.Progress ~ '%'] %}{% endif -%}
|
||||
{%- if p.TimeRemainingSec is defined and (p.TimeRemainingSec | int(0)) > 0 and ev not in [2, 3] -%}
|
||||
{%- set s = p.TimeRemainingSec | int(0) -%}
|
||||
{%- set parts = parts + ['<b>Time left:</b> ' ~ (s // 3600) ~ 'h ' ~ ((s % 3600) // 60) ~ 'm'] -%}
|
||||
{%- endif -%}
|
||||
{%- if p.DurationSec is defined and ev in [2, 3] -%}
|
||||
{%- set d = p.DurationSec | int(0) -%}
|
||||
{%- set parts = parts + ['<b>Ran for:</b> ' ~ (d // 3600) ~ 'h ' ~ ((d % 3600) // 60) ~ 'm'] -%}
|
||||
{%- endif -%}
|
||||
{%- if p.Error %}{% set parts = parts + ['<b>Error:</b> ' ~ p.Error] %}{% endif -%}
|
||||
{%- if p.QuickViewUrl %}{% set parts = parts + ['<a href="' ~ p.QuickViewUrl ~ '">Open printer</a>'] %}{% endif -%}
|
||||
{{ parts | join('<br>') }}
|
||||
|
||||
# Send it. If a snapshot ever breaks delivery, remove the images line.
|
||||
- action: notify.notify_events_alerts
|
||||
data:
|
||||
message: "{{ body if body else event_name }}"
|
||||
data:
|
||||
token: !secret notify_event_octoeverywhere_token
|
||||
title: "{{ printer }}: {{ event_name }}"
|
||||
level: "{{ level }}"
|
||||
priority: "{{ priority }}"
|
||||
images: "{{ images }}"
|
||||
@@ -35,3 +35,12 @@ notion_update_print:
|
||||
"date:Completed:start": { "date": { "start": "{{ completed }}", "time_zone": "America/New_York" } }
|
||||
}
|
||||
}
|
||||
|
||||
# Google Chat incoming webhook (space webhook) for OctoEverywhere printer alerts.
|
||||
# Call with `payload` = a dict shaped like a Chat Message body (text and/or cardsV2).
|
||||
# Used by automations/05_printers_and_workshop/05c_octoeverywhere_google_chat.yaml.
|
||||
google_chat_octoeverywhere:
|
||||
url: !secret google_chat_octoeverywhere_webhook
|
||||
method: POST
|
||||
content_type: "application/json; charset=UTF-8"
|
||||
payload: "{{ payload | to_json }}"
|
||||
|
||||
Reference in New Issue
Block a user