An ESPHome external component that creates real, executable ESPHome automations at runtime by parsing JSON automation definitions stored in flash preferences. The component dynamically instantiates ESPHome trigger and action classes, resolves entities by their object IDs, and wires them together into functioning automations.
- Dynamic automation creation: Creates real ESPHome
Automation<>objects at runtime from JSON - Entity resolution: Resolves binary sensors, switches, and lights using ESPHome's object ID registry
- Real trigger classes: Instantiates
PressTrigger,ReleaseTriggerfrom JSON definitions - Real action classes: Creates
TurnOnAction,TurnOffAction,ToggleActionfor switches and lights - Persistent storage: Saves JSON configurations to flash memory (survives reboots, max 4KB)
- Runtime updates: Load new JSON and recreate all automations on-the-fly
- Safe memory management: Uses
unique_ptrfor proper lifecycle management
Unlike typical ESPHome automations that are defined at compile time, this component:
- Parses JSON automation definitions at runtime
- Resolves entities using
fnv1_hash(object_id)andApp.get_*_by_key() - Creates trigger objects (e.g.,
binary_sensor::PressTrigger) - Creates action objects (e.g.,
light::TurnOnAction) - Wires them together into
Automation<>objects that execute when triggers fire
The automations function exactly like compile-time ESPHome automations, but are created dynamically from JSON stored in flash.
- Copy the
componentsfolder to your ESPHome project directory - Reference it in your YAML configuration:
external_components:
- source:
type: local
path: componentsexternal_components:
- source: github://yourusername/esphome-json-automation
components: [ json_automation ]preferences:
flash_write_interval: 5min
binary_sensor:
- platform: gpio
id: button
pin: GPIO0
switch:
- platform: gpio
id: fan
pin: GPIO12
light:
- platform: binary
id: bedroom_light
output: light_output
json_automation:
id: my_automations
json_data: |
{
"automations": [
{
"id": "button_press_light",
"trigger": {
"type": "binary_sensor",
"condition": "on_press",
"parameters": {
"object_id": "button"
}
},
"actions": [
"light.turn_on: bedroom_light"
]
}
]
}json_automation:
id: my_automations
on_automation_loaded:
then:
- logger.log:
format: "Created %d automations from JSON"
args: ['data.c_str()']
on_json_error:
then:
- logger.log:
format: "JSON Error: %s"
args: ['error.c_str()']
level: ERROR{
"automations": [
{
"id": "unique_automation_id",
"trigger": {
"type": "binary_sensor",
"condition": "on_press",
"parameters": {
"object_id": "button_object_id"
}
},
"actions": [
"switch.turn_on: fan_object_id",
"light.toggle: light_object_id"
]
}
]
}Currently supported:
- binary_sensor
on_press: Createsbinary_sensor::PressTriggeron_release: Createsbinary_sensor::ReleaseTrigger
Currently supported:
- Switch actions:
switch.turn_on,switch.turn_off,switch.toggle - Light actions:
light.turn_on,light.turn_off,light.toggle
Entities are resolved by their object_id using ESPHome's hash-based registry:
"parameters": {
"object_id": "my_button"
}The component:
- Calculates
fnv1_hash("my_button") - Looks up the entity using
App.get_binary_sensor_by_key(hash) - Uses the resolved entity to create the trigger/action
Load new JSON and recreate all automations:
button:
- platform: template
name: "Load Custom Automation"
on_press:
- json_automation.load_json:
id: my_automations
json_data: |
{
"automations": [
{
"id": "new_automation",
"trigger": {
"type": "binary_sensor",
"condition": "on_press",
"parameters": {
"object_id": "button"
}
},
"actions": [
"switch.toggle: fan"
]
}
]
}This will:
- Clear all existing automations
- Parse the new JSON
- Create new automation objects
- Wire them to triggers and actions
Save current configuration to flash preferences:
- json_automation.save_json:
id: my_automations{
"automations": [
{
"id": "button_press_light",
"trigger": {
"type": "binary_sensor",
"condition": "on_press",
"parameters": {
"object_id": "button"
}
},
"actions": [
"light.turn_on: bedroom_light"
]
},
{
"id": "button_release_light",
"trigger": {
"type": "binary_sensor",
"condition": "on_release",
"parameters": {
"object_id": "button"
}
},
"actions": [
"light.turn_off: bedroom_light",
"switch.toggle: fan"
]
}
]
}esphome:
name: json-automation-example
esp32:
board: esp32dev
logger:
level: DEBUG
api:
encryption:
key: "your-encryption-key"
ota:
- platform: esphome
password: "your-ota-password"
wifi:
ssid: "your-ssid"
password: "your-password"
preferences:
flash_write_interval: 5min
external_components:
- source:
type: local
path: components
binary_sensor:
- platform: gpio
id: button
pin:
number: GPIO0
inverted: true
mode: INPUT_PULLUP
switch:
- platform: gpio
id: fan
pin: GPIO12
output:
- platform: gpio
id: light_output
pin: GPIO13
light:
- platform: binary
id: bedroom_light
output: light_output
json_automation:
id: my_automations
json_data: |
{
"automations": [...]
}The component uses ESPHome's preferences system to store JSON in flash memory:
- Write cycles: Flash memory has limited write cycles (~100,000)
- Write interval: Configure
flash_write_intervalappropriately (default: 1min) - Data size: JSON is limited to 4KB maximum to protect flash memory
preferences:
flash_write_interval: 5min # Reduce flash wearRun the included validation script to check component structure:
python validate_component.pyThis validates:
- Python configuration structure
- C++ header and implementation
- Example YAML configuration
- Example JSON structure
The component creates real ESPHome automation objects at runtime:
// Simplified pseudo-code of what happens internally
auto *trigger = new binary_sensor::PressTrigger(sensor);
auto *action = new light::TurnOnAction<>(light);
auto *automation = new Automation<>(trigger);
automation->add_action(action);Entities are resolved using ESPHome's object ID hashing:
uint32_t hash = esphome::fnv1_hash(object_id);
auto *sensor = App.get_binary_sensor_by_key(hash);The component uses unique_ptr for safe memory management:
std::vector<std::unique_ptr<Automation<>>> automation_objects_;When clear_automations() is called, the vector is cleared and all automations are properly destroyed.
The component uses LATE setup priority to ensure all entities are registered before attempting resolution:
float get_setup_priority() const override {
return setup_priority::LATE;
}- Triggers: Only
binary_sensortriggers (on_press,on_release) - Actions: Only string-based actions (switch/light control)
- No object actions: Delay, lambdas, and complex actions not supported
- No parameters: Actions don't support additional parameters (brightness, color, etc.)
These limitations keep the implementation simple and avoid template complexity. Future versions may expand support.
ESPHome uses heavy template metaprogramming. Creating templated objects at runtime requires knowing types at compile time. The current implementation focuses on:
- Non-templated triggers:
Trigger<>(binary sensor state changes) - Non-templated actions:
Action<>(simple on/off/toggle) - Simple actions: Avoiding lambdas and complex parameter passing
This provides a working dynamic automation system while keeping the code maintainable.
- Check entity resolution: Ensure
object_idmatches exactly (case-sensitive) - Enable debug logging: Set
logger: level: DEBUG - Check logs for errors: Look for "Failed to resolve" messages
- Verify entities exist: Ensure binary sensors/switches/lights are defined
- Check JSON syntax with a validator
- Verify the
automationsarray exists in JSON - Check for size limit (4KB maximum)
- Look for parsing errors in logs
- Ensure ESPHome version is recent (tested with 2024.x)
- Check that all referenced entities are defined
- Verify external component path is correct
components/json_automation/
├── __init__.py # Python config validation & code generation
├── json_automation.h # C++ header with class definition
└── json_automation.cpp # C++ implementation with factories
example.yaml # Example ESPHome config
example_automation.json # Example JSON automations
validate_component.py # Component validator
-
Compile time (
__init__.py):- Validates YAML configuration
- Generates C++ code registration
-
Boot time (
setup()):- Loads JSON from preferences or uses provided json_data
- Parses JSON to extract automation definitions
- Calls
create_all_automations()to build runtime objects
-
Runtime (
create_all_automations()):- Iterates through parsed automation rules
- Creates trigger objects via
create_trigger_from_rule() - Creates action objects via
create_action_from_string() - Wires triggers and actions into
Automation<>objects - Stores automations in
automation_objects_vector
-
Update time (
LoadJsonAction):- Calls
clear_automations()to destroy old objects - Parses new JSON
- Creates new automation objects
- Calls
This component is built using ESPHome's external component system:
- Include in your ESPHome YAML configuration
- Run
esphome compile your_config.yaml - Upload to your ESP device
This component is provided as-is for use with ESPHome projects.
Contributions are welcome! Please ensure:
- Code follows ESPHome coding standards
- Validation script passes
- Examples are updated for new features
- Documentation is clear and complete