Skip to content

Instantly share code, notes, and snippets.

@JonBons
Created December 29, 2025 00:13
Show Gist options
  • Select an option

  • Save JonBons/fc74282a4147c14d8e07c043eff061f6 to your computer and use it in GitHub Desktop.

Select an option

Save JonBons/fc74282a4147c14d8e07c043eff061f6 to your computer and use it in GitHub Desktop.
atoms3lite-tally-cam1.yaml
substitutions:
device_name: atoms3lite-tally-cam1
friendly_name: Atoms3 Lite Tally Cam1
# ---------- TallyArbiter ----------
tally_prefix: "tallyarbiter"
tally_device_id: "YOUR_DEVICE_ID"
program_bus_id: "334e4eda"
preview_bus_id: "e393251c"
# ---------- LED ----------
led_pin: GPIO35
led_brightness: "1.0"
# ---------- Fallback colors ----------
program_fallback_color: "#ff0000"
preview_fallback_color: "#00ff00"
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32-s3-devkitc-1
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "HA_API_KEY"
ota:
- platform: esphome
password: "OTA_PW"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Atoms3-Lite-Tally-Cam1"
password: "AP_FB_PW"
# ----------------------------
# MQTT Configuration
# ----------------------------
# Subscribes to TallyArbiter MQTT topics:
# - {prefix}/device/{device_id}/bus/{bus_id} - Bus state with color info
# - {prefix}/device/{device_id}/status - Device connection status
mqtt:
broker: !secret mqtt_broker
port: !secret mqtt_port
username: !secret mqtt_username
password: !secret mqtt_password
on_message:
# -------- PROGRAM BUS --------
- topic: ${tally_prefix}/device/${tally_device_id}/bus/${program_bus_id}
then:
- lambda: |-
// Parse JSON string manually to extract state and color
std::string payload = x.c_str();
bool is_active = payload.find("\"state\":\"ON\"") != std::string::npos;
bool state_changed = (id(program_active) != is_active);
id(program_active) = is_active;
// Extract busColor from JSON (look for "busColor":"#xxxxxx")
bool color_changed = false;
std::string color = "";
size_t color_pos = payload.find("\"busColor\":\"");
if (color_pos != std::string::npos) {
color_pos += 11; // length of "busColor":"
size_t color_end = payload.find("\"", color_pos);
if (color_end != std::string::npos) {
color = payload.substr(color_pos, color_end - color_pos);
if (color.size() == 7 && color[0] == '#') {
int new_r = strtol(color.substr(1,2).c_str(), nullptr, 16);
int new_g = strtol(color.substr(3,2).c_str(), nullptr, 16);
int new_b = strtol(color.substr(5,2).c_str(), nullptr, 16);
if (id(program_color)[0] != new_r || id(program_color)[1] != new_g || id(program_color)[2] != new_b) {
color_changed = true;
id(program_color)[0] = new_r;
id(program_color)[1] = new_g;
id(program_color)[2] = new_b;
}
}
}
}
// Only update LED if state or color changed
if (state_changed || color_changed) {
id(update_led).execute();
}
# -------- PREVIEW BUS --------
- topic: ${tally_prefix}/device/${tally_device_id}/bus/${preview_bus_id}
then:
- lambda: |-
// Parse JSON string manually to extract state and color
std::string payload = x.c_str();
bool is_active = payload.find("\"state\":\"ON\"") != std::string::npos;
bool state_changed = (id(preview_active) != is_active);
id(preview_active) = is_active;
// Extract busColor from JSON (look for "busColor":"#xxxxxx")
bool color_changed = false;
std::string color = "";
size_t color_pos = payload.find("\"busColor\":\"");
if (color_pos != std::string::npos) {
color_pos += 11; // length of "busColor":"
size_t color_end = payload.find("\"", color_pos);
if (color_end != std::string::npos) {
color = payload.substr(color_pos, color_end - color_pos);
if (color.size() == 7 && color[0] == '#') {
int new_r = strtol(color.substr(1,2).c_str(), nullptr, 16);
int new_g = strtol(color.substr(3,2).c_str(), nullptr, 16);
int new_b = strtol(color.substr(5,2).c_str(), nullptr, 16);
if (id(preview_color)[0] != new_r || id(preview_color)[1] != new_g || id(preview_color)[2] != new_b) {
color_changed = true;
id(preview_color)[0] = new_r;
id(preview_color)[1] = new_g;
id(preview_color)[2] = new_b;
}
}
}
}
// Only update LED if state or color changed
if (state_changed || color_changed) {
id(update_led).execute();
}
# -------- DEVICE STATUS --------
- topic: ${tally_prefix}/device/${tally_device_id}/status
then:
- lambda: |-
id(mqtt_connected) = (std::string(x.c_str()) == "online");
# ----------------------------
# Globals
# ----------------------------
globals:
- id: program_active
type: bool
restore_value: no
initial_value: "false"
- id: preview_active
type: bool
restore_value: no
initial_value: "false"
# RGB color arrays [R, G, B] - initialized to fallback colors
- id: program_color
type: int[3]
restore_value: no
initial_value: |-
{ strtol("ff", nullptr, 16), // Red (matches program_fallback_color)
strtol("00", nullptr, 16),
strtol("00", nullptr, 16) }
- id: preview_color
type: int[3]
restore_value: no
initial_value: |-
{ strtol("00", nullptr, 16),
strtol("ff", nullptr, 16), // Green (matches preview_fallback_color)
strtol("00", nullptr, 16) }
- id: mqtt_connected
type: bool
restore_value: no
initial_value: "false"
# ----------------------------
# RGB LED
# ----------------------------
light:
- platform: neopixelbus
id: tally_led
type: GRB
variant: WS2812
pin: ${led_pin}
num_leds: 1
restore_mode: ALWAYS_OFF
default_transition_length: 0s
# ----------------------------
# Binary Sensors
# ----------------------------
binary_sensor:
- platform: template
name: "MQTT Connected"
id: mqtt_connected_sensor
device_class: connectivity
lambda: |-
return id(mqtt_connected); // Tracks TallyArbiter connection status
# ----------------------------
# LED Update Logic
# ----------------------------
# Priority: Program > Preview > Off
script:
- id: update_led
then:
- if:
condition:
lambda: 'return id(program_active);'
then:
- light.turn_on:
id: tally_led
brightness: ${led_brightness}
red: !lambda 'return id(program_color)[0] / 255.0;'
green: !lambda 'return id(program_color)[1] / 255.0;'
blue: !lambda 'return id(program_color)[2] / 255.0;'
else:
- if:
condition:
lambda: 'return id(preview_active);'
then:
- light.turn_on:
id: tally_led
brightness: ${led_brightness}
red: !lambda 'return id(preview_color)[0] / 255.0;'
green: !lambda 'return id(preview_color)[1] / 255.0;'
blue: !lambda 'return id(preview_color)[2] / 255.0;'
else:
- light.turn_off:
id: tally_led
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment