🧠 Introducing HASSL — Home Assistant Simple Scripting Language

Hi everyone,

I started using Home Assistant about a year ago — mostly as a fun project for my Raspberry Pi 5 and to get all my smart devices playing nicely with HomeKit.

Once I started writing automations, I realized how complex even “simple” tasks can be — like syncing multiple lights and switches without loops, or handling motion-based lighting with manual overrides.

Blueprints were often too limited, and Node-RED looked like another learning curve.
What I really wanted was an Arduino-style scripting experience — something where I could describe logic naturally, and let a compiler handle the YAML.


:sparkles: Enter HASSL

HASSL is a small domain-specific language (DSL) that compiles human-readable .hassl scripts into fully functional Home Assistant packages.

It automatically:

  • Syncs lights, switches, and fans safely (no feedback loops)
  • Supports schedules like enable from 08:00 until 19:00
  • Adds not_by guards to prevent loops
  • Generates helpers, scripts, and automations automatically
  • Survives HA restarts (schedules re-evaluate automatically)

:jigsaw: Example

alias light  = light.wesley_lamp
alias motion = binary_sensor.wesley_motion_motion
alias lux    = sensor.wesley_motion_illuminance

schedule wake_hours:
  enable from 08:00 until 19:00;

rule motion_light:
  schedule use wake_hours;
  if (motion && lux < 50)
  then light = on;
  wait (!motion for 10m) light = off

rule manual_off:
  if (light == off) not_by any_hassl
  then disable rule motion_light for 3m

Compile it:

hasslc myroom.hassl -o ./packages/myroom/

Drop the package into /config/packages/ — and it just works.


:framed_picture: Visual Overview

Before (traditional YAML):

- id: "motion_light"
  trigger:
    - platform: state
      entity_id: binary_sensor.hall_motion
  condition:
    - condition: template
      value_template: "{{ states('sensor.hall_lux') | float < 50 }}"
  action:
    - service: light.turn_on
      target:
        entity_id: light.hall

After (HASSL):

if (motion && lux < 50) then light = on

:bulb: Try it out

:package: GitHub: https://github.com/adanowitz/hassl
:snake: PyPI: pip install hassl

This is an early release (v0.2.0), but it’s already powering my lighting and motion automations reliably.
I’d love feedback from anyone who wants a simpler, developer-friendly way to write Home Assistant logic!


:heart: Feedback Welcome

If you try HASSL, please share what kinds of automations you build — motion lighting, sync groups, energy-saving, etc.
Contributions and suggestions are always welcome!

3 Likes

Can you show the “compiled” YAML this produces?

1 Like

This looks like you have worked hard on this and I hope it works better for you than stock YAML.

I feel that you have named this Appropriately but also Unfortunately at the same time.
How do you pronounce the name. like ‘hassle’?

There’s a reason HASS is in the process of being scrubbed from external HA name spaces anyway…
Do you know what that word translates to in German?

Sure! It actually splits it across two files in the package, a yaml containing the rules/automations, and a yaml containing generated helpers.

The generated rules:

automation:
- alias: HASSL schedule wake_hours start
  mode: single
  trigger:
  - platform: time
    at: 08:00:00
  action:
  - service: input_boolean.turn_on
    target:
      entity_id: input_boolean.hassl_schedule_wake_hours
- alias: HASSL schedule wake_hours end
  mode: single
  trigger:
  - platform: time
    at: '19:00:00'
  action:
  - service: input_boolean.turn_off
    target:
      entity_id: input_boolean.hassl_schedule_wake_hours
- alias: HASSL schedule wake_hours maintain
  mode: single
  trigger:
  - platform: homeassistant
    event: start
  - platform: time_pattern
    minutes: /1
  condition: []
  action:
  - choose:
    - conditions:
      - &id001
        condition: template
        value_template: '{% set now_s = now().strftime(''%H:%M'') %}{% set s = ''08:00''
          %}{% set e = ''19:00'' %}{{ (s < e and (now_s >= s and now_s < e)) or (s
          >= e and (now_s >= s or now_s < e)) }}'
      sequence:
      - service: input_boolean.turn_on
        target:
          entity_id: input_boolean.hassl_schedule_wake_hours
    - conditions:
      - condition: not
        conditions:
        - *id001
      sequence:
      - service: input_boolean.turn_off
        target:
          entity_id: input_boolean.hassl_schedule_wake_hours
