hassl 0.2.1__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hassl-0.2.1/hassl.egg-info → hassl-0.3.1}/PKG-INFO +123 -10
- hassl-0.3.1/README.md +265 -0
- hassl-0.3.1/hassl/__init__.py +1 -0
- hassl-0.3.1/hassl/ast/nodes.py +86 -0
- hassl-0.3.1/hassl/cli.py +372 -0
- hassl-0.3.1/hassl/codegen/generate.py +6 -0
- hassl-0.3.1/hassl/codegen/init.py +3 -0
- hassl-0.3.1/hassl/codegen/package.py +910 -0
- {hassl-0.2.1 → hassl-0.3.1}/hassl/codegen/rules_min.py +204 -196
- {hassl-0.2.1 → hassl-0.3.1}/hassl/parser/hassl.lark +75 -8
- hassl-0.3.1/hassl/parser/transform.py +696 -0
- hassl-0.3.1/hassl/semantics/analyzer.py +421 -0
- {hassl-0.2.1 → hassl-0.3.1/hassl.egg-info}/PKG-INFO +123 -10
- {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/SOURCES.txt +7 -1
- {hassl-0.2.1 → hassl-0.3.1}/pyproject.toml +1 -1
- {hassl-0.2.1 → hassl-0.3.1}/setup.cfg +1 -1
- hassl-0.3.1/tests/test_imports_and_schedules.py +257 -0
- hassl-0.3.1/tests/test_schedule_windows_codegen.py +123 -0
- hassl-0.2.1/README.md +0 -152
- hassl-0.2.1/hassl/ast/nodes.py +0 -34
- hassl-0.2.1/hassl/cli.py +0 -42
- hassl-0.2.1/hassl/codegen/package.py +0 -335
- hassl-0.2.1/hassl/parser/transform.py +0 -272
- hassl-0.2.1/hassl/semantics/__init__.py +0 -0
- hassl-0.2.1/hassl/semantics/analyzer.py +0 -145
- {hassl-0.2.1 → hassl-0.3.1}/LICENSE +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/MANIFEST.in +0 -0
- {hassl-0.2.1/hassl → hassl-0.3.1/hassl/ast}/__init__.py +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/hassl/codegen/__init__.py +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/hassl/codegen/yaml_emit.py +0 -0
- {hassl-0.2.1/hassl/ast → hassl-0.3.1/hassl/parser}/__init__.py +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/hassl/parser/loader.py +0 -0
- {hassl-0.2.1/hassl/parser → hassl-0.3.1/hassl/semantics}/__init__.py +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/hassl/semantics/domains.py +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/dependency_links.txt +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/entry_points.txt +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/requires.txt +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/top_level.txt +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/tests/test_codegen_sync_basic.py +0 -0
- {hassl-0.2.1 → hassl-0.3.1}/tests/test_golden_ir_sync_shared.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hassl
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: HASSL: Home Assistant Simple Scripting Language
|
|
5
5
|
Home-page: https://github.com/adanowitz/hassl
|
|
6
6
|
Author: adanowitz
|
|
@@ -17,6 +17,8 @@ Dynamic: license-file
|
|
|
17
17
|
|
|
18
18
|
> **Home Assistant Simple Scripting Language**
|
|
19
19
|
|
|
20
|
+

|
|
21
|
+
|
|
20
22
|
HASSL is a human-friendly domain-specific language (DSL) for building **loop-safe**, **deterministic**, and **composable** automations for [Home Assistant](https://www.home-assistant.io/).
|
|
21
23
|
|
|
22
24
|
It compiles lightweight `.hassl` scripts into fully functional YAML packages that plug directly into Home Assistant, replacing complex automations with a clean, readable syntax.
|
|
@@ -28,16 +30,20 @@ It compiles lightweight `.hassl` scripts into fully functional YAML packages tha
|
|
|
28
30
|
- **Readable DSL** → write logic like natural language (`if motion && lux < 50 then light = on`)
|
|
29
31
|
- **Sync devices** → keep switches, dimmers, and fans perfectly in sync
|
|
30
32
|
- **Schedules** → declare time-based gates (`enable from 08:00 until 19:00`)
|
|
33
|
+
- **Weekday/weekend/holiday schedules** → full support for Home Assistant’s **Workday integration** (v0.3.1)
|
|
31
34
|
- **Loop-safe** → context ID tracking prevents feedback loops
|
|
32
35
|
- **Per-rule enable gates** → `disable rule` or `enable rule` dynamically
|
|
33
36
|
- **Inline waits** → `wait (!motion for 10m)` works like native HA triggers
|
|
34
37
|
- **Color temperature in Kelvin** → `light.kelvin = 2700`
|
|
38
|
+
- **Modular packages/imports** → split automations across files with public/private exports
|
|
35
39
|
- **Auto-reload resilience** → schedules re-evaluate automatically on HA restart
|
|
36
40
|
|
|
37
41
|
---
|
|
38
42
|
|
|
39
43
|
## 🧰 Example
|
|
40
44
|
|
|
45
|
+
### Basic standalone script
|
|
46
|
+
|
|
41
47
|
```hassl
|
|
42
48
|
alias light = light.wesley_lamp
|
|
43
49
|
alias motion = binary_sensor.wesley_motion_motion
|
|
@@ -64,6 +70,45 @@ Produces a complete Home Assistant package with:
|
|
|
64
70
|
- Sync automations for linked devices
|
|
65
71
|
- Rule-based automations with schedules and `not_by` guards
|
|
66
72
|
|
|
73
|
+
### Using imports across packages
|
|
74
|
+
|
|
75
|
+
```hassl
|
|
76
|
+
# packages/std/shared.hassl
|
|
77
|
+
package std.shared
|
|
78
|
+
|
|
79
|
+
alias light = light.wesley_lamp
|
|
80
|
+
alias motion = binary_sensor.wesley_motion_motion
|
|
81
|
+
alias lux = sensor.wesley_motion_illuminance
|
|
82
|
+
|
|
83
|
+
schedule wake_hours:
|
|
84
|
+
enable from 08:00 until 19:00;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```hassl
|
|
88
|
+
# packages/home/landing.hassl
|
|
89
|
+
package home.landing
|
|
90
|
+
import std.shared.*
|
|
91
|
+
|
|
92
|
+
rule wesley_motion_light:
|
|
93
|
+
schedule use wake_hours;
|
|
94
|
+
if (motion && lux < 50)
|
|
95
|
+
then light = on;
|
|
96
|
+
wait (!motion for 10m) light = off
|
|
97
|
+
|
|
98
|
+
rule landing_manual_off:
|
|
99
|
+
if (light == off) not_by any_hassl
|
|
100
|
+
then disable rule wesley_motion_light for 3m
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This setup produces:
|
|
104
|
+
- One **shared package** defining reusable aliases and schedules
|
|
105
|
+
- A **landing package** importing and reusing those exports
|
|
106
|
+
|
|
107
|
+
Together, they generate:
|
|
108
|
+
- ✅ Shared schedule sensor (`binary_sensor.hassl_schedule_std_shared_wake_hours_active`)
|
|
109
|
+
- ✅ Cross-package rule automations gated by that schedule
|
|
110
|
+
- ✅ Context-safe helpers and syncs for both packages
|
|
111
|
+
|
|
67
112
|
---
|
|
68
113
|
|
|
69
114
|
## 🏗 Installation
|
|
@@ -103,6 +148,7 @@ Each `.hassl` file compiles into an isolated package — no naming collisions, n
|
|
|
103
148
|
| `scripts_<pkg>.yaml` | Writer scripts with context stamping |
|
|
104
149
|
| `sync_<pkg>_*.yaml` | Sync automations for each property |
|
|
105
150
|
| `rules_bundled_<pkg>.yaml` | Rule logic automations + schedules |
|
|
151
|
+
| `schedules_<pkg>.yaml` | Time/sun-based schedule sensors (v0.3.1) |
|
|
106
152
|
|
|
107
153
|
---
|
|
108
154
|
|
|
@@ -120,7 +166,7 @@ Each `.hassl` file compiles into an isolated package — no naming collisions, n
|
|
|
120
166
|
|
|
121
167
|
## 🔒 Loop Safety & Context Tracking
|
|
122
168
|
|
|
123
|
-
HASSL automatically writes the **parent context ID** into helper entities before performing actions
|
|
169
|
+
HASSL automatically writes the **parent context ID** into helper entities before performing actions.
|
|
124
170
|
This ensures `not_by any_hassl` and `not_by rule("name")` guards work flawlessly, preventing infinite feedback.
|
|
125
171
|
|
|
126
172
|
---
|
|
@@ -129,23 +175,91 @@ This ensures `not_by any_hassl` and `not_by rule("name")` guards work flawlessly
|
|
|
129
175
|
|
|
130
176
|
All schedules are restart-safe:
|
|
131
177
|
|
|
132
|
-
- `
|
|
133
|
-
-
|
|
178
|
+
- `binary_sensor.hassl_schedule_<package>_<name>_active` automatically re-evaluates on startup.
|
|
179
|
+
- Clock and sun-based windows update continuously through HA’s template engine.
|
|
134
180
|
- Missed events (like mid-day restarts) are recovered automatically.
|
|
135
181
|
|
|
136
182
|
---
|
|
137
183
|
|
|
138
|
-
##
|
|
184
|
+
## 🗓️ Holiday & Workday Integration (v0.3.1)
|
|
185
|
+
|
|
186
|
+
HASSL now supports `holidays <id>:` schedules tied to Home Assistant’s **Workday** integration.
|
|
187
|
+
|
|
188
|
+
To enable holiday and weekday/weekend-aware schedules:
|
|
189
|
+
|
|
190
|
+
### 1️⃣ Create two Workday sensors in Home Assistant
|
|
191
|
+
|
|
192
|
+
You must create **two Workday integrations** through the Home Assistant UI.
|
|
193
|
+
|
|
194
|
+
#### Sensor 1 — `binary_sensor.hassl_<id>_workday`
|
|
195
|
+
- **Workdays:** Mon–Fri
|
|
196
|
+
- **Excludes:** `holiday`
|
|
197
|
+
- **Meaning:** ON only on real workdays (Mon–Fri that are not holidays).
|
|
198
|
+
|
|
199
|
+
#### Sensor 2 — `binary_sensor.hassl_<id>_not_holiday`
|
|
200
|
+
- **Workdays:** Mon–Sun
|
|
201
|
+
- **Excludes:** `holiday`
|
|
202
|
+
- **Meaning:** ON every day except official holidays (including weekends).
|
|
203
|
+
|
|
204
|
+
> In both, set your **Country** and optional **Province/Region** as needed for your locale (e.g., `US`, `CA`, `GB`, etc.).
|
|
205
|
+
> After setup, rename the entity IDs to exactly match:
|
|
206
|
+
> - `binary_sensor.hassl_<id>_workday`
|
|
207
|
+
> - `binary_sensor.hassl_<id>_not_holiday`
|
|
208
|
+
> where `<id>` matches the identifier used in your `.hassl` file (e.g., `us_ca`).
|
|
209
|
+
|
|
210
|
+
HASSL derives:
|
|
211
|
+
- `binary_sensor.hassl_holiday_<id>` → ON on holidays (even when they fall on weekends).
|
|
212
|
+
|
|
213
|
+
### Truth table
|
|
214
|
+
|
|
215
|
+
| Day type | `hassl_<id>_workday` | `hassl_<id>_not_holiday` | `hassl_holiday_<id>` (derived) |
|
|
216
|
+
|------------------------------|-----------------------|---------------------------|---------------------------------|
|
|
217
|
+
| Tue (normal) | on | on | off |
|
|
218
|
+
| Sat (normal weekend) | off | on | off |
|
|
219
|
+
| Mon that’s an official holiday | off | off | on |
|
|
220
|
+
| Sat that’s an official holiday | off | off | on |
|
|
221
|
+
|
|
222
|
+
This distinction lets you build precise schedules like:
|
|
223
|
+
```hassl
|
|
224
|
+
holidays us_ca:
|
|
225
|
+
country="US", province="CA"
|
|
226
|
+
|
|
227
|
+
schedule master_wake:
|
|
228
|
+
on weekdays 06:00–22:00 except holidays us_ca;
|
|
229
|
+
on weekends 08:00–22:00;
|
|
230
|
+
on holidays us_ca 09:00–22:00;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
🧩 **Note:**
|
|
234
|
+
Both sensors must be created manually in the Home Assistant UI — integrations can’t be defined in YAML.
|
|
235
|
+
Once created, HASSL automatically references them in generated automations.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## ⚗️ Experimental: Date & Month Range Schedules
|
|
240
|
+
|
|
241
|
+
HASSL v0.3.1 includes early support for:
|
|
242
|
+
|
|
243
|
+
```hassl
|
|
244
|
+
on months Jun–Aug 07:00–22:00;
|
|
245
|
+
on dates 12-24..01-02 06:00–20:00;
|
|
246
|
+
```
|
|
139
247
|
|
|
140
|
-
|
|
248
|
+
These may compile successfully but are **not yet validated in production**.
|
|
249
|
+
They’re marked **experimental** and will be verified after template automation support (v0.4 milestone).
|
|
141
250
|
|
|
142
|
-
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 📚 Documentation
|
|
254
|
+
|
|
255
|
+
- [Quickstart Guide](./quickstart.md)
|
|
256
|
+
- [Language Specification](./Hassl_spec.md)
|
|
143
257
|
|
|
144
258
|
---
|
|
145
259
|
|
|
146
260
|
## 🧩 Contributing
|
|
147
261
|
|
|
148
|
-
Contributions, tests, and ideas welcome
|
|
262
|
+
Contributions, tests, and ideas welcome!
|
|
149
263
|
To run tests locally:
|
|
150
264
|
|
|
151
265
|
```bash
|
|
@@ -158,10 +272,9 @@ Please open pull requests for grammar improvements, new device domains, or sched
|
|
|
158
272
|
|
|
159
273
|
## 📄 License
|
|
160
274
|
|
|
161
|
-
MIT License © 2025
|
|
275
|
+
MIT License © 2025
|
|
162
276
|
Created and maintained by [@adanowitz](https://github.com/adanowitz)
|
|
163
277
|
|
|
164
278
|
---
|
|
165
279
|
|
|
166
280
|
**HASSL** — simple, reliable, human-readable automations for Home Assistant.
|
|
167
|
-
|
hassl-0.3.1/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# HASSL
|
|
2
|
+
|
|
3
|
+
> **Home Assistant Simple Scripting Language**
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
HASSL is a human-friendly domain-specific language (DSL) for building **loop-safe**, **deterministic**, and **composable** automations for [Home Assistant](https://www.home-assistant.io/).
|
|
8
|
+
|
|
9
|
+
It compiles lightweight `.hassl` scripts into fully functional YAML packages that plug directly into Home Assistant, replacing complex automations with a clean, readable syntax.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🚀 Features
|
|
14
|
+
|
|
15
|
+
- **Readable DSL** → write logic like natural language (`if motion && lux < 50 then light = on`)
|
|
16
|
+
- **Sync devices** → keep switches, dimmers, and fans perfectly in sync
|
|
17
|
+
- **Schedules** → declare time-based gates (`enable from 08:00 until 19:00`)
|
|
18
|
+
- **Weekday/weekend/holiday schedules** → full support for Home Assistant’s **Workday integration** (v0.3.1)
|
|
19
|
+
- **Loop-safe** → context ID tracking prevents feedback loops
|
|
20
|
+
- **Per-rule enable gates** → `disable rule` or `enable rule` dynamically
|
|
21
|
+
- **Inline waits** → `wait (!motion for 10m)` works like native HA triggers
|
|
22
|
+
- **Color temperature in Kelvin** → `light.kelvin = 2700`
|
|
23
|
+
- **Modular packages/imports** → split automations across files with public/private exports
|
|
24
|
+
- **Auto-reload resilience** → schedules re-evaluate automatically on HA restart
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 🧰 Example
|
|
29
|
+
|
|
30
|
+
### Basic standalone script
|
|
31
|
+
|
|
32
|
+
```hassl
|
|
33
|
+
alias light = light.wesley_lamp
|
|
34
|
+
alias motion = binary_sensor.wesley_motion_motion
|
|
35
|
+
alias lux = sensor.wesley_motion_illuminance
|
|
36
|
+
|
|
37
|
+
schedule wake_hours:
|
|
38
|
+
enable from 08:00 until 19:00;
|
|
39
|
+
|
|
40
|
+
rule wesley_motion_light:
|
|
41
|
+
schedule use wake_hours;
|
|
42
|
+
if (motion && lux < 50)
|
|
43
|
+
then light = on;
|
|
44
|
+
wait (!motion for 10m) light = off
|
|
45
|
+
|
|
46
|
+
rule landing_manual_off:
|
|
47
|
+
if (light == off) not_by any_hassl
|
|
48
|
+
then disable rule wesley_motion_light for 3m
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Produces a complete Home Assistant package with:
|
|
52
|
+
|
|
53
|
+
- Helpers (`input_boolean`, `input_text`, `input_number`)
|
|
54
|
+
- Context-aware writer scripts
|
|
55
|
+
- Sync automations for linked devices
|
|
56
|
+
- Rule-based automations with schedules and `not_by` guards
|
|
57
|
+
|
|
58
|
+
### Using imports across packages
|
|
59
|
+
|
|
60
|
+
```hassl
|
|
61
|
+
# packages/std/shared.hassl
|
|
62
|
+
package std.shared
|
|
63
|
+
|
|
64
|
+
alias light = light.wesley_lamp
|
|
65
|
+
alias motion = binary_sensor.wesley_motion_motion
|
|
66
|
+
alias lux = sensor.wesley_motion_illuminance
|
|
67
|
+
|
|
68
|
+
schedule wake_hours:
|
|
69
|
+
enable from 08:00 until 19:00;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```hassl
|
|
73
|
+
# packages/home/landing.hassl
|
|
74
|
+
package home.landing
|
|
75
|
+
import std.shared.*
|
|
76
|
+
|
|
77
|
+
rule wesley_motion_light:
|
|
78
|
+
schedule use wake_hours;
|
|
79
|
+
if (motion && lux < 50)
|
|
80
|
+
then light = on;
|
|
81
|
+
wait (!motion for 10m) light = off
|
|
82
|
+
|
|
83
|
+
rule landing_manual_off:
|
|
84
|
+
if (light == off) not_by any_hassl
|
|
85
|
+
then disable rule wesley_motion_light for 3m
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This setup produces:
|
|
89
|
+
- One **shared package** defining reusable aliases and schedules
|
|
90
|
+
- A **landing package** importing and reusing those exports
|
|
91
|
+
|
|
92
|
+
Together, they generate:
|
|
93
|
+
- ✅ Shared schedule sensor (`binary_sensor.hassl_schedule_std_shared_wake_hours_active`)
|
|
94
|
+
- ✅ Cross-package rule automations gated by that schedule
|
|
95
|
+
- ✅ Context-safe helpers and syncs for both packages
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 🏗 Installation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git clone https://github.com/adanowitz/hassl.git
|
|
103
|
+
cd hassl
|
|
104
|
+
pip install -e .
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Verify:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
hasslc --help
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## ⚙️ Usage
|
|
116
|
+
|
|
117
|
+
1. Create a `.hassl` script (e.g., `living_room.hassl`).
|
|
118
|
+
2. Compile it into a Home Assistant package:
|
|
119
|
+
```bash
|
|
120
|
+
hasslc living_room.hassl -o ./packages/living_room/
|
|
121
|
+
```
|
|
122
|
+
3. Copy the package into `/config/packages/` and reload automations.
|
|
123
|
+
|
|
124
|
+
Each `.hassl` file compiles into an isolated package — no naming collisions, no shared helpers.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 📦 Output Files
|
|
129
|
+
|
|
130
|
+
| File | Description |
|
|
131
|
+
| -------------------------- | --------------------------------------------- |
|
|
132
|
+
| `helpers_<pkg>.yaml` | Defines all helpers (booleans, text, numbers) |
|
|
133
|
+
| `scripts_<pkg>.yaml` | Writer scripts with context stamping |
|
|
134
|
+
| `sync_<pkg>_*.yaml` | Sync automations for each property |
|
|
135
|
+
| `rules_bundled_<pkg>.yaml` | Rule logic automations + schedules |
|
|
136
|
+
| `schedules_<pkg>.yaml` | Time/sun-based schedule sensors (v0.3.1) |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 🧠 Concepts
|
|
141
|
+
|
|
142
|
+
| Concept | Description |
|
|
143
|
+
| ------------ | --------------------------------------------------------------- |
|
|
144
|
+
| **Alias** | Maps short names to HA entities (`alias light = light.kitchen`) |
|
|
145
|
+
| **Sync** | Keeps multiple devices aligned across on/off, brightness, etc. |
|
|
146
|
+
| **Rule** | Defines reactive logic with guards, waits, and control flow. |
|
|
147
|
+
| **Schedule** | Defines active time windows, reusable across rules. |
|
|
148
|
+
| **Tag** | Lightweight metadata stored in `input_text` helpers. |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 🔒 Loop Safety & Context Tracking
|
|
153
|
+
|
|
154
|
+
HASSL automatically writes the **parent context ID** into helper entities before performing actions.
|
|
155
|
+
This ensures `not_by any_hassl` and `not_by rule("name")` guards work flawlessly, preventing infinite feedback.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 🕒 Schedules That Survive Restarts
|
|
160
|
+
|
|
161
|
+
All schedules are restart-safe:
|
|
162
|
+
|
|
163
|
+
- `binary_sensor.hassl_schedule_<package>_<name>_active` automatically re-evaluates on startup.
|
|
164
|
+
- Clock and sun-based windows update continuously through HA’s template engine.
|
|
165
|
+
- Missed events (like mid-day restarts) are recovered automatically.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 🗓️ Holiday & Workday Integration (v0.3.1)
|
|
170
|
+
|
|
171
|
+
HASSL now supports `holidays <id>:` schedules tied to Home Assistant’s **Workday** integration.
|
|
172
|
+
|
|
173
|
+
To enable holiday and weekday/weekend-aware schedules:
|
|
174
|
+
|
|
175
|
+
### 1️⃣ Create two Workday sensors in Home Assistant
|
|
176
|
+
|
|
177
|
+
You must create **two Workday integrations** through the Home Assistant UI.
|
|
178
|
+
|
|
179
|
+
#### Sensor 1 — `binary_sensor.hassl_<id>_workday`
|
|
180
|
+
- **Workdays:** Mon–Fri
|
|
181
|
+
- **Excludes:** `holiday`
|
|
182
|
+
- **Meaning:** ON only on real workdays (Mon–Fri that are not holidays).
|
|
183
|
+
|
|
184
|
+
#### Sensor 2 — `binary_sensor.hassl_<id>_not_holiday`
|
|
185
|
+
- **Workdays:** Mon–Sun
|
|
186
|
+
- **Excludes:** `holiday`
|
|
187
|
+
- **Meaning:** ON every day except official holidays (including weekends).
|
|
188
|
+
|
|
189
|
+
> In both, set your **Country** and optional **Province/Region** as needed for your locale (e.g., `US`, `CA`, `GB`, etc.).
|
|
190
|
+
> After setup, rename the entity IDs to exactly match:
|
|
191
|
+
> - `binary_sensor.hassl_<id>_workday`
|
|
192
|
+
> - `binary_sensor.hassl_<id>_not_holiday`
|
|
193
|
+
> where `<id>` matches the identifier used in your `.hassl` file (e.g., `us_ca`).
|
|
194
|
+
|
|
195
|
+
HASSL derives:
|
|
196
|
+
- `binary_sensor.hassl_holiday_<id>` → ON on holidays (even when they fall on weekends).
|
|
197
|
+
|
|
198
|
+
### Truth table
|
|
199
|
+
|
|
200
|
+
| Day type | `hassl_<id>_workday` | `hassl_<id>_not_holiday` | `hassl_holiday_<id>` (derived) |
|
|
201
|
+
|------------------------------|-----------------------|---------------------------|---------------------------------|
|
|
202
|
+
| Tue (normal) | on | on | off |
|
|
203
|
+
| Sat (normal weekend) | off | on | off |
|
|
204
|
+
| Mon that’s an official holiday | off | off | on |
|
|
205
|
+
| Sat that’s an official holiday | off | off | on |
|
|
206
|
+
|
|
207
|
+
This distinction lets you build precise schedules like:
|
|
208
|
+
```hassl
|
|
209
|
+
holidays us_ca:
|
|
210
|
+
country="US", province="CA"
|
|
211
|
+
|
|
212
|
+
schedule master_wake:
|
|
213
|
+
on weekdays 06:00–22:00 except holidays us_ca;
|
|
214
|
+
on weekends 08:00–22:00;
|
|
215
|
+
on holidays us_ca 09:00–22:00;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
🧩 **Note:**
|
|
219
|
+
Both sensors must be created manually in the Home Assistant UI — integrations can’t be defined in YAML.
|
|
220
|
+
Once created, HASSL automatically references them in generated automations.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## ⚗️ Experimental: Date & Month Range Schedules
|
|
225
|
+
|
|
226
|
+
HASSL v0.3.1 includes early support for:
|
|
227
|
+
|
|
228
|
+
```hassl
|
|
229
|
+
on months Jun–Aug 07:00–22:00;
|
|
230
|
+
on dates 12-24..01-02 06:00–20:00;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
These may compile successfully but are **not yet validated in production**.
|
|
234
|
+
They’re marked **experimental** and will be verified after template automation support (v0.4 milestone).
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 📚 Documentation
|
|
239
|
+
|
|
240
|
+
- [Quickstart Guide](./quickstart.md)
|
|
241
|
+
- [Language Specification](./Hassl_spec.md)
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 🧩 Contributing
|
|
246
|
+
|
|
247
|
+
Contributions, tests, and ideas welcome!
|
|
248
|
+
To run tests locally:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
pytest tests/
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Please open pull requests for grammar improvements, new device domains, or scheduling logic.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 📄 License
|
|
259
|
+
|
|
260
|
+
MIT License © 2025
|
|
261
|
+
Created and maintained by [@adanowitz](https://github.com/adanowitz)
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
**HASSL** — simple, reliable, human-readable automations for Home Assistant.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.1"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from dataclasses import dataclass, asdict, field
|
|
2
|
+
from typing import List, Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Alias:
|
|
6
|
+
name: str
|
|
7
|
+
entity: str
|
|
8
|
+
private: bool = False
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Sync:
|
|
12
|
+
kind: str
|
|
13
|
+
members: List[str]
|
|
14
|
+
name: str
|
|
15
|
+
invert: List[str] = field(default_factory=list)
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class IfClause:
|
|
19
|
+
condition: Dict[str, Any]
|
|
20
|
+
actions: List[Dict[str, Any]]
|
|
21
|
+
|
|
22
|
+
# ---- NEW: Holiday sets & structured schedule windows ----
|
|
23
|
+
@dataclass
|
|
24
|
+
class HolidaySet:
|
|
25
|
+
id: str
|
|
26
|
+
country: str
|
|
27
|
+
province: Optional[str] = None
|
|
28
|
+
add: List[str] = field(default_factory=list) # YYYY-MM-DD
|
|
29
|
+
remove: List[str] = field(default_factory=list)
|
|
30
|
+
workdays: List[str] = field(default_factory=lambda: ["mon","tue","wed","thu","fri"])
|
|
31
|
+
excludes: List[str] = field(default_factory=lambda: ["sat","sun","holiday"])
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class PeriodSelector:
|
|
35
|
+
# kind = 'months' | 'dates' | 'range'
|
|
36
|
+
kind: str
|
|
37
|
+
# data:
|
|
38
|
+
# - months: {"list":[Mon,...]} or {"range":[Mon,Mon]}
|
|
39
|
+
# - dates: {"start":"MM-DD","end":"MM-DD"}
|
|
40
|
+
# - range: {"start":"YYYY-MM-DD","end":"YYYY-MM-DD"}
|
|
41
|
+
data: Dict[str, Any]
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ScheduleWindow:
|
|
45
|
+
start: str # "HH:MM"
|
|
46
|
+
end: str # "HH:MM"
|
|
47
|
+
day_selector: str # "weekdays" | "weekends" | "daily"
|
|
48
|
+
period: Optional[PeriodSelector] = None
|
|
49
|
+
holiday_ref: Optional[str] = None # id from HolidaySet (for 'except'/'only')
|
|
50
|
+
holiday_mode: Optional[str] = None # "except" | "only" | None
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class Schedule:
|
|
54
|
+
name: str
|
|
55
|
+
# raw clauses as produced by the transformer (legacy form)
|
|
56
|
+
clauses: List[Dict[str, Any]]
|
|
57
|
+
# structured windows for the new 'on ...' syntax (optional)
|
|
58
|
+
windows: List[ScheduleWindow] = field(default_factory=list)
|
|
59
|
+
private: bool = False
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class Rule:
|
|
63
|
+
name: str
|
|
64
|
+
# allow schedule dicts
|
|
65
|
+
clauses: List[Any]
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class Program:
|
|
69
|
+
statements: List[object]
|
|
70
|
+
package: Optional[str] = None
|
|
71
|
+
# normalized import entries (dicts) from the transformer:
|
|
72
|
+
# {"type":"import","module": "...", "kind": "glob|list|alias", "items":
|
|
73
|
+
#[...], "as": "name"|None}
|
|
74
|
+
imports: List[Dict[str, Any]] = field(default_factory=list)
|
|
75
|
+
def to_dict(self):
|
|
76
|
+
def enc(x):
|
|
77
|
+
if isinstance(x, (Alias, Sync, Rule, IfClause, Schedule,
|
|
78
|
+
HolidaySet, ScheduleWindow, PeriodSelector)):
|
|
79
|
+
d = asdict(x); d["type"] = x.__class__.__name__; return d
|
|
80
|
+
return x
|
|
81
|
+
return {
|
|
82
|
+
"type": "Program",
|
|
83
|
+
"package": self.package,
|
|
84
|
+
"imports": self.imports,
|
|
85
|
+
"statements": [enc(s) for s in self.statements],
|
|
86
|
+
}
|