Skip to main content
Smarthome 15 mins

The Dilemma: Home Assistant Automations, Node-RED, or PyScript?

There are many ways to bring logic into your smart home. But which one is right for you? A comparison between native Home Assistant automations, the visual Node-RED, and the programmatic solution via PyScript — and why the “YAML hell” has lost its terror today.

The Dilemma: Home Assistant Automations, Node-RED, or PyScript?
Table of Contents

Anyone who’s been in the Home Assistant universe for a while might still remember the early days with a slight shudder. Back then, creating automations was a pure text exercise in YAML YAMLData format (YAML Ain’t Markup Language). Used in Home Assistant for configurations, automations and scenes. Easy to read but error-prone with incorrect indentation files. And by that, I don’t mean relaxed configuration — I mean the daily battle against the invisible final boss: the space character.

Welcome to YAML Indentation Hell.

One space too few? Configuration error. One space too many? The system won’t boot. A tab instead of two spaces? Catastrophe! You often felt like a bomb disposal expert, trying with trembling hands to slide the entity_id exactly two spaces under the action block. You’d stare at the screen for hours, doubting your sanity, only to discover that line 42 was a millimeter too far left.

⚠ [affiliate] Keine Produkte mit Cache für home-assistant. Bitte affiliate-sync --lang en ausführen.

Those who — like me — came from systems like IP-Symcon had it especially hard. I used to be an absolute “PHP guy.” So in the Symcon world, I could easily just program even the most complex automations because I spoke the language.

In Home Assistant, however, you initially felt lost in a text desert, painfully missing a real scripting language and needing Zen-Buddhist patience just to turn on the hallway light via a motion sensor in the dark. More extensive automations were only possible directly in YAML.

Fortunately, Home Assistant has evolved massively. The graphical automation editor has become powerful and takes this Sisyphean task off your hands. In parallel, Node-RED Node-REDVisual programming environment for flows. Often used as a Home Assistant add-on to create complex automations graphically via drag & dropRead more → has established itself as the quasi-standard for complex workflows, and for coders, there’s PyScript PyScriptEnables writing Home Assistant automations in Python instead of YAML. Provides more flexibility for complex logic with loops and conditionsRead more → — a powerful Python integration.

But when do you use which tool? I’ve looked at the three most common methods and compare their pros and cons.

The Scenario: Light On with Motion

To make the approaches comparable, we’ll use an absolutely classic example found in almost every household: A motion sensor in the hallway should turn on a lamp, but only when it’s dark enough (brightness < 20 lux). When no more motion is detected, the light should turn off again.

1. Native Home Assistant Automations

The built-in engine has matured enormously in recent years. What used to be tedious YAML hacking can now be almost entirely clicked together via the user interface ( UI UIUser Interface – the visible surface of software (windows, menus, buttons). In HA: the dashboard (Lovelace), settings and the add-on store ). Home Assistant still writes the YAML in the background, but you no longer have to look at it — and most importantly, you no longer have to manually indent it.

The flow is linear: Trigger TriggerTrigger of a Home Assistant automation – the event that starts the flow (e.g. sunset, sensor value, time). First building block of every automation (motion detected) -> Condition (only when dark) -> Action (light on) -> Trigger (motion) no motion detected -> Delay wait 5 minutes -> Action light off

Even though you mostly click today, it’s worth looking at the code that’s generated in the background to understand the structure:

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
alias: "Hallway Light: Motion and Brightness"
mode: restart
triggers:
  # Trigger 1: Motion detected
  - trigger: state
    entity_id: binary_sensor.hallway_motion_occupancy
    from: "off"
    to: "on"
    id: "motion_detected"

  # Trigger 2: No motion for 5 minutes
  - trigger: state
    entity_id: binary_sensor.hallway_motion_occupancy
    to: "off"
    for:
      minutes: 5
    id: "no_motion"

conditions: []

