esphome devices & packages
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -1 +1 @@
|
|||||||
{"pid": 67, "version": 1, "ha_version": "2026.5.4", "start_ts": 1779529638.1563158}
|
{"pid": 71, "version": 1, "ha_version": "2026.6.0", "start_ts": 1780535631.6044178}
|
||||||
@@ -0,0 +1,916 @@
|
|||||||
|
#:########################################################################################:#
|
||||||
|
# TITLE: LED MATRIX 1
|
||||||
|
# zorruno.com layout v1.1 2026
|
||||||
|
# ESP32-C3 SuperMini MAX7219 LED matrix clock and scroll display.
|
||||||
|
#:########################################################################################:#
|
||||||
|
# REFERENCES:
|
||||||
|
# Original Project Reference https://github.com/RealDeco/matrixclock-esphome
|
||||||
|
# Fonts: https://github.com/RealDeco/matrixclock-esphome/tree/main/fonts
|
||||||
|
# 3D Printed Case (includes project notes in the 3mf files)
|
||||||
|
# https://github.com/RealDeco/matrixclock-esphome/tree/main/3DPrint
|
||||||
|
#:########################################################################################:#
|
||||||
|
# REPO:
|
||||||
|
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-ledmatrix1.yaml
|
||||||
|
#:########################################################################################:#
|
||||||
|
# VERSIONS:
|
||||||
|
# V1.3 2026-05-24 Added native API action for Home Assistant direct scroll text commands.
|
||||||
|
# V1.2 2026-05-24 Added 1-15 brightness level slider and improved operation notes.
|
||||||
|
# V1.1 2026-05-23 Updated yaml to zorruno layout V1.1, cleaned comments, fixed SNTP time IDs,
|
||||||
|
# added current_mins fallback display, removed unused MQTT topic, and fixed
|
||||||
|
# blank scroll handling.
|
||||||
|
# V1.0 2026-05-23 Initial Version
|
||||||
|
#:########################################################################################:#
|
||||||
|
# HARDWARE:
|
||||||
|
# - ESP32-C3 SuperMini.
|
||||||
|
# - MAX7219 4-chip LED matrix display, typically 32 x 8 pixels.
|
||||||
|
# - SPI wiring configured below:
|
||||||
|
# - MOSI: GPIO8
|
||||||
|
# - CS: GPIO9
|
||||||
|
# - SCK: GPIO10
|
||||||
|
# - Power note: 4 MAX7219 modules needs a suitable external 5 V supply.
|
||||||
|
# - Keep ESP32-C3 GND and matrix power supply GND connected together.
|
||||||
|
#:########################################################################################:#
|
||||||
|
# OPERATION NOTES:
|
||||||
|
# - Main display:
|
||||||
|
# - Shows a 32 x 8 LED matrix clock using the MAX7219 display component.
|
||||||
|
# - Uses common/sntp_common.yaml as the primary time source via id(sntp_time).
|
||||||
|
# - If SNTP is not valid, uses id(current_mins) as a fallback clock.
|
||||||
|
# - In fallback mode the colon stays solid, so it is obvious time is not fully synced.
|
||||||
|
#
|
||||||
|
# - Clock controls:
|
||||||
|
# - Clock format can be selected as 12h or 24h from Home Assistant or MQTT.
|
||||||
|
# - 12h mode draws a small A or P indicator at the far right of the display.
|
||||||
|
#
|
||||||
|
# - Brightness controls:
|
||||||
|
# - The Display light entity can turn the matrix on/off and set proportional brightness.
|
||||||
|
# - The Brightness Level number slider sets the exact MAX7219 intensity from 1 to 15.
|
||||||
|
# - MQTT brightness accepts 0 to turn the display off, or 1-15 to set exact intensity.
|
||||||
|
# - The 1-15 slider keeps the last non-zero brightness value when the display is off.
|
||||||
|
#
|
||||||
|
# - Scroll text controls:
|
||||||
|
# - Scroll Text is exposed as a Home Assistant text entity.
|
||||||
|
# - Native API action esphome.esp_ledmatrix1_scroll_text can scroll text directly from Home Assistant.
|
||||||
|
# - The text box clears itself after a valid scroll request is accepted.
|
||||||
|
# - Empty scroll requests are ignored and do not replay the previous message.
|
||||||
|
# - Scroll Delay controls the delay in milliseconds between scroll steps.
|
||||||
|
#
|
||||||
|
# - Scroll transitions:
|
||||||
|
# - Before scrolling, the clock always slides down and off the display.
|
||||||
|
# - After scrolling, the selected re-entry effect runs.
|
||||||
|
# - Available re-entry effects are Slide up, Slide down, and Pixel fall.
|
||||||
|
# - Pixel fall hides the clock until the particle effect finishes.
|
||||||
|
#:########################################################################################:#
|
||||||
|
# MQTT COMMANDS:
|
||||||
|
# - ${mqtt_command_topic}/scroll : Text payload to scroll once across the display.
|
||||||
|
# - ${mqtt_command_topic}/mode : Payload 12 or 24 to select clock format.
|
||||||
|
# - ${mqtt_command_topic}/bright : Payload 0 turns display off, 1-15 sets exact brightness.
|
||||||
|
# - ${mqtt_status_topic}/status : Publishes online/offline status.
|
||||||
|
#:########################################################################################:#
|
||||||
|
# HOME ASSISTANT API ACTIONS:
|
||||||
|
# - esphome.esp_ledmatrix1_scroll_text
|
||||||
|
# data:
|
||||||
|
# message: "Text to scroll"
|
||||||
|
#:########################################################################################:#
|
||||||
|
# OFFLINE NOTES:
|
||||||
|
# a) Home Assistant offline, but WiFi/network and MQTT online:
|
||||||
|
# - Clock display should continue using SNTP if the NTP servers are reachable.
|
||||||
|
# - MQTT scroll, brightness, and 12h/24h mode commands should still work.
|
||||||
|
#
|
||||||
|
# b) MQTT offline, but WiFi/network and Home Assistant API online:
|
||||||
|
# - Home Assistant text, light, number, and select entities should still work.
|
||||||
|
# - Clock display should continue using SNTP if the NTP servers are reachable.
|
||||||
|
#
|
||||||
|
# c) Entire WiFi/network offline:
|
||||||
|
# - SNTP will not be available after boot.
|
||||||
|
# - Clock display will use the current_mins fallback from common/sntp_common.yaml.
|
||||||
|
# - Accurate time is needed for the clock, so fallback time will drift.
|
||||||
|
# - For a rough reset, power-cycle the device at 12:00 noon.
|
||||||
|
# - Home Assistant and MQTT controls will not be available while offline.
|
||||||
|
#:########################################################################################:#
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# SUBSTITUTIONS: Specific device variable substitutions
|
||||||
|
# If NOT using a secrets file, just replace these with the passwords etc in quotes.
|
||||||
|
#:########################################################################################:#
|
||||||
|
substitutions:
|
||||||
|
# Device Naming
|
||||||
|
device_name: "esp-ledmatrix1"
|
||||||
|
friendly_name: "LED Matrix Display"
|
||||||
|
description_comment: "LED Matrix Display (Layout V1.1)"
|
||||||
|
device_area: "Lounge" # Allows ESP device to be automatically linked to an Area in Home Assistant.
|
||||||
|
|
||||||
|
# Project Naming
|
||||||
|
project_name: "Generic.ESP32 Supermini"
|
||||||
|
project_version: "v1.3"
|
||||||
|
|
||||||
|
# Passwords & Secrets
|
||||||
|
api_key: !secret esp-api_key
|
||||||
|
ota_pass: !secret esp-ota_pass
|
||||||
|
static_ip_address: !secret esp-ledmatrix1_ip # Substitutions cannot be used inside secret names.
|
||||||
|
mqtt_command_main_topic: !secret mqtt_command_main_topic
|
||||||
|
mqtt_status_main_topic: !secret mqtt_status_main_topic
|
||||||
|
|
||||||
|
# If changing IP addresses, update current_ip_address here, otherwise it remains static_ip_address.
|
||||||
|
# Do not forget to switch it back when the address change is complete.
|
||||||
|
current_ip_address: ${static_ip_address}
|
||||||
|
|
||||||
|
# MQTT LOCAL Controls
|
||||||
|
mqtt_device_name: "ledmatrix1"
|
||||||
|
mqtt_command_topic: "${mqtt_command_main_topic}/${mqtt_device_name}"
|
||||||
|
mqtt_status_topic: "${mqtt_status_main_topic}/${mqtt_device_name}"
|
||||||
|
|
||||||
|
# Device Settings
|
||||||
|
log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG, VERBOSE, VERY_VERBOSE
|
||||||
|
update_interval: "60s"
|
||||||
|
|
||||||
|
# Matrix Display Settings
|
||||||
|
pin_mosi: GPIO8
|
||||||
|
pin_cs: GPIO9
|
||||||
|
pin_sck: GPIO10
|
||||||
|
num_chips: "4"
|
||||||
|
scroll_delay_ms: "15" # ms between scroll steps
|
||||||
|
matrix_initial_brightness: "2" # Default MAX7219 intensity, 1-15.
|
||||||
|
y_offset: "0"
|
||||||
|
y_scroll_offset: "0"
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# PACKAGES: Included Common Packages
|
||||||
|
# https://esphome.io/components/packages.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
packages:
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
|
common_wifi: !include
|
||||||
|
file: common/network_common.yaml
|
||||||
|
vars:
|
||||||
|
local_device_name: "${device_name}"
|
||||||
|
local_static_ip_address: "${static_ip_address}"
|
||||||
|
local_ota_pass: "${ota_pass}"
|
||||||
|
local_current_ip_address: "${current_ip_address}"
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
|
common_api: !include
|
||||||
|
file: common/api_common.yaml
|
||||||
|
#file: common/api_common_noencryption.yaml
|
||||||
|
vars:
|
||||||
|
local_api_key: "${api_key}"
|
||||||
|
|
||||||
|
#### MQTT ####
|
||||||
|
common_mqtt: !include
|
||||||
|
file: common/mqtt_common.yaml
|
||||||
|
vars:
|
||||||
|
local_device_name: "${device_name}"
|
||||||
|
|
||||||
|
#### WEB PORTAL ####
|
||||||
|
common_webportal: !include common/webportal_common.yaml
|
||||||
|
|
||||||
|
#### SNTP (Needed for the clock display) ####
|
||||||
|
common_sntp: !include common/sntp_common.yaml
|
||||||
|
|
||||||
|
#### DIAGNOSTICS Sensors ####
|
||||||
|
diag_basic: !include common/include_basic_diag_sensors.yaml
|
||||||
|
#diag_more: !include common/include_more_diag_sensors.yaml
|
||||||
|
#diag_debug: !include common/include_debug_diag_sensors.yaml
|
||||||
|
#diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# ESPHOME:
|
||||||
|
# https://esphome.io/components/esphome.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
esphome:
|
||||||
|
name: "${device_name}"
|
||||||
|
friendly_name: "${friendly_name}"
|
||||||
|
comment: "${description_comment}" # Appears on the ESPHome page in Home Assistant.
|
||||||
|
area: "${device_area}"
|
||||||
|
project:
|
||||||
|
name: "${project_name}"
|
||||||
|
version: "${project_version}"
|
||||||
|
on_boot:
|
||||||
|
priority: 600
|
||||||
|
then:
|
||||||
|
# Ensure Home Assistant shows the text box as empty rather than unknown.
|
||||||
|
- delay: 500ms
|
||||||
|
- lambda: |-
|
||||||
|
id(ha_scroll_text).publish_state("");
|
||||||
|
- component.update: matrix
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# ESP PLATFORM AND FRAMEWORK:
|
||||||
|
# https://esphome.io/components/esp32/
|
||||||
|
#:########################################################################################:#
|
||||||
|
esp32:
|
||||||
|
variant: esp32c3
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
version: recommended
|
||||||
|
|
||||||
|
#esp8266:
|
||||||
|
# board: esp01_1m
|
||||||
|
# early_pin_init: false # Recommended false where switches are involved. Defaults to true.
|
||||||
|
# board_flash_mode: dout # Default is dout.
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# LOGGER: ESPHome Logging Enable
|
||||||
|
# https://esphome.io/components/logger.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
logger:
|
||||||
|
level: "${log_level}" # INFO suggested, or DEBUG for testing.
|
||||||
|
#baud_rate: 0 # Set to 0 for no UART logging if UART is used for another device.
|
||||||
|
#esp8266_store_log_strings_in_flash: false
|
||||||
|
#tx_buffer_size: 64
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# API COMPONENT: Device-specific Home Assistant native API actions
|
||||||
|
# https://esphome.io/components/api.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
api:
|
||||||
|
actions:
|
||||||
|
# Home Assistant action: esphome.esp_ledmatrix1_scroll_text
|
||||||
|
# Example HA data: { message: "Hello from Home Assistant" }
|
||||||
|
- action: scroll_text
|
||||||
|
variables:
|
||||||
|
message: string
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
std::string s = message;
|
||||||
|
|
||||||
|
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
|
||||||
|
s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());
|
||||||
|
s.erase(std::remove_if(s.begin(), s.end(),
|
||||||
|
[](unsigned char c){ return c < 0x20; }), s.end());
|
||||||
|
|
||||||
|
// Ignore empty API scroll commands completely.
|
||||||
|
if (s.empty()) return;
|
||||||
|
|
||||||
|
// Keep the HA text entity visually in sync with this direct API action.
|
||||||
|
id(ha_scroll_text).publish_state(s.c_str());
|
||||||
|
|
||||||
|
id(scroll_text) = s;
|
||||||
|
id(scroll_x) = ${num_chips} * 8;
|
||||||
|
id(start_scroll_sequence).execute();
|
||||||
|
id(clear_scroll_text_box).execute();
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# GLOBALS:
|
||||||
|
# https://esphome.io/components/globals.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
globals:
|
||||||
|
# Text currently being scrolled across the matrix.
|
||||||
|
- id: scroll_text
|
||||||
|
type: std::string
|
||||||
|
initial_value: ""
|
||||||
|
|
||||||
|
# Current X position of the scrolling text.
|
||||||
|
- id: scroll_x
|
||||||
|
type: int
|
||||||
|
initial_value: '0'
|
||||||
|
|
||||||
|
# True while a scroll message is active.
|
||||||
|
- id: scrolling
|
||||||
|
type: bool
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
|
# False = 24h mode, true = 12h mode.
|
||||||
|
- id: use_12h_mode
|
||||||
|
type: bool
|
||||||
|
restore_value: true
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
|
# Runtime scroll delay, controlled by the Scroll Delay number entity.
|
||||||
|
- id: scroll_delay_runtime
|
||||||
|
type: int
|
||||||
|
restore_value: true
|
||||||
|
initial_value: '${scroll_delay_ms}'
|
||||||
|
|
||||||
|
# MAX7219 intensity: 0=OFF, 1..15=ON. Driven by the HA light and brightness slider.
|
||||||
|
- id: matrix_intensity
|
||||||
|
type: int
|
||||||
|
restore_value: false
|
||||||
|
initial_value: '${matrix_initial_brightness}'
|
||||||
|
|
||||||
|
# Last non-zero brightness level selected by HA, MQTT, or the 1-15 slider.
|
||||||
|
# This allows the exact slider to keep its value when the display is switched off.
|
||||||
|
- id: matrix_last_nonzero_intensity
|
||||||
|
type: int
|
||||||
|
restore_value: true
|
||||||
|
initial_value: '${matrix_initial_brightness}'
|
||||||
|
|
||||||
|
# Brightness sync loop protection for HA light <-> MQTT brightness updates.
|
||||||
|
- id: suppress_mqtt_bright_sync
|
||||||
|
type: bool
|
||||||
|
restore_value: false
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
|
- id: target_mqtt_bright
|
||||||
|
type: int
|
||||||
|
restore_value: false
|
||||||
|
initial_value: '-1'
|
||||||
|
|
||||||
|
- id: last_mqtt_bright
|
||||||
|
type: int
|
||||||
|
restore_value: false
|
||||||
|
initial_value: '-1'
|
||||||
|
|
||||||
|
# Re-entry effect after scrolling: 0=Slide up, 1=Slide down, 2=Pixel fall.
|
||||||
|
- id: transition_mode
|
||||||
|
type: int
|
||||||
|
restore_value: true
|
||||||
|
initial_value: '0'
|
||||||
|
|
||||||
|
# Slide transition state.
|
||||||
|
- id: transition_active
|
||||||
|
type: bool
|
||||||
|
restore_value: false
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
|
- id: transition_y
|
||||||
|
type: int
|
||||||
|
restore_value: false
|
||||||
|
initial_value: '0'
|
||||||
|
|
||||||
|
# Pixel-fall transition state.
|
||||||
|
- id: pixel_fall_active
|
||||||
|
type: bool
|
||||||
|
restore_value: false
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
|
- id: pixel_fall_gen
|
||||||
|
type: int
|
||||||
|
restore_value: false
|
||||||
|
initial_value: '0'
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# FONT COMPONENT:
|
||||||
|
# https://esphome.io/components/font/
|
||||||
|
#:########################################################################################:#
|
||||||
|
font:
|
||||||
|
# Clock digits. Keep glyphs limited to reduce firmware size.
|
||||||
|
- file: "fonts/Eight-Bit-Dragon.ttf"
|
||||||
|
id: digit5
|
||||||
|
size: 8
|
||||||
|
glyphs: ["0123456789: -"]
|
||||||
|
|
||||||
|
# General 5x8 font for scroll text.
|
||||||
|
- file: "fonts/5x8.bdf"
|
||||||
|
id: font5x8
|
||||||
|
size: 8
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# SPI COMPONENT:
|
||||||
|
# https://esphome.io/components/spi.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
spi:
|
||||||
|
clk_pin: ${pin_sck}
|
||||||
|
mosi_pin: ${pin_mosi}
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# MQTT: Device-specific MQTT command triggers
|
||||||
|
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
||||||
|
#:########################################################################################:#
|
||||||
|
mqtt:
|
||||||
|
discovery: false
|
||||||
|
birth_message:
|
||||||
|
topic: "${mqtt_status_topic}/status"
|
||||||
|
payload: "online"
|
||||||
|
qos: 0
|
||||||
|
retain: true
|
||||||
|
will_message:
|
||||||
|
topic: "${mqtt_status_topic}/status"
|
||||||
|
payload: "offline"
|
||||||
|
qos: 0
|
||||||
|
retain: true
|
||||||
|
on_message:
|
||||||
|
# Scroll a text payload once across the matrix.
|
||||||
|
- topic: "${mqtt_command_topic}/scroll"
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
std::string s = x.c_str();
|
||||||
|
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
|
||||||
|
s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());
|
||||||
|
s.erase(std::remove_if(s.begin(), s.end(),
|
||||||
|
[](unsigned char c){ return c < 0x20; }), s.end());
|
||||||
|
|
||||||
|
// Ignore empty MQTT scroll commands completely.
|
||||||
|
if (s.empty()) return;
|
||||||
|
|
||||||
|
id(scroll_text) = s;
|
||||||
|
id(scroll_x) = ${num_chips} * 8;
|
||||||
|
id(start_scroll_sequence).execute();
|
||||||
|
|
||||||
|
# Set 12h or 24h clock mode from MQTT.
|
||||||
|
- topic: "${mqtt_command_topic}/mode"
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
if (x == "12") {
|
||||||
|
id(use_12h_mode) = true;
|
||||||
|
} else if (x == "24") {
|
||||||
|
id(use_12h_mode) = false;
|
||||||
|
}
|
||||||
|
- component.update: clock_format_select
|
||||||
|
- component.update: matrix
|
||||||
|
|
||||||
|
# Brightness from MQTT. 0=OFF, 1..15=ON brightness level.
|
||||||
|
- topic: "${mqtt_command_topic}/bright"
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
std::string s = x.c_str();
|
||||||
|
s.erase(std::remove_if(s.begin(), s.end(),
|
||||||
|
[](unsigned char c){ return c < 0x20 || c == 0x7F; }), s.end());
|
||||||
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
|
||||||
|
[](unsigned char ch){ return !std::isspace(ch); }));
|
||||||
|
s.erase(std::find_if(s.rbegin(), s.rend(),
|
||||||
|
[](unsigned char ch){ return !std::isspace(ch); }).base(), s.end());
|
||||||
|
|
||||||
|
if (s.empty() || !std::all_of(s.begin(), s.end(),
|
||||||
|
[](unsigned char c){ return std::isdigit(c); })) return;
|
||||||
|
|
||||||
|
int v = atoi(s.c_str());
|
||||||
|
if (v < 0) v = 0;
|
||||||
|
if (v > 15) v = 15;
|
||||||
|
|
||||||
|
id(suppress_mqtt_bright_sync) = true;
|
||||||
|
|
||||||
|
if (v == 0) {
|
||||||
|
id(matrix_light).turn_off().perform();
|
||||||
|
} else {
|
||||||
|
id(matrix_last_nonzero_intensity) = v;
|
||||||
|
id(matrix_brightness_number).publish_state((float) v);
|
||||||
|
|
||||||
|
auto call = id(matrix_light).turn_on();
|
||||||
|
call.set_brightness((float) v / 15.0f);
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
id(last_mqtt_bright) = v;
|
||||||
|
id(suppress_mqtt_bright_sync) = false;
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# TEXT COMPONENT:
|
||||||
|
# https://esphome.io/components/text/template/
|
||||||
|
#:########################################################################################:#
|
||||||
|
text:
|
||||||
|
- platform: template
|
||||||
|
id: ha_scroll_text
|
||||||
|
name: "${friendly_name} Scroll Text"
|
||||||
|
icon: "mdi:message-text-outline"
|
||||||
|
mode: text
|
||||||
|
max_length: 80
|
||||||
|
optimistic: false
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
std::string s = x.c_str();
|
||||||
|
|
||||||
|
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
|
||||||
|
s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());
|
||||||
|
s.erase(std::remove_if(s.begin(), s.end(),
|
||||||
|
[](unsigned char c){ return c < 0x20; }), s.end());
|
||||||
|
|
||||||
|
// Publish the cleaned text so the HA UI updates immediately.
|
||||||
|
id(ha_scroll_text).publish_state(s.c_str());
|
||||||
|
|
||||||
|
// Empty text only clears the entity. It should not start a scroll.
|
||||||
|
if (s.empty()) return;
|
||||||
|
|
||||||
|
id(scroll_text) = s;
|
||||||
|
id(scroll_x) = ${num_chips} * 8;
|
||||||
|
id(start_scroll_sequence).execute();
|
||||||
|
id(clear_scroll_text_box).execute();
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# NUMBER COMPONENT:
|
||||||
|
# https://esphome.io/components/number/template/
|
||||||
|
#:########################################################################################:#
|
||||||
|
number:
|
||||||
|
- platform: template
|
||||||
|
id: scroll_delay_number
|
||||||
|
name: "${friendly_name} Scroll Delay"
|
||||||
|
icon: "mdi:timer-outline"
|
||||||
|
unit_of_measurement: "ms"
|
||||||
|
min_value: 1
|
||||||
|
max_value: 100
|
||||||
|
step: 1
|
||||||
|
restore_value: true
|
||||||
|
initial_value: ${scroll_delay_ms}
|
||||||
|
optimistic: true
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(scroll_delay_runtime) = (int) x;
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
id: matrix_brightness_number
|
||||||
|
name: "${friendly_name} Brightness Level"
|
||||||
|
icon: "mdi:brightness-6"
|
||||||
|
min_value: 1
|
||||||
|
max_value: 15
|
||||||
|
step: 1
|
||||||
|
restore_value: true
|
||||||
|
initial_value: ${matrix_initial_brightness}
|
||||||
|
optimistic: true
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
int v = (int) lroundf(x);
|
||||||
|
if (v < 1) v = 1;
|
||||||
|
if (v > 15) v = 15;
|
||||||
|
|
||||||
|
id(matrix_last_nonzero_intensity) = v;
|
||||||
|
|
||||||
|
// Setting the exact brightness slider also turns the display on.
|
||||||
|
auto call = id(matrix_light).turn_on();
|
||||||
|
call.set_brightness((float) v / 15.0f);
|
||||||
|
call.perform();
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# OUTPUT COMPONENT:
|
||||||
|
# https://esphome.io/components/output/
|
||||||
|
#:########################################################################################:#
|
||||||
|
output:
|
||||||
|
# Template output lets the HA light and brightness slider control MAX7219 intensity.
|
||||||
|
- platform: template
|
||||||
|
id: matrix_brightness_out
|
||||||
|
type: float
|
||||||
|
write_action:
|
||||||
|
- lambda: |-
|
||||||
|
int pub = 0;
|
||||||
|
|
||||||
|
if (state <= 0.001f) {
|
||||||
|
id(matrix_intensity) = 0;
|
||||||
|
id(matrix).turn_on_off(false);
|
||||||
|
pub = 0;
|
||||||
|
} else {
|
||||||
|
int v = (int) lroundf(state * 15.0f);
|
||||||
|
if (v < 1) v = 1;
|
||||||
|
if (v > 15) v = 15;
|
||||||
|
|
||||||
|
id(matrix_intensity) = v;
|
||||||
|
id(matrix_last_nonzero_intensity) = v;
|
||||||
|
id(matrix_brightness_number).publish_state((float) v);
|
||||||
|
id(matrix).turn_on_off(true);
|
||||||
|
pub = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
id(target_mqtt_bright) = pub;
|
||||||
|
|
||||||
|
- component.update: matrix
|
||||||
|
|
||||||
|
# Publish brightness back to MQTT unless this update came from MQTT.
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: |-
|
||||||
|
return !id(suppress_mqtt_bright_sync)
|
||||||
|
&& (id(target_mqtt_bright) != id(last_mqtt_bright));
|
||||||
|
then:
|
||||||
|
- mqtt.publish:
|
||||||
|
topic: "${mqtt_command_topic}/bright"
|
||||||
|
payload: !lambda |-
|
||||||
|
char buf[6];
|
||||||
|
snprintf(buf, sizeof(buf), "%d", id(target_mqtt_bright));
|
||||||
|
return std::string(buf);
|
||||||
|
qos: 0
|
||||||
|
retain: true
|
||||||
|
- lambda: |-
|
||||||
|
id(last_mqtt_bright) = id(target_mqtt_bright);
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# LIGHT COMPONENT:
|
||||||
|
# https://esphome.io/components/light/monochromatic/
|
||||||
|
#:########################################################################################:#
|
||||||
|
light:
|
||||||
|
- platform: monochromatic
|
||||||
|
id: matrix_light
|
||||||
|
name: "${friendly_name} Display"
|
||||||
|
icon: "mdi:brightness-6"
|
||||||
|
output: matrix_brightness_out
|
||||||
|
gamma_correct: 1.0
|
||||||
|
default_transition_length: 0s
|
||||||
|
restore_mode: RESTORE_DEFAULT_ON
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# SELECT COMPONENT:
|
||||||
|
# https://esphome.io/components/select/template/
|
||||||
|
#:########################################################################################:#
|
||||||
|
select:
|
||||||
|
- platform: template
|
||||||
|
id: clock_format_select
|
||||||
|
name: "${friendly_name} Clock Format"
|
||||||
|
icon: "mdi:clock-time-three-outline"
|
||||||
|
options:
|
||||||
|
- "24"
|
||||||
|
- "12"
|
||||||
|
lambda: |-
|
||||||
|
return esphome::optional<std::string>(std::string(id(use_12h_mode) ? "12" : "24"));
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
id(use_12h_mode) = (x == "12");
|
||||||
|
- component.update: clock_format_select
|
||||||
|
- component.update: matrix
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
id: transition_select
|
||||||
|
name: "${friendly_name} Transition Effect"
|
||||||
|
icon: "mdi:transition"
|
||||||
|
options:
|
||||||
|
- "Slide up"
|
||||||
|
- "Slide down"
|
||||||
|
- "Pixel fall"
|
||||||
|
lambda: |-
|
||||||
|
if (id(transition_mode) == 2) return std::string("Pixel fall");
|
||||||
|
if (id(transition_mode) == 1) return std::string("Slide down");
|
||||||
|
return std::string("Slide up");
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
if (x == "Pixel fall") id(transition_mode) = 2;
|
||||||
|
else if (x == "Slide down") id(transition_mode) = 1;
|
||||||
|
else id(transition_mode) = 0;
|
||||||
|
- component.update: transition_select
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# SCRIPT COMPONENT:
|
||||||
|
# https://esphome.io/components/script.html
|
||||||
|
#:########################################################################################:#
|
||||||
|
script:
|
||||||
|
# Clear the HA text box shortly after a scroll request has been accepted.
|
||||||
|
- id: clear_scroll_text_box
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- delay: 400ms
|
||||||
|
- lambda: |-
|
||||||
|
id(ha_scroll_text).publish_state("");
|
||||||
|
|
||||||
|
# Sequence: exit slide-down -> scroll -> selected re-entry.
|
||||||
|
- id: start_scroll_sequence
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(scrolling) = false;
|
||||||
|
id(pixel_fall_active) = false;
|
||||||
|
- component.update: matrix
|
||||||
|
- script.execute: clock_exit_down
|
||||||
|
- script.wait: clock_exit_down
|
||||||
|
- lambda: |-
|
||||||
|
id(scrolling) = true;
|
||||||
|
- script.execute: do_scroll
|
||||||
|
|
||||||
|
# Scroll the stored text until it has moved fully off the left side.
|
||||||
|
- id: do_scroll
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- while:
|
||||||
|
condition:
|
||||||
|
lambda: |-
|
||||||
|
return id(scrolling);
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(scroll_x) -= 1;
|
||||||
|
const int end_limit = - (int(id(scroll_text).length()) * 6);
|
||||||
|
if (id(scroll_x) < end_limit) {
|
||||||
|
id(scrolling) = false;
|
||||||
|
|
||||||
|
if (id(transition_mode) == 1) {
|
||||||
|
id(clock_slide_down_entry).execute();
|
||||||
|
} else if (id(transition_mode) == 2) {
|
||||||
|
id(clock_pixel_fall_transition).execute();
|
||||||
|
} else {
|
||||||
|
id(clock_slide_up_entry).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- component.update: matrix
|
||||||
|
- delay: !lambda |-
|
||||||
|
return (uint32_t) id(scroll_delay_runtime);
|
||||||
|
|
||||||
|
# Exit: slide clock down off-screen and hold it there to prevent a one-frame flash.
|
||||||
|
- id: clock_exit_down
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_active) = true;
|
||||||
|
id(transition_y) = ${y_offset};
|
||||||
|
- repeat:
|
||||||
|
count: 9
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_y) += 1;
|
||||||
|
- component.update: matrix
|
||||||
|
- delay: 45ms
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_active) = true;
|
||||||
|
id(transition_y) = 8;
|
||||||
|
- component.update: matrix
|
||||||
|
|
||||||
|
# Re-entry: clock slides down into place from above.
|
||||||
|
- id: clock_slide_down_entry
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_active) = true;
|
||||||
|
id(transition_y) = -8;
|
||||||
|
- repeat:
|
||||||
|
count: 9
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_y) += 1;
|
||||||
|
- component.update: matrix
|
||||||
|
- delay: 60ms
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_active) = false;
|
||||||
|
id(transition_y) = ${y_offset};
|
||||||
|
- component.update: matrix
|
||||||
|
|
||||||
|
# Re-entry: clock slides up into place from below.
|
||||||
|
- id: clock_slide_up_entry
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_active) = true;
|
||||||
|
id(transition_y) = 8;
|
||||||
|
- repeat:
|
||||||
|
count: 9
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_y) -= 1;
|
||||||
|
- component.update: matrix
|
||||||
|
- delay: 60ms
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_active) = false;
|
||||||
|
id(transition_y) = ${y_offset};
|
||||||
|
- component.update: matrix
|
||||||
|
|
||||||
|
# Re-entry: falling pixels effect before the clock redraws.
|
||||||
|
- id: clock_pixel_fall_transition
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(transition_active) = false;
|
||||||
|
id(transition_y) = ${y_offset};
|
||||||
|
id(pixel_fall_active) = true;
|
||||||
|
id(pixel_fall_gen) += 1;
|
||||||
|
- repeat:
|
||||||
|
count: 18
|
||||||
|
then:
|
||||||
|
- component.update: matrix
|
||||||
|
- delay: 55ms
|
||||||
|
- lambda: |-
|
||||||
|
id(pixel_fall_active) = false;
|
||||||
|
id(transition_active) = false;
|
||||||
|
id(transition_y) = ${y_offset};
|
||||||
|
- component.update: matrix
|
||||||
|
|
||||||
|
#:########################################################################################:#
|
||||||
|
# DISPLAY COMPONENT:
|
||||||
|
# https://esphome.io/components/display/max7219digit/
|
||||||
|
#:########################################################################################:#
|
||||||
|
display:
|
||||||
|
- platform: max7219digit
|
||||||
|
id: matrix
|
||||||
|
cs_pin: ${pin_cs}
|
||||||
|
num_chips: ${num_chips}
|
||||||
|
intensity: 2
|
||||||
|
update_interval: 500ms
|
||||||
|
rotate_chip: 0
|
||||||
|
reverse_enable: false
|
||||||
|
flip_x: false
|
||||||
|
|
||||||
|
lambda: |-
|
||||||
|
// Apply brightness only when it changes to reduce flicker.
|
||||||
|
static int last_brightness = -1;
|
||||||
|
if (id(matrix_intensity) != last_brightness) {
|
||||||
|
if (id(matrix_intensity) > 0) {
|
||||||
|
int v = id(matrix_intensity);
|
||||||
|
if (v > 15) v = 15;
|
||||||
|
it.intensity((uint8_t) v);
|
||||||
|
}
|
||||||
|
last_brightness = id(matrix_intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id(matrix_intensity) == 0) return;
|
||||||
|
|
||||||
|
it.clear();
|
||||||
|
|
||||||
|
// Scroll text has priority over clock display.
|
||||||
|
if (id(scrolling)) {
|
||||||
|
it.print(id(scroll_x), ${y_scroll_offset}, id(font5x8), id(scroll_text).c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pixel fall shows only particles while active.
|
||||||
|
if (id(pixel_fall_active)) {
|
||||||
|
static int last_gen = -1;
|
||||||
|
static int px[24];
|
||||||
|
static int py[24];
|
||||||
|
static uint32_t rng = 1;
|
||||||
|
|
||||||
|
auto rand_u32 = [&]() -> uint32_t {
|
||||||
|
rng = rng * 1664525u + 1013904223u;
|
||||||
|
return rng;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (last_gen != id(pixel_fall_gen)) {
|
||||||
|
last_gen = id(pixel_fall_gen);
|
||||||
|
rng = 0xA5A5A5A5u ^ (uint32_t) id(pixel_fall_gen);
|
||||||
|
for (int i = 0; i < 24; i++) {
|
||||||
|
px[i] = (int)(rand_u32() % 32);
|
||||||
|
py[i] = - (int)(rand_u32() % 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 24; i++) {
|
||||||
|
int spd = 1 + (int)(rand_u32() % 2);
|
||||||
|
py[i] += spd;
|
||||||
|
|
||||||
|
if (py[i] > 10) {
|
||||||
|
px[i] = (int)(rand_u32() % 32);
|
||||||
|
py[i] = - (int)(rand_u32() % 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int t = 0; t < 2; t++) {
|
||||||
|
int yy = py[i] - t;
|
||||||
|
int xx = px[i];
|
||||||
|
if (xx >= 0 && xx <= 31 && yy >= 0 && yy <= 7) {
|
||||||
|
it.draw_pixel_at(xx, yy, Color::WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer real SNTP time. Fall back to current_mins from common/sntp_common.yaml.
|
||||||
|
auto now = id(sntp_time).now();
|
||||||
|
bool time_valid = now.is_valid();
|
||||||
|
|
||||||
|
int raw_hour = 0;
|
||||||
|
int minute_to_show = 0;
|
||||||
|
int second_to_show = 0;
|
||||||
|
|
||||||
|
if (time_valid) {
|
||||||
|
raw_hour = now.hour;
|
||||||
|
minute_to_show = now.minute;
|
||||||
|
second_to_show = now.second;
|
||||||
|
} else {
|
||||||
|
int mins = id(current_mins);
|
||||||
|
if (mins < 0) mins = 0;
|
||||||
|
if (mins >= 1440) mins = mins % 1440;
|
||||||
|
|
||||||
|
raw_hour = mins / 60;
|
||||||
|
minute_to_show = mins % 60;
|
||||||
|
second_to_show = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hour_to_show = raw_hour;
|
||||||
|
if (id(use_12h_mode)) {
|
||||||
|
hour_to_show = raw_hour % 12;
|
||||||
|
if (hour_to_show == 0) hour_to_show = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
char hh[3], mm[3];
|
||||||
|
snprintf(hh, sizeof(hh), "%02d", hour_to_show);
|
||||||
|
snprintf(mm, sizeof(mm), "%02d", minute_to_show);
|
||||||
|
|
||||||
|
const int x_h1 = 3;
|
||||||
|
const int x_h2 = 9;
|
||||||
|
const int x_m1 = 17;
|
||||||
|
const int x_m2 = 23;
|
||||||
|
|
||||||
|
const int y = (id(transition_active) ? id(transition_y) : ${y_offset});
|
||||||
|
|
||||||
|
char ch[2] = { hh[0], 0 };
|
||||||
|
it.print(x_h1, y, id(digit5), ch);
|
||||||
|
ch[0] = hh[1];
|
||||||
|
it.print(x_h2, y, id(digit5), ch);
|
||||||
|
|
||||||
|
ch[0] = mm[0];
|
||||||
|
it.print(x_m1, y, id(digit5), ch);
|
||||||
|
ch[0] = mm[1];
|
||||||
|
it.print(x_m2, y, id(digit5), ch);
|
||||||
|
|
||||||
|
// In 12h mode, draw a tiny A or P indicator at the far right.
|
||||||
|
if (id(use_12h_mode)) {
|
||||||
|
const bool is_pm = raw_hour >= 12;
|
||||||
|
|
||||||
|
const uint8_t A_rows[4] = { 2, 5, 7, 5 };
|
||||||
|
const uint8_t P_rows[4] = { 6, 5, 6, 4 };
|
||||||
|
const uint8_t *L = is_pm ? P_rows : A_rows;
|
||||||
|
|
||||||
|
const int x_ampm = 29;
|
||||||
|
const int y_ampm = y + 4;
|
||||||
|
|
||||||
|
for (int r = 0; r < 4; r++) {
|
||||||
|
uint8_t row = L[r];
|
||||||
|
for (int c = 0; c < 3; c++) {
|
||||||
|
if (row & (1 << (2 - c))) {
|
||||||
|
int xx = x_ampm + c;
|
||||||
|
int yy = y_ampm + r;
|
||||||
|
if (yy >= 0 && yy <= 7 && xx >= 0 && xx <= 31) {
|
||||||
|
it.draw_pixel_at(xx, yy, Color::WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blink the colon when SNTP is valid. Keep it solid during fallback time.
|
||||||
|
const bool colon_on = time_valid ? ((second_to_show % 2) == 0) : true;
|
||||||
|
if (colon_on) {
|
||||||
|
const int x_col = (x_h2 + x_m1) / 2 + 2;
|
||||||
|
int y1 = y + 2;
|
||||||
|
int y2 = y + 5;
|
||||||
|
if (y1 >= 0 && y1 <= 7) it.draw_pixel_at(x_col, y1, Color::WHITE);
|
||||||
|
if (y2 >= 0 && y2 <= 7) it.draw_pixel_at(x_col, y2, Color::WHITE);
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ binary_sensor:
|
|||||||
all: false
|
all: false
|
||||||
entities:
|
entities:
|
||||||
- binary_sensor.underhouse_pir_sensor_south_occupancy
|
- binary_sensor.underhouse_pir_sensor_south_occupancy
|
||||||
|
- binary_sensor.underhouse_pir_sensor_north_occupancy
|
||||||
# FUTURE PIR: uncomment this when the north PIR exists.
|
# FUTURE PIR: uncomment this when the north PIR exists.
|
||||||
# - binary_sensor.underhouse_pir_sensor_north_occupancy
|
# - binary_sensor.underhouse_pir_sensor_north_occupancy
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
# PACKAGE: View Road Announcement Router
|
# PACKAGE: View Road Announcement Router
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Version: 1.2
|
# Version: 1.4
|
||||||
# Date: 2026-05-06
|
# Date: 2026-06-02
|
||||||
#
|
#
|
||||||
# Purpose:
|
# Purpose:
|
||||||
# Central announcement router for general Home Assistant announcements.
|
# Central announcement router for general Home Assistant announcements.
|
||||||
@@ -11,7 +11,136 @@
|
|||||||
# values such as channels, title, short_announcement, long_announcement,
|
# values such as channels, title, short_announcement, long_announcement,
|
||||||
# payload, and indicator.
|
# payload, and indicator.
|
||||||
#
|
#
|
||||||
|
# How to call this script:
|
||||||
|
#
|
||||||
|
# - action: script.view_road_announcement
|
||||||
|
# data:
|
||||||
|
# channels: "pushover_zorruno, lounge_google_home_voice, led_matrix_1"
|
||||||
|
# title: "View Road"
|
||||||
|
# short_announcement: "Washing complete"
|
||||||
|
# long_announcement: "The washing machine has finished."
|
||||||
|
# payload: ""
|
||||||
|
# indicator: "dry,1,15,10,2,1"
|
||||||
|
#
|
||||||
|
# Available channels:
|
||||||
|
#
|
||||||
|
# pushover_zorruno:
|
||||||
|
# Uses:
|
||||||
|
# title
|
||||||
|
# long_announcement
|
||||||
|
#
|
||||||
|
# pushover_kim:
|
||||||
|
# Uses:
|
||||||
|
# title
|
||||||
|
# long_announcement
|
||||||
|
#
|
||||||
|
# sony_tv_lounge:
|
||||||
|
# Uses:
|
||||||
|
# title
|
||||||
|
# short_announcement
|
||||||
|
# payload
|
||||||
|
# Message format:
|
||||||
|
# "<short_announcement> is <payload>"
|
||||||
|
#
|
||||||
|
# lounge_google_home_voice:
|
||||||
|
# Also accepts alias:
|
||||||
|
# google_home_voice
|
||||||
|
# Uses:
|
||||||
|
# long_announcement
|
||||||
|
# Fallback:
|
||||||
|
# If long_announcement is blank, uses short_announcement and payload.
|
||||||
|
# Quiet time:
|
||||||
|
# Blocked by default when input_boolean.quiet_time is on.
|
||||||
|
#
|
||||||
|
# lounge_touchscreen_voice:
|
||||||
|
# Placeholder only.
|
||||||
|
# Quiet time:
|
||||||
|
# Blocked by default when input_boolean.quiet_time is on.
|
||||||
|
#
|
||||||
|
# mqtt_view_rd_feed:
|
||||||
|
# Placeholder only.
|
||||||
|
#
|
||||||
|
# led_matrix_1:
|
||||||
|
# Uses:
|
||||||
|
# short_announcement
|
||||||
|
# indicator
|
||||||
|
# Publishes to MQTT topic:
|
||||||
|
# viewroad-commands/ledmatrix1/announce
|
||||||
|
# Message format:
|
||||||
|
# "<short_announcement> [<indicator>]"
|
||||||
|
# Example:
|
||||||
|
# "Washing complete [dry,1,15,10,2,1]"
|
||||||
|
#
|
||||||
|
# led_matrix_2:
|
||||||
|
# Future channel.
|
||||||
|
# Uses the same format as led_matrix_1.
|
||||||
|
# Publishes to MQTT topic:
|
||||||
|
# viewroad-commands/ledmatrix2/announce
|
||||||
|
#
|
||||||
|
# LED matrix announcement format:
|
||||||
|
#
|
||||||
|
# Message text [identifier,transition_mode,speed,repeat_seconds,repeat_times,sounder_quantity]
|
||||||
|
#
|
||||||
|
# Example script call:
|
||||||
|
#
|
||||||
|
# - action: script.view_road_announcement
|
||||||
|
# data:
|
||||||
|
# channels: "led_matrix_1"
|
||||||
|
# short_announcement: "Washing complete"
|
||||||
|
# indicator: "dry,1,15,10,2,1"
|
||||||
|
#
|
||||||
|
# Result sent to MQTT:
|
||||||
|
#
|
||||||
|
# Washing complete [dry,1,15,10,2,1]
|
||||||
|
#
|
||||||
|
# Indicator fields:
|
||||||
|
#
|
||||||
|
# identifier:
|
||||||
|
# Announcement identifier used by the LED matrix ESPHome logic.
|
||||||
|
# It is cleaned by ESPHome to letters, numbers, underscore, or dash, and
|
||||||
|
# is limited to 8 characters.
|
||||||
|
#
|
||||||
|
# transition_mode:
|
||||||
|
# 0 = Slide up
|
||||||
|
# 1 = Slide down
|
||||||
|
# 2 = Pixel fall
|
||||||
|
#
|
||||||
|
# speed:
|
||||||
|
# Per-message scroll delay in ms.
|
||||||
|
# Clamped by ESPHome from 1 to 100.
|
||||||
|
#
|
||||||
|
# repeat_seconds:
|
||||||
|
# Delay between completed scrolls.
|
||||||
|
# Clamped by ESPHome from 0 to 99.
|
||||||
|
#
|
||||||
|
# repeat_times:
|
||||||
|
# Total number of scroll displays.
|
||||||
|
# Clamped by ESPHome from 0 to 99.
|
||||||
|
# 0 is treated as a one-shot announcement.
|
||||||
|
#
|
||||||
|
# sounder_quantity:
|
||||||
|
# Number of announcement displays that should beep first.
|
||||||
|
# 0 means no sounder beep.
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# The announcement router does not parse, clean, or clamp the indicator
|
||||||
|
# fields. It passes the indicator through to the LED matrix in square
|
||||||
|
# brackets. The LED matrix ESPHome YAML handles validation and behaviour.
|
||||||
|
#
|
||||||
# Changes:
|
# Changes:
|
||||||
|
# 1.4:
|
||||||
|
# - Documented LED matrix indicator format.
|
||||||
|
# - Clarified that LED matrix indicator validation is handled by ESPHome.
|
||||||
|
#
|
||||||
|
# 1.3:
|
||||||
|
# - Added led_matrix_1 announcement channel.
|
||||||
|
# - Added future led_matrix_2 announcement channel.
|
||||||
|
# - Added MQTT topic settings for LED matrix announcement channels.
|
||||||
|
# - Added channel enable helpers for led_matrix_1 and led_matrix_2.
|
||||||
|
# - Added package instructions for calling script.view_road_announcement.
|
||||||
|
# - Moved more configurable values into the package settings section.
|
||||||
|
# - Added lounge Google Home cast chime suppression setting.
|
||||||
|
#
|
||||||
# 1.2:
|
# 1.2:
|
||||||
# - Updated Lounge Google Home voice channel to use working Piper TTS setup.
|
# - Updated Lounge Google Home voice channel to use working Piper TTS setup.
|
||||||
# - Added Piper voice option en_GB-alba-medium.
|
# - Added Piper voice option en_GB-alba-medium.
|
||||||
@@ -30,21 +159,6 @@
|
|||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# PUSHOVER NOTIFY SERVICES
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Pushover is configured using the Home Assistant Pushover integration.
|
|
||||||
#
|
|
||||||
# Expected notify entities:
|
|
||||||
#
|
|
||||||
# notify.pushover_zorruno
|
|
||||||
# notify.pushover_kim
|
|
||||||
#
|
|
||||||
# No YAML notify: block is needed in this package.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# CHANNEL ENABLE HELPERS
|
# CHANNEL ENABLE HELPERS
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -80,6 +194,16 @@ input_boolean:
|
|||||||
icon: mdi:message-processing
|
icon: mdi:message-processing
|
||||||
initial: true
|
initial: true
|
||||||
|
|
||||||
|
announcement_channel_led_matrix_1:
|
||||||
|
name: Announcement Channel - LED Matrix 1
|
||||||
|
icon: mdi:led-strip-variant
|
||||||
|
initial: true
|
||||||
|
|
||||||
|
announcement_channel_led_matrix_2:
|
||||||
|
name: Announcement Channel - LED Matrix 2
|
||||||
|
icon: mdi:led-strip-variant
|
||||||
|
initial: true
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# MAIN ANNOUNCEMENT ROUTER SCRIPT
|
# MAIN ANNOUNCEMENT ROUTER SCRIPT
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -95,7 +219,7 @@ script:
|
|||||||
channels:
|
channels:
|
||||||
name: Channels
|
name: Channels
|
||||||
description: Comma separated list of announcement channels to use.
|
description: Comma separated list of announcement channels to use.
|
||||||
example: "pushover_zorruno, sony_tv_lounge"
|
example: "pushover_zorruno, sony_tv_lounge, led_matrix_1"
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
|
|
||||||
@@ -130,26 +254,26 @@ script:
|
|||||||
|
|
||||||
indicator:
|
indicator:
|
||||||
name: Indicator
|
name: Indicator
|
||||||
description: Future indicator data such as LED number, colour, or flash pattern.
|
description: LED matrix indicator data in the format identifier,transition_mode,speed,repeat_seconds,repeat_times,sounder_quantity.
|
||||||
example: "red, led 2, flash fast"
|
example: "dry,1,15,10,2,1"
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
|
|
||||||
sequence:
|
sequence:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DEFAULT VALUES
|
# PACKAGE SETTINGS / SUBSTITUTIONS
|
||||||
##########################################################################
|
##########################################################################
|
||||||
#
|
#
|
||||||
# If a value is not passed, the default below is used.
|
# Home Assistant packages do not support ESPHome-style substitutions.
|
||||||
# If a value is passed as "", it clears the default to blank.
|
# These variables act as the package-level settings for this script.
|
||||||
#
|
#
|
||||||
# quiet_time_blocked_channels:
|
# If a value is not passed to the script, the default below is used.
|
||||||
# Channels in this list will not run while input_boolean.quiet_time is on.
|
# If a value is passed as "", it clears the default to blank.
|
||||||
#
|
#
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
- variables:
|
- variables:
|
||||||
default_channels: "pushover_zorruno, pushover_kim, sony_tv_lounge, lounge_google_home_voice, lounge_touchscreen_voice, mqtt_view_rd_feed"
|
default_channels: "pushover_zorruno, pushover_kim, sony_tv_lounge, lounge_google_home_voice, lounge_touchscreen_voice, mqtt_view_rd_feed, led_matrix_1, led_matrix_2"
|
||||||
default_title: "View Road"
|
default_title: "View Road"
|
||||||
default_short_announcement: "Home automation announcement"
|
default_short_announcement: "Home automation announcement"
|
||||||
default_long_announcement: "Home automation announcement"
|
default_long_announcement: "Home automation announcement"
|
||||||
@@ -165,10 +289,29 @@ script:
|
|||||||
lounge_google_home_voice_tts_entity: "tts.piper"
|
lounge_google_home_voice_tts_entity: "tts.piper"
|
||||||
lounge_google_home_voice_piper_voice: "en_GB-jenny_dioco-medium"
|
lounge_google_home_voice_piper_voice: "en_GB-jenny_dioco-medium"
|
||||||
lounge_google_home_voice_volume_level: 0.80
|
lounge_google_home_voice_volume_level: 0.80
|
||||||
|
lounge_google_home_voice_suppress_cast_chime: false
|
||||||
|
|
||||||
|
led_matrix_1_mqtt_topic: "viewroad-commands/ledmatrix1/announce"
|
||||||
|
led_matrix_2_mqtt_topic: "viewroad-commands/ledmatrix2/announce"
|
||||||
|
led_matrix_mqtt_qos: 0
|
||||||
|
led_matrix_mqtt_retain: false
|
||||||
|
led_matrix_leading_space_guard: ""
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# RESOLVE PASSED VALUES
|
# RESOLVE PASSED VALUES
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
#
|
||||||
|
# Lowercase field names are preferred:
|
||||||
|
# channels
|
||||||
|
# title
|
||||||
|
# short_announcement
|
||||||
|
# long_announcement
|
||||||
|
# payload
|
||||||
|
# indicator
|
||||||
|
#
|
||||||
|
# Some uppercase variants are also accepted for tolerance.
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
- variables:
|
- variables:
|
||||||
ann_channels_raw: >-
|
ann_channels_raw: >-
|
||||||
@@ -263,6 +406,17 @@ script:
|
|||||||
{{ payload_text }}
|
{{ payload_text }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
ann_led_matrix_message: >-
|
||||||
|
{% set short_text = short_announcement if short_announcement is defined
|
||||||
|
else Short_Announcement if Short_Announcement is defined
|
||||||
|
else default_short_announcement %}
|
||||||
|
{% set short_text = short_text | string %}
|
||||||
|
{% set indicator_text = ann_indicator | string | trim %}
|
||||||
|
{{ (led_matrix_leading_space_guard if short_text[0:1] == ' ' else '') ~ short_text ~
|
||||||
|
(' [' ~ indicator_text ~ ']'
|
||||||
|
if indicator_text | length > 0
|
||||||
|
else '') }}
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHANNEL: PUSHOVER ZORRUNO
|
# CHANNEL: PUSHOVER ZORRUNO
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@@ -405,3 +559,78 @@ script:
|
|||||||
# Placeholder only. No action yet.
|
# Placeholder only. No action yet.
|
||||||
#
|
#
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# CHANNEL: LOUNGE LED MATRIX DISPLAY - MATRIX 1
|
||||||
|
##########################################################################
|
||||||
|
#
|
||||||
|
# Channel name:
|
||||||
|
# led_matrix_1
|
||||||
|
#
|
||||||
|
# Uses:
|
||||||
|
# short_announcement
|
||||||
|
# indicator
|
||||||
|
#
|
||||||
|
# Publishes:
|
||||||
|
# <short_announcement> [<indicator>]
|
||||||
|
#
|
||||||
|
# Example payload:
|
||||||
|
# Washing complete [dry,1,15,10,2,1]
|
||||||
|
#
|
||||||
|
# Indicator format:
|
||||||
|
# identifier,transition_mode,speed,repeat_seconds,repeat_times,sounder_quantity
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
- if:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ ',led_matrix_1,' in ann_channels_normalised }}"
|
||||||
|
- condition: state
|
||||||
|
entity_id: input_boolean.announcement_channel_led_matrix_1
|
||||||
|
state: "on"
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ ann_led_matrix_message | string | trim | length > 0 }}"
|
||||||
|
then:
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "{{ led_matrix_1_mqtt_topic }}"
|
||||||
|
payload: "{{ ann_led_matrix_message }}"
|
||||||
|
qos: "{{ led_matrix_mqtt_qos | int(0) }}"
|
||||||
|
retain: "{{ led_matrix_mqtt_retain | bool(false) }}"
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# CHANNEL: LOUNGE LED MATRIX DISPLAY - MATRIX 2
|
||||||
|
##########################################################################
|
||||||
|
#
|
||||||
|
# Future channel.
|
||||||
|
#
|
||||||
|
# Channel name:
|
||||||
|
# led_matrix_2
|
||||||
|
#
|
||||||
|
# Uses:
|
||||||
|
# short_announcement
|
||||||
|
# indicator
|
||||||
|
#
|
||||||
|
# Publishes:
|
||||||
|
# <short_announcement> [<indicator>]
|
||||||
|
#
|
||||||
|
# Indicator format:
|
||||||
|
# identifier,transition_mode,speed,repeat_seconds,repeat_times,sounder_quantity
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
- if:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ ',led_matrix_2,' in ann_channels_normalised }}"
|
||||||
|
- condition: state
|
||||||
|
entity_id: input_boolean.announcement_channel_led_matrix_2
|
||||||
|
state: "on"
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ ann_led_matrix_message | string | trim | length > 0 }}"
|
||||||
|
then:
|
||||||
|
- action: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "{{ led_matrix_2_mqtt_topic }}"
|
||||||
|
payload: "{{ ann_led_matrix_message }}"
|
||||||
|
qos: "{{ led_matrix_mqtt_qos | int(0) }}"
|
||||||
|
retain: "{{ led_matrix_mqtt_retain | bool(false) }}"
|
||||||
|
|||||||