@khalido/home 0.0.0-stage → 0.1.0
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.
- package/.env.example +25 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +77 -2
- package/dist/clock.d.ts +17 -0
- package/dist/clock.js +43 -0
- package/dist/config.d.ts +273 -0
- package/dist/config.js +392 -0
- package/dist/connector.d.ts +34 -0
- package/dist/connector.js +47 -0
- package/dist/connectors/amber/client.d.ts +61 -0
- package/dist/connectors/amber/client.js +106 -0
- package/dist/connectors/amber/index.d.ts +30 -0
- package/dist/connectors/amber/index.js +94 -0
- package/dist/connectors/cast/codec.d.ts +38 -0
- package/dist/connectors/cast/codec.js +102 -0
- package/dist/connectors/cast/index.d.ts +60 -0
- package/dist/connectors/cast/index.js +426 -0
- package/dist/connectors/cast/schemas.d.ts +167 -0
- package/dist/connectors/cast/schemas.js +87 -0
- package/dist/connectors/cast/serve.d.ts +11 -0
- package/dist/connectors/cast/serve.js +57 -0
- package/dist/connectors/cast/state.d.ts +62 -0
- package/dist/connectors/cast/state.js +133 -0
- package/dist/connectors/cast/tls.d.ts +3 -0
- package/dist/connectors/cast/tls.js +67 -0
- package/dist/connectors/cast/writes.d.ts +80 -0
- package/dist/connectors/cast/writes.js +390 -0
- package/dist/connectors/daikin/client.d.ts +65 -0
- package/dist/connectors/daikin/client.js +221 -0
- package/dist/connectors/daikin/index.d.ts +41 -0
- package/dist/connectors/daikin/index.js +108 -0
- package/dist/connectors/enphase/client.d.ts +65 -0
- package/dist/connectors/enphase/client.js +173 -0
- package/dist/connectors/enphase/index.d.ts +43 -0
- package/dist/connectors/enphase/index.js +174 -0
- package/dist/connectors/enphase/schemas.d.ts +51 -0
- package/dist/connectors/enphase/schemas.js +34 -0
- package/dist/connectors/hue/client.d.ts +48 -0
- package/dist/connectors/hue/client.js +95 -0
- package/dist/connectors/hue/events.d.ts +41 -0
- package/dist/connectors/hue/events.js +156 -0
- package/dist/connectors/hue/index.d.ts +42 -0
- package/dist/connectors/hue/index.js +205 -0
- package/dist/connectors/hue/schemas.d.ts +65 -0
- package/dist/connectors/hue/schemas.js +48 -0
- package/dist/connectors/hue/state.d.ts +43 -0
- package/dist/connectors/hue/state.js +84 -0
- package/dist/connectors/printers/client.d.ts +28 -0
- package/dist/connectors/printers/client.js +74 -0
- package/dist/connectors/printers/codec.d.ts +76 -0
- package/dist/connectors/printers/codec.js +246 -0
- package/dist/connectors/printers/found.d.ts +15 -0
- package/dist/connectors/printers/found.js +35 -0
- package/dist/connectors/printers/index.d.ts +52 -0
- package/dist/connectors/printers/index.js +282 -0
- package/dist/connectors/printers/state.d.ts +60 -0
- package/dist/connectors/printers/state.js +99 -0
- package/dist/connectors/spotify/changes.d.ts +32 -0
- package/dist/connectors/spotify/changes.js +84 -0
- package/dist/connectors/spotify/client.d.ts +199 -0
- package/dist/connectors/spotify/client.js +270 -0
- package/dist/connectors/spotify/index.d.ts +92 -0
- package/dist/connectors/spotify/index.js +745 -0
- package/dist/connectors/up/client.d.ts +183 -0
- package/dist/connectors/up/client.js +308 -0
- package/dist/connectors/up/index.d.ts +91 -0
- package/dist/connectors/up/index.js +213 -0
- package/dist/connectors/up/spend.d.ts +78 -0
- package/dist/connectors/up/spend.js +213 -0
- package/dist/connectors/up/webhook.d.ts +13 -0
- package/dist/connectors/up/webhook.js +32 -0
- package/dist/connectors/weather/blocks.d.ts +51 -0
- package/dist/connectors/weather/blocks.js +106 -0
- package/dist/connectors/weather/bom.d.ts +37 -0
- package/dist/connectors/weather/bom.js +237 -0
- package/dist/connectors/weather/ftp.d.ts +5 -0
- package/dist/connectors/weather/ftp.js +46 -0
- package/dist/connectors/weather/google.d.ts +31 -0
- package/dist/connectors/weather/google.js +364 -0
- package/dist/connectors/weather/index.d.ts +53 -0
- package/dist/connectors/weather/index.js +267 -0
- package/dist/connectors/weather/open-meteo.d.ts +15 -0
- package/dist/connectors/weather/open-meteo.js +285 -0
- package/dist/connectors/weather/rain.d.ts +19 -0
- package/dist/connectors/weather/rain.js +51 -0
- package/dist/connectors/weather/source.d.ts +149 -0
- package/dist/connectors/weather/source.js +77 -0
- package/dist/connectors/weather/sun.d.ts +48 -0
- package/dist/connectors/weather/sun.js +65 -0
- package/dist/discover.d.ts +195 -0
- package/dist/discover.js +446 -0
- package/dist/energy.d.ts +61 -0
- package/dist/energy.js +162 -0
- package/dist/finders-entry.d.ts +1 -0
- package/dist/finders-entry.js +2 -0
- package/dist/finders.d.ts +45 -0
- package/dist/finders.js +173 -0
- package/dist/health.d.ts +28 -0
- package/dist/health.js +35 -0
- package/dist/home.d.ts +81 -0
- package/dist/home.js +114 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/lane.d.ts +17 -0
- package/dist/lane.js +74 -0
- package/dist/locate.d.ts +27 -0
- package/dist/locate.js +59 -0
- package/dist/manifest.d.ts +31 -0
- package/dist/manifest.js +4 -0
- package/dist/manifests.d.ts +5 -0
- package/dist/manifests.js +23 -0
- package/dist/node-fetch.d.ts +9 -0
- package/dist/node-fetch.js +25 -0
- package/dist/poll.d.ts +10 -0
- package/dist/poll.js +33 -0
- package/docs/cast.md +133 -0
- package/docs/cli.md +39 -0
- package/docs/config.md +36 -0
- package/docs/decisions.md +150 -0
- package/docs/devices.md +128 -0
- package/docs/enphase-notes.md +24 -0
- package/docs/google.md +92 -0
- package/docs/ideas.md +69 -0
- package/docs/matter.md +55 -0
- package/docs/performance.md +12 -0
- package/docs/philosophy.md +23 -0
- package/docs/up.md +84 -0
- package/docs/using.md +65 -0
- package/docs/weather-probe.md +63 -0
- package/examples/home.jsonc +74 -0
- package/examples/home.svg +76 -0
- package/home.schema.json +474 -0
- package/llms.txt +32 -0
- package/package.json +143 -4
- package/skills/draw-home/README.md +26 -0
- package/skills/draw-home/SKILL.md +82 -0
- package/skills/use-home/SKILL.md +86 -0
- package/src/cli/cache.ts +29 -0
- package/src/cli/cast.ts +172 -0
- package/src/cli/config.ts +52 -0
- package/src/cli/context.ts +118 -0
- package/src/cli/discover.ts +175 -0
- package/src/cli/enphase.ts +62 -0
- package/src/cli/hue.ts +124 -0
- package/src/cli/locate.ts +47 -0
- package/src/cli/main.ts +182 -0
- package/src/cli/printers.ts +21 -0
- package/src/cli/probe.ts +274 -0
- package/src/cli/spotify.ts +76 -0
- package/src/cli/status.ts +228 -0
- package/src/cli/up.ts +350 -0
- package/src/clock.ts +62 -0
- package/src/config.ts +578 -0
- package/src/connector.ts +77 -0
- package/src/connectors/amber/README.md +40 -0
- package/src/connectors/amber/client.ts +157 -0
- package/src/connectors/amber/index.ts +118 -0
- package/src/connectors/cast/README.md +43 -0
- package/src/connectors/cast/codec.ts +126 -0
- package/src/connectors/cast/index.ts +551 -0
- package/src/connectors/cast/schemas.ts +101 -0
- package/src/connectors/cast/serve.ts +72 -0
- package/src/connectors/cast/state.ts +203 -0
- package/src/connectors/cast/tls.ts +100 -0
- package/src/connectors/cast/writes.ts +572 -0
- package/src/connectors/daikin/README.md +78 -0
- package/src/connectors/daikin/client.ts +295 -0
- package/src/connectors/daikin/index.ts +145 -0
- package/src/connectors/enphase/README.md +64 -0
- package/src/connectors/enphase/client.ts +244 -0
- package/src/connectors/enphase/index.ts +241 -0
- package/src/connectors/enphase/schemas.ts +45 -0
- package/src/connectors/hue/README.md +50 -0
- package/src/connectors/hue/client.ts +157 -0
- package/src/connectors/hue/events.ts +182 -0
- package/src/connectors/hue/index.ts +261 -0
- package/src/connectors/hue/schemas.ts +60 -0
- package/src/connectors/hue/state.ts +124 -0
- package/src/connectors/printers/README.md +46 -0
- package/src/connectors/printers/client.ts +114 -0
- package/src/connectors/printers/codec.ts +292 -0
- package/src/connectors/printers/found.ts +46 -0
- package/src/connectors/printers/index.ts +385 -0
- package/src/connectors/printers/state.ts +182 -0
- package/src/connectors/spotify/README.md +80 -0
- package/src/connectors/spotify/changes.ts +127 -0
- package/src/connectors/spotify/client.ts +387 -0
- package/src/connectors/spotify/index.ts +929 -0
- package/src/connectors/up/README.md +35 -0
- package/src/connectors/up/client.ts +518 -0
- package/src/connectors/up/index.ts +370 -0
- package/src/connectors/up/spend.ts +280 -0
- package/src/connectors/up/webhook.ts +62 -0
- package/src/connectors/weather/README.md +84 -0
- package/src/connectors/weather/blocks.ts +173 -0
- package/src/connectors/weather/bom.ts +344 -0
- package/src/connectors/weather/ftp.ts +48 -0
- package/src/connectors/weather/google.ts +425 -0
- package/src/connectors/weather/index.ts +380 -0
- package/src/connectors/weather/open-meteo.ts +309 -0
- package/src/connectors/weather/rain.ts +73 -0
- package/src/connectors/weather/source.ts +236 -0
- package/src/connectors/weather/sun.ts +114 -0
- package/src/discover.ts +634 -0
- package/src/energy.ts +221 -0
- package/src/finders-entry.ts +2 -0
- package/src/finders.ts +247 -0
- package/src/health.ts +74 -0
- package/src/home.ts +205 -0
- package/src/index.ts +62 -0
- package/src/lane.ts +110 -0
- package/src/locate.ts +85 -0
- package/src/manifest.ts +43 -0
- package/src/manifests.ts +27 -0
- package/src/node-fetch.ts +41 -0
- package/src/poll.ts +33 -0
package/docs/devices.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Devices worth buying
|
|
2
|
+
|
|
3
|
+
2026-10-16. Australian prices are rough and mostly unverified; check the shop before buying. The connector survey (what the library could cover) is in [ideas.md](ideas.md); this page is about what to put in the house.
|
|
4
|
+
|
|
5
|
+
The test for each device: does it work locally, does Google Home see it, what does it cost in AU, how long does the battery last, and could this library read it. The library has no Matter or MQTT connector today. A Matter device is only readable here once a Matter controller exists (see [Matter in one paragraph](#matter-in-one-paragraph)). A device with a local HTTP API (Shelly) is readable with a small connector.
|
|
6
|
+
|
|
7
|
+
## Buy first
|
|
8
|
+
|
|
9
|
+
1. **Shelly Plus Plug S or PM Mini Gen3** (about A$35–45, unverified). Local JSON-RPC, push, energy per plug. The best template for an energy connector, already in ideas.md.
|
|
10
|
+
2. **Shelly EM Gen3 or Pro 3EM** (about A$60–200, unverified) if the board has room. A second, independent view of the house against Enphase. Local API.
|
|
11
|
+
3. **IKEA TIMMERFLOTTE** (A$14.99 on ikea.com/au, checked 2026-10-16). Cheapest Matter-over-Thread climate sensor with a display. Verdict below.
|
|
12
|
+
4. **A mmWave presence sensor**: Aqara FP2 (Wi-Fi, Matter via the Aqara hub, unverified) or a battery Thread sensor such as the Aqara FP300. Presence from a router's view of phones flaps; a real sensor does not.
|
|
13
|
+
5. **Leak sensors, any Thread or Zigbee kind** (IKEA KLIPPBOK, about A$15, unverified in AU). Cheap, and the one alert worth having.
|
|
14
|
+
|
|
15
|
+
## TIMMERFLOTTE verdict
|
|
16
|
+
|
|
17
|
+
Buy two or three and treat the numbers as indicative, not calibrated. Don't use it to drive heating or cooling decisions that need tenths of a degree.
|
|
18
|
+
|
|
19
|
+
- **Facts checked on [ikea.com/au](https://www.ikea.com/au/en/p/timmerflotte-temperature-humidity-sensor-smart-00618950/):** A$14.99, article 006.189.50, Matter over Thread, needs a Thread border router (a recent eero or a Nest Hub is one). Two AAA cells, not included; IKEA recommends LADDA rechargeables. A$12.99 for four. A small display shows temperature then humidity when you press the front. 4 out of 5 from 451 reviews.
|
|
20
|
+
- **Stated accuracy:** none on the AU page or in the reviews I could read. Sensor chip: not published, unverified. Reporting interval: not published, unverified. Thread sensors normally report on a change threshold plus a slow heartbeat; check the Matter cluster in the controller.
|
|
21
|
+
- **Tests:** [Matter Alpha](https://www.matteralpha.com/review/ikea-timmerflotte-review) liked it, gave no numbers, and said battery life is "months", not a figure. [Chris Smart](https://chrissmart.de/en/ikea-timmer-fleet-in-test-new-matt-sensor/) compared it with reference sensors and called the values "realistic and stable", also without numbers. [T3](https://www.t3.com/home-living/smart-home/ikea-timmerflotte-temperature-and-humidity-sensor-review-a-budget-friendly-way-to-monitor-your-home-environment) reports hardly any difference from a smart thermostat. I found no published side-by-side with a calibrated reference, and no Reddit or Home Assistant forum thread I could open (search blocked there), so the accuracy question is open.
|
|
22
|
+
- **Buyer complaints on IKEA stores:** one unit read more than 2 degrees off; humidity less accurate than temperature; some units drop off Thread every few weeks to months until the batteries are swapped; the battery percentage barely falls before the sensor stops; a button that stopped working. Anonymous and a small sample, but the drop-off reports are the real risk.
|
|
23
|
+
- **Google Home:** Google's [supported-devices page](https://developers.home.google.com/matter/supported-devices) lists the Matter temperature sensor and humidity sensor types, so it should pair and show both. One AU buyer reported failing to connect to Google Home. I found no test of how the app displays humidity. Unverified; try one unit first.
|
|
24
|
+
- **This library:** readable only through a Matter controller. Temperature and humidity are standard clusters, so no per-device code.
|
|
25
|
+
|
|
26
|
+
## Temperature and humidity
|
|
27
|
+
|
|
28
|
+
| Device | Radio | Local? | Google Home? | AU price | Notes |
|
|
29
|
+
| --------------------------------- | ----------------------- | ------ | ------------------------ | -------------------------- | --------------------------------------------- |
|
|
30
|
+
| IKEA TIMMERFLOTTE | Thread (Matter) | yes | yes, unconfirmed display | A$14.99 | display on press, 2 AAA |
|
|
31
|
+
| Aqara TH sensor (Matter versions) | Thread / Zigbee | yes | yes | about A$25–35, unverified | ±0.3 °C / ±3 %RH claimed by Aqara, unverified |
|
|
32
|
+
| Eve Weather / Room | Thread (Matter) | yes | yes | about A$80–100, unverified | better-known accuracy, pricey |
|
|
33
|
+
| SwitchBot Meter | Bluetooth, hub to cloud | partly | via hub | about A$20–30 | local needs the hub's API; skip |
|
|
34
|
+
|
|
35
|
+
Buy the IKEA unit for rooms. Buy Eve or Aqara only where accuracy matters. Battery life on all of these is months to two years on AAA or coin cells.
|
|
36
|
+
|
|
37
|
+
## Motion, occupancy and presence
|
|
38
|
+
|
|
39
|
+
| Device | Radio | Local? | Google Home? | AU price | Notes |
|
|
40
|
+
| ----------------- | --------------------------- | ----------- | ------------ | --------------------------- | ------------------------------------ |
|
|
41
|
+
| IKEA MYGGSPRAY | Thread (Matter) | yes | yes | unverified | indoor/outdoor motion |
|
|
42
|
+
| Aqara FP2 | Wi-Fi, Matter via Aqara hub | yes via hub | yes | about A$110–130, unverified | mmWave, zones; needs power |
|
|
43
|
+
| Aqara FP300 | Thread / Zigbee | yes | yes | unverified | battery mmWave, newer |
|
|
44
|
+
| Hue motion sensor | Zigbee on the bridge | yes | via Hue | about A$55–70 | already covered by the hue connector |
|
|
45
|
+
|
|
46
|
+
The Hue bridge already exposes its motion sensors; that is free. Add mmWave only for rooms where people sit still.
|
|
47
|
+
|
|
48
|
+
## Door and window contacts
|
|
49
|
+
|
|
50
|
+
| Device | Radio | Local? | Google Home? | AU price | Notes |
|
|
51
|
+
| ----------------------- | --------------- | ------ | ------------ | ---------------------- | ----- |
|
|
52
|
+
| IKEA MYGGBETT | Thread (Matter) | yes | yes | unverified | |
|
|
53
|
+
| Aqara contact sensor P2 | Thread (Matter) | yes | yes | about A$30, unverified | |
|
|
54
|
+
| Eve Door & Window | Thread (Matter) | yes | yes | about A$60, unverified | |
|
|
55
|
+
|
|
56
|
+
Google lists the contact sensor type as supported. CR2032 cells last 1–2 years.
|
|
57
|
+
|
|
58
|
+
## Leak sensors
|
|
59
|
+
|
|
60
|
+
| Device | Radio | Local? | Google Home? | AU price | Notes |
|
|
61
|
+
| ----------------------- | --------------- | ------ | -------------------- | ---------------------- | ----------- |
|
|
62
|
+
| IKEA KLIPPBOK | Thread (Matter) | yes | not listed by Google | unverified | cheapest |
|
|
63
|
+
| Aqara water leak sensor | Zigbee / Thread | yes | via hub | about A$25, unverified | |
|
|
64
|
+
| Eve Water Guard | Thread | yes | unverified | unverified | cable probe |
|
|
65
|
+
|
|
66
|
+
Google's page has no leak-detector device type. The alert would show in another controller (or this library) but not in Google Home. Unverified for each model.
|
|
67
|
+
|
|
68
|
+
## Air quality (CO2, PM2.5)
|
|
69
|
+
|
|
70
|
+
| Device | Radio | Local? | Google Home? | AU price | Notes |
|
|
71
|
+
| -------------------------- | --------------- | ----------------- | ------------ | ---------- | ----------------------------------------------------- |
|
|
72
|
+
| Aqara TVOC sensor | Zigbee | yes | via hub | unverified | VOC only, no CO2 |
|
|
73
|
+
| Airthings / Awair | Wi-Fi / BLE | local API on some | unverified | A$200+ | CO2 and PM2.5 properly |
|
|
74
|
+
| IKEA VINDSTYRKA / ALPSTUGA | Zigbee / Thread | yes | unverified | unverified | PM2.5, ALPSTUGA adds CO2 per IKEA's range, unverified |
|
|
75
|
+
|
|
76
|
+
Google supports the Matter air quality type (spec 1.2). Cheap CO2 sensors vary a lot; a local API or Matter matters more than the brand.
|
|
77
|
+
|
|
78
|
+
## Smart plugs with energy monitoring
|
|
79
|
+
|
|
80
|
+
| Device | Radio | Local? | Google Home? | AU price | Notes |
|
|
81
|
+
| ------------------------------ | --------------- | ---------------------------------- | -------------------------- | ------------------------- | -------------------------------------- |
|
|
82
|
+
| Shelly Plus Plug S / Mini Gen3 | Wi-Fi (+BT) | yes, JSON-RPC and MQTT | no (not Matter by default) | about A$35–45, unverified | library-friendly |
|
|
83
|
+
| IKEA GRILLPLATS | Thread (Matter) | yes | on/off yes | AU variant unverified | energy through Google is not supported |
|
|
84
|
+
| Eve Energy | Thread (Matter) | yes | on/off yes | about A$60–70, unverified | |
|
|
85
|
+
| Sonoff S60 / Tapo P110 | Wi-Fi | Sonoff local mode; Tapo cloud-tied | partial | about A$20–30 | Tapo protocol churns (see ideas.md) |
|
|
86
|
+
|
|
87
|
+
Google's page lists the plug type with only On/Off and Level Control, so power readings will not show in the app. Use Shelly for the numbers, and Matter plugs only where on/off is the point.
|
|
88
|
+
|
|
89
|
+
## Buttons and remotes
|
|
90
|
+
|
|
91
|
+
IKEA BILRESA (two-button and scroll-wheel; Thread, Matter) is the cheap one, a few dollars in Europe, AU unverified. Hue dimmer and Aqara buttons work if you already run those hubs. Buttons need a controller to act; nothing for this library to read.
|
|
92
|
+
|
|
93
|
+
## Ceiling fans
|
|
94
|
+
|
|
95
|
+
DC ceiling fans come two ways. Some have Wi-Fi built in, usually Tuya (the Smart Life app): those can be read and driven locally once their local key is pulled from a Tuya developer account, the way tinytuya does. Most use an RF remote: a vendor Wi-Fi module (often Tuya again), or a bridge that learns the remote (Bond Bridge, Broadlink RM4 Pro), which sends commands but cannot read the fan's state. A wall relay gives on/off only, not speed. Check the model before buying a module; compatibility lists differ between retailers.
|
|
96
|
+
|
|
97
|
+
## Blinds
|
|
98
|
+
|
|
99
|
+
Google supports the Matter window covering type. IKEA and Aqara motors, and SwitchBot Curtain (Bluetooth, hub needed), exist; prices and Matter status in AU unverified. Only buy if a window is hard to reach.
|
|
100
|
+
|
|
101
|
+
## Energy monitoring
|
|
102
|
+
|
|
103
|
+
| Device | Radio | Local? | Google Home? | AU price | Notes |
|
|
104
|
+
| ------------------------ | --------------- | -------------------------- | ------------ | --------------------------- | --------------------------------------------------------- |
|
|
105
|
+
| Shelly Pro 3EM / EM Gen3 | Wi-Fi, Ethernet | yes, JSON-RPC | no | about A$100–200, unverified | clamps on the board; needs an electrician for the install |
|
|
106
|
+
| Enphase IQ Gateway | LAN | yes, the enphase connector | no | comes with the solar | covers solar and use |
|
|
107
|
+
|
|
108
|
+
Enphase gives solar and use. A Shelly on a circuit (AC, hot water, pool) tells you what one load costs, which Amber prices make worth knowing.
|
|
109
|
+
|
|
110
|
+
## Water and gas
|
|
111
|
+
|
|
112
|
+
Nothing worth buying. Utility meters have no public local API (ideas.md). Clamp-on pulse readers exist but are installer jobs. Skip.
|
|
113
|
+
|
|
114
|
+
## Matter in one paragraph
|
|
115
|
+
|
|
116
|
+
Matter is the application layer: a common set of device types and clusters (temperature, occupancy, on/off) so one controller reads many brands. Thread is one radio it can ride on, a low-power mesh that suits battery sensors and needs a border router (recent eeros, Nest Hubs, Apple TVs). Wi-Fi Matter devices skip Thread. A device joins a fabric by a commissioning code (a QR or 11-digit setup code) that sets up an encrypted session; after that every message is encrypted and signed, and each fabric holds its own operational certificate for each node, so a second controller (matter.js beside Google Home) gets its own fabric and its own keys via a share code, and removing one does not touch the other. The device's own attestation certificate proves it is the certified product. The catch for this library: it is local and standard, but needs a controller that stores fabric keys in a file, which the library never does.
|
|
117
|
+
|
|
118
|
+
## Sources
|
|
119
|
+
|
|
120
|
+
- [IKEA AU TIMMERFLOTTE](https://www.ikea.com/au/en/p/timmerflotte-temperature-humidity-sensor-smart-00618950/)
|
|
121
|
+
- [Matter Alpha review](https://www.matteralpha.com/review/ikea-timmerflotte-review)
|
|
122
|
+
- [Chris Smart test](https://chrissmart.de/en/ikea-timmer-fleet-in-test-new-matt-sensor/)
|
|
123
|
+
- [T3 review](https://www.t3.com/home-living/smart-home/ikea-timmerflotte-temperature-and-humidity-sensor-review-a-budget-friendly-way-to-monitor-your-home-environment)
|
|
124
|
+
- [Matter Alpha: IKEA's 21 Matter devices](https://www.matteralpha.com/news/ikea-launches-21-new-smart-home-devices-with-native-matter-support)
|
|
125
|
+
- [Google Home supported Matter device types](https://developers.home.google.com/matter/supported-devices)
|
|
126
|
+
- [CSA listing, TIMMERFLOTTE](https://csa-iot.org/csa_product/timmerflotte-temp-hmd-sensor/)
|
|
127
|
+
- [Home Assistant Shelly integration](https://www.home-assistant.io/integrations/shelly/)
|
|
128
|
+
- [matter.js](https://github.com/project-chip/matter.js)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Enphase: field notes
|
|
2
|
+
|
|
3
|
+
Investigations behind the Enphase connector, moved out of [its README](../src/connectors/enphase/README.md) on 2026-10-14: moving the gateway between networks, what the first probe found, and the cloud API that was not chosen.
|
|
4
|
+
|
|
5
|
+
## Moving it to another Wi-Fi
|
|
6
|
+
|
|
7
|
+
**The owner's path: the Enphase app.** No Installer Toolkit needed. Stand near the gateway, logged in as the system owner, and open Menu ▸ Settings ▸ Connectivity ▸ Configure Wi-Fi. When asked, **briefly press** the gateway's AP Mode button: its AP Mode LED turns solid green and it opens its own network, `ENVOY_nnnnnn` (the last six digits of the serial). Pick the home network and enter its password. Source: Enphase support, [Reconnect your gateway to Wi-Fi using the Enphase App](https://support.enphase.com/s/article/reconnecting-your-gateway-to-wi-fi-using-the-enphase-app). That page renders only in a browser; these steps come from search summaries of it and its Dutch and French versions (unconfirmed word for word).
|
|
8
|
+
|
|
9
|
+
- **Do not hold the button.** A press starts an eight-hour AP session; **holding it 5 s starts WPS** instead. If it was held, wait 2 minutes and press briefly (Dutch support article, unconfirmed).
|
|
10
|
+
- **Fallback: the gateway's own page.** On `ENVOY_nnnnnn`, open `http://172.30.1.1` (support article, unconfirmed), or the local interface at its LAN address, then Wi-Fi ▸ Connect Other Network, and enter the network name and password ([Envoy-S manual](https://portal.segen.co.uk/reseller/docs/Envoy-S-MAN-EN-INTL.pdf), 141-00035 Rev 02, "Envoy-S is not Connecting with a New Router"). On software 7 and later that page asks for the owner token (TEB-00060).
|
|
11
|
+
|
|
12
|
+
**LEDs** (manual): Network Communications flashing green while it connects, **solid green within three minutes** when it reaches Enphase, solid amber when it has the local network but missed its report (five minutes), off with no network. AP Mode solid green while the AP session runs; off in normal use. Never cut power while the LEDs flash green: a software update is running.
|
|
13
|
+
|
|
14
|
+
**Still "down" in the app afterwards:** the gateway reports every 15 minutes (KO's observation), so allow 15 to 30 minutes. If the LED is solid green, the cloud catches up on the next report. If it is amber or off, power-cycle the gateway at its breaker (Enphase community moderator, unconfirmed) and retry.
|
|
15
|
+
|
|
16
|
+
**Guest Wi-Fi:** eero keeps guest devices apart from the main network. A gateway there can reach Enphase but not jaldi or the `home` CLI. Use the main network, then reserve its address in the eero app.
|
|
17
|
+
|
|
18
|
+
## Probe (2026-10-13)
|
|
19
|
+
|
|
20
|
+
The gateway was first on the guest Wi-Fi, which eero keeps apart from the main network, so `home probe` found nothing; on the main network it answered `/info` over HTTPS only (port 80 closed).
|
|
21
|
+
|
|
22
|
+
## The cloud alternative
|
|
23
|
+
|
|
24
|
+
The Enlighten v4 API at [developer-v4.enphase.com](https://developer-v4.enphase.com/): OAuth 2 plus an API key, free Watt plan at 10 requests a minute and 1,000 a month, site-level data only ([plans](https://developer-v4.enphase.com/developer-plans)). Not planned while the local API works.
|
package/docs/google.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Google: rain soon, a calendar, and whether it is one connector
|
|
2
|
+
|
|
3
|
+
Written 2026-10-15 from KO's questions, chota-bot's code, Google's docs and one live probe. The rain slice is built (see the weather README); the hourly slice and the calendar are not.
|
|
4
|
+
|
|
5
|
+
**Short answer.** Add a `rain` slice to the weather connector from Google's minute forecast, with the hourly forecast as fallback. Add a `calendar` connector that reads the Google Calendar API over `fetch`, with the token passed in by the caller. Keep Google as a vendor, not a connector: Weather and Calendar share nothing but a name.
|
|
6
|
+
|
|
7
|
+
## What is set up with Google today
|
|
8
|
+
|
|
9
|
+
| Where | What | How |
|
|
10
|
+
| --------- | --------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
11
|
+
| home | Weather API (Maps Platform, billed). `GOOGLE_WEATHER_KEY` | `fetch` and zod, no SDK. `src/connectors/weather/google.ts` |
|
|
12
|
+
| home | Google Cast | Local protocol, no Google account. Not an API. |
|
|
13
|
+
| chota-bot | Weather API, its own key | Duplicates `google.ts` (to be replaced by this library) |
|
|
14
|
+
| chota-bot | Calendar. `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | better-auth Google provider, scope `calendar.readonly`; `googleapis` 186 |
|
|
15
|
+
| chota-bot | `@ai-sdk/google` (Gemini) | Not about the home |
|
|
16
|
+
|
|
17
|
+
The home library uses no Google SDK. `googleapis` is large and pulls in Node-only auth code. Calendar needs one endpoint. `fetch` plus zod is enough, as it is for Weather.
|
|
18
|
+
|
|
19
|
+
## How chota-bot reads the calendar
|
|
20
|
+
|
|
21
|
+
KO signs in once at `/admin` with an allowed Google account. better-auth stores the OAuth tokens in chota's database. `auth.api.getAccessToken()` refreshes them. `tools/calendar.ts` calls the Calendar API through `googleapis` for one calendar id (the family calendar, set in `chota.config.ts`). Ranges: today, tomorrow, week, next week, weekend. It keeps an in-memory cache keyed by range and Sydney date, and has a title search.
|
|
22
|
+
|
|
23
|
+
## Rain soon
|
|
24
|
+
|
|
25
|
+
Google has two precipitation sources. Both are in the Weather API.
|
|
26
|
+
|
|
27
|
+
- **Minute forecast.** `GET /v1/forecast/minutes:lookup`. Probed 2026-10-15 for Sydney CBD (curl): HTTP 200, 1.0 s, 24 segments of 15 minutes, 6 hours in all. Each segment has `type` (NONE, RAIN, SNOW, HAIL), `probability` 0–100, `qpf` in mm and `intensity` (NO_INTENSITY, LIGHT, MODERATE, HEAVY). All 24 were dry at the time.
|
|
28
|
+
- **Hourly forecast.** `forecast/hours:lookup`. We already call it (`hours=6`) and read only `uvIndex` and `cloudCover`. The same response holds `precipitation.probability.percent`, `precipitation.qpf` and `thunderstormProbability`. Reading them costs no extra call.
|
|
29
|
+
|
|
30
|
+
Caveats:
|
|
31
|
+
|
|
32
|
+
- Google's page calls the minute forecast **Experimental**: no backward-compatibility promise. It does not say how the forecast is made, so "ML nowcast" is unconfirmed.
|
|
33
|
+
- Its coverage page lists Australia for current, hourly and daily forecasts, but says nothing about the minute forecast. The probe shows it works for Sydney today.
|
|
34
|
+
- The page gives no price or quota. Google's terms allow caching minute values for one hour at most.
|
|
35
|
+
- The 2-hour window is the first 8 segments.
|
|
36
|
+
|
|
37
|
+
**Design.**
|
|
38
|
+
|
|
39
|
+
- A new `rain` slice on `WeatherSource`. Google's minute forecast owns it. Without it, `rainSoon` is built from the hourly probability and says so (`modelled`, coarser).
|
|
40
|
+
- One owning function, `rainSoon(slice, at, minutes = 120)`, returns `{ startsInMin?, endsInMin?, chance, mm, intensity }`, or nothing when the window is dry. Every consumer calls it.
|
|
41
|
+
- Poll every 15 minutes, which matches the segment length. At that rate it is about 2,900 calls a month. The existing code assumes 10,000 free calls a month across the key; whether that figure covers this endpoint is unverified.
|
|
42
|
+
- Unknown stays unknown: a failed read keeps the last good slice with its `asOf`, and a segment with no probability is not read as dry.
|
|
43
|
+
|
|
44
|
+
## Hourly forecast and one condition word
|
|
45
|
+
|
|
46
|
+
chota's morning print needs an hourly slice (rain chance, mm, condition) for lines such as "rain 2–4pm". Add `hourly` to the weather slices. Sources give conditions as BOM codes 1–19, WMO codes or Google type names. One function maps them to a small set (clear, cloudy, rain, storm …) and keeps the source's own text beside it.
|
|
47
|
+
|
|
48
|
+
## The calendar connector
|
|
49
|
+
|
|
50
|
+
`src/connectors/calendar/`, `iot_class` cloud_polling, polled every 15 minutes, `refresh()` as for the others.
|
|
51
|
+
|
|
52
|
+
**Sources.** One `CalendarSource` shape, as the weather has `WeatherSource`.
|
|
53
|
+
|
|
54
|
+
| | Google Calendar API | ICS feed |
|
|
55
|
+
| ---------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
56
|
+
| Build | First | Later, when a non-Google calendar is needed |
|
|
57
|
+
| Recurring events | Google expands them (`singleEvents=true`) | We expand them ourselves (RRULE) |
|
|
58
|
+
| Freshness | Live | Google's secret-address feed lags. Third-party pages say 8 to 48 hours; no Google page found |
|
|
59
|
+
| Auth | OAuth token | A secret URL, kept as an env name |
|
|
60
|
+
| Fits | Family calendar | School, Apple, Outlook feeds |
|
|
61
|
+
|
|
62
|
+
**Auth.** The caller passes `getToken(): Promise<string>`. The library stores no token. chota passes its better-auth token. A `home calendar login` in the CLI (loopback OAuth, token file in `~/.config/home/`, 0600, like `home enphase login`) comes later and needs an OAuth client in Google Cloud.
|
|
63
|
+
|
|
64
|
+
**State.** The next 14 days per calendar, trimmed to id, title, start, end, all-day, location. Not kept by default: description, attendees, links. All-day events are dates, not instants; they use the home's `timeZone` from the config, never the machine's. Weather has this bug today (`hhmm` in `weather/index.ts`). Calendar must not copy it.
|
|
65
|
+
|
|
66
|
+
**Helpers**, pure: `eventsOn(day)`, `next()`, `search(text)`. One owner for "what is today".
|
|
67
|
+
|
|
68
|
+
**Push.** Google can post to a public URL when a calendar changes. The library has no server. The caller verifies it and calls `home.refresh("calendar")`, the same pattern as Up's webhooks.
|
|
69
|
+
|
|
70
|
+
**Config.** Calendar ids go in the home config as `calendars: [{ id, name }]`. They are identifiers, not secrets. Open: KO to confirm.
|
|
71
|
+
|
|
72
|
+
## Is Google one connector?
|
|
73
|
+
|
|
74
|
+
No. Home Assistant has separate integrations per Google service, and so do we by the same logic. A connector here is one service with its own auth, cadence and failure modes. Weather uses an API key and a 15-minute poll. Calendar uses a user's OAuth token and a different poll. Joining them gives one folder with two unrelated halves.
|
|
75
|
+
|
|
76
|
+
Share only what repeats. The rule of three applies: Weather has a key, Calendar has a token, and a helper for Google's error shape waits for a third Google service. Candidates, none researched: Maps Routes travel time ("leave now for school"; overlaps with Transport NSW), and Books (no clear home use).
|
|
77
|
+
|
|
78
|
+
## Order
|
|
79
|
+
|
|
80
|
+
1. Read the hourly rain fields and add the `hourly` slice and the condition mapping (no new calls).
|
|
81
|
+
2. `rain` slice and `rainSoon` from the minute forecast.
|
|
82
|
+
3. Calendar connector, Google source, `getToken` passed in.
|
|
83
|
+
4. Transport NSW (`docs/ideas.md`).
|
|
84
|
+
5. ICS source, `home calendar login`.
|
|
85
|
+
|
|
86
|
+
chota-bot's `tools/weather.ts` and `tools/calendar.ts` can then shrink to formatting.
|
|
87
|
+
|
|
88
|
+
## For KO to decide
|
|
89
|
+
|
|
90
|
+
1. Use an Experimental Google endpoint for rain soon, with the hourly forecast as the fallback? Recommend yes.
|
|
91
|
+
2. Calendar ids in the home config? Recommend yes.
|
|
92
|
+
3. First version takes `getToken` from the caller only, with the CLI login later? Recommend yes.
|
package/docs/ideas.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Ideas: what else to connect
|
|
2
|
+
|
|
3
|
+
Candidates researched (Sonnet, 2026-10-14), ranked by value over effort for this home. None is decided; KO picks. "Unverified" means from memory or a thin search, to be checked before building.
|
|
4
|
+
|
|
5
|
+
## Worth it
|
|
6
|
+
|
|
7
|
+
1. **Up Bank, the money connector.** Shaped in [up.md](up.md): reads, the two gated writes, the spend helpers, the `home up` verbs and the message to the kids.
|
|
8
|
+
2. **Transport NSW departures.** Free API key, JSON over `fetch`, "next bus or train from home". Home Assistant has `transport_nsw`. [Trip Planner API](https://opendataforum.transport.nsw.gov.au/t/trip-planner-api-now-available/646).
|
|
9
|
+
3. **NSW air quality and UV.** Keyless public feeds: [NSW air quality data services](https://data.nsw.gov.au/data/dataset/nsw-air-quality-data-services) (`get_SiteDetails`, `get_Observations`; times in AEST, not daylight time; the user guide is from 2021, check the endpoints), ARPANSA UV (format unverified). Hourly; would sit beside the weather sources.
|
|
10
|
+
4. **Bin days** from a council iCal feed, if the council publishes one. One fetch and a parser.
|
|
11
|
+
5. **Who's home**, from the LAN, as an experiment in a Node-only file: phones sleep their Wi-Fi so ARP and ping flap; mDNS announcements or DHCP leases are better, but the leases are on eero. Prototype before committing.
|
|
12
|
+
6. **Shelly** plugs with energy, only if KO buys one: local, push, clean JSON ([HA shelly](https://www.home-assistant.io/integrations/shelly/)); the best template for a "plug with energy" connector.
|
|
13
|
+
7. **Printers over AirPrint and IPP** (asked for via jaldi's session, 2026-10-14; KO's test printer is a Brother laser on Wi-Fi). Discovery is Cast's shape: mDNS `_ipp._tcp` and `_ipps._tcp` through the existing dns-sd finder, TXT `UUID` as the identity in the config (like `castId`), name from TXT `ty` or the instance name, `pdl`, `URF`, `product`, `adminurl` for what it is and what it speaks. Status over IPP Get-Printer-Attributes, a small binary-over-HTTP-POST encoding worth hand-rolling like the Cast codec rather than the old `ipp` npm package: `printer-state`, `printer-state-reasons` (toner-low, media-empty, door-open), `marker-names` and `marker-levels`, `media-ready`, `queued-job-count`. Polled on the shared poller every few minutes, relocator on failure. Printers go on and off, so an unreachable one shows offline with "last seen" for a configurable number of days (14, say) before `unavailable`. Writes later and behind KO's hand: Identify-Printer (a beep) as the harmless first, then Print-Job with `application/pdf` where `pdl` lists it. Fixtures from documentation values only.
|
|
14
|
+
8. **What the power cost** (asked for via jaldi's session, 2026-10-14): last month's total and a daily or monthly average beside the live price, where the grid flow enters the house on the plan. Amber v1 has no bills endpoint; `GET /sites/{id}/usage?startDate&endDate` returns 5-minute intervals per channel (`general` import, `feedIn` export) with `kwh`, `cost` in cents (negative for export credit), `perKwh`, `spotPerKwh`, `quality` (`billable` or `estimated`), `descriptor`, `spikeStatus`, `tariffInformation`, `nemTime`. Limits found live: 7 days per request (422 beyond), about a day's lag, `resolution=30` ignored. A month is five calls against the 50-per-5-minutes budget, fine once a day. Shape: one owning function in the energy layer beside `energyNow` summing `cost` per channel per day, import and export never merged, with a `billable` count so a day with estimated intervals says so; daily totals cached in a consumer-owned file like the address cache so a sidecar does not re-read a month at each start; today's partial cost stays off the line. Fixtures from made-up intervals.
|
|
15
|
+
|
|
16
|
+
## Not worth it
|
|
17
|
+
|
|
18
|
+
- **eero**: cloud only, reverse-engineered from the app ([eero on PyPI](https://pypi.org/project/eero/)), login by verification code, a history of breaking. Presence from it flaps.
|
|
19
|
+
- **Matter controller**: waits for a Matter device worth reading (a temperature sensor). The plan, the security model and the network needs are in [matter.md](matter.md): Home Assistant's matter.js-based Matter server on the Pi, Google Home as the main controller, a runtime-neutral `matter` connector over its WebSocket.
|
|
20
|
+
- **Apple TV and HomePod**: tvOS 15+ folded MRP into an AirPlay 2 tunnel that needs AirPlay credentials; Companion is early-stage and a HomePod cannot show a PIN ([pyatv features](https://pyatv.dev/documentation/supported_features/)). No TypeScript library. At most a pyatv subprocess.
|
|
21
|
+
- **Tapo and Kasa**: local but the protocol churns by firmware and Tapo needs a cloud credential. **Roborock**: cloud login for the device key. **NBN modem, Sydney Water, gas**: no stable public API. **Pollen**: no good Australian feed found.
|
|
22
|
+
|
|
23
|
+
## What other homes run (survey, 2026-10-16)
|
|
24
|
+
|
|
25
|
+
Home Assistant's public install counts ([current_data.json](https://analytics.home-assistant.io/current_data.json), 701,678 active installs, 21,086 in Australia; HA gives no per-country integration counts, so the Australian notes are judgement). Already covered and near the top: Cast 284k, IPP 163k, Hue 88k. Lower: Enphase 10.7k, Daikin 6.5k, Amber 1.4k. The library covers devices KO owns (decisions), so this is a guide for anyone extending it, not a plan.
|
|
26
|
+
|
|
27
|
+
| Category | Connector | Installs | Local, no cloud account | Effort | Why |
|
|
28
|
+
| -------- | ------------------------------- | ------------------ | -------------------------------- | ----------------------- | --------------------------------------------------------------- |
|
|
29
|
+
| Energy | Shelly | 141k | yes, Gen2 JSON-RPC | small | most-installed local energy device; plugs and meters |
|
|
30
|
+
| Energy | Fronius | 10.1k | yes, Solar API v1 | small | common Australian inverter, one GET |
|
|
31
|
+
| Energy | forecast.solar | 67k | keyless free tier | small | solar forecast beside Enphase |
|
|
32
|
+
| Energy | Tesla Powerwall | 2.1k + fleet 5.3k | PW2 yes; PW3 mostly cloud | medium | the commonest home battery here |
|
|
33
|
+
| Energy | SMA, GoodWe | 5.0k, 3.7k | yes (GoodWe over UDP, Node-only) | medium | next most common Australian inverters |
|
|
34
|
+
| Energy | Sungrow, Huawei | custom, Modbus TCP | yes | medium, Node-only | big local market share, poorly served |
|
|
35
|
+
| Climate | AirTouch 5 | 543 | yes, TCP | medium, Node-only | Australian ducted zoning |
|
|
36
|
+
| Climate | Mitsubishi MELCloud, Sensibo | 7.0k, 6.0k | no | small to medium | split systems after Daikin; Sensibo covers any IR AC |
|
|
37
|
+
| Lighting | WLED | 34k | yes | small | trivial JSON |
|
|
38
|
+
| Media | Sonos | 90k | yes | medium | the biggest local media brand after Cast |
|
|
39
|
+
| Media | Samsung, LG TVs | 95k, 68k | yes, WebSocket with pairing | medium | on or off, what is playing |
|
|
40
|
+
| Security | Reolink, UniFi Protect, Frigate | 62k, 25k, 29k | yes | medium | cameras and person events |
|
|
41
|
+
| Network | MQTT | 270k | yes | medium | one connector reaches Zigbee2MQTT, Tasmota, ESPHome and Frigate |
|
|
42
|
+
| Network | Synology, NUT UPS | 52k, 25k | yes | small | NAS and UPS health |
|
|
43
|
+
| Sensors | Ecowitt | 16k | yes, the station posts to you | small, needs a listener | backyard weather station |
|
|
44
|
+
|
|
45
|
+
Takeaways: energy is where this library is strongest and where demand is; one MQTT connector would cover the cheap-sensor world without per-device code; Australian AC beyond Daikin is cloud or custom-only. Rejected before and still rejected: Tuya (cloud, churn), Matter, Apple TV, Roborock. Transport NSW has 98 HA installs: it is here for chota-bot, not for popularity.
|
|
46
|
+
|
|
47
|
+
## From chota-bot (Haiku scan, 2026-10-14)
|
|
48
|
+
|
|
49
|
+
At the time the bot did not import this library; it is moving onto it now (2026-11), and rain soon and day blocks have since been built. What it computed about the home that the library owns, so the bot should call the library instead: Google weather parsing (`tools/weather.ts`, duplicating `google.ts`'s `toSlices`), a 06:00–18:00 day/night proxy (`+layout.svelte`; `sunNow(...).daylight` is the owner), the condition-to-icon map (check against `weatherText`). What the bot has that the library lacks and should grow: **rain soon** ("rain in the next N minutes") and **per-day forecast blocks** as pure helpers over the forecast, one owner for the rain window; **Transport NSW departures** (item 2 above; the bot's `tools/bus.ts` is the shape: key via fetch, typed departures with `dueMins`, `cancelled`, `realtime`); a daily **sunrise and sunset times** helper beside `sunNow` for a daily print. Stays in the bot: the surf report, calendar, school, chores, lists, Telegram formatting, the agent prompts, the box hardware.
|
|
50
|
+
|
|
51
|
+
## Recording what happens (2026-10-16, later)
|
|
52
|
+
|
|
53
|
+
Worth having for questions like "how long are lights left on" or "AC hours against outside temperature". The bot, which runs all day, owns it: one SQLite table it can query and filter, `events(at, source, entity, kind, value)`, with `value` as JSON and `at` in UTC ms, backed up with `VACUUM INTO`. Durations come from SQL window functions (`LEAD`) over on and off rows, not extra tables. The library's part, when it comes: a pure function from two snapshots to typed events (no files, no SQL). Not recorded: what has its own record (Up transactions, Amber bills, Enphase history). Pitfalls: Hue is chatty (record semantic changes, not every event), mark when the bot was down so a gap never reads as "off", batch writes on an SD card.
|
|
54
|
+
|
|
55
|
+
## The CLI and the connectors
|
|
56
|
+
|
|
57
|
+
Two ideas from KO, 2026-10-14, not yet decided:
|
|
58
|
+
|
|
59
|
+
- **Every connector gets a `home <connector>` command** that prints only its lines (and `--json`), and its README opens with that command under "Try it", so a visitor on GitHub or an agent sees the connector and how to run it in one screen.
|
|
60
|
+
- **The CLI derived from the library.** Done 2026-10-14 for the CLI split and the manifest; still open is the `actions` list. The plan was: parse arguments with [cac](https://github.com/cacjs/cac) (7.1.0, maintained as of 2026-10) instead of `util.parseArgs`, and register commands from a per-connector manifest (`name`, `iotClass`, `pollSeconds`, `env`, `access`, status lines, `commands`, `actions`). The manifest then drives `home status`, `home <connector>`, `.env.example`, the README table, and the typed action list jaldi asked for, so the library and the CLI stay one thing.
|
|
61
|
+
|
|
62
|
+
## Spotify search and library (KO, 2026-11-08)
|
|
63
|
+
|
|
64
|
+
Soloist plays URIs; it can't search or read a library. Starting music by name ("play some Seal", "my Soft 90s playlist") needs Spotify's cloud Web API beside it: search returns a URI, and the spotify connector's `play(uri)` plays it. **KO's call, 2026-11-08: search lives in chota-bot, not here.** This library turns a box into a speaker; the bot owns its music search tool. chota-bot checked live that catalogue search works with client credentials, no user sign-in, on a development-mode app, and that Soloist played the URI it found. The notes below stay as the research.
|
|
65
|
+
|
|
66
|
+
- **Search without a user** (catalogue only): the Client Credentials flow reaches "only endpoints that do not access user information" ([client credentials](https://developer.spotify.com/documentation/web-api/tutorials/client-credentials-flow)). The search reference names no flow, but the scopes page lists search under `user-read-private`, so whether an app-only token can search is unsettled: one test call answers it. Search needs a `market`. Since February 2026 a development-mode app gets at most 10 results per search ([migration guide](https://developer.spotify.com/documentation/web-api/tutorials/february-2026-migration-guide)).
|
|
67
|
+
- **A user's library** needs that user to sign in: `playlist-read-private`, `playlist-read-collaborative`, `user-library-read` ([scopes](https://developer.spotify.com/documentation/web-api/concepts/scopes)). Playlist items come back only for playlists the user owns or collaborates on.
|
|
68
|
+
- **Development mode** ([quota modes](https://developer.spotify.com/documentation/web-api/concepts/quota-modes)): 5 signed-in users per app, the app owner needs Premium (a lapse stops the app), and extended quota is for organisations with 250k monthly users, so this stays a development-mode app. Fine for one household.
|
|
69
|
+
- **Not yet checked live:** whether Soloist plays a URI it was sent while no phone is around (it needs a session), and whether its session survives a restart.
|
package/docs/matter.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Matter
|
|
2
|
+
|
|
3
|
+
Not built. It waits for the first Matter device worth reading, a temperature sensor most likely. When it comes, the plan is a `matter` connector that talks WebSocket to a Matter server on the Pi, with Google Home staying the main controller. This page holds what we found, so the next session starts from it.
|
|
4
|
+
|
|
5
|
+
## The words
|
|
6
|
+
|
|
7
|
+
- **Matter** is the language: standard device types (light, plug, temperature sensor) and their attributes, so one controller reads any brand. Buy for the Matter logo.
|
|
8
|
+
- **Thread** is one radio Matter runs over: a low-power mesh, right for battery sensors. Wi-Fi and Ethernet are the others. A Thread logo alone does not mean Matter.
|
|
9
|
+
- A **Thread border router** links the Thread mesh to the home network: recent eeros, Nest Hub 2nd gen and Max, Nest Wifi Pro, Apple TV 4K, HomePod mini. Mains-powered Thread devices (bulbs, plugs) also extend the mesh.
|
|
10
|
+
- A **fabric** is one controller's set of devices and keys: Google Home's, Apple Home's, ours.
|
|
11
|
+
|
|
12
|
+
## Security: who can control a device
|
|
13
|
+
|
|
14
|
+
Being on the LAN is not enough. A new device carries a setup code (a QR code or 11 digits). Commissioning uses it once to prove you hold the device, then gives the device an operational certificate for that fabric. From then on it obeys only controllers holding that fabric's certificate, over encrypted sessions. The setup code is spent once commissioned; adding a second controller needs a fresh one-time code from a controller already on the device (multi-admin). The standard asks every device to hold at least 5 fabrics; removing one leaves the others alone.
|
|
15
|
+
|
|
16
|
+
## How this house would run it
|
|
17
|
+
|
|
18
|
+
1. **Google Home is the main controller.** Commission every device in the Google Home app first, so voice and the app keep working.
|
|
19
|
+
2. **The Pi joins as a second admin.** In Google Home, open the device's pairing window ("Linked Matter apps & services"), get the one-time code, and commission it into our fabric.
|
|
20
|
+
3. **Our controller talks straight to the device** over IPv6 on the LAN; Thread devices through a border router. No cloud. It reads attributes and subscribes to changes (a sleepy sensor pushes; never poll it).
|
|
21
|
+
4. **The fabric's keys live with the controller**, on the Pi, in a file. The library stores no keys, so the library never holds them.
|
|
22
|
+
|
|
23
|
+
## How the library would talk Matter
|
|
24
|
+
|
|
25
|
+
| Option | What | For | Against |
|
|
26
|
+
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
27
|
+
| **matterjs-server**, a WebSocket client in the library (the plan) | The Open Home Foundation's [Matter server](https://github.com/matter-js/matterjs-server) on the Pi (`npx matter-server` or its Docker image, port 5580, `/ws`); a `matter` connector speaks its WebSocket API | Home Assistant's own Matter server since app 9.0 (June 2026), so the device quirks get fixed there. Keys stay in the server's `--storage-path`, not in our code. WebSocket is a web standard: the connector stays runtime-neutral | One more process on the Pi. Beta, not CSA re-certified. Its API is shaped for Home Assistant |
|
|
28
|
+
| matter.js in-process | [matter.js](https://github.com/project-chip/matter.js) as a controller inside chota | No extra process | Node-only code, UDP and mDNS in the consumer; chota owns fabric storage and every quirk |
|
|
29
|
+
| python-matter-server | The old Home Assistant server | | End of life: 8.1.2 was its last release |
|
|
30
|
+
|
|
31
|
+
The Hue bridge can expose its lights over Matter. Don't use it: Home Assistant advises against it too, since the native Hue API (the hue connector) does more.
|
|
32
|
+
|
|
33
|
+
## Network
|
|
34
|
+
|
|
35
|
+
From Home Assistant's notes, which hold for any controller:
|
|
36
|
+
|
|
37
|
+
- IPv6 enabled on the Pi (link-local is enough; no IPv6 internet or DHCPv6 needed), and IPv6 multicast passing freely.
|
|
38
|
+
- One flat network: Wi-Fi Matter devices, border routers and the Pi on the same LAN or VLAN. Multicast "optimisation", IGMP snooping and client isolation break discovery.
|
|
39
|
+
- Wi-Fi Matter devices commission on 2.4 GHz.
|
|
40
|
+
- Commissioning needs a phone (Android 8.1+, iOS 16+) close to the device: it uses Bluetooth only to pair.
|
|
41
|
+
|
|
42
|
+
## First steps
|
|
43
|
+
|
|
44
|
+
1. Buy one Thread sensor (an IKEA TIMMERFLOTTE: see [devices.md](devices.md)) and pair it in Google Home.
|
|
45
|
+
2. On the Pi: `npx matter-server --storage-path ~/.local/state/matter`. Check that IPv6 is on.
|
|
46
|
+
3. Share the sensor from Google Home with a one-time code; commission it through the server's WebSocket API with a throwaway script.
|
|
47
|
+
4. Read temperature and humidity; restart the server and read again (keys and node ids survive).
|
|
48
|
+
5. Then decide the connector: its state is per-device attributes by Matter device type, `iot_class` `local_push`.
|
|
49
|
+
|
|
50
|
+
## Sources
|
|
51
|
+
|
|
52
|
+
- [Home Assistant: Matter](https://www.home-assistant.io/integrations/matter/): commissioning, sharing from Google Home, border routers, network requirements, troubleshooting.
|
|
53
|
+
- [Home Assistant: the Matter upgrade](https://www.home-assistant.io/blog/2026/06/23/the-matter-upgrade-youve-been-waiting-for/): Matter Server 9.0 on matter.js.
|
|
54
|
+
- [matter-js/matterjs-server](https://github.com/matter-js/matterjs-server) (Apache-2.0), [matter-js/python-matter-server](https://github.com/matter-js/python-matter-server) (end of life), [project-chip/matter.js](https://github.com/project-chip/matter.js) (Apache-2.0).
|
|
55
|
+
- [Google: supported Matter device types](https://developers.home.google.com/matter/supported-devices).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
`bun run bench`, Bun 1.4.2, Apple M5, 2026-10-13. Means per iteration:
|
|
4
|
+
|
|
5
|
+
| What | Time |
|
|
6
|
+
| ------------------------------------------------------ | ------ |
|
|
7
|
+
| Parse 1,000 event-stream messages in 4 KiB chunks | 475 µs |
|
|
8
|
+
| Apply 1,000 events to a snapshot | 208 µs |
|
|
9
|
+
| Rooms view of a snapshot (fixture: 2 groups, 2 lights) | 0.6 µs |
|
|
10
|
+
| Queue 100 writes (fake clock, so overhead only) | 14 µs |
|
|
11
|
+
|
|
12
|
+
Against the bridge (Bun 1.4.2, measured by curl and `fetch` on KO's LAN): 124 ms for a cold pinned request, 16 ms kept alive.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Philosophy
|
|
2
|
+
|
|
3
|
+
I want my house in a TypeScript import. A Telegram bot, a Mac launcher and a terminal all ask the same questions: are the lights on, what is the AC doing, what is the roof making, what does power cost right now, is it about to rain. This library answers them, from memory, in a millisecond, and takes the few writes I actually make: lights off, a scene, an announcement on the speakers.
|
|
4
|
+
|
|
5
|
+
It is not a home automation server. Home Assistant is the reference and the inspiration. But it is a thing you run, with its own UI, database and rules engine, and my apps would talk to it over yet another API. I wanted the devices in-process, one folder per device, covering only what I own.
|
|
6
|
+
|
|
7
|
+
## How it is shaped
|
|
8
|
+
|
|
9
|
+
- **One folder per device.** `src/connectors/hue`, `daikin`, `enphase`, `amber`, `weather`, `cast`. Each has a client that talks to the device, an integration that keeps its state fresh, a README with the official docs and what the live device answered. A new device is a new folder; nothing in the core changes.
|
|
10
|
+
- **The snapshot is the cache.** Every integration keeps its own state current, by event stream or on its own timer. Callers read a snapshot that never touches a device. A slow device never slows a keystroke.
|
|
11
|
+
- **One call does the right thing.** `hue()` pins the bridge's certificate, keeps the connection alive, spaces writes to Signify's rate limit and reconnects with backoff. There is nothing to switch on and no configuration to get wrong.
|
|
12
|
+
- **Nothing is hidden or corrected.** Every field a device reports is optional, and an odd reading passes through with a flag rather than being clamped or rounded. If the solar meter says today equals the lifetime total, you see that.
|
|
13
|
+
- **Your keys, your storage.** The library never reads the environment or a file and never writes a key. Callers pass addresses, keys and a `fetch`. Only the `home` command reads a shell.
|
|
14
|
+
- **Runs in Bun and Node.** `fetch`, Web Streams and zod, nothing else, so the same code serves the launcher and the bot. The few things that need a socket or a process live on their own subpaths and are passed in.
|
|
15
|
+
- **Libraries over hand-rolled code**, with one exception: when the only libraries are unmaintained or GPL, I write the small version and say which sources I read.
|
|
16
|
+
|
|
17
|
+
## What it borrows
|
|
18
|
+
|
|
19
|
+
Home Assistant's vocabulary and its quality scale: a connector is `local_push` or `local_polling` or `cloud_polling`, keeps the last good data through a failed poll, stops polling on a rejected key, and never surprises the device with more writes than its vendor allows. Its integration source is where the protocol facts live; this is a pocket version of that pattern, without the parts that need a UI.
|
|
20
|
+
|
|
21
|
+
## What it is not
|
|
22
|
+
|
|
23
|
+
Not a rules engine. Automations ("hot day, excess solar, AC on") belong in the apps that import this, once the numbers have been watched for a while. No cloud between me and my devices: Hue, Daikin, Enphase and Cast are read on the LAN, and off it they say so. Amber and the weather are cloud services and are polled as such. Not a platform: there is no plugin host, and a device I do not own is a device this library does not cover.
|
package/docs/up.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Up Bank: the money connector
|
|
2
|
+
|
|
3
|
+
Proposed 2026-10-14 from two Sonnet research passes over [Up's API](https://developer.up.com.au/), its [OpenAPI spec](https://github.com/up-banking/api/blob/master/v1/openapi.json), [fane-ye/up-bank-mcp](https://github.com/fane-ye/up-bank-mcp), [xaviershay/up-finance-skills](https://github.com/xaviershay/up-finance-skills) and [edkranz/up-mcp](https://github.com/edkranz/up-mcp). Built 2026-10-16 as `src/connectors/up/`; its README says what the live read found and what is still unverified. This page stays as the design.
|
|
4
|
+
|
|
5
|
+
**Short answer.** A connector like the others: `src/connectors/up/`, `iot_class` cloud_polling every 15 minutes, one token (`UP_TOKEN`) over `fetch`, zod schemas, about 150 lines plus pure helpers. It reads; the two writes Up allows (category, tags) are methods on the object and explicit `home up` verbs, never called by the integration, the same gate as Cast. No MCP: an agent drives it through the library or the CLI, which already exist.
|
|
6
|
+
|
|
7
|
+
## What KO wants from it
|
|
8
|
+
|
|
9
|
+
Surface the household's spending to the family through the home bot, so "utilities" and "groceries" are real numbers from real purchases. Let an agent or the CLI pull the latest transactions (JSON or CSV), see Up's categories, find what still needs a category, and set one so Up's auto-categorisation improves. The library stays deterministic; an agent (or Jev) decides what to tag, the library applies it.
|
|
10
|
+
|
|
11
|
+
## What the three repos taught
|
|
12
|
+
|
|
13
|
+
- **up-bank-mcp** is a thin wrapper: 20 tools, raw API JSON back, no shaping, no transfer exclusion, writes report success without reading the result. No licence; read for the endpoint list only.
|
|
14
|
+
- **up-finance-skills** has the useful idea: a transaction is _spend_ only after excluding transfers between own accounts, covers, forwards, round-ups and interest, and every report carries a validation block (date range, raw and filtered counts, categories reconciling to the total). It matches those movements by description text, which is fragile; the API gives `transferAccount` for own-account transfers and `roundUp` on the purchase, but names no field for cover and forward (**unconfirmed** beyond description matching).
|
|
15
|
+
- **up-mcp**: thin, MIT, 2025.
|
|
16
|
+
|
|
17
|
+
## The API facts that shape it
|
|
18
|
+
|
|
19
|
+
- Child categories only can be assigned; parents are rejected; `isCategorizable` must be true. `PATCH …/relationships/category` with `data: null` uncategorises (204). `filter[category]` accepts a parent and returns its children's transactions.
|
|
20
|
+
- Tags: at most 6 per transaction, identified by label; duplicate adds and absent removals are ignored.
|
|
21
|
+
- Whether a category set through the API teaches Up about that merchant is **not documented**: the first live write on one transaction, then the next purchase from that merchant, answers it.
|
|
22
|
+
- HELD versus SETTLED: `holdInfo` keeps the held amounts; `amount` is the settled value; `settledAt` null while held. `foreignAmount` null for domestic. `cashback.amount` positive.
|
|
23
|
+
- No rate limit published: read `X-RateLimit-Remaining`, back off on 429. Webhooks need a public URL and an HMAC check: jaldi's or the bot's job, not the library's.
|
|
24
|
+
- The token can write (categories, tags, webhooks). Read-only is this connector's discipline: GETs in the integration, writes only through the two methods.
|
|
25
|
+
|
|
26
|
+
## Surface
|
|
27
|
+
|
|
28
|
+
**`client.ts`** (`{ fetch, token }` in, zod-typed values out, every field optional):
|
|
29
|
+
|
|
30
|
+
- Reads: `accounts()`, `transactions({ since, until, status, category, tag, accountId, limit })` following cursors to `limit` and a page cap and returning `{ transactions, truncated }` so a cut-off result never looks complete; `transaction(id)`; `categories()`; `tags()`; `ping()`.
|
|
31
|
+
- Writes: `setCategory(id, categoryId | null)`, `addTags(id, labels)`, `removeTags(id, labels)`. Each re-reads the transaction and returns it, so the caller sees what Up now holds.
|
|
32
|
+
|
|
33
|
+
**`spend.ts`**, pure functions over `Transaction[]`, one owner per number:
|
|
34
|
+
|
|
35
|
+
- `isSpend(t)`: settled, amount below zero, no `transferAccount`, not a round-up movement, description not on the cover/forward list. The one place that rule lives.
|
|
36
|
+
- `spendByCategory(txs, categories, window)` grouped to parents by the tree; `spendByMerchant(txs)`.
|
|
37
|
+
- `uncategorised(txs)`: categorisable with no category.
|
|
38
|
+
- `recurring(txs)`: the same normalised merchant in three or more distinct months at a similar amount; labelled a heuristic, since Up has no subscription endpoint.
|
|
39
|
+
- `unusual(txs)`: an amount well above that merchant's median with three or more prior samples.
|
|
40
|
+
- Anything odd (a missing amount, held and settled differing) goes to `anomaly`, never clamped.
|
|
41
|
+
|
|
42
|
+
**State** in the snapshot: the window (month to date and last month), `asOf`, totals with the excluded transfers counted and summed so a figure is auditable, spend by parent and child category, top merchants, the uncategorised ids, the last 20 transactions trimmed to id, time, description, amount, category, status, tags. Not kept: `rawText`, `message`, balances, account ids, attachments, the full history.
|
|
43
|
+
|
|
44
|
+
**CLI**, `home up …`:
|
|
45
|
+
|
|
46
|
+
| Verb | Prints | |
|
|
47
|
+
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | --------- |
|
|
48
|
+
| `home up` | the window, spend, top five categories, uncategorised count | read |
|
|
49
|
+
| `home up transactions [--since] [--until] [--category] [--tag] [--status] [--json\|--csv]` | trimmed rows | read |
|
|
50
|
+
| `home up categories` | the tree with ids | read |
|
|
51
|
+
| `home up uncategorised` | categorisable transactions with no category | read |
|
|
52
|
+
| `home up spend [--by category\|merchant]` | the helper's output | read |
|
|
53
|
+
| `home up categorise <id> <category\|none>` | the transaction before and after | **write** |
|
|
54
|
+
| `home up tag <id> <tag> [--remove]` | the transaction before and after | **write** |
|
|
55
|
+
|
|
56
|
+
No bulk "fix all" verb. Writes exist so an agent with its own judgement can act one transaction at a time.
|
|
57
|
+
|
|
58
|
+
## Webhooks
|
|
59
|
+
|
|
60
|
+
Polling stays the source of truth; a webhook only makes it faster. Up posts an event when a transaction is created, settled or deleted; the library has no server, so receiving it is the bot's or the sidecar's job, and the whole handler is one line: verify, then `home.refresh("up")`, which makes the integration read now. The snapshot shape, `isSpend` and the helpers do not change. The same pattern holds for any future connector with a push side: the push nudges the poll.
|
|
61
|
+
|
|
62
|
+
What the library carries so a consumer needs nothing else:
|
|
63
|
+
|
|
64
|
+
- **Client**: `webhooks()`, `webhook(id)`, `webhookLogs(id)` as reads; `createWebhook(url, description)`, `deleteWebhook(id)`, `pingWebhook(id)` as writes, gated like the others. The `secretKey` comes back once, on creation; the client returns it and never stores or logs it.
|
|
65
|
+
- **A pure `verifyWebhook(rawBody, signatureHeader, secret)`** over Web Crypto (HMAC-SHA256, constant-time compare), runtime-neutral, and `parseWebhookEvent(body)` returning the event type and the transaction id.
|
|
66
|
+
- **CLI**: `home up webhooks` (list), `home up webhooks get|logs <id>`, `home up webhooks ping <id>`, and `create <url>` and `delete <id>` as writes behind KO's hand.
|
|
67
|
+
|
|
68
|
+
When to set one up: only when the bot should say "a purchase just happened" within seconds. For a daily or on-demand summary, the 15-minute poll is enough, and the bot needs no public URL. (Hue already pushes over its event stream; Amber's v1 API has no webhooks.)
|
|
69
|
+
|
|
70
|
+
## The message to the family
|
|
71
|
+
|
|
72
|
+
Only numbers the API returned, through `spendByCategory`, with Up's own category names and the excluded transfers stated. The shape, with invented figures: "October so far: $2,140 on the house. Groceries $620, utilities $410 (power $230, water $90), eating out $180. Up from $1,980 last month. 3 purchases still need a category." No budgets, no "you saved", no judgement; the bot adds those if KO wants them.
|
|
73
|
+
|
|
74
|
+
## Privacy
|
|
75
|
+
|
|
76
|
+
The token is never printed. Nothing real in the repo, fixtures or tests: fixtures come from the docs' example payloads. Balances, account names, `message` and `rawText` do not reach the bot by default. Everything lives in memory; the library writes nothing to disk.
|
|
77
|
+
|
|
78
|
+
## Build the writes from the start
|
|
79
|
+
|
|
80
|
+
Yes. Three endpoints, about 30 lines, reversible (a category resets to null, a tag comes off). The gate is the design: methods on the object, explicit single-transaction verbs, the integration never writes, and the first live write is KO's on one transaction, which also answers whether Up learns from it.
|
|
81
|
+
|
|
82
|
+
## Before building
|
|
83
|
+
|
|
84
|
+
One live `GET /categories` and one `GET /transactions?page[size]=5` with the token (field names and category ids only, nothing kept) to confirm the ids and the transfer and round-up shapes.
|
package/docs/using.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Using the library from Bun and Node
|
|
2
|
+
|
|
3
|
+
## Before you start: reserve device addresses
|
|
4
|
+
|
|
5
|
+
Give every device a fixed address in your router (a DHCP reservation; eero, UniFi and most routers have one): the Hue bridge, the Enphase gateway, the AC. The library takes addresses as parameters and the CLI reads them from the environment; discovery (`home discover`) finds devices, but mDNS and UDP broadcasts miss now and then, and a device on the guest Wi-Fi is invisible to the main network.
|
|
6
|
+
|
|
7
|
+
## Bun and Node
|
|
8
|
+
|
|
9
|
+
**Bun** (jaldi, chota-bot): leave out `fetch` and `hue()` pins with Bun's `tls` fetch option.
|
|
10
|
+
|
|
11
|
+
**Node**: Node's `fetch` takes no CA, so Hue and Enphase need the fetches in `@khalido/home/node`, built on undici:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { NODE_TLS, nodePinnedFetch } from "@khalido/home/node";
|
|
15
|
+
loadHome(config, { env: process.env, readFile, tls: NODE_TLS }); // from a home config
|
|
16
|
+
hue({ host, bridgeId, key, ca, fetch: nodePinnedFetch(bridgeId, ca) }); // or one integration
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Checked on Node 24.20 and 26.11 against a local TLS server (2026-10-28): the right CA and CN pass, another certificate or CN is rejected. Not yet against a real bridge.
|
|
20
|
+
|
|
21
|
+
Pairing, pinning, the event stream and the rate limits are in [`src/connectors/hue/README.md`](../src/connectors/hue/README.md). On the local network the bridge still needs an application key: without one it answers 403. `home pair` gets one with the link button; no cloud account is involved.
|
|
22
|
+
|
|
23
|
+
## One home
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createHome, health, loadHome } from "@khalido/home";
|
|
27
|
+
|
|
28
|
+
const home = createHome(loadHome(config, options), (snapshot, change) => {
|
|
29
|
+
if (change.after.status !== change.before.status)
|
|
30
|
+
log(`${change.name}: ${change.before.status} → ${change.after.status}`);
|
|
31
|
+
});
|
|
32
|
+
home.start(signal);
|
|
33
|
+
home.part("weather")?.state; // typed by name, read from memory
|
|
34
|
+
await home.refresh("amber"); // read now; resolves at its next report
|
|
35
|
+
health(home.snapshot(), Date.now()); // per integration: ok, stale or down, with lastGoodAt, downSince and the thresholds
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`createHome` stamps each part with `lastGoodAt` (its last good read) and `downSince` (when it stopped being connected). `health` judges from those: a polling connector is stale after 2½ polls with no good read and down after 3 polls not connected; Hue and Cast are down after 2 minutes not connected. Health takes each connector's interval from its manifest; a home that changes one (the weather's `pollMinutes`) passes it: `health(snapshot, now, { pollSeconds: { weather: 60 * 60 } })`. For a one-shot read (a script, a printed brief), `settle(integrations, { ms })` runs them until each has answered and returns the snapshot.
|
|
39
|
+
|
|
40
|
+
## Writes
|
|
41
|
+
|
|
42
|
+
Writes hang off the integration: `home.integration("daikin")?.client.write(setMode("cool"))`, `home.integration("hue")?.client.recallScene(id)`, `home.integration("cast")?.setVolume(…)`, `home.integration("spotify")?.play(link)`. Each goes through the device's write lane: spaced, and a burst to one light or the AC merges so the last value lands. Spotify's writes resolve with `{ state, confirmed }`, the state once Soloist reported the change, and `duck(fn, { to })` and `interrupt(fn)` turn the music down or pause it around an announcement, returning what happened to it (`music`: `untouched`, `restored`, `kept` or `failed`). `speakerState(part)` says the speaker in one word (offline, waiting, idle, playing, paused) and `spotifyChanges(change.before, change.after)` turns an `onChange` into events (started, resumed, paused, stopped, track, stalled, recovered, offline, waiting, picked), those from the library's own `duck` or `interrupt` marked `own`.
|
|
43
|
+
|
|
44
|
+
## Text for people and agents
|
|
45
|
+
|
|
46
|
+
Each connector's manifest turns its part into lines: `manifest.lines(part)`, the first line its summary (`import { manifest } from "@khalido/home/amber"`). The text helpers say the same in one line: `weatherText`, `blockText`, `acText`, `enphaseText`, `amberText`, `castText`, `printerText`, `spotifyText`, `upText`.
|
|
47
|
+
|
|
48
|
+
## Energy
|
|
49
|
+
|
|
50
|
+
`energyNow(enphase, amber)` is solar, use, grid flow and its cost now. `bestWindow(amber.forecast, 2, { by: "price", from: Date.now() })` is the cheapest 2 hours ahead in Amber's forecast; `by: "renewables"` the greenest.
|
|
51
|
+
|
|
52
|
+
## Testing an app without devices
|
|
53
|
+
|
|
54
|
+
An `Integration` is a name, `run` and `refresh`, so a fixed part is a few lines:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const fixed = <S>(name: string, part: Part<S>): Integration<S> => ({
|
|
58
|
+
name,
|
|
59
|
+
refresh() {},
|
|
60
|
+
run: (signal, update) =>
|
|
61
|
+
new Promise(
|
|
62
|
+
(done) => (update(part), signal.addEventListener("abort", () => done())),
|
|
63
|
+
),
|
|
64
|
+
});
|
|
65
|
+
```
|