actions:
  - choose:
      # Scenario A: Turn light on (only when dark)
      - conditions:
          - condition: trigger
            id: "motion_detected"
          - condition: numeric_state
            entity_id: sensor.hallway_motion_illuminance
            below: 20
        sequence:
          - action: light.turn_on
            target:
              entity_id: light.hallway_ceiling # YOUR LIGHT
            data:
              brightness_pct: 100 # Set brightness
              transition: 1       # Smooth fade-in

      # Scenario B: Turn light off
      - conditions:
          - condition: trigger
            id: "no_motion"
        sequence:
          - action: light.turn_off
            target:
              entity_id: light.hallway_ceiling # YOUR LIGHT
            data:
              transition: 2       # Smooth fade-out

That’s solid, readable, but even with this simple logic you can see why it can become confusing with more complex nesting — and we haven’t even defined the “off” automation after, say, 5 minutes that would also survive a Home Assistant reboot — so no simple delay.

A huge advantage of native automations is the trace function. You can visually and interactively track why an automation triggered — or why it didn’t. This is worth its weight in gold for troubleshooting and, in my opinion, is now better solved than debugging in many other tools.

Advantages:

  • Deeply integrated: No additional software needed, Home Assistant updates cover everything.
  • Performance: Since it runs directly in the core, latency is minimal.
  • Debugging: The trace timeline is excellent for error analysis.
  • Blueprints: You can use ready-made templates from the community without reinventing the wheel.

Disadvantages:

  • Complexity with branching: As soon as you need nested “if-then” logic or loops, the UI quickly becomes confusing.
  • YAML knowledge: For very specific things (templates), you end up back in the code editor — but at least usually only as one-liners.

2. Node-RED

For many, Node-RED is the holy grail of home automation. It’s a visual programming tool where you connect “nodes” with wires. It usually runs as an add-on alongside Home Assistant.

For visual types, this is a blessing. You can literally see the flow of data. Our example here would be a chain of: Event State Node (motion) -> Switch Node (brightness check) -> Call Service Node (light on).

Node-RED plays to its strengths when the logic becomes complex. When data needs to be fetched from an API APIApplication Programming Interface – defined interface through which programs communicate. Home Assistant offers a REST and a WebSocket API (e.g. for MCP) , reformatted, filtered, and then sent to three different services, Node-RED can often be clicked together in just a few minutes. In YAML, that would be a nightmare you’d wake up from drenched in sweat.

A perfect example of this is my project where I made our door camera smarter . When the doorbell rang, the TV had to be turned on, switched to the right HDMI input, and the camera image displayed. Such chains of different protocols (HDMI-CEC, MQTT MQTTLightweight publish/subscribe messaging protocol. Used in smart homes to exchange sensor data and control commands between devices , camera streams) are Node-RED’s specialty.

However, Node-RED introduces another layer of complexity. It’s a standalone system that needs to be maintained. If the add-on fails, the lights stay off even though Home Assistant is still running. It also tempts you to build huge “spaghetti monsters” — confusing cable-clutter flows that you yourself won’t understand after three months. Plus, you can get lost in the vast selection of custom nodes, and it’s often a challenge just to find the right node.

Advantages:

  • Visual overview: Data flows are intuitively graspable.
  • Powerful: Enormous library of “palettes” (extensions) for almost every use case.
  • Flexibility: Ideal for complex logic and data manipulation.

Disadvantages:

  • Additional maintenance: Another system that needs to be maintained.
  • Overhead: For simple “light on/off” tasks, often like using a sledgehammer to crack a nut.
  • Separation: The logic lives outside the actual Home Assistant configuration (mind your backup strategy!).

