homebridge-jlr-smartcar 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 ADDED
@@ -0,0 +1,118 @@
1
+ # homebridge-jlr-smartcar
2
+
3
+ [![npm](https://img.shields.io/npm/v/homebridge-jlr-smartcar)](https://www.npmjs.com/package/homebridge-jlr-smartcar)
4
+ [![CI](https://github.com/StefanPeetz/homebridge-jlr-incontrol-v2/actions/workflows/ci.yml/badge.svg)](https://github.com/StefanPeetz/homebridge-jlr-incontrol-v2/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ Homebridge plugin for **Jaguar Land Rover InControl** – powered by the [Smartcar API](https://smartcar.com).
8
+
9
+ > **Why Smartcar?**
10
+ > JLR deprecated their unofficial password-based API in 2024 and now requires OTP/Passkey for all logins, making direct automation impossible. Smartcar holds an official JLR partnership and provides a stable OAuth 2.0 API.
11
+
12
+ ## Supported features
13
+
14
+ | Feature | Status |
15
+ |---|---|
16
+ | Lock / Unlock | ✅ |
17
+ | Battery level (EV/PHEV) | ✅ |
18
+ | Charging status | ✅ |
19
+ | Low battery alert | ✅ |
20
+ | Fuel level | ✅ |
21
+ | Range (km) | ✅ |
22
+ | Odometer | ✅ |
23
+ | Location | ✅ |
24
+ | Climate / Preconditioning | ❌ Not available via Smartcar |
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install -g homebridge-jlr-smartcar
30
+ ```
31
+
32
+ Or install via the **Homebridge UI** by searching for `homebridge-jlr-smartcar`.
33
+
34
+ ## Setup
35
+
36
+ ### 1. Create a free Smartcar app
37
+
38
+ 1. Go to [dashboard.smartcar.com](https://dashboard.smartcar.com) and sign up
39
+ 2. Create a new application
40
+ 3. Add `http://localhost:52625/callback` to **Redirect URIs**
41
+ 4. Note your **Client ID** and **Client Secret**
42
+
43
+ ### 2. Configure Homebridge
44
+
45
+ Add to `config.json`:
46
+
47
+ ```json
48
+ {
49
+ "platforms": [
50
+ {
51
+ "platform": "JlrInControl",
52
+ "name": "JLR InControl",
53
+ "clientId": "YOUR_SMARTCAR_CLIENT_ID",
54
+ "clientSecret": "YOUR_SMARTCAR_CLIENT_SECRET",
55
+ "pollIntervalSeconds": 300
56
+ }
57
+ ]
58
+ }
59
+ ```
60
+
61
+ ### 3. Authorize your vehicle (one-time)
62
+
63
+ Restart Homebridge. The logs will show:
64
+
65
+ ```
66
+ [Smartcar] ACTION REQUIRED: Open this URL in your browser:
67
+ [Smartcar] http://localhost:52625/auth
68
+ ```
69
+
70
+ Open that URL → log in with your JLR account → authorize. Tokens are saved to `~/.homebridge/smartcar-tokens.json` and refresh automatically – no repeat login needed.
71
+
72
+ ### Configuration options
73
+
74
+ | Option | Type | Default | Description |
75
+ |---|---|---|---|
76
+ | `clientId` | string | **required** | Smartcar Client ID |
77
+ | `clientSecret` | string | **required** | Smartcar Client Secret |
78
+ | `redirectUri` | string | `http://localhost:52625/callback` | Must match your Smartcar app settings |
79
+ | `pin` | string | | Vehicle PIN (reserved for future use) |
80
+ | `pollIntervalSeconds` | integer | `300` | How often to poll vehicle state (min: 60) |
81
+
82
+ ## Smartcar pricing
83
+
84
+ | Tier | Calls/month | Price |
85
+ |---|---|---|
86
+ | Free | 500 | $0 |
87
+ | Starter | Unlimited | $2.99 / month |
88
+
89
+ At 300s poll interval: ~8,640 calls/month → Starter plan recommended for daily use.
90
+ At 1800s (30 min): ~1,440 calls/month → fits within free tier.
91
+
92
+ ## Building from source
93
+
94
+ ```bash
95
+ git clone https://github.com/StefanPeetz/homebridge-jlr-incontrol-v2.git
96
+ cd homebridge-jlr-incontrol-v2
97
+ npm install
98
+ npm run build
99
+ ```
100
+
101
+ ## Releasing a new version
102
+
103
+ 1. Bump `version` in `package.json`
104
+ 2. Commit and push to `main`
105
+ 3. GitHub Actions auto-creates the Git tag
106
+ 4. Create a **GitHub Release** from that tag
107
+ 5. The `publish.yml` workflow automatically publishes to npm
108
+
109
+ > **Prerequisite:** Add your npm token as a repository secret named `NPM_TOKEN`
110
+ > (GitHub repo → Settings → Secrets → Actions → New secret)
111
+
112
+ ## Changelog
113
+
114
+ See [CHANGELOG.md](CHANGELOG.md).
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1,49 @@
1
+ {
2
+ "pluginAlias": "JlrInControl",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "headerDisplay": "Jaguar Land Rover InControl via [Smartcar API](https://smartcar.com). Create a free app at [dashboard.smartcar.com](https://dashboard.smartcar.com), add `http://localhost:52625/callback` as redirect URI, then paste your credentials below.",
6
+ "schema": {
7
+ "type": "object",
8
+ "properties": {
9
+ "name": {
10
+ "title": "Platform name",
11
+ "type": "string",
12
+ "default": "JLR InControl"
13
+ },
14
+ "clientId": {
15
+ "title": "Smartcar Client ID",
16
+ "type": "string",
17
+ "required": true
18
+ },
19
+ "clientSecret": {
20
+ "title": "Smartcar Client Secret",
21
+ "type": "string",
22
+ "required": true
23
+ },
24
+ "redirectUri": {
25
+ "title": "OAuth Redirect URI",
26
+ "type": "string",
27
+ "default": "http://localhost:52625/callback",
28
+ "description": "Must exactly match what you entered in the Smartcar dashboard."
29
+ },
30
+ "pollIntervalSeconds": {
31
+ "title": "Poll interval (seconds)",
32
+ "type": "integer",
33
+ "default": 300,
34
+ "minimum": 60,
35
+ "description": "How often to fetch vehicle state. At 300 s ≈ 8,640 calls/month (Starter plan). At 1800 s ≈ 1,440/month (Free plan)."
36
+ },
37
+ "notifyWebhookUrl": {
38
+ "title": "Re-auth notification webhook URL (optional)",
39
+ "type": "string",
40
+ "description": "When the Smartcar token is about to expire (7 days before), a POST request with JSON body is sent here. Works with ntfy.sh, Pushover, Home Assistant webhooks, n8n, etc. Leave empty to disable."
41
+ },
42
+ "pin": {
43
+ "title": "Vehicle PIN (reserved)",
44
+ "type": "string",
45
+ "description": "Currently unused. Reserved for future JLR security PIN support."
46
+ }
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,4 @@
1
+ import { API } from 'homebridge';
2
+ declare const _default: (api: API) => void;
3
+ export = _default;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;yBAIvB,KAAK,GAAG;AAAlB,kBAEE"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ const settings_1 = require("./settings");
3
+ const platform_1 = require("./platform");
4
+ module.exports = (api) => {
5
+ api.registerPlatform(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, platform_1.JlrPlatform);
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,yCAAwD;AACxD,yCAAyC;AAEzC,iBAAS,CAAC,GAAQ,EAAE,EAAE;IACpB,GAAG,CAAC,gBAAgB,CAAC,sBAAW,EAAE,wBAAa,EAAE,sBAAW,CAAC,CAAC;AAChE,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge';
2
+ export declare class JlrPlatform implements DynamicPlatformPlugin {
3
+ readonly log: Logger;
4
+ readonly api: API;
5
+ readonly Service: typeof Service;
6
+ readonly Characteristic: typeof Characteristic;
7
+ private readonly accessories;
8
+ private client;
9
+ private readonly config;
10
+ private reauthSensorAccessory?;
11
+ private reauthSensorService?;
12
+ constructor(log: Logger, config: PlatformConfig, api: API);
13
+ configureAccessory(accessory: PlatformAccessory): void;
14
+ private setupReauthSensor;
15
+ private setReauthSensor;
16
+ private init;
17
+ private registerVehicles;
18
+ }
19
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACP,cAAc,EACf,MAAM,YAAY,CAAC;AAUpB,qBAAa,WAAY,YAAW,qBAAqB;aAarC,GAAG,EAAE,MAAM;aAEX,GAAG,EAAE,GAAG;IAd1B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAC;IACxC,SAAgB,cAAc,EAAE,OAAO,cAAc,CAAC;IAEtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAGtC,OAAO,CAAC,qBAAqB,CAAC,CAAoB;IAClD,OAAO,CAAC,mBAAmB,CAAC,CAA+C;gBAGzD,GAAG,EAAE,MAAM,EAC3B,MAAM,EAAE,cAAc,EACN,GAAG,EAAE,GAAG;IAS1B,kBAAkB,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAMtD,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,eAAe;YAmBT,IAAI;IAwClB,OAAO,CAAC,gBAAgB;CAuBzB"}
@@ -0,0 +1,145 @@
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.JlrPlatform = void 0;
37
+ const path = __importStar(require("path"));
38
+ const settings_1 = require("./settings");
39
+ const smartcar_client_1 = require("./smartcar-client");
40
+ const vehicle_accessory_1 = require("./vehicle-accessory");
41
+ // UUID suffix for the "Re-Auth Required" sensor
42
+ const REAUTH_SENSOR_SUFFIX = '-reauth-sensor';
43
+ class JlrPlatform {
44
+ constructor(log, config, api) {
45
+ this.log = log;
46
+ this.api = api;
47
+ this.accessories = [];
48
+ this.Service = api.hap.Service;
49
+ this.Characteristic = api.hap.Characteristic;
50
+ this.config = config;
51
+ this.api.on('didFinishLaunching', () => this.init());
52
+ }
53
+ configureAccessory(accessory) {
54
+ this.accessories.push(accessory);
55
+ }
56
+ // ─── Re-auth sensor ───────────────────────────────────────────────────────
57
+ setupReauthSensor() {
58
+ const uuid = this.api.hap.uuid.generate('jlr-smartcar' + REAUTH_SENSOR_SUFFIX);
59
+ const existing = this.accessories.find(a => a.UUID === uuid);
60
+ const accessory = existing
61
+ ?? new this.api.platformAccessory('JLR Re-Auth Required', uuid);
62
+ this.reauthSensorService =
63
+ accessory.getService(this.Service.OccupancySensor) ??
64
+ accessory.addService(this.Service.OccupancySensor, 'JLR Re-Auth Required');
65
+ // Start clear
66
+ this.reauthSensorService
67
+ .getCharacteristic(this.Characteristic.OccupancyDetected)
68
+ .updateValue(this.Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED);
69
+ if (!existing) {
70
+ this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
71
+ this.log.info('[JLR] Registered re-auth sensor accessory');
72
+ }
73
+ else {
74
+ this.api.updatePlatformAccessories([accessory]);
75
+ }
76
+ this.reauthSensorAccessory = accessory;
77
+ }
78
+ setReauthSensor(required) {
79
+ if (!this.reauthSensorService)
80
+ return;
81
+ const Char = this.Characteristic.OccupancyDetected;
82
+ this.reauthSensorService
83
+ .getCharacteristic(this.Characteristic.OccupancyDetected)
84
+ .updateValue(required ? Char.OCCUPANCY_DETECTED : Char.OCCUPANCY_NOT_DETECTED);
85
+ if (required) {
86
+ this.log.warn('[JLR] HomeKit "JLR Re-Auth Required" sensor is now ACTIVE. ' +
87
+ 'Open http://localhost:52625/auth to re-authorize.');
88
+ }
89
+ else {
90
+ this.log.info('[JLR] HomeKit "JLR Re-Auth Required" sensor cleared.');
91
+ }
92
+ }
93
+ // ─── Init ─────────────────────────────────────────────────────────────────
94
+ async init() {
95
+ if (!this.config.clientId || !this.config.clientSecret) {
96
+ this.log.error('[JLR] Missing clientId / clientSecret in config.json. ' +
97
+ 'Create a free app at https://dashboard.smartcar.com');
98
+ return;
99
+ }
100
+ // Register the re-auth sensor before doing anything network-related
101
+ this.setupReauthSensor();
102
+ const tokenPath = path.join(this.api.user.storagePath(), 'smartcar-tokens.json');
103
+ this.client = new smartcar_client_1.SmartcarClient({
104
+ clientId: this.config.clientId,
105
+ clientSecret: this.config.clientSecret,
106
+ redirectUri: this.config.redirectUri,
107
+ tokenStorePath: tokenPath,
108
+ notifyWebhookUrl: this.config.notifyWebhookUrl,
109
+ log: this.log,
110
+ });
111
+ // Wire up the callback so the sensor updates immediately
112
+ this.client.onReauthRequired = (required) => this.setReauthSensor(required);
113
+ try {
114
+ await this.client.ensureAuthenticated();
115
+ const vehicles = await this.client.getVehicles();
116
+ this.registerVehicles(vehicles);
117
+ }
118
+ catch (err) {
119
+ this.log.error('[JLR] Init failed: %s', err.message);
120
+ }
121
+ }
122
+ // ─── Vehicle registration ─────────────────────────────────────────────────
123
+ registerVehicles(vehicles) {
124
+ const pollInterval = (this.config.pollIntervalSeconds ?? 300) * 1000;
125
+ for (const vehicle of vehicles) {
126
+ const uuid = this.api.hap.uuid.generate(vehicle.vin);
127
+ const existing = this.accessories.find(a => a.UUID === uuid);
128
+ const accessory = existing ?? new this.api.platformAccessory(vehicle.nickname, uuid);
129
+ accessory.context.vehicle = vehicle;
130
+ accessory.context.pin = this.config.pin ?? '';
131
+ const acc = new vehicle_accessory_1.VehicleAccessory(this, accessory, this.client, this.log);
132
+ acc.startPolling(pollInterval);
133
+ if (!existing) {
134
+ this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
135
+ this.log.info('[JLR] Registered new vehicle: %s (%s)', vehicle.nickname, vehicle.vin);
136
+ }
137
+ else {
138
+ this.api.updatePlatformAccessories([accessory]);
139
+ this.log.info('[JLR] Restored vehicle: %s (%s)', vehicle.nickname, vehicle.vin);
140
+ }
141
+ }
142
+ }
143
+ }
144
+ exports.JlrPlatform = JlrPlatform;
145
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const PLATFORM_NAME = "JlrInControl";
2
+ export declare const PLUGIN_NAME = "homebridge-jlr-smartcar";
3
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAC5C,eAAO,MAAM,WAAW,4BAA8B,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PLUGIN_NAME = exports.PLATFORM_NAME = void 0;
4
+ exports.PLATFORM_NAME = 'JlrInControl';
5
+ exports.PLUGIN_NAME = 'homebridge-jlr-smartcar';
6
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":";;;AAAa,QAAA,aAAa,GAAG,cAAc,CAAC;AAC/B,QAAA,WAAW,GAAK,yBAAyB,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { Logger } from 'homebridge';
2
+ import { JlrVehicleSummary, JlrVehicleState } from './types';
3
+ export declare class SmartcarClient {
4
+ private http;
5
+ private tokens?;
6
+ private tokenPath;
7
+ private oauthServer?;
8
+ private readonly clientId;
9
+ private readonly clientSecret;
10
+ private readonly redirectUri;
11
+ private readonly notifyWebhookUrl?;
12
+ private readonly log;
13
+ onReauthRequired?: (required: boolean) => void;
14
+ constructor(params: {
15
+ clientId: string;
16
+ clientSecret: string;
17
+ redirectUri?: string;
18
+ tokenStorePath: string;
19
+ notifyWebhookUrl?: string;
20
+ log: Logger;
21
+ });
22
+ private saveTokens;
23
+ private loadTokens;
24
+ /**
25
+ * Returns true if the refresh_token is less than REAUTH_WARNING_THRESHOLD away
26
+ * from expiry (or already expired / missing).
27
+ */
28
+ needsReauth(): boolean;
29
+ /** Days until re-auth is required (negative = already overdue). */
30
+ daysUntilReauth(): number;
31
+ private triggerReauthNotification;
32
+ private isAccessTokenValid;
33
+ ensureAuthenticated(): Promise<void>;
34
+ private buildAuthUrl;
35
+ private startOAuthFlow;
36
+ private exchangeCode;
37
+ private refreshTokens;
38
+ private authHeaders;
39
+ private get;
40
+ private post;
41
+ getVehicles(): Promise<JlrVehicleSummary[]>;
42
+ getVehicleState(vehicleId: string, vin: string): Promise<JlrVehicleState>;
43
+ lock(vehicleId: string): Promise<void>;
44
+ unlock(vehicleId: string): Promise<void>;
45
+ }
46
+ //# sourceMappingURL=smartcar-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smartcar-client.d.ts","sourceRoot":"","sources":["../src/smartcar-client.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAkB,iBAAiB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAY7E,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,MAAM,CAAC,CAAiB;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAc;IAElC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAKtB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;gBAE1C,MAAM,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,GAAG,EAAE,MAAM,CAAC;KACb;IAYD,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,UAAU;IAWlB;;;OAGG;IACH,WAAW,IAAI,OAAO;IAQtB,mEAAmE;IACnE,eAAe,IAAI,MAAM;YAQX,yBAAyB;IA+BvC,OAAO,CAAC,kBAAkB;IAIpB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B1C,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,cAAc;YA8DR,YAAY;YAuBZ,aAAa;IAsC3B,OAAO,CAAC,WAAW;YAIL,GAAG;YAQH,IAAI;IAUZ,WAAW,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IA4B3C,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IA+DzE,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C"}
@@ -0,0 +1,369 @@
1
+ "use strict";
2
+ //
3
+ // Smartcar API client for JLR InControl Homebridge plugin
4
+ //
5
+ // OAuth 2.0 flow:
6
+ // 1. User visits http://homebridge-ip:52625/auth (one-time setup)
7
+ // 2. Redirect to Smartcar consent page
8
+ // 3. Smartcar redirects back to /callback with ?code=...
9
+ // 4. We exchange code for tokens and store them in tokenStore
10
+ // 5. Plugin uses refresh_token automatically from then on
11
+ //
12
+ // Re-auth notification:
13
+ // - 7 days before refresh_token expires: HomeKit "Auth Required" sensor fires
14
+ // + optional webhook POST to notifyWebhookUrl
15
+ // - On actual refresh failure: same, plus OAuth server starts automatically
16
+ //
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ var __importDefault = (this && this.__importDefault) || function (mod) {
51
+ return (mod && mod.__esModule) ? mod : { "default": mod };
52
+ };
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.SmartcarClient = void 0;
55
+ const axios_1 = __importDefault(require("axios"));
56
+ const http = __importStar(require("http"));
57
+ const fs = __importStar(require("fs"));
58
+ const SMARTCAR_AUTH_URL = 'https://connect.smartcar.com/oauth/authorize';
59
+ const SMARTCAR_TOKEN_URL = 'https://auth.smartcar.com/oauth/token';
60
+ const SMARTCAR_API_BASE = 'https://api.smartcar.com/v2.0';
61
+ const OAUTH_SERVER_PORT = 52625;
62
+ // Smartcar refresh tokens expire after ~60 days.
63
+ // We warn 7 days before expiry so there is plenty of time to re-auth.
64
+ const REFRESH_TOKEN_TTL_MS = 60 * 24 * 60 * 60 * 1000; // 60 days
65
+ const REAUTH_WARNING_THRESHOLD = 7 * 24 * 60 * 60 * 1000; // 7 days
66
+ class SmartcarClient {
67
+ constructor(params) {
68
+ this.clientId = params.clientId;
69
+ this.clientSecret = params.clientSecret;
70
+ this.redirectUri = params.redirectUri ?? `http://localhost:${OAUTH_SERVER_PORT}/callback`;
71
+ this.tokenPath = params.tokenStorePath;
72
+ this.notifyWebhookUrl = params.notifyWebhookUrl;
73
+ this.log = params.log;
74
+ this.http = axios_1.default.create({ timeout: 30000 });
75
+ }
76
+ // ─── Token persistence ────────────────────────────────────────────────────
77
+ saveTokens(tokens) {
78
+ fs.writeFileSync(this.tokenPath, JSON.stringify(tokens, null, 2), 'utf-8');
79
+ this.tokens = tokens;
80
+ this.log.info('[Smartcar] Tokens saved to %s', this.tokenPath);
81
+ }
82
+ loadTokens() {
83
+ try {
84
+ const raw = fs.readFileSync(this.tokenPath, 'utf-8');
85
+ return JSON.parse(raw);
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ }
91
+ // ─── Re-auth warning ──────────────────────────────────────────────────────
92
+ /**
93
+ * Returns true if the refresh_token is less than REAUTH_WARNING_THRESHOLD away
94
+ * from expiry (or already expired / missing).
95
+ */
96
+ needsReauth() {
97
+ if (!this.tokens)
98
+ return true;
99
+ const refreshTokenExpiresAt = (this.tokens.refresh_token_obtained_at ?? (this.tokens.expires_at - 7200 * 1000))
100
+ + REFRESH_TOKEN_TTL_MS;
101
+ return refreshTokenExpiresAt - Date.now() < REAUTH_WARNING_THRESHOLD;
102
+ }
103
+ /** Days until re-auth is required (negative = already overdue). */
104
+ daysUntilReauth() {
105
+ if (!this.tokens)
106
+ return 0;
107
+ const refreshTokenExpiresAt = (this.tokens.refresh_token_obtained_at ?? (this.tokens.expires_at - 7200 * 1000))
108
+ + REFRESH_TOKEN_TTL_MS;
109
+ return Math.round((refreshTokenExpiresAt - Date.now()) / (24 * 60 * 60 * 1000));
110
+ }
111
+ async triggerReauthNotification() {
112
+ const days = this.daysUntilReauth();
113
+ const authUrl = `http://localhost:${OAUTH_SERVER_PORT}/auth`;
114
+ this.log.warn('[Smartcar] ⚠️ Re-auth required in %d day(s). Open: %s', Math.max(0, days), authUrl);
115
+ // Trip HomeKit sensor
116
+ this.onReauthRequired?.(true);
117
+ // Optional webhook (e.g. ntfy.sh, Pushover, Home Assistant, n8n)
118
+ if (this.notifyWebhookUrl) {
119
+ try {
120
+ await this.http.post(this.notifyWebhookUrl, {
121
+ title: 'JLR InControl: Re-auth required',
122
+ message: `Smartcar token expires in ${Math.max(0, days)} day(s). Open ${authUrl} to re-authorize.`,
123
+ url: authUrl,
124
+ days,
125
+ });
126
+ this.log.info('[Smartcar] Webhook notification sent to %s', this.notifyWebhookUrl);
127
+ }
128
+ catch (err) {
129
+ this.log.warn('[Smartcar] Webhook notification failed: %s', err.message);
130
+ }
131
+ }
132
+ }
133
+ // ─── Session management ───────────────────────────────────────────────────
134
+ isAccessTokenValid() {
135
+ return !!(this.tokens && this.tokens.expires_at > Date.now() + 60000);
136
+ }
137
+ async ensureAuthenticated() {
138
+ if (!this.tokens) {
139
+ this.tokens = this.loadTokens() ?? undefined;
140
+ }
141
+ if (!this.tokens) {
142
+ this.log.warn('[Smartcar] No tokens found – starting OAuth setup server...');
143
+ await this.startOAuthFlow();
144
+ return;
145
+ }
146
+ // Proactive warning: 7 days before refresh_token dies
147
+ if (this.needsReauth()) {
148
+ await this.triggerReauthNotification();
149
+ }
150
+ else {
151
+ // Clear HomeKit sensor if everything is fine
152
+ this.onReauthRequired?.(false);
153
+ }
154
+ if (!this.isAccessTokenValid()) {
155
+ await this.refreshTokens();
156
+ }
157
+ }
158
+ // ─── OAuth 2.0 flow ───────────────────────────────────────────────────────
159
+ buildAuthUrl() {
160
+ const params = new URLSearchParams({
161
+ response_type: 'code',
162
+ client_id: this.clientId,
163
+ redirect_uri: this.redirectUri,
164
+ scope: 'required:read_vehicle_info read_vin read_charge read_battery read_fuel read_location read_odometer control_security',
165
+ mode: 'live',
166
+ });
167
+ return `${SMARTCAR_AUTH_URL}?${params.toString()}`;
168
+ }
169
+ startOAuthFlow() {
170
+ return new Promise((resolve, reject) => {
171
+ // If server already running (e.g. triggered twice), don't double-bind
172
+ if (this.oauthServer?.listening) {
173
+ return;
174
+ }
175
+ this.oauthServer = http.createServer((req, res) => {
176
+ const url = new URL(req.url ?? '/', `http://localhost:${OAUTH_SERVER_PORT}`);
177
+ if (url.pathname === '/auth') {
178
+ const authUrl = this.buildAuthUrl();
179
+ res.writeHead(302, { Location: authUrl });
180
+ res.end();
181
+ return;
182
+ }
183
+ if (url.pathname === '/callback') {
184
+ const code = url.searchParams.get('code');
185
+ const error = url.searchParams.get('error');
186
+ if (error || !code) {
187
+ res.writeHead(400, { 'Content-Type': 'text/html' });
188
+ res.end('<h2>❌ Auth failed: ' + (error ?? 'no code') + '</h2>');
189
+ reject(new Error('OAuth error: ' + error));
190
+ return;
191
+ }
192
+ this.exchangeCode(code)
193
+ .then(() => {
194
+ res.writeHead(200, { 'Content-Type': 'text/html' });
195
+ res.end('<h2>✅ Smartcar connected!</h2>' +
196
+ '<p>You can close this tab. Homebridge will continue automatically.</p>');
197
+ this.oauthServer?.close();
198
+ // Clear the HomeKit alert
199
+ this.onReauthRequired?.(false);
200
+ resolve();
201
+ })
202
+ .catch(err => {
203
+ res.writeHead(500, { 'Content-Type': 'text/html' });
204
+ res.end('<h2>❌ Token exchange failed</h2><pre>' + err.message + '</pre>');
205
+ reject(err);
206
+ });
207
+ return;
208
+ }
209
+ res.writeHead(404);
210
+ res.end();
211
+ });
212
+ this.oauthServer.listen(OAUTH_SERVER_PORT, () => {
213
+ const authUrl = `http://localhost:${OAUTH_SERVER_PORT}/auth`;
214
+ this.log.warn('[Smartcar] ════════════════════════════════════════════════════');
215
+ this.log.warn('[Smartcar] ACTION REQUIRED: Open this URL in your browser:');
216
+ this.log.warn('[Smartcar] %s', authUrl);
217
+ this.log.warn('[Smartcar] ════════════════════════════════════════════════════');
218
+ });
219
+ });
220
+ }
221
+ async exchangeCode(code) {
222
+ this.log.info('[Smartcar] Exchanging auth code for tokens...');
223
+ const resp = await this.http.post(SMARTCAR_TOKEN_URL, new URLSearchParams({
224
+ grant_type: 'authorization_code',
225
+ code,
226
+ redirect_uri: this.redirectUri,
227
+ }), {
228
+ auth: { username: this.clientId, password: this.clientSecret },
229
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
230
+ });
231
+ this.saveTokens({
232
+ access_token: resp.data.access_token,
233
+ refresh_token: resp.data.refresh_token,
234
+ expires_at: Date.now() + (resp.data.expires_in ?? 7200) * 1000,
235
+ refresh_token_obtained_at: Date.now(),
236
+ });
237
+ }
238
+ async refreshTokens() {
239
+ if (!this.tokens?.refresh_token)
240
+ throw new Error('No refresh token available');
241
+ this.log.info('[Smartcar] Refreshing access token...');
242
+ try {
243
+ const resp = await this.http.post(SMARTCAR_TOKEN_URL, new URLSearchParams({
244
+ grant_type: 'refresh_token',
245
+ refresh_token: this.tokens.refresh_token,
246
+ }), {
247
+ auth: { username: this.clientId, password: this.clientSecret },
248
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
249
+ });
250
+ this.saveTokens({
251
+ access_token: resp.data.access_token,
252
+ refresh_token: resp.data.refresh_token ?? this.tokens.refresh_token,
253
+ expires_at: Date.now() + (resp.data.expires_in ?? 7200) * 1000,
254
+ // Only reset the clock if Smartcar issued a new refresh_token
255
+ refresh_token_obtained_at: resp.data.refresh_token
256
+ ? Date.now()
257
+ : this.tokens.refresh_token_obtained_at,
258
+ });
259
+ }
260
+ catch (err) {
261
+ this.log.error('[Smartcar] Token refresh failed – starting re-auth...');
262
+ this.tokens = undefined;
263
+ fs.rmSync(this.tokenPath, { force: true });
264
+ await this.triggerReauthNotification();
265
+ await this.startOAuthFlow();
266
+ throw err;
267
+ }
268
+ }
269
+ // ─── API helpers ──────────────────────────────────────────────────────────
270
+ authHeaders() {
271
+ return { Authorization: `Bearer ${this.tokens.access_token}` };
272
+ }
273
+ async get(path) {
274
+ await this.ensureAuthenticated();
275
+ const resp = await this.http.get(`${SMARTCAR_API_BASE}${path}`, {
276
+ headers: this.authHeaders(),
277
+ });
278
+ return resp.data;
279
+ }
280
+ async post(path, body) {
281
+ await this.ensureAuthenticated();
282
+ const resp = await this.http.post(`${SMARTCAR_API_BASE}${path}`, body, {
283
+ headers: { ...this.authHeaders(), 'Content-Type': 'application/json' },
284
+ });
285
+ return resp.data;
286
+ }
287
+ // ─── Vehicle queries ──────────────────────────────────────────────────────
288
+ async getVehicles() {
289
+ const data = await this.get('/vehicles');
290
+ const ids = data.vehicles ?? [];
291
+ const summaries = await Promise.all(ids.map(async (id) => {
292
+ try {
293
+ const info = await this.get(`/vehicles/${id}`);
294
+ const vinData = await this.get(`/vehicles/${id}/vin`);
295
+ return {
296
+ id,
297
+ vin: vinData.vin,
298
+ nickname: `${info.year} ${info.make} ${info.model}`,
299
+ model: info.model,
300
+ };
301
+ }
302
+ catch {
303
+ this.log.warn('[Smartcar] Could not load info for vehicle %s', id);
304
+ return { id, vin: id, nickname: id };
305
+ }
306
+ }));
307
+ this.log.info('[Smartcar] Found %d vehicle(s)', summaries.length);
308
+ return summaries;
309
+ }
310
+ async getVehicleState(vehicleId, vin) {
311
+ const [chargeRes, locationRes, odometerRes] = await Promise.allSettled([
312
+ this.get(`/vehicles/${vehicleId}/charge`),
313
+ this.get(`/vehicles/${vehicleId}/location`),
314
+ this.get(`/vehicles/${vehicleId}/odometer`),
315
+ ]);
316
+ let batteryLevel;
317
+ let charging;
318
+ let fuelLevelPercent;
319
+ let rangeKm;
320
+ if (chargeRes.status === 'fulfilled') {
321
+ const c = chargeRes.value;
322
+ charging = c.state === 'CHARGING';
323
+ if (c.battery) {
324
+ batteryLevel = Math.round(c.battery.percentRemaining * 100);
325
+ const rv = c.battery.range.value;
326
+ rangeKm = c.battery.range.unit === 'miles' ? Math.round(rv * 1.60934) : Math.round(rv);
327
+ }
328
+ if (c.fuel)
329
+ fuelLevelPercent = Math.round(c.fuel.percentRemaining * 100);
330
+ }
331
+ let latitude;
332
+ let longitude;
333
+ let isMoving;
334
+ if (locationRes.status === 'fulfilled') {
335
+ latitude = locationRes.value.latitude;
336
+ longitude = locationRes.value.longitude;
337
+ isMoving = (locationRes.value.speed?.value ?? 0) > 2;
338
+ }
339
+ let odometerKm;
340
+ if (odometerRes.status === 'fulfilled') {
341
+ const d = odometerRes.value.distance;
342
+ odometerKm = d.unit === 'miles' ? Math.round(d.value * 1.60934) : Math.round(d.value);
343
+ }
344
+ let isLocked = false;
345
+ try {
346
+ const sec = await this.get(`/vehicles/${vehicleId}/security`);
347
+ isLocked = sec.isLocked;
348
+ }
349
+ catch {
350
+ this.log.debug('[Smartcar] security endpoint not available for this vehicle');
351
+ }
352
+ return {
353
+ vin, isLocked, batteryLevel, charging,
354
+ lowBattery: batteryLevel !== undefined ? batteryLevel < 20 : undefined,
355
+ fuelLevelPercent, rangeKm, odometerKm,
356
+ latitude, longitude, isMoving,
357
+ lastUpdated: new Date().toISOString(),
358
+ };
359
+ }
360
+ // ─── Commands ─────────────────────────────────────────────────────────────
361
+ async lock(vehicleId) {
362
+ await this.post(`/vehicles/${vehicleId}/security`, { action: 'LOCK' });
363
+ }
364
+ async unlock(vehicleId) {
365
+ await this.post(`/vehicles/${vehicleId}/security`, { action: 'UNLOCK' });
366
+ }
367
+ }
368
+ exports.SmartcarClient = SmartcarClient;
369
+ //# sourceMappingURL=smartcar-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smartcar-client.js","sourceRoot":"","sources":["../src/smartcar-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ export interface SmartcarTokens {
2
+ access_token: string;
3
+ refresh_token: string;
4
+ expires_at: number;
5
+ refresh_token_obtained_at?: number;
6
+ }
7
+ export interface PluginConfig {
8
+ platform: string;
9
+ name: string;
10
+ clientId: string;
11
+ clientSecret: string;
12
+ redirectUri?: string;
13
+ pin?: string;
14
+ pollIntervalSeconds?: number;
15
+ notifyWebhookUrl?: string;
16
+ }
17
+ export interface JlrVehicleSummary {
18
+ id: string;
19
+ vin: string;
20
+ nickname: string;
21
+ model?: string;
22
+ }
23
+ export interface JlrVehicleState {
24
+ vin: string;
25
+ isLocked: boolean;
26
+ batteryLevel?: number;
27
+ charging?: boolean;
28
+ lowBattery?: boolean;
29
+ fuelLevelPercent?: number;
30
+ rangeKm?: number;
31
+ odometerKm?: number;
32
+ latitude?: number;
33
+ longitude?: number;
34
+ isMoving?: boolean;
35
+ lastUpdated: string;
36
+ }
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import { PlatformAccessory, Logger } from 'homebridge';
2
+ import { JlrPlatform } from './platform';
3
+ import { SmartcarClient } from './smartcar-client';
4
+ export declare class VehicleAccessory {
5
+ private readonly platform;
6
+ private readonly accessory;
7
+ private readonly client;
8
+ private readonly log;
9
+ private lockService;
10
+ private batteryService;
11
+ private state;
12
+ private pollTimer?;
13
+ private get vehicle();
14
+ constructor(platform: JlrPlatform, accessory: PlatformAccessory, client: SmartcarClient, log: Logger);
15
+ private setupServices;
16
+ private getLockCurrentState;
17
+ private getLockTargetState;
18
+ private setLockTargetState;
19
+ startPolling(intervalMs: number): void;
20
+ private poll;
21
+ private pushStateToHomeKit;
22
+ }
23
+ //# sourceMappingURL=vehicle-accessory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vehicle-accessory.d.ts","sourceRoot":"","sources":["../src/vehicle-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EAEjB,MAAM,EACP,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD,qBAAa,gBAAgB;IAYzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAdtB,OAAO,CAAC,WAAW,CAAW;IAC9B,OAAO,CAAC,cAAc,CAAW;IAEjC,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,SAAS,CAAC,CAAiC;IAEnD,OAAO,KAAK,OAAO,GAElB;gBAGkB,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,iBAAiB,EAC5B,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,MAAM;IAO9B,OAAO,CAAC,aAAa;IA+CrB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,kBAAkB;YAMZ,kBAAkB;IAoBhC,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;YAKxB,IAAI;IAgBlB,OAAO,CAAC,kBAAkB;CA2B3B"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VehicleAccessory = void 0;
4
+ class VehicleAccessory {
5
+ get vehicle() {
6
+ return this.accessory.context.vehicle;
7
+ }
8
+ constructor(platform, accessory, client, log) {
9
+ this.platform = platform;
10
+ this.accessory = accessory;
11
+ this.client = client;
12
+ this.log = log;
13
+ this.state = null;
14
+ this.setupServices();
15
+ }
16
+ // ─── HomeKit services ─────────────────────────────────────────────────────
17
+ setupServices() {
18
+ const { Service, Characteristic } = this.platform;
19
+ // Accessory info
20
+ this.accessory
21
+ .getService(Service.AccessoryInformation)
22
+ .setCharacteristic(Characteristic.Manufacturer, 'Jaguar Land Rover')
23
+ .setCharacteristic(Characteristic.Model, this.vehicle.model ?? 'JLR Vehicle')
24
+ .setCharacteristic(Characteristic.SerialNumber, this.vehicle.vin);
25
+ // Lock mechanism
26
+ this.lockService =
27
+ this.accessory.getService(Service.LockMechanism) ??
28
+ this.accessory.addService(Service.LockMechanism, 'Door Lock');
29
+ this.lockService
30
+ .getCharacteristic(Characteristic.LockCurrentState)
31
+ .onGet(() => this.getLockCurrentState());
32
+ this.lockService
33
+ .getCharacteristic(Characteristic.LockTargetState)
34
+ .onGet(() => this.getLockTargetState())
35
+ .onSet((value) => this.setLockTargetState(value));
36
+ // Battery (EV)
37
+ this.batteryService =
38
+ this.accessory.getService(Service.Battery) ??
39
+ this.accessory.addService(Service.Battery, 'Battery');
40
+ this.batteryService
41
+ .getCharacteristic(Characteristic.BatteryLevel)
42
+ .onGet(() => this.state?.batteryLevel ?? 0);
43
+ this.batteryService
44
+ .getCharacteristic(Characteristic.ChargingState)
45
+ .onGet(() => {
46
+ if (this.state?.charging)
47
+ return 1; // CHARGING
48
+ return 0; // NOT_CHARGING
49
+ });
50
+ this.batteryService
51
+ .getCharacteristic(Characteristic.StatusLowBattery)
52
+ .onGet(() => (this.state?.lowBattery ? 1 : 0));
53
+ }
54
+ // ─── Lock handlers ────────────────────────────────────────────────────────
55
+ getLockCurrentState() {
56
+ const { Characteristic } = this.platform;
57
+ if (this.state?.isLocked)
58
+ return Characteristic.LockCurrentState.SECURED;
59
+ return Characteristic.LockCurrentState.UNSECURED;
60
+ }
61
+ getLockTargetState() {
62
+ const { Characteristic } = this.platform;
63
+ if (this.state?.isLocked)
64
+ return Characteristic.LockTargetState.SECURED;
65
+ return Characteristic.LockTargetState.UNSECURED;
66
+ }
67
+ async setLockTargetState(value) {
68
+ const { Characteristic } = this.platform;
69
+ try {
70
+ if (value === Characteristic.LockTargetState.SECURED) {
71
+ this.log.info('[%s] Locking...', this.vehicle.nickname);
72
+ await this.client.lock(this.vehicle.id);
73
+ }
74
+ else {
75
+ this.log.info('[%s] Unlocking...', this.vehicle.nickname);
76
+ await this.client.unlock(this.vehicle.id);
77
+ }
78
+ // optimistic update
79
+ if (this.state)
80
+ this.state.isLocked = value === Characteristic.LockTargetState.SECURED;
81
+ this.pushStateToHomeKit();
82
+ }
83
+ catch (err) {
84
+ this.log.error('[%s] Lock command failed: %s', this.vehicle.nickname, err.message);
85
+ }
86
+ }
87
+ // ─── Polling ──────────────────────────────────────────────────────────────
88
+ startPolling(intervalMs) {
89
+ this.poll(); // immediate first poll
90
+ this.pollTimer = setInterval(() => this.poll(), intervalMs);
91
+ }
92
+ async poll() {
93
+ try {
94
+ this.state = await this.client.getVehicleState(this.vehicle.id, this.vehicle.vin);
95
+ this.log.debug('[%s] State: locked=%s battery=%s%% charging=%s', this.vehicle.nickname, this.state.isLocked, this.state.batteryLevel ?? 'N/A', this.state.charging);
96
+ this.pushStateToHomeKit();
97
+ }
98
+ catch (err) {
99
+ this.log.warn('[%s] Poll failed: %s', this.vehicle.nickname, err.message);
100
+ }
101
+ }
102
+ pushStateToHomeKit() {
103
+ if (!this.state)
104
+ return;
105
+ const { Characteristic } = this.platform;
106
+ this.lockService
107
+ .updateCharacteristic(Characteristic.LockCurrentState, this.state.isLocked
108
+ ? Characteristic.LockCurrentState.SECURED
109
+ : Characteristic.LockCurrentState.UNSECURED);
110
+ if (this.state.batteryLevel !== undefined) {
111
+ this.batteryService.updateCharacteristic(Characteristic.BatteryLevel, this.state.batteryLevel);
112
+ this.batteryService.updateCharacteristic(Characteristic.StatusLowBattery, this.state.lowBattery ? 1 : 0);
113
+ }
114
+ if (this.state.charging !== undefined) {
115
+ this.batteryService.updateCharacteristic(Characteristic.ChargingState, this.state.charging ? 1 : 0);
116
+ }
117
+ }
118
+ }
119
+ exports.VehicleAccessory = VehicleAccessory;
120
+ //# sourceMappingURL=vehicle-accessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vehicle-accessory.js","sourceRoot":"","sources":["../src/vehicle-accessory.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "homebridge-jlr-smartcar",
3
+ "version": "1.0.0",
4
+ "description": "Homebridge plugin for Jaguar Land Rover InControl via Smartcar API",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "watch": "tsc --watch",
10
+ "lint": "eslint src --ext .ts",
11
+ "prepublishOnly": "npm run build"
12
+ },
13
+ "keywords": [
14
+ "homebridge-plugin",
15
+ "jaguar",
16
+ "landrover",
17
+ "jlr",
18
+ "incontrol",
19
+ "smartcar"
20
+ ],
21
+ "author": "StefanPeetz",
22
+ "license": "MIT",
23
+ "homepage": "https://github.com/StefanPeetz/homebridge-jlr-smartcar#readme",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/StefanPeetz/homebridge-jlr-smartcar.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/StefanPeetz/homebridge-jlr-smartcar/issues"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0",
33
+ "homebridge": ">=1.6.0"
34
+ },
35
+ "dependencies": {
36
+ "axios": "^1.7.0"
37
+ },
38
+ "devDependencies": {
39
+ "@homebridge/plugin-ui-utils": "^2.0.0",
40
+ "@types/node": "^20.0.0",
41
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
42
+ "@typescript-eslint/parser": "^7.0.0",
43
+ "eslint": "^8.57.0",
44
+ "homebridge": "^1.8.0",
45
+ "typescript": "^5.4.0"
46
+ },
47
+ "peerDependencies": {
48
+ "homebridge": "^1.6.0"
49
+ }
50
+ }