@thecloudseeker/homebridge-skoda 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.
Files changed (65) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +29 -0
  3. package/README.md +184 -2
  4. package/config.schema.json +275 -0
  5. package/dist/accessory/services/battery.d.ts +20 -0
  6. package/dist/accessory/services/battery.js +36 -0
  7. package/dist/accessory/services/battery.js.map +1 -0
  8. package/dist/accessory/services/body.d.ts +33 -0
  9. package/dist/accessory/services/body.js +68 -0
  10. package/dist/accessory/services/body.js.map +1 -0
  11. package/dist/accessory/services/charging.d.ts +26 -0
  12. package/dist/accessory/services/charging.js +80 -0
  13. package/dist/accessory/services/charging.js.map +1 -0
  14. package/dist/accessory/services/climate.d.ts +48 -0
  15. package/dist/accessory/services/climate.js +206 -0
  16. package/dist/accessory/services/climate.js.map +1 -0
  17. package/dist/accessory/services/common.d.ts +56 -0
  18. package/dist/accessory/services/common.js +104 -0
  19. package/dist/accessory/services/common.js.map +1 -0
  20. package/dist/accessory/vehicleAccessory.d.ts +13 -0
  21. package/dist/accessory/vehicleAccessory.js +60 -0
  22. package/dist/accessory/vehicleAccessory.js.map +1 -0
  23. package/dist/api/client.d.ts +41 -0
  24. package/dist/api/client.js +170 -0
  25. package/dist/api/client.js.map +1 -0
  26. package/dist/api/errors.d.ts +31 -0
  27. package/dist/api/errors.js +52 -0
  28. package/dist/api/errors.js.map +1 -0
  29. package/dist/api/rateBudget.d.ts +53 -0
  30. package/dist/api/rateBudget.js +113 -0
  31. package/dist/api/rateBudget.js.map +1 -0
  32. package/dist/api/types.d.ts +174 -0
  33. package/dist/api/types.js +22 -0
  34. package/dist/api/types.js.map +1 -0
  35. package/dist/config.d.ts +70 -0
  36. package/dist/config.js +151 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.js +6 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/platform.d.ts +12 -0
  42. package/dist/platform.js +123 -0
  43. package/dist/platform.js.map +1 -0
  44. package/dist/settings.d.ts +3 -0
  45. package/dist/settings.js +13 -0
  46. package/dist/settings.js.map +1 -0
  47. package/dist/util/geo.d.ts +7 -0
  48. package/dist/util/geo.js +16 -0
  49. package/dist/util/geo.js.map +1 -0
  50. package/dist/util/logger.d.ts +12 -0
  51. package/dist/util/logger.js +25 -0
  52. package/dist/util/logger.js.map +1 -0
  53. package/dist/util/redact.d.ts +4 -0
  54. package/dist/util/redact.js +15 -0
  55. package/dist/util/redact.js.map +1 -0
  56. package/dist/vehicle/mapping.d.ts +39 -0
  57. package/dist/vehicle/mapping.js +160 -0
  58. package/dist/vehicle/mapping.js.map +1 -0
  59. package/dist/vehicle/snapshot.d.ts +25 -0
  60. package/dist/vehicle/snapshot.js +93 -0
  61. package/dist/vehicle/snapshot.js.map +1 -0
  62. package/dist/vehicle/vehicleController.d.ts +58 -0
  63. package/dist/vehicle/vehicleController.js +248 -0
  64. package/dist/vehicle/vehicleController.js.map +1 -0
  65. package/package.json +64 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release, for the official MyŠkoda Public API (contract 1.1.0).