Here are a few node recommendations for NodeRed nonetheless. The first 3 make many complex tasks much easier:

  • node-red-contrib-bigtimer — Powerful timer functions. Can handle sunrise/sunset (with offset), holidays, manual overrides, and even output MQTT text. Perfect for shutter and light control.
  • node-red-contrib-cron-plus — Supports CRON syntax and dynamic creation of schedules at runtime.
  • node-red-contrib-boolean-logic-ultimate — Saves you from huge “switch” cascades or complex if/else blocks in function nodes. Offers AND, OR, XOR gates. Example: “Light on only IF (motion detected) AND (it’s night) AND (NOT TV is on).” It also stores states persistently across restarts.
  • node-red-contrib-looptimer-advanced — For making things blink or repeating notifications (“Garage is still open!”) until someone responds.
  • node-red-contrib-influxdb — Write directly to InfluxDB.
  • node-red-contrib-zigbee2mqtt — Access zigbee2mqtt.
  • node-red-contrib-huemagic — Very powerful palette for Philips Hue. Offers more options (e.g., dynamic scenes, “Color Loop”) than the standard HA entities.

3. PyScript (Python) — The AppDaemon Killer?

For a long time, AppDaemon was the undisputed top dog for anyone who wanted more power than YAML could offer. Don’t get me wrong, AppDaemon is powerful and has its reason for existing (especially for complex dashboards) — but it’s also “its own beast.” It runs as a separate instance alongside Home Assistant, needs extra configuration, requires its own connection setup, and often feels unnecessarily complicated when you just want to “quickly” write an intelligent script.

Here comes PyScript, and for me, it’s the much leaner and more charming alternative. PyScript doesn’t run alongside but essentially inside Home Assistant. You save the overhead of a separate application. You have direct access to all entities as if they were native Python variables.

PyScript is installed via HACS HACSHome Assistant Community Store – unofficial marketplace for integrations, add-ons and custom dashboards from the community , and you store the scripts in the /config/pyscript/ folder.

Now, I’ve always worked in the realm of C-like programming languages — PHP, C, C++, JS, or C#. Here, structures are formed with curly braces, and indentation is useful for readability but doesn’t change functionality. My adjustment to Python was quite significant. However, Python rewards you with enforced, highly readable, and very elegant code that any tech-savvy and willing-to-learn Home Assistant fan can learn.

Let’s look at our light scenario in PyScript. Here’s what the same logic looks like when programmed:

Python

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Enter your real entity IDs here
MOTION_SENSOR = "binary_sensor.hallway_motion_occupancy"
LIGHT_ENTITY  = "light.hallway_ceiling"
LUX_SENSOR    = "sensor.hallway_motion_illuminance"

LUX_THRESHOLD = 20
TIMEOUT_SEC   = 300 # 5 minutes in seconds

# --- FUNCTION 1: TURN ON ---
# Triggers when the motion sensor jumps to 'on'
@state_trigger(f"{MOTION_SENSOR} == 'on'")
def hallway_light_on():
    # Get the current brightness value
    # IMPORTANT: In HA, states are always strings,
    # so use float() to convert
    try:
        current_lux = float(state.get(LUX_SENSOR))
    except (ValueError, TypeError):
        # Fallback if sensor is
        # 'unavailable' -> turn light on for safety
        #current_lux = 0

    # Check condition
    if current_lux < LUX_THRESHOLD:
        light.turn_on(entity_id=LIGHT_ENTITY, brightness_pct=100, transition=1)
        log.info(f"PyScript: Hallway light on (Lux: {current_lux})")

# --- FUNCTION 2: TURN OFF ---
# Triggers ONLY when the sensor has been CONTINUOUSLY 'off' for 300 seconds
@state_trigger(f"{MOTION_SENSOR} == 'off'", state_hold=TIMEOUT_SEC)
def hallway_light_off():
    # Check if light is actually on (saves radio traffic)
    if state.get(LIGHT_ENTITY) == "on":
        light.turn_off(entity_id=LIGHT_ENTITY, transition=2)
        log.info("PyScript: Hallway light off after timeout")

Or without comments:

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
MOTION_SENSOR = "binary_sensor.hallway_motion_occupancy"
LIGHT_ENTITY  = "light.hallway_ceiling"
LUX_SENSOR    = "sensor.hallway_motion_illuminance"