- id: motion_light__1
  alias: 'HASSL motion_light #1'
  mode: restart
  trigger:
  - platform: state
    entity_id: binary_sensor.wesley_motion_motion
  - platform: state
    entity_id: sensor.wesley_motion_illuminance
  condition:
  - condition: state
    entity_id: input_boolean.hassl_gate_motion_light
    state: 'on'
  - condition: state
    entity_id: input_boolean.hassl_schedule_wake_hours
    state: 'on'
  - condition: and
    conditions:
    - condition: state
      entity_id: binary_sensor.wesley_motion_motion
      state: 'on'
    - condition: template
      value_template: '{{ states(''sensor.wesley_motion_illuminance'')|float(0) <
        50 }}'
  action:
  - service: input_text.set_value
    data:
      entity_id: input_text.hassl_ctx_light_wesley_lamp
      value: '{{ this.context.id }}'
  - service: homeassistant.turn_on
    target:
      entity_id: light.wesley_lamp
  - wait_for_trigger:
    - platform: template
      value_template: '{{ (not (is_state(''binary_sensor.wesley_motion_motion'',''on'')))
        }}'
      for: 00:10:00
  - service: input_text.set_value
    data:
      entity_id: input_text.hassl_ctx_light_wesley_lamp
      value: '{{ this.context.id }}'
  - service: homeassistant.turn_off
    target:
      entity_id: light.wesley_lamp
- id: manual_off__1
  alias: 'HASSL manual_off #1'
  mode: restart
  trigger:
  - platform: state
    entity_id: light.wesley_lamp
  condition:
  - condition: state
    entity_id: input_boolean.hassl_gate_manual_off
    state: 'on'
  - condition: state
    entity_id: light.wesley_lamp
    state: 'off'
  - condition: template
    value_template: '{{ trigger.to_state.context.parent_id != states(''input_text.hassl_ctx_light_wesley_lamp'')
      }}'
  action:
  - service: input_boolean.turn_off
    target:
      entity_id: input_boolean.hassl_gate_motion_light
  - delay: 00:03:00
  - service: input_boolean.turn_on
    target:
      entity_id: input_boolean.hassl_gate_motion_light

The helpers:

# Generated by HASSL codegen
input_text:
  hassl_ctx_light_wesley_lamp:
    name: HASSL Ctx light.wesley_lamp
    max: 64
input_boolean:
  hassl_gate_manual_off:
    name: HASSL Gate manual_off
    initial: 'on'
  hassl_gate_motion_light:
    name: HASSL Gate motion_light
    initial: 'on'
  hassl_schedule_wake_hours:
    name: HASSL Schedule wake_hours
    initial: 'off'
input_number: {}
1 Like

I’ll admit, the name and pronunciation Hassl=>“hassle” was intentional and a bit tongue-in-cheek. For me, getting sync working, or combining a motion sensor with a Leviton smart switch it was easy enough to get it working 90% the way I wanted, but getting that last 10% (eliminating race conditions when rapidly actuating the dimmer switch on a sync, or getting the switch to act as a manual override for a light switch when the switch state and light state are identical) seemed like it would be a major hassle. As this was my first big coding project working with genAI, learning to work on code with Chat was also a bit of a hassle.

If the name ist zu hässlich for the community, though, I’m open to changing it.

Then there’s this…

Cannot claim to be a HA expert,… but I do find yaml ‘a bit of a headache’,… arduino ‘C’ I can manage…
so will look a little deeper at your creation…
BTW,… if you google search HASSL,. you can find this web site,… https://hassl.co/features,… some desktop management thing,… so a slight change of name maybe useful…
I will try,… many tx

Interesting.

You just got some love at XDA.

2 Likes

That’s how/why I landed here.

1 Like

Oh man, that’s awesome! I had no idea! Working on adding weekday vs weekend schedules and schedule periods now (e.g. October–December keep holiday smart plugs on from 4pm until 10pm), hopefully it’ll be out as v3.1 in the next few days (end of next week tops!)

1 Like