homebridge-boiler-ai 1.0.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/README.md +199 -0
- package/config.schema.json +184 -0
- package/dist/ai.d.ts +7 -0
- package/dist/ai.js +116 -0
- package/dist/ai.js.map +1 -0
- package/dist/boiler.d.ts +3 -0
- package/dist/boiler.js +44 -0
- package/dist/boiler.js.map +1 -0
- package/dist/boilerAccessory.d.ts +12 -0
- package/dist/boilerAccessory.js +37 -0
- package/dist/boilerAccessory.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +37 -0
- package/dist/platform.js +432 -0
- package/dist/platform.js.map +1 -0
- package/dist/prompt.d.ts +8 -0
- package/dist/prompt.js +146 -0
- package/dist/prompt.js.map +1 -0
- package/dist/settings.d.ts +42 -0
- package/dist/settings.js +43 -0
- package/dist/settings.js.map +1 -0
- package/dist/state.d.ts +23 -0
- package/dist/state.js +78 -0
- package/dist/state.js.map +1 -0
- package/dist/switcher.d.ts +4 -0
- package/dist/switcher.js +119 -0
- package/dist/switcher.js.map +1 -0
- package/dist/tempModel.d.ts +6 -0
- package/dist/tempModel.js +97 -0
- package/dist/tempModel.js.map +1 -0
- package/dist/weather.d.ts +14 -0
- package/dist/weather.js +123 -0
- package/dist/weather.js.map +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Homebridge Boiler AI
|
|
2
|
+
|
|
3
|
+
AI-powered hot water controller for Homebridge. Once configured, it runs fully autonomously — checking weather, estimating your tank temperature, and turning the boiler on/off as needed. No daily interaction required.
|
|
4
|
+
|
|
5
|
+
The AI makes sure you have hot water when you need it — using solar heating when possible and only running the electric heater when necessary.
|
|
6
|
+
|
|
7
|
+
Works with [Switcher](#switcher), [Shelly](#shelly), [Tasmota](#tasmota), and [any HTTP-controllable plug](#other-smart-plugs).
|
|
8
|
+
|
|
9
|
+
## Setup (5 minutes)
|
|
10
|
+
|
|
11
|
+
Install the plugin from the Homebridge UI: search for **homebridge-boiler-ai** and click install.
|
|
12
|
+
|
|
13
|
+
Then configure in the plugin settings (or paste into `config.json`):
|
|
14
|
+
|
|
15
|
+
**Minimal config — Switcher:**
|
|
16
|
+
|
|
17
|
+
```jsonc
|
|
18
|
+
{
|
|
19
|
+
"platform": "BoilerAI",
|
|
20
|
+
"name": "Boiler AI",
|
|
21
|
+
|
|
22
|
+
// ── Required ──────────────────────────────────
|
|
23
|
+
"geminiApiKey": "YOUR_GEMINI_API_KEY", // get from https://aistudio.google.com/apikey
|
|
24
|
+
"location": "Tel Aviv", // your city (verify: wttr.in/YourCity)
|
|
25
|
+
"timezone": "Asia/Jerusalem", // your timezone
|
|
26
|
+
"switcher": {
|
|
27
|
+
"deviceId": "Switcher_Touch_386C" // name from Switcher app, IP, or hex ID
|
|
28
|
+
},
|
|
29
|
+
"usage": [
|
|
30
|
+
{ "time": "07:00", "label": "Morning shower", "liters": 60, "temp": 45 },
|
|
31
|
+
{ "time": "20:00", "label": "Evening shower", "liters": 100, "temp": 50 }
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
// ── Optional ──────────────────────────────────
|
|
35
|
+
// "tank": { "liters": 120, "heaterKw": 2.5 }, // auto-detected from location
|
|
36
|
+
// "xaiApiKey": "", // alternative to Gemini
|
|
37
|
+
// "switcher.token": "", // only if you get auth errors
|
|
38
|
+
// "maxDurationMinutes": 90 // safety cap per cycle
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Minimal config — Shelly / HTTP plug:**
|
|
43
|
+
|
|
44
|
+
```jsonc
|
|
45
|
+
{
|
|
46
|
+
"platform": "BoilerAI",
|
|
47
|
+
"name": "Boiler AI",
|
|
48
|
+
|
|
49
|
+
// ── Required ──────────────────────────────────
|
|
50
|
+
"geminiApiKey": "YOUR_GEMINI_API_KEY",
|
|
51
|
+
"location": "Tel Aviv",
|
|
52
|
+
"timezone": "Asia/Jerusalem",
|
|
53
|
+
"boilerPlug": {
|
|
54
|
+
"onUrl": "http://192.168.1.50/relay/0?turn=on",
|
|
55
|
+
"offUrl": "http://192.168.1.50/relay/0?turn=off"
|
|
56
|
+
},
|
|
57
|
+
"usage": [
|
|
58
|
+
{ "time": "07:00", "label": "Morning shower", "liters": 60, "temp": 45 },
|
|
59
|
+
{ "time": "20:00", "label": "Evening shower", "liters": 100, "temp": 50 }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### 1. AI API Key
|
|
67
|
+
|
|
68
|
+
Get an API key from [Google AI Studio](https://aistudio.google.com/apikey) — sign in, click "Create API Key", and paste it into the **Gemini API Key** field.
|
|
69
|
+
|
|
70
|
+
The plugin uses Gemini Flash-Lite which has a free tier (1,000 requests/day — the plugin uses ~5-10/day). New Google accounts may need to set up billing first, but usage within the free limits is not charged. See [Gemini API pricing](https://ai.google.dev/gemini-api/docs/pricing) for current details.
|
|
71
|
+
|
|
72
|
+
Alternatively, you can use [xAI Grok](https://console.x.ai/) (paid, but faster responses).
|
|
73
|
+
|
|
74
|
+
### 2. Location
|
|
75
|
+
|
|
76
|
+
Enter your city name in the **Location** field. To verify it works, open `wttr.in/YourCity` in your browser (e.g. [wttr.in/Tel+Aviv](https://wttr.in/Tel+Aviv)) — if it shows the right weather, use that city name.
|
|
77
|
+
|
|
78
|
+
Enter your timezone in the **Timezone** field. To find it, run `timedatectl | grep "Time zone"`.
|
|
79
|
+
|
|
80
|
+
### 3. Smart Plug
|
|
81
|
+
|
|
82
|
+
Tell the plugin how to turn your boiler on and off. Pick your plug type:
|
|
83
|
+
|
|
84
|
+
#### Switcher
|
|
85
|
+
|
|
86
|
+
Native support — the plugin finds and controls the Switcher directly on your local network. No URLs needed, no extra plugins.
|
|
87
|
+
|
|
88
|
+
Enter your device name (as it appears in the Switcher app), IP address, or device ID — any of these work:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
"switcher": {
|
|
92
|
+
"deviceId": "Switcher_Touch_386C"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If you get auth errors in the logs, your model may need a token — get it from https://switcher.co.il/GetKey/ and add `"token": "your-token"`.
|
|
97
|
+
|
|
98
|
+
> **Note:** If you have `homebridge-switcher-platform` installed, disable or remove it first — two plugins can't control the same Switcher device simultaneously.
|
|
99
|
+
|
|
100
|
+
#### Shelly
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
"boilerPlug": {
|
|
104
|
+
"onUrl": "http://192.168.1.50/relay/0?turn=on",
|
|
105
|
+
"offUrl": "http://192.168.1.50/relay/0?turn=off"
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For Gen2+ (Plus/Pro series):
|
|
110
|
+
```json
|
|
111
|
+
"boilerPlug": {
|
|
112
|
+
"onUrl": "http://192.168.1.50/rpc/Switch.Set?id=0&on=true",
|
|
113
|
+
"offUrl": "http://192.168.1.50/rpc/Switch.Set?id=0&on=false"
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### Tasmota
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
"boilerPlug": {
|
|
121
|
+
"onUrl": "http://192.168.1.51/cm?cmnd=Power%20On",
|
|
122
|
+
"offUrl": "http://192.168.1.51/cm?cmnd=Power%20Off"
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Other smart plugs
|
|
127
|
+
|
|
128
|
+
Any plug with an HTTP on/off URL works. For plugs that need POST requests or auth headers:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
"boilerPlug": {
|
|
132
|
+
"onUrl": "http://192.168.1.53/api/switch/on",
|
|
133
|
+
"offUrl": "http://192.168.1.53/api/switch/off",
|
|
134
|
+
"method": "POST",
|
|
135
|
+
"headers": "{\"Authorization\": \"Bearer TOKEN\", \"Content-Type\": \"application/json\"}",
|
|
136
|
+
"body": "{\"device\": \"boiler\"}"
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> **Note:** Use either `switcher` or `boilerPlug` — not both.
|
|
141
|
+
|
|
142
|
+
### 4. Hot Water Schedule
|
|
143
|
+
|
|
144
|
+
Add the times your household needs hot water:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
"usage": [
|
|
148
|
+
{ "time": "07:00", "label": "Morning shower", "liters": 60, "temp": 45 },
|
|
149
|
+
{ "time": "18:30", "label": "Kid bath", "liters": 50, "temp": 45 },
|
|
150
|
+
{ "time": "22:00", "label": "Evening shower", "liters": 100, "temp": 50 }
|
|
151
|
+
]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The plugin checks automatically ~1 hour before each event and only heats if needed. On sunny days, the sun does the work and the electric heater stays off.
|
|
155
|
+
|
|
156
|
+
### Tank (auto-detected)
|
|
157
|
+
|
|
158
|
+
On first startup, the plugin detects the standard tank specs for your location automatically. If your tank is different, override by adding the `tank` section to your config:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
"tank": {
|
|
162
|
+
"liters": 120,
|
|
163
|
+
"heaterKw": 2.5,
|
|
164
|
+
"solar": true
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Set `"solar": false` if your tank is electric-only (no rooftop solar panel).
|
|
169
|
+
|
|
170
|
+
## How it works
|
|
171
|
+
|
|
172
|
+
**The plugin is fully autonomous.** Once configured, it runs on its own. Before each time you need hot water, it:
|
|
173
|
+
|
|
174
|
+
1. Fetches weather and sunrise/sunset for your location
|
|
175
|
+
2. Estimates the tank temperature from heating history and solar gain
|
|
176
|
+
3. Asks the AI whether heating is needed and for how long
|
|
177
|
+
4. Turns the boiler on/off via your smart plug
|
|
178
|
+
|
|
179
|
+
This runs in the background as long as Homebridge is running — no interaction needed.
|
|
180
|
+
|
|
181
|
+
**Important:** There is no physical temperature sensor. The tank temperature is estimated based on weather conditions, solar gain, heating history, and standby heat loss. The AI uses this estimate to make decisions. It works well in practice, but it's a model — not a measurement.
|
|
182
|
+
|
|
183
|
+
On the first day after installation, the system has no heating history, so the initial temperature estimate may be off. After the first heating cycle it calibrates itself and becomes more accurate over time.
|
|
184
|
+
|
|
185
|
+
### The HomeKit switch
|
|
186
|
+
|
|
187
|
+
The boiler appears as a switch in the Home app, but it does **not** enable/disable the system. Think of it as a button:
|
|
188
|
+
|
|
189
|
+
- **Tap ON** = "check now" — manually triggers one AI decision. The switch turns itself back off afterward.
|
|
190
|
+
- **Tap OFF** = emergency stop — immediately turns off the boiler if it's heating.
|
|
191
|
+
|
|
192
|
+
The automatic schedule runs regardless. You never need to touch the switch — it's there for manual overrides only.
|
|
193
|
+
|
|
194
|
+
## Safety
|
|
195
|
+
|
|
196
|
+
- **Max duration cap** — no single cycle exceeds 90 minutes (configurable)
|
|
197
|
+
- **Watchdog timer** — force-stops 5 minutes after max, no matter what
|
|
198
|
+
- **Crash recovery** — sends OFF on Homebridge restart if boiler was left on
|
|
199
|
+
- **Retry logic** — 3 attempts for every on/off command, with emergency double-off on failure
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pluginAlias": "BoilerAI",
|
|
3
|
+
"pluginType": "platform",
|
|
4
|
+
"singular": true,
|
|
5
|
+
"headerDisplay": "AI-powered solar hot water tank controller. Uses Gemini or Grok to decide when to run the electric heater, optimizing for minimal electricity by leveraging solar heating.",
|
|
6
|
+
"schema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": {
|
|
10
|
+
"title": "Plugin Name",
|
|
11
|
+
"type": "string",
|
|
12
|
+
"default": "Boiler AI"
|
|
13
|
+
},
|
|
14
|
+
"location": {
|
|
15
|
+
"title": "Location",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "City name for weather (test with: curl wttr.in/YourCity)",
|
|
18
|
+
"placeholder": "Tel Aviv",
|
|
19
|
+
"required": true
|
|
20
|
+
},
|
|
21
|
+
"timezone": {
|
|
22
|
+
"title": "Timezone",
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "IANA timezone (find yours: timedatectl | grep 'Time zone')",
|
|
25
|
+
"placeholder": "Asia/Jerusalem",
|
|
26
|
+
"required": true
|
|
27
|
+
},
|
|
28
|
+
"geminiApiKey": {
|
|
29
|
+
"title": "Gemini API Key",
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Google Gemini API key (free tier available at aistudio.google.com)",
|
|
32
|
+
"x-schema-form": { "type": "password" }
|
|
33
|
+
},
|
|
34
|
+
"xaiApiKey": {
|
|
35
|
+
"title": "xAI (Grok) API Key",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Optional. If set, Grok is preferred over Gemini",
|
|
38
|
+
"x-schema-form": { "type": "password" }
|
|
39
|
+
},
|
|
40
|
+
"tank": {
|
|
41
|
+
"title": "Tank",
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"liters": {
|
|
45
|
+
"title": "Capacity (liters)",
|
|
46
|
+
"type": "integer",
|
|
47
|
+
"default": 120,
|
|
48
|
+
"minimum": 20,
|
|
49
|
+
"maximum": 500,
|
|
50
|
+
"required": true
|
|
51
|
+
},
|
|
52
|
+
"heaterKw": {
|
|
53
|
+
"title": "Heater Power (kW)",
|
|
54
|
+
"type": "number",
|
|
55
|
+
"default": 2.5,
|
|
56
|
+
"minimum": 0.5,
|
|
57
|
+
"maximum": 10,
|
|
58
|
+
"required": true
|
|
59
|
+
},
|
|
60
|
+
"solar": {
|
|
61
|
+
"title": "Has Solar Collector",
|
|
62
|
+
"type": "boolean",
|
|
63
|
+
"default": true,
|
|
64
|
+
"description": "Disable for electric-only tanks"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"boilerPlug": {
|
|
69
|
+
"title": "Boiler Smart Plug",
|
|
70
|
+
"type": "object",
|
|
71
|
+
"properties": {
|
|
72
|
+
"onUrl": {
|
|
73
|
+
"title": "ON URL",
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "HTTP URL to turn boiler on",
|
|
76
|
+
"required": true
|
|
77
|
+
},
|
|
78
|
+
"offUrl": {
|
|
79
|
+
"title": "OFF URL",
|
|
80
|
+
"type": "string",
|
|
81
|
+
"description": "HTTP URL to turn boiler off",
|
|
82
|
+
"required": true
|
|
83
|
+
},
|
|
84
|
+
"method": {
|
|
85
|
+
"title": "HTTP Method",
|
|
86
|
+
"type": "string",
|
|
87
|
+
"default": "GET",
|
|
88
|
+
"oneOf": [
|
|
89
|
+
{ "title": "GET", "enum": ["GET"] },
|
|
90
|
+
{ "title": "POST", "enum": ["POST"] }
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
"headers": {
|
|
94
|
+
"title": "HTTP Headers (JSON)",
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": "Optional JSON object, e.g. {\"Authorization\": \"Bearer token\"}"
|
|
97
|
+
},
|
|
98
|
+
"body": {
|
|
99
|
+
"title": "Request Body",
|
|
100
|
+
"type": "string",
|
|
101
|
+
"description": "Optional body for POST requests"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"switcher": {
|
|
106
|
+
"title": "Switcher (Israeli boiler plug)",
|
|
107
|
+
"type": "object",
|
|
108
|
+
"description": "If configured, the plugin controls the boiler via Switcher directly (no HTTP URLs needed). Leave empty to use HTTP smart plug instead.",
|
|
109
|
+
"properties": {
|
|
110
|
+
"deviceId": {
|
|
111
|
+
"title": "Device ID",
|
|
112
|
+
"type": "string",
|
|
113
|
+
"description": "Device name (as shown in Switcher app), IP address, or hex device ID"
|
|
114
|
+
},
|
|
115
|
+
"deviceIp": {
|
|
116
|
+
"title": "Device IP (optional)",
|
|
117
|
+
"type": "string",
|
|
118
|
+
"description": "Optional — device is auto-discovered on your network. Set only if discovery fails."
|
|
119
|
+
},
|
|
120
|
+
"token": {
|
|
121
|
+
"title": "Token (optional)",
|
|
122
|
+
"type": "string",
|
|
123
|
+
"description": "Only if needed — get it from https://switcher.co.il/GetKey/",
|
|
124
|
+
"x-schema-form": { "type": "password" }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"usage": {
|
|
129
|
+
"title": "Hot Water Usage",
|
|
130
|
+
"type": "array",
|
|
131
|
+
"items": {
|
|
132
|
+
"type": "object",
|
|
133
|
+
"properties": {
|
|
134
|
+
"time": {
|
|
135
|
+
"title": "Time (HH:MM)",
|
|
136
|
+
"type": "string",
|
|
137
|
+
"pattern": "^\\d{2}:\\d{2}$",
|
|
138
|
+
"required": true
|
|
139
|
+
},
|
|
140
|
+
"label": {
|
|
141
|
+
"title": "Label",
|
|
142
|
+
"type": "string",
|
|
143
|
+
"required": true
|
|
144
|
+
},
|
|
145
|
+
"liters": {
|
|
146
|
+
"title": "Liters",
|
|
147
|
+
"type": "integer",
|
|
148
|
+
"minimum": 5,
|
|
149
|
+
"required": true
|
|
150
|
+
},
|
|
151
|
+
"temp": {
|
|
152
|
+
"title": "Temperature (°C)",
|
|
153
|
+
"type": "number",
|
|
154
|
+
"minimum": 30,
|
|
155
|
+
"maximum": 65,
|
|
156
|
+
"required": true
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"default": [
|
|
161
|
+
{ "time": "06:00", "label": "Morning wash", "liters": 30, "temp": 38 },
|
|
162
|
+
{ "time": "18:30", "label": "Kid bath", "liters": 50, "temp": 45 },
|
|
163
|
+
{ "time": "22:00", "label": "Showers", "liters": 120, "temp": 50 }
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
"maxDurationMinutes": {
|
|
167
|
+
"title": "Max Heating Duration (minutes)",
|
|
168
|
+
"type": "integer",
|
|
169
|
+
"default": 90,
|
|
170
|
+
"minimum": 10,
|
|
171
|
+
"maximum": 120,
|
|
172
|
+
"description": "Safety cap per heating cycle"
|
|
173
|
+
},
|
|
174
|
+
"aiTemperature": {
|
|
175
|
+
"title": "AI Temperature",
|
|
176
|
+
"type": "number",
|
|
177
|
+
"default": 0.3,
|
|
178
|
+
"minimum": 0,
|
|
179
|
+
"maximum": 1,
|
|
180
|
+
"description": "Lower = more conservative decisions"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
package/dist/ai.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Logger } from 'homebridge';
|
|
2
|
+
import * as http from 'http';
|
|
3
|
+
export declare function callAI(prompt: string, timeoutSecs: number, xaiApiKey?: string, geminiApiKey?: string, temperature?: number, log?: Logger): Promise<string>;
|
|
4
|
+
declare function httpRequest(url: string, options: http.RequestOptions & {
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}, body?: string): Promise<string>;
|
|
7
|
+
export { httpRequest };
|
package/dist/ai.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.callAI = callAI;
|
|
37
|
+
exports.httpRequest = httpRequest;
|
|
38
|
+
const https = __importStar(require("https"));
|
|
39
|
+
const http = __importStar(require("http"));
|
|
40
|
+
async function callAI(prompt, timeoutSecs, xaiApiKey, geminiApiKey, temperature = 0.3, log) {
|
|
41
|
+
if (xaiApiKey) {
|
|
42
|
+
return callOpenAICompatible('https://api.x.ai/v1/chat/completions', xaiApiKey, 'grok-3-mini-fast', prompt, timeoutSecs, temperature);
|
|
43
|
+
}
|
|
44
|
+
if (geminiApiKey) {
|
|
45
|
+
return callGeminiREST(geminiApiKey, 'gemini-2.5-flash-lite', prompt, timeoutSecs);
|
|
46
|
+
}
|
|
47
|
+
throw new Error('No AI API key set (need geminiApiKey or xaiApiKey)');
|
|
48
|
+
}
|
|
49
|
+
function callOpenAICompatible(endpoint, apiKey, model, prompt, timeoutSecs, temperature) {
|
|
50
|
+
const body = JSON.stringify({
|
|
51
|
+
model,
|
|
52
|
+
messages: [{ role: 'user', content: prompt }],
|
|
53
|
+
temperature,
|
|
54
|
+
});
|
|
55
|
+
return httpRequest(endpoint, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
60
|
+
},
|
|
61
|
+
timeout: timeoutSecs * 1000,
|
|
62
|
+
}, body).then(data => {
|
|
63
|
+
const result = JSON.parse(data);
|
|
64
|
+
if (!result.choices?.length)
|
|
65
|
+
throw new Error('Empty AI response');
|
|
66
|
+
return result.choices[0].message.content;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function callGeminiREST(apiKey, model, prompt, timeoutSecs) {
|
|
70
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
|
|
71
|
+
const body = JSON.stringify({
|
|
72
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
73
|
+
});
|
|
74
|
+
return httpRequest(url, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
'x-goog-api-key': apiKey,
|
|
79
|
+
},
|
|
80
|
+
timeout: timeoutSecs * 1000,
|
|
81
|
+
}, body).then(data => {
|
|
82
|
+
const result = JSON.parse(data);
|
|
83
|
+
if (!result.candidates?.[0]?.content?.parts?.[0]?.text) {
|
|
84
|
+
throw new Error('Empty AI response');
|
|
85
|
+
}
|
|
86
|
+
return result.candidates[0].content.parts[0].text;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function httpRequest(url, options, body) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const lib = url.startsWith('https') ? https : http;
|
|
92
|
+
const req = lib.request(url, options, (res) => {
|
|
93
|
+
let data = '';
|
|
94
|
+
res.on('data', chunk => data += chunk);
|
|
95
|
+
res.on('end', () => {
|
|
96
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
97
|
+
resolve(data);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
reject(new Error(`API error ${res.statusCode}: ${data.slice(0, 200)}`));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
req.on('error', reject);
|
|
105
|
+
if (options.timeout) {
|
|
106
|
+
req.setTimeout(options.timeout, () => {
|
|
107
|
+
req.destroy();
|
|
108
|
+
reject(new Error('Request timeout'));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (body)
|
|
112
|
+
req.write(body);
|
|
113
|
+
req.end();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=ai.js.map
|
package/dist/ai.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"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"}
|
package/dist/boiler.d.ts
ADDED
package/dist/boiler.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendWebhook = sendWebhook;
|
|
4
|
+
const ai_1 = require("./ai");
|
|
5
|
+
const WEBHOOK_RETRIES = 3;
|
|
6
|
+
const WEBHOOK_TIMEOUT = 5000;
|
|
7
|
+
const WEBHOOK_RETRY_DELAY = 2000;
|
|
8
|
+
async function sendWebhook(on, plug, log) {
|
|
9
|
+
const url = on ? plug.onUrl : plug.offUrl;
|
|
10
|
+
const method = (plug.method || 'GET').toUpperCase();
|
|
11
|
+
let headers = {};
|
|
12
|
+
if (plug.headers) {
|
|
13
|
+
try {
|
|
14
|
+
headers = JSON.parse(plug.headers);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
log.warn('WEBHOOK: failed to parse headers JSON');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
let lastErr = null;
|
|
21
|
+
for (let attempt = 1; attempt <= WEBHOOK_RETRIES; attempt++) {
|
|
22
|
+
try {
|
|
23
|
+
await (0, ai_1.httpRequest)(url, {
|
|
24
|
+
method,
|
|
25
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
26
|
+
timeout: WEBHOOK_TIMEOUT,
|
|
27
|
+
}, plug.body || undefined);
|
|
28
|
+
log.info(`WEBHOOK: boiler ${on ? 'true' : 'false'} (attempt ${attempt})`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
lastErr = err;
|
|
33
|
+
log.warn(`WEBHOOK: attempt ${attempt} failed: ${lastErr.message}`);
|
|
34
|
+
if (attempt < WEBHOOK_RETRIES) {
|
|
35
|
+
await sleep(WEBHOOK_RETRY_DELAY);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`Webhook failed after ${WEBHOOK_RETRIES} attempts: ${lastErr?.message}`);
|
|
40
|
+
}
|
|
41
|
+
function sleep(ms) {
|
|
42
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=boiler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boiler.js","sourceRoot":"","sources":["../src/boiler.ts"],"names":[],"mappings":";;AAQA,kCAkCC;AAxCD,6BAAmC;AAEnC,MAAM,eAAe,GAAG,CAAC,CAAC;AAC1B,MAAM,eAAe,GAAG,IAAI,CAAC;AAC7B,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAE1B,KAAK,UAAU,WAAW,CAAC,EAAW,EAAE,IAAsB,EAAE,GAAW;IAChF,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;IAC1C,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAEpD,IAAI,OAAO,GAA2B,EAAE,CAAC;IACzC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,IAAI,OAAO,GAAiB,IAAI,CAAC;IACjC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,eAAe,EAAE,OAAO,EAAE,EAAE,CAAC;QAC5D,IAAI,CAAC;YACH,MAAM,IAAA,gBAAW,EAAC,GAAG,EAAE;gBACrB,MAAM;gBACN,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;gBAC9D,OAAO,EAAE,eAAe;aACzB,EAAE,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC;YAE3B,GAAG,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,aAAa,OAAO,GAAG,CAAC,CAAC;YAC1E,OAAO;QACT,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,GAAG,GAAY,CAAC;YACvB,GAAG,CAAC,IAAI,CAAC,oBAAoB,OAAO,YAAY,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;YACnE,IAAI,OAAO,GAAG,eAAe,EAAE,CAAC;gBAC9B,MAAM,KAAK,CAAC,mBAAmB,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,wBAAwB,eAAe,cAAc,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PlatformAccessory, CharacteristicValue, Logger } from 'homebridge';
|
|
2
|
+
import { BoilerAIPlatform } from './platform';
|
|
3
|
+
export declare class BoilerAccessory {
|
|
4
|
+
private readonly platform;
|
|
5
|
+
private readonly accessory;
|
|
6
|
+
private readonly log;
|
|
7
|
+
private service;
|
|
8
|
+
constructor(platform: BoilerAIPlatform, accessory: PlatformAccessory, log: Logger);
|
|
9
|
+
getOn(): Promise<CharacteristicValue>;
|
|
10
|
+
setOn(value: CharacteristicValue): Promise<void>;
|
|
11
|
+
updateState(on: boolean): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BoilerAccessory = void 0;
|
|
4
|
+
class BoilerAccessory {
|
|
5
|
+
constructor(platform, accessory, log) {
|
|
6
|
+
this.platform = platform;
|
|
7
|
+
this.accessory = accessory;
|
|
8
|
+
this.log = log;
|
|
9
|
+
this.accessory.getService(this.platform.Service.AccessoryInformation)
|
|
10
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Boiler AI')
|
|
11
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'Solar Hot Water Controller')
|
|
12
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, 'BOILER-AI-001');
|
|
13
|
+
this.service = this.accessory.getService(this.platform.Service.Switch)
|
|
14
|
+
|| this.accessory.addService(this.platform.Service.Switch, 'Boiler AI');
|
|
15
|
+
this.service.getCharacteristic(this.platform.Characteristic.On)
|
|
16
|
+
.onGet(this.getOn.bind(this))
|
|
17
|
+
.onSet(this.setOn.bind(this));
|
|
18
|
+
}
|
|
19
|
+
async getOn() {
|
|
20
|
+
return this.platform.isBoilerOn();
|
|
21
|
+
}
|
|
22
|
+
async setOn(value) {
|
|
23
|
+
if (value) {
|
|
24
|
+
this.log.info('HomeKit: triggering AI decision cycle');
|
|
25
|
+
this.platform.triggerDecisionCycle('homekit');
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.log.info('HomeKit: emergency stop');
|
|
29
|
+
await this.platform.stopBoiler();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
updateState(on) {
|
|
33
|
+
this.service.updateCharacteristic(this.platform.Characteristic.On, on);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.BoilerAccessory = BoilerAccessory;
|
|
37
|
+
//# sourceMappingURL=boilerAccessory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boilerAccessory.js","sourceRoot":"","sources":["../src/boilerAccessory.ts"],"names":[],"mappings":";;;AAGA,MAAa,eAAe;IAG1B,YACmB,QAA0B,EAC1B,SAA4B,EAC5B,GAAW;QAFX,aAAQ,GAAR,QAAQ,CAAkB;QAC1B,cAAS,GAAT,SAAS,CAAmB;QAC5B,QAAG,GAAH,GAAG,CAAQ;QAE5B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAE;aACnE,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,YAAY,EAAE,WAAW,CAAC;aACzE,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,EAAE,4BAA4B,CAAC;aACnF,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;QAEjF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC;eACjE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAE1E,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;aAC5D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aAC5B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAA0B;QACpC,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;YACvD,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;YACzC,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QACnC,CAAC;IACH,CAAC;IAED,WAAW,CAAC,EAAW;QACrB,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACzE,CAAC;CACF;AAtCD,0CAsCC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const settings_1 = require("./settings");
|
|
4
|
+
const platform_1 = require("./platform");
|
|
5
|
+
exports.default = (api) => {
|
|
6
|
+
api.registerPlatform(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, platform_1.BoilerAIPlatform);
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AACA,yCAAwD;AACxD,yCAA8C;AAE9C,kBAAe,CAAC,GAAQ,EAAQ,EAAE;IAChC,GAAG,CAAC,gBAAgB,CAAC,sBAAW,EAAE,wBAAa,EAAE,2BAAgB,CAAC,CAAC;AACrE,CAAC,CAAC"}
|