LUX_THRESHOLD = 20
TIMEOUT_SEC   = 300

@state_trigger(f"{MOTION_SENSOR} == 'on'")
def hallway_light_on():

    try:
        current_lux = float(state.get(LUX_SENSOR))
    except (ValueError, TypeError):
        current_lux = 0

    if current_lux < LUX_THRESHOLD:
        light.turn_on(entity_id=LIGHT_ENTITY, brightness_pct=100, transition=1)

@state_trigger(f"{MOTION_SENSOR} == 'off'", state_hold=TIMEOUT_SEC)
def hallway_light_off():
    if state.get(LIGHT_ENTITY) == "on":
        light.turn_off(entity_id=LIGHT_ENTITY, transition=2)

This is extremely clean, short, and readable — provided you know Python. Thanks to the variables at the beginning, you can very easily reuse the code by simply swapping out the entities. PyScript becomes particularly exciting for two topics that are becoming increasingly important:

  1. AI and AI applications: Python is the lingua franca of artificial intelligence. If you want to call the OpenAI API, use LangChain, or feed data to a local LLM LLMLarge Language Model – AI model trained on vast amounts of text that can understand and generate natural language. Examples: GPT-4, Claude, Gemini, DeepSeek. Foundation for chatbots like ChatGPT and GitHub Copilot to control your heating truly intelligently, this is the place. In PyScript, you simply import the relevant libraries and you’re ready to go. In AppDaemon’s often isolated environment, integrating external libraries is often quite a hassle. PyScript sits right at the source.
  2. “Light” data persistence: Sometimes you want to store values that survive a restart. Normally, you clutter your Home Assistant instance with countless helper entities (input_text, input_number, input_boolean) for this. For every value you want to remember, a helper must be created. This unnecessarily bloats the entity list and makes the system sluggish. With PyScript, you save yourself this material battle. You simply use standard Python functions (open(), write()) to write data to a local text or JSON file in the config folder.
  3. Vibe Coding : Excellent coding support through AI tools. ChatGPT, Gemini, Claude ClaudeAI assistant by Anthropic (Sonnet, Opus, Haiku models). Uses MCP natively – Claude Desktop was one of the first MCP clients and can access your HA server directly , etc. “speak” Python best and can immediately deliver runnable suggestions or help with debugging. With YAML, the tools regularly fail as soon as automations become more complex. Plus, there’s a huge amount of Python libraries you can use.

Advantages:

  • Simpler than AppDaemon: no separate instance, no connection overhead, no boilerplate code
  • AI-Ready: Perfect environment for integrating modern AI libraries.
  • No helper flood: Data is stored in files instead of having to create dozens of helper entities.
  • Compact: Lots of logic in little space.
  • VSCode: With the VSCode Editor as an addon , you get plenty of coding support (and proper indentation).

Disadvantages:

  • Learning curve: Without Python knowledge, you’re facing a wall.
  • Coding: Requires a “developer mindset.” Without any coding experience, it gets difficult.
  • No UI: Code remains code — there’s nothing to click here.
  • VSCode: … you should learn that too

The Comparison at a Glance

Feature Native Automation AutomationRule-based workflow in Home Assistant: triggers, conditions and actions automatically execute a flow (e.g. turn on lights at sunset) Node-RED PyScript
Entry Barrier Low Medium High (Python knowledge)
Visualization UI / Traces Flow-Chart None (Code)
Debugging Excellent (Traces) Good (Debug Node) Log-Files
Maintenance Effort Minimal Medium ( Add-on Add-onAdditional software package for Home Assistant OS/Supervised, installed via the add-on store (renamed to ‘Apps’ in newer HA versions). Runs as its own container alongside core – e.g. the SSH add-on ) Minimal (Text files)
Complexity Quickly becomes confusing Well structurable Perfect for AI & complex logic
Architecture Native Core Separate Container Integrated (simpler than AppDaemon)