6
+
7
+ - **One accessory per vehicle**, with a service for each feature the vehicle supports: battery, charging (outlet), charge limit (lightbulb brightness), air conditioning (heater cooler), auxiliary heating with S-PIN, active ventilation, "Home" occupancy sensor, and contact sensors for doors, windows, trunk, bonnet, sunroof and lock state. An optional humidity sensor mirrors the state of charge for automations.
8
+ - **Within the rate limit:** at most 20 requests per hour per vehicle, following the `RateLimit-*` headers, with four requests kept free for commands. HomeKit reads come from the cache and never reach the API.
9
+ - **Commands** update HomeKit right away and are confirmed by one refresh shortly after. The plugin backs off on `429`, `5xx` and network errors, and stale data shows as a fault on the sensors.
10
+ - **API keys:** an optional second key for commands, a warning seven days before a key expires, and a clear message for expired, invalid or deleted keys. Keys and the S-PIN never appear in the log, the VIN only as its last four characters.
11
+ - **Homebridge UI** settings with password fields for the keys and the S-PIN.
12
+ - The ventilation switch shows the state the API reports, like the MyŠkoda app. A ventilation started through the Public API runs but is not reported as running.
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026 Eike Dreier
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its contributors
17
+ may be used to endorse or promote products derived from this software
18
+ without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,3 +1,185 @@
1
- # Temporary Holding Version
1
+ # Homebridge Škoda
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![CI](https://github.com/thecloudseeker/homebridge-skoda/actions/workflows/ci.yml/badge.svg)](https://github.com/thecloudseeker/homebridge-skoda/actions/workflows/ci.yml)
4
+ [![License](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg)](LICENSE)
5
+
6
+ Homebridge plugin that brings Škoda vehicles into Apple Home through the official
7
+ [MyŠkoda Public API](https://public.api.connect.skoda-auto.cz/docs): state of charge, charging,
8
+ air conditioning, auxiliary heating, "at home" detection, doors, windows and lock state.
9
+
10
+ > [!IMPORTANT]
11
+ > This is **not an official Škoda project**. It is not affiliated with, endorsed by or supported by
12
+ > ŠKODA AUTO a.s. "Škoda" and "MyŠkoda" are trademarks of their respective owner.
13
+ >
14
+ > Škoda provides the MyŠkoda Public API **for personal end-user use only**. Do not use this plugin
15
+ > to build or operate commercial products or services.
16
+
17
+ ## Features
18
+
19
+ Each vehicle becomes one HomeKit accessory. A service is only created if it is enabled in the
20
+ config **and** the vehicle reports that it supports it.
21
+
22
+ | HomeKit service | Shows | Controls | Default |
23
+ | ---------------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------------------------- | ------- |
24
+ | Battery | State of charge, charging state, low battery below a threshold | – | on |
25
+ | Humidity sensor "State of Charge" | State of charge in %, so you can build automations on it | – | off |
26
+ | Outlet "Charging" | _On_ = charging, _Outlet In Use_ = cable plugged in | Start / stop charging | on |
27
+ | Lightbulb "Charge Limit" | Brightness = charging limit (50–100 % in steps of 10) | Set charging limit | on |
28
+ | Heater Cooler "Climate" | Active, heating / cooling, target temperature | Start / stop air conditioning, set temperature | on |
29
+ | Switch "Auxiliary Heating" | Running | Start / stop (needs your S-PIN) | on |
30
+ | Switch "Ventilation" | Running, as far as the vehicle reports it (see notes) | Start / stop active ventilation | off |
31
+ | Occupancy sensor "Home" | Parked within a radius around your home | – | on |
32
+ | Contact sensors "Doors", "Windows", "Trunk", "Bonnet", "Sunroof" | Open / closed | – | on |
33
+ | Contact sensor "Locked" | Closed = locked, open = unlocked | – (the API cannot lock or unlock) | on |
34
+
35
+ What the API does **not** offer, and therefore this plugin neither: locking and unlocking, honking
36
+ and flashing, waking the vehicle up, cabin temperature, and push updates.
37
+
38
+ ### HomeKit notes
39
+
40
+ - **Climate.** The API has no cabin temperature reading, so _Current Temperature_ mirrors the
41
+ target temperature. The API has no heat or cool mode either, so the mode is fixed to _Auto_ and
42
+ both thresholds always hold the same value. Temperatures range from 16 to 29.5 °C in steps of
43
+ 0.5 °C. Changing the temperature while the air conditioning is running restarts it (stop,
44
+ 10 s pause, start), which costs two requests. While it is off, the new temperature is used on
45
+ the next start.
46
+ - **Charge limit.** HomeKit has no control for a percentage setpoint, so the limit is shown as the
47
+ brightness of a lightbulb. It cannot be switched off. Values snap to 50–100 % in steps of 10,
48
+ as the vehicles require.
49
+ - **Ventilation.** A ventilation started through the Public API runs, but Škoda does not report it
50
+ as running: in a live test the API said _off_ during the whole 10-minute run, and the MyŠkoda
51
+ app showed it the same way. A ventilation started in the app is reported correctly. The switch
52
+ therefore always shows the reported state: after you switch it on in HomeKit it returns to _off_
53
+ once the vehicle has accepted the start (see the log), and it shows _on_ when the ventilation
54
+ was started in the app.
55
+ - **Lock.** Exposed as a read-only contact sensor rather than a lock, because the API cannot
56
+ lock or unlock. A lock tile in the Home app would suggest otherwise.
57
+ - **Stale data.** If the last successful update is older than three polling intervals, the sensors
58
+ show a fault (_StatusFault_). Battery, outlet, switches and the heater cooler have no fault
59
+ indicator in HomeKit.
60
+
61
+ ## Requirements
62
+
63
+ - A connected Škoda vehicle and a MyŠkoda account
64
+ - Homebridge 1.8 or 2.x
65
+ - Node.js 22, 24 or 26
66
+
67
+ ## Installation
68
+
69
+ The plugin is not published to npm yet. Once it is, search for `@thecloudseeker/homebridge-skoda`
70
+ in the Homebridge UI, or run:
71
+
72
+ ```sh
73
+ npm install -g @thecloudseeker/homebridge-skoda
74
+ ```
75
+
76
+ ## Creating an API key
77
+
78
+ 1. Open the **MyŠkoda app** and go to **Profile › Smart Home**. On your phone you can also open
79
+ [go.skoda.eu/api-keys](https://go.skoda.eu/api-keys) directly.
80
+ 2. Create a new API key and select the vehicle(s) it should cover.
81
+ 3. Copy the key into the plugin config together with the vehicle's VIN.
82
+
83
+ Keys are bound to the vehicles you selected and **they expire**. The plugin warns in the log seven
84
+ days before a key expires. Once it has expired, the plugin logs an error and retries every hour
85
+ (failed authentication does not count against the rate limit). Renew the key in the app and
86
+ update the config.
87
+
88
+ You may configure a second key for commands only (_Command API key_), for example to keep
89
+ permissions apart. **A second key does not give you more requests**: the rate limit applies per
90
+ vehicle, and both keys share it.
91
+
92
+ ## Configuration
93
+
94
+ The easiest way is the Homebridge UI. In `config.json` it looks like this:
95
+
96
+ ```json
97
+ {
98
+ "platform": "Skoda",
99
+ "vehicles": [
100
+ {
101
+ "name": "Enyaq",
102
+ "vin": "TMBJB9NY5RF999999",
103
+ "token": "<your API key>",
104
+ "home": { "lat": 50.4128, "lon": 14.9069, "radiusMeters": 150 },
105
+ "pollingIntervalMinutes": 5,
106
+ "features": { "stateOfChargeSensor": true }
107
+ }
108
+ ]
109
+ }
110
+ ```
111
+
112
+ | Option | Default | Description |
113
+ | ------------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
114
+ | `name` | `Škoda <last 4 of VIN>` | Accessory name in HomeKit |
115
+ | `vin` | – | Vehicle identification number (required) |
116
+ | `token` | – | API key (required) |
117
+ | `commandToken` | – | Separate API key for commands |
118
+ | `spin` | – | S-PIN, only needed for the auxiliary heating |
119
+ | `home.lat`, `home.lon` | – | Home coordinates for the "Home" sensor |
120
+ | `home.radiusMeters` | `150` | Radius around home, at least 10 m |
121
+ | `pollingIntervalMinutes` | `5` | Polling interval, at least 4 (see below) |
122
+ | `commandRefreshDelaySeconds` | `90` | Delay before the refresh after a command (15–600) |
123
+ | `lowBatteryThreshold` | `20` | Low battery below this state of charge (%) |
124
+ | `climateTemperature` | `21` | Cabin temperature (°C) until the vehicle reports one |
125
+ | `airConditioningWithoutExternalPower` | – | Allow air conditioning on battery; unset keeps the vehicle setting |
126
+ | `auxiliaryHeatingDurationMinutes` | – | Auxiliary heating run time; unset keeps the vehicle setting |
127
+ | `features.*` | see table above | `battery`, `stateOfChargeSensor`, `charging`, `chargingLimit`, `climate`, `auxiliaryHeating`, `activeVentilation`, `presence`, `doors`, `windows`, `trunk`, `bonnet`, `sunroof`, `lock` |
128
+
129
+ ## Rate limit
130
+
131
+ The MyŠkoda Public API currently allows **20 requests per hour per vehicle**. Škoda says this value
132
+ is not final, so the plugin follows the `RateLimit-*` headers of every response. Every request
133
+ counts, including commands and failed requests with server errors.
134
+
135
+ The plugin is built around that limit:
136
+
137
+ - **One request per update.** A single call returns all vehicle data.
138
+ - **HomeKit never triggers requests.** Opening the Home app reads from the plugin's cache.
139
+ - **Four requests per hour stay free** for commands and their follow-up refresh. That makes 4
140
+ minutes the shortest polling interval; shorter values are raised with a warning. The default of
141
+ 5 minutes uses 12 requests per hour.
142
+ - **After a command**, HomeKit shows the expected state right away, and the plugin refreshes once
143
+ after `commandRefreshDelaySeconds` to confirm it. Several commands in a row share one refresh.
144
+ - **When the budget is used up**, commands are rejected ("No Response" in the Home app) instead of
145
+ being sent, and polling waits until requests are available again.
146
+ - **Errors back off.** On `429` and `503` the plugin waits for `Retry-After`. On other server and
147
+ network errors it waits one polling interval, then doubles the wait up to 30 minutes.
148
+
149
+ Vehicles report their state on their own schedule, so data can be older than the polling interval
150
+ suggests, and a command may take a minute or two to show up.
151
+
152
+ ## Privacy and security
153
+
154
+ - API keys and the S-PIN are never written to the log. The VIN is shortened to its last four
155
+ characters (`…9999`).
156
+ - The Homebridge UI shows the keys and the S-PIN as password fields. They are still stored in
157
+ plain text in Homebridge's `config.json`, like all plugin settings. Protect that file.
158
+ - The plugin talks only to `public.api.connect.skoda-auto.cz`.
159
+
160
+ ## Troubleshooting
161
+
162
+ | Log message | Meaning |
163
+ | ------------------------------------------------------ | --------------------------------------------------------------- |
164
+ | `The API key has expired` | Create or renew a key in the MyŠkoda app and update the config |
165
+ | `The API key does not cover this vehicle or operation` | The key was created for another vehicle, or the VIN is wrong |
166
+ | `No vehicle found for the configured VIN` | Check the VIN |
167
+ | `rate limit budget exhausted` | Too many commands in the last hour; try again later |
168
+ | `the vehicle declined the request` | The vehicle refused for now (e.g. low battery); try again later |
169
+ | `this function is currently disabled for the vehicle` | Enable the service for the vehicle in the MyŠkoda app |
170
+
171
+ ## Development
172
+
173
+ ```sh
174
+ npm install
175
+ npm run lint
176
+ npm run typecheck
177
+ npm test
178
+ npm run build
179
+ ```
180
+
181
+ The tests mock the API completely and contain no real keys or vehicle data.
182
+
183
+ ## License
184
+
185
+ [BSD 3-Clause](LICENSE)
@@ -0,0 +1,275 @@
1
+ {
2
+ "pluginAlias": "Skoda",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "strictValidation": false,
6
+ "headerDisplay": "Connects Škoda vehicles through the official **MyŠkoda Public API**. Create an API key in the MyŠkoda app under *Profile › Smart Home* (or open [go.skoda.eu/api-keys](https://go.skoda.eu/api-keys) on your phone). The API allows **20 requests per hour per vehicle**, so data is polled every few minutes and commands take a moment to show up. This is not an official Škoda project.",
7
+ "footerDisplay": "See the [README](https://github.com/thecloudseeker/homebridge-skoda#readme) for details on each feature.",
8
+ "schema": {
9
+ "type": "object",
10
+ "properties": {
11
+ "vehicles": {
12
+ "title": "Vehicles",
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "properties": {
17
+ "name": {
18
+ "title": "Name",
19
+ "type": "string",
20
+ "placeholder": "Enyaq",
21
+ "description": "Name of the accessory in HomeKit."
22
+ },
23
+ "vin": {
24
+ "title": "VIN",
25
+ "type": "string",
26
+ "required": true,
27
+ "minLength": 17,
28
+ "maxLength": 17,
29
+ "pattern": "^[A-HJ-NPR-Za-hj-npr-z0-9]{17}$",
30
+ "description": "17-character vehicle identification number."
31
+ },
32
+ "token": {
33
+ "title": "API key",
34
+ "type": "string",
35
+ "required": true,
36
+ "description": "API key from the MyŠkoda app. Used for polling, and for commands unless a command key is set."
37
+ },
38
+ "commandToken": {
39
+ "title": "Command API key (optional)",
40
+ "type": "string",
41
+ "description": "Separate key for commands, e.g. with different permissions. Both keys share the vehicle's rate limit, so a second key does not allow more requests."
42
+ },
43
+ "spin": {
44
+ "title": "S-PIN (optional)",
45
+ "type": "string",
46
+ "description": "Security PIN of your MyŠkoda account. Only needed for the auxiliary heating; it is sent with each start and never logged."
47
+ },
48
+ "home": {
49
+ "title": "Home location",
50
+ "type": "object",
51
+ "properties": {
52
+ "lat": {
53
+ "title": "Latitude",
54
+ "type": "number",
55
+ "minimum": -90,
56
+ "maximum": 90
57
+ },
58
+ "lon": {
59
+ "title": "Longitude",
60
+ "type": "number",
61
+ "minimum": -180,
62
+ "maximum": 180
63
+ },
64
+ "radiusMeters": {
65
+ "title": "Radius (m)",
66
+ "type": "integer",
67
+ "minimum": 10,
68
+ "placeholder": 150
69
+ }
70
+ }
71
+ },
72
+ "pollingIntervalMinutes": {
73
+ "title": "Polling interval (minutes)",
74
+ "type": "integer",
75
+ "minimum": 4,
76
+ "maximum": 1440,
77
+ "placeholder": 5,
78
+ "description": "Minimum 4: 20 requests per hour minus 4 kept free for commands and their follow-up refresh."
79
+ },
80
+ "commandRefreshDelaySeconds": {
81
+ "title": "Refresh after command (seconds)",
82
+ "type": "integer",
83
+ "minimum": 15,
84
+ "maximum": 600,
85
+ "placeholder": 90,
86
+ "description": "Delay before the single follow-up refresh after a command."
87
+ },
88
+ "lowBatteryThreshold": {
89
+ "title": "Low battery threshold (%)",
90
+ "type": "integer",
91
+ "minimum": 0,
92
+ "maximum": 100,
93
+ "placeholder": 20
94
+ },
95
+ "climateTemperature": {
96
+ "title": "Default cabin temperature (°C)",
97
+ "type": "number",
98
+ "minimum": 16,
99
+ "maximum": 29.5,
100
+ "multipleOf": 0.5,
101
+ "placeholder": 21,
102
+ "description": "Used until the vehicle reports its own target temperature."
103
+ },
104
+ "airConditioningWithoutExternalPower": {
105
+ "title": "Allow air conditioning on battery",
106
+ "type": "boolean",
107
+ "description": "Sent with each start. Leave unset to keep the vehicle's setting."
108
+ },
109
+ "auxiliaryHeatingDurationMinutes": {
110
+ "title": "Auxiliary heating duration (minutes)",
111
+ "type": "integer",
112
+ "minimum": 1,
113
+ "maximum": 60,
114
+ "description": "Leave empty to use the vehicle's setting."
115
+ },
116
+ "features": {
117
+ "title": "Features",
118
+ "type": "object",
119
+ "properties": {
120
+ "battery": {
121
+ "title": "Battery",
122
+ "type": "boolean",
123
+ "default": true
124
+ },
125
+ "stateOfChargeSensor": {
126
+ "title": "State of charge as humidity sensor (for automations)",
127
+ "type": "boolean",
128
+ "default": false
129
+ },
130
+ "charging": {
131
+ "title": "Charging (outlet)",
132
+ "type": "boolean",
133
+ "default": true
134
+ },
135
+ "chargingLimit": {
136
+ "title": "Charge limit (lightbulb brightness)",
137
+ "type": "boolean",
138
+ "default": true
139
+ },
140
+ "climate": {
141
+ "title": "Air conditioning (heater cooler)",
142
+ "type": "boolean",
143
+ "default": true
144
+ },
145
+ "auxiliaryHeating": {
146
+ "title": "Auxiliary heating (needs S-PIN)",
147
+ "type": "boolean",
148
+ "default": true
149
+ },
150
+ "activeVentilation": {
151
+ "title": "Active ventilation",
152
+ "type": "boolean",
153
+ "default": false
154
+ },
155
+ "presence": {
156
+ "title": "At home (needs home location)",
157
+ "type": "boolean",
158
+ "default": true
159
+ },
160
+ "doors": {
161
+ "title": "Doors",
162
+ "type": "boolean",
163
+ "default": true
164
+ },
165
+ "windows": {
166
+ "title": "Windows",
167
+ "type": "boolean",
168
+ "default": true
169
+ },
170
+ "trunk": {
171
+ "title": "Trunk",
172
+ "type": "boolean",
173
+ "default": true
174
+ },
175
+ "bonnet": {
176
+ "title": "Bonnet",
177
+ "type": "boolean",
178
+ "default": true
179
+ },
180
+ "sunroof": {
181
+ "title": "Sunroof",
182
+ "type": "boolean",
183
+ "default": true
184
+ },
185
+ "lock": {
186
+ "title": "Locked (read-only)",
187
+ "type": "boolean",
188
+ "default": true
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ },
197
+ "layout": [
198
+ {
199
+ "key": "vehicles",
200
+ "type": "array",
201
+ "orderable": false,
202
+ "buttonText": "Add vehicle",
203
+ "items": [
204
+ "vehicles[].name",
205
+ "vehicles[].vin",
206
+ { "key": "vehicles[].token", "type": "password" },
207
+ {
208
+ "type": "fieldset",
209
+ "title": "Commands",
210
+ "expandable": true,
211
+ "expanded": false,
212
+ "items": [
213
+ { "key": "vehicles[].commandToken", "type": "password" },
214
+ { "key": "vehicles[].spin", "type": "password" },
215
+ "vehicles[].commandRefreshDelaySeconds",
216
+ "vehicles[].climateTemperature",
217
+ "vehicles[].airConditioningWithoutExternalPower",
218
+ "vehicles[].auxiliaryHeatingDurationMinutes"
219
+ ]
220
+ },
221
+ {
222
+ "type": "fieldset",
223
+ "title": "Home location",
224
+ "description": "Used for the \"Home\" occupancy sensor.",
225
+ "expandable": true,
226
+ "expanded": false,
227
+ "items": [
228
+ {
229
+ "type": "flex",
230
+ "flex-flow": "row wrap",
231
+ "items": [
232
+ "vehicles[].home.lat",
233
+ "vehicles[].home.lon",
234
+ "vehicles[].home.radiusMeters"
235
+ ]
236
+ }
237
+ ]
238
+ },
239
+ {
240
+ "type": "fieldset",
241
+ "title": "Polling",
242
+ "expandable": true,
243
+ "expanded": false,
244
+ "items": [
245
+ "vehicles[].pollingIntervalMinutes",
246
+ "vehicles[].lowBatteryThreshold"
247
+ ]
248
+ },
249
+ {
250
+ "type": "fieldset",
251
+ "title": "Features",
252
+ "description": "Services are only created if the vehicle supports them.",
253
+ "expandable": true,
254
+ "expanded": false,
255
+ "items": [
256
+ "vehicles[].features.battery",
257
+ "vehicles[].features.stateOfChargeSensor",
258
+ "vehicles[].features.charging",
259
+ "vehicles[].features.chargingLimit",
260
+ "vehicles[].features.climate",
261
+ "vehicles[].features.auxiliaryHeating",
262
+ "vehicles[].features.activeVentilation",
263
+ "vehicles[].features.presence",
264
+ "vehicles[].features.doors",
265
+ "vehicles[].features.windows",
266
+ "vehicles[].features.trunk",
267
+ "vehicles[].features.bonnet",
268
+ "vehicles[].features.sunroof",
269
+ "vehicles[].features.lock"
270
+ ]
271
+ }
272
+ ]
273
+ }
274
+ ]
275
+ }
@@ -0,0 +1,20 @@
1
+ import type { Service } from "homebridge";
2
+ import type { Vehicle } from "../../api/types.js";
3
+ import { ServiceBinding, type ServiceType } from "./common.js";
4
+ /** Battery: level, charging state and low battery below the configured threshold. */
5
+ export declare class BatteryBinding extends ServiceBinding {
6
+ readonly feature = "battery";
7
+ protected readonly serviceType: ServiceType;
8
+ protected readonly subtype = "battery";
9
+ protected readonly displayName = "Battery";
10
+ protected render(_service: Service, vehicle: Vehicle): void;
11
+ }
12
+ /** Humidity sensor that mirrors the state of charge, because HomeKit cannot trigger on battery levels. */
13
+ export declare class StateOfChargeSensorBinding extends ServiceBinding {
14
+ readonly feature = "stateOfChargeSensor";
15
+ protected readonly serviceType: ServiceType;
16
+ protected readonly subtype = "state-of-charge";
17
+ protected readonly displayName = "State of Charge";
18
+ protected get hasStatusFault(): boolean;
19
+ protected render(_service: Service, vehicle: Vehicle): void;
20
+ }
@@ -0,0 +1,36 @@
1
+ import { batteryValues, stateOfCharge } from "../../vehicle/mapping.js";
2
+ import { ServiceBinding } from "./common.js";
3
+ /** Battery: level, charging state and low battery below the configured threshold. */
4
+ export class BatteryBinding extends ServiceBinding {
5
+ feature = "battery";
6
+ serviceType = this.ctx.hap.Service.Battery;
7
+ subtype = "battery";
8
+ displayName = "Battery";
9
+ render(_service, vehicle) {
10
+ const { BatteryLevel, ChargingState, StatusLowBattery } = this.ctx.hap.Characteristic;
11
+ const values = batteryValues(vehicle, this.ctx.settings.lowBatteryThreshold);
12
+ this.set(BatteryLevel, values.level);
13
+ if (values.charging !== undefined) {
14
+ this.set(ChargingState, values.charging ? ChargingState.CHARGING : ChargingState.NOT_CHARGING);
15
+ }
16
+ if (values.low !== undefined) {
17
+ this.set(StatusLowBattery, values.low
18
+ ? StatusLowBattery.BATTERY_LEVEL_LOW
19
+ : StatusLowBattery.BATTERY_LEVEL_NORMAL);
20
+ }
21
+ }
22
+ }
23
+ /** Humidity sensor that mirrors the state of charge, because HomeKit cannot trigger on battery levels. */
24
+ export class StateOfChargeSensorBinding extends ServiceBinding {
25
+ feature = "stateOfChargeSensor";
26
+ serviceType = this.ctx.hap.Service.HumiditySensor;
27
+ subtype = "state-of-charge";
28
+ displayName = "State of Charge";
29
+ get hasStatusFault() {
30
+ return true;
31
+ }
32
+ render(_service, vehicle) {
33
+ this.set(this.ctx.hap.Characteristic.CurrentRelativeHumidity, stateOfCharge(vehicle));
34
+ }
35
+ }
36
+ //# sourceMappingURL=battery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"battery.js","sourceRoot":"","sources":["../../../src/accessory/services/battery.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAAoB,MAAM,aAAa,CAAC;AAE/D,qFAAqF;AACrF,MAAM,OAAO,cAAe,SAAQ,cAAc;IACvC,OAAO,GAAG,SAAS,CAAC;IACV,WAAW,GAAgB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;IACxD,OAAO,GAAG,SAAS,CAAC;IACpB,WAAW,GAAG,SAAS,CAAC;IAEjC,MAAM,CAAC,QAAiB,EAAE,OAAgB;QAClD,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,GACrD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;QAC9B,MAAM,MAAM,GAAG,aAAa,CAC1B,OAAO,EACP,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CACtC,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,GAAG,CACN,aAAa,EACb,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,YAAY,CACtE,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,GAAG,CACN,gBAAgB,EAChB,MAAM,CAAC,GAAG;gBACR,CAAC,CAAC,gBAAgB,CAAC,iBAAiB;gBACpC,CAAC,CAAC,gBAAgB,CAAC,oBAAoB,CAC1C,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAED,0GAA0G;AAC1G,MAAM,OAAO,0BAA2B,SAAQ,cAAc;IACnD,OAAO,GAAG,qBAAqB,CAAC;IACtB,WAAW,GAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC;IACnB,OAAO,GAAG,iBAAiB,CAAC;IAC5B,WAAW,GAAG,iBAAiB,CAAC;IAEnD,IAAuB,cAAc;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAES,MAAM,CAAC,QAAiB,EAAE,OAAgB;QAClD,IAAI,CAAC,GAAG,CACN,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,uBAAuB,EACnD,aAAa,CAAC,OAAO,CAAC,CACvB,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,33 @@
1
+ import type { Service } from "homebridge";
2
+ import type { Vehicle } from "../../api/types.js";
3
+ import type { FeatureToggles } from "../../config.js";
4
+ import { type BodyValues } from "../../vehicle/mapping.js";
5
+ import { ServiceBinding, type BindingContext, type ServiceType } from "./common.js";
6
+ type BodyFeature = Extract<keyof FeatureToggles, "doors" | "windows" | "trunk" | "bonnet" | "sunroof" | "lock">;
7
+ interface ContactDefinition {
8
+ feature: BodyFeature;
9
+ displayName: string;
10
+ /** `true` means "contact not detected" (open, or unlocked for the lock sensor). */
11
+ isOpen: (values: BodyValues) => boolean | undefined;
12
+ }
13
+ export declare class ContactBinding extends ServiceBinding {
14
+ private readonly definition;
15
+ readonly feature: BodyFeature;
16
+ protected readonly serviceType: ServiceType;
17
+ protected readonly subtype: string;
18
+ protected readonly displayName: string;
19
+ constructor(ctx: BindingContext, definition: ContactDefinition);
20
+ protected get hasStatusFault(): boolean;
21
+ protected render(_service: Service, vehicle: Vehicle): void;
22
+ }
23
+ export declare function contactBindings(ctx: BindingContext): ContactBinding[];
24
+ /** Occupancy sensor "Home": parked within the configured radius around the home coordinates. */
25
+ export declare class PresenceBinding extends ServiceBinding {
26
+ readonly feature = "presence";
27
+ protected readonly serviceType: ServiceType;
28
+ protected readonly subtype = "home";
29
+ protected readonly displayName = "Home";
30
+ protected get hasStatusFault(): boolean;
31
+ protected render(_service: Service, vehicle: Vehicle): void;
32
+ }
33
+ export {};