@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.
- package/CHANGELOG.md +12 -0
- package/LICENSE +29 -0
- package/README.md +184 -2
- package/config.schema.json +275 -0
- package/dist/accessory/services/battery.d.ts +20 -0
- package/dist/accessory/services/battery.js +36 -0
- package/dist/accessory/services/battery.js.map +1 -0
- package/dist/accessory/services/body.d.ts +33 -0
- package/dist/accessory/services/body.js +68 -0
- package/dist/accessory/services/body.js.map +1 -0
- package/dist/accessory/services/charging.d.ts +26 -0
- package/dist/accessory/services/charging.js +80 -0
- package/dist/accessory/services/charging.js.map +1 -0
- package/dist/accessory/services/climate.d.ts +48 -0
- package/dist/accessory/services/climate.js +206 -0
- package/dist/accessory/services/climate.js.map +1 -0
- package/dist/accessory/services/common.d.ts +56 -0
- package/dist/accessory/services/common.js +104 -0
- package/dist/accessory/services/common.js.map +1 -0
- package/dist/accessory/vehicleAccessory.d.ts +13 -0
- package/dist/accessory/vehicleAccessory.js +60 -0
- package/dist/accessory/vehicleAccessory.js.map +1 -0
- package/dist/api/client.d.ts +41 -0
- package/dist/api/client.js +170 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/errors.d.ts +31 -0
- package/dist/api/errors.js +52 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/rateBudget.d.ts +53 -0
- package/dist/api/rateBudget.js +113 -0
- package/dist/api/rateBudget.js.map +1 -0
- package/dist/api/types.d.ts +174 -0
- package/dist/api/types.js +22 -0
- package/dist/api/types.js.map +1 -0
- package/dist/config.d.ts +70 -0
- package/dist/config.js +151 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +12 -0
- package/dist/platform.js +123 -0
- package/dist/platform.js.map +1 -0
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +13 -0
- package/dist/settings.js.map +1 -0
- package/dist/util/geo.d.ts +7 -0
- package/dist/util/geo.js +16 -0
- package/dist/util/geo.js.map +1 -0
- package/dist/util/logger.d.ts +12 -0
- package/dist/util/logger.js +25 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/redact.d.ts +4 -0
- package/dist/util/redact.js +15 -0
- package/dist/util/redact.js.map +1 -0
- package/dist/vehicle/mapping.d.ts +39 -0
- package/dist/vehicle/mapping.js +160 -0
- package/dist/vehicle/mapping.js.map +1 -0
- package/dist/vehicle/snapshot.d.ts +25 -0
- package/dist/vehicle/snapshot.js +93 -0
- package/dist/vehicle/snapshot.js.map +1 -0
- package/dist/vehicle/vehicleController.d.ts +58 -0
- package/dist/vehicle/vehicleController.js +248 -0
- package/dist/vehicle/vehicleController.js.map +1 -0
- 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
|
-
#
|
|
1
|
+
# Homebridge Škoda
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/thecloudseeker/homebridge-skoda/actions/workflows/ci.yml)
|
|
4
|
+
[](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 {};
|