Reality Check: Where the Methods Hit Their Limits

Theory is good, practice is better. To understand why you should know (or at least understand) all three tools, let’s look at examples where the other approach fails or is simply no fun.

1. The Standard Case: Motion Sensor & Light

Winner: Native Automation

You could build this in Node-RED. You could write a Python script for it (see above). But why?

For a simple “motion on = light on” logic, PyScript is absolute overkill. You don’t bring out the big toolbox with programming language, file handling, and imports just to flip a switch. That’s using a sledgehammer to crack a nut.

Node-RED is also actually unnecessary here: If the Node-RED container hangs during an update, you’re standing in the dark. Native automations are more robust here, quicker to create, and require zero maintenance.

2. The “Webhook Parser”: Data from External Sources

Winner: Node-RED

Imagine a complex JSON object coming in via webhook from an external service (e.g., a non-integrated solar system or a DIY sensor). You need to break down this JSON, extract three values, check if value A is greater than value B, and then send the result via MQTT payload in separate topics.

  • Native Automation: This ends in “Jinja2 template hell.” Anyone who’s ever tried to parse nested JSON in YAML templates knows what I’m talking about. It’s unreadable and error-prone.
  • PyScript: Possible, but you first have to “catch” the webhook in Python.
  • Node-RED: A dream. An http-in node, a function node (or change node), MQTT-send-node, and done. You can visually see where the data flows.

3. The “Mathematician”: Dynamic Heating Curves & Arrays

Winner: PyScript

The goal: Build a dynamic heating control that doesn’t just take the outside temperature, but goes through a list of the last 24 hours of values, calculates the average, removes outliers, factors in the sun’s position, and writes this value to a file to use as a reference tomorrow.

  • Native Automation: Nearly impossible, or a construct of 20 helper entities and endless template sensors.
  • Node-RED: You end up in a function node where you… write JavaScript. So if you have to code anyway, why not do it in a proper environment?
  • PyScript: This is where Python plays to its strengths. Iterating lists, using statistical functions, writing files — that’s 10 lines of clean, readable code without helper chaos.

Conclusion and Recommendation

There is no “better” or “worse,” there’s only the right tool for the respective job.

I’ve personally evolved over the years. Initially, I outsourced a lot externally, simply to escape YAML hell. Nowadays, I follow the approach: As native as possible.

  1. Standard automations: I handle lights, heating, and simple notifications 90% with native Home Assistant automations. The UI is now so good and the traces so helpful that there’s no reason to fire up an external engine for this.
  2. Data processing & APIs: When JSON data needs to be parsed from a website or complex linkages are needed that are hard to represent in the UI, Node-RED is the tool of choice.
  3. AI, mathematics & storage: When calculations need to be made, data needs to be quickly written to a file (without helper chaos), or AI needs to be integrated into the smart home, PyScript is the royal road. It’s easier than AppDaemon and feels “closer” to the system.

My advice: Start with the built-in tools. Only when you hit hard limits there or notice that the automation is becoming unreadable should you look toward Node-RED. And if you know Python? You should check out PyScript — it could become a new love. And if you don’t know Python? The possibilities and elegance of PyScript might give you a compelling reason to dive into it.


Hi! I'm Markus – the Fricklr. ⚡ Electrical engineer, 🎵 musician (bass, guitar, keys) and professionally 'something with the internet' 🌐 for over 30 years – nowadays with AI too 🤖 – a 55-year-old generalist from Bavaria, Germany.

Your feedback helps me write even more interesting posts. After "Yes" there's also a small option to support my work.

Explore related topics

Content Map →
Show related content as a list
💡 Drag, hover & click to explore
Bigger dots are more relevant

What do you think? Leave a comment!

Share your thoughts, questions or experiences with the community.

Links marked with this symbol are affiliate links. As an Amazon Associate I earn from qualifying purchases. There are no additional costs for you.