homebridge-chargecore 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +136 -2
- package/config.schema.json +107 -0
- package/dist/charger-accessory.d.ts +21 -0
- package/dist/charger-accessory.d.ts.map +1 -0
- package/dist/charger-accessory.js +98 -0
- package/dist/charger-accessory.js.map +1 -0
- package/dist/custom-characteristics.d.ts +10 -0
- package/dist/custom-characteristics.d.ts.map +1 -0
- package/dist/custom-characteristics.js +29 -0
- package/dist/custom-characteristics.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +19 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +79 -0
- package/dist/platform.js.map +1 -0
- package/dist/settings.d.ts +4 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +4 -0
- package/dist/settings.js.map +1 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/wss-server.d.ts +35 -0
- package/dist/wss-server.d.ts.map +1 -0
- package/dist/wss-server.js +441 -0
- package/dist/wss-server.js.map +1 -0
- package/package.json +51 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,137 @@
|
|
|
1
|
-
#
|
|
1
|
+
# homebridge-chargecore
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`homebridge-chargecore` is an OCPP 1.6 JSON central system for exposing Chargecore EV wall chargers as HomeKit accessories.
|
|
4
|
+
|
|
5
|
+
## Supported Devices
|
|
6
|
+
The following Chargecore devices have been tested with this plugin
|
|
7
|
+
- AC003PT27
|
|
8
|
+
|
|
9
|
+
## Charger configuration
|
|
10
|
+
|
|
11
|
+
<table>
|
|
12
|
+
<tr>
|
|
13
|
+
<td width="55%" valign="top">
|
|
14
|
+
<img src="images/chargecore-url.jpg" alt="Chargecore WebSocket URL configuration" width="100%">
|
|
15
|
+
</td>
|
|
16
|
+
<td width="45%" valign="top">
|
|
17
|
+
<ol>
|
|
18
|
+
<li>Log in through the cog icon (default password: <code>123456</code>).</li>
|
|
19
|
+
<li>Go to <strong>Settings → URL</strong>.</li>
|
|
20
|
+
<li>Replace the URL with <code>ws://HOME_BRIDGE_HOST:9443/chargecore</code>.</li>
|
|
21
|
+
<li>Press <strong>OK</strong> and wait for the success message to disappear. Do not press OK again.</li>
|
|
22
|
+
</ol>
|
|
23
|
+
</td>
|
|
24
|
+
</tr>
|
|
25
|
+
</table>
|
|
26
|
+
|
|
27
|
+
## HomeKit representation
|
|
28
|
+
|
|
29
|
+
HomeKit does not define an EV charger service, so this plugin uses the closest native model:
|
|
30
|
+
|
|
31
|
+
- An **Outlet** provides on/off control in Apple Home and through Siri.
|
|
32
|
+
- `OutletInUse` is true while the charger reports `charging` or draws more than 50 W.
|
|
33
|
+
- Standard `StatusActive` and `StatusFault` characteristics reflect availability and faults.
|
|
34
|
+
- An optional **Contact Sensor** indicates that a vehicle is plugged in.
|
|
35
|
+
- Eve-compatible custom characteristics expose live power (W), cumulative energy (kWh), voltage (V), current (A), and charger state. Apple Home generally does not render custom energy values; apps such as Eve or Home+ do.
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
- Homebridge 2.x
|
|
40
|
+
- Node.js 22 or 24
|
|
41
|
+
- An OCPP 1.6 JSON charger acting as a WebSocket client
|
|
42
|
+
|
|
43
|
+
## Install and build
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install
|
|
47
|
+
npm run build
|
|
48
|
+
npm link
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
Add the platform through Homebridge UI, or add this to `config.json`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"platform": "ChargeCore",
|
|
58
|
+
"name": "ChargeCore",
|
|
59
|
+
"host": "0.0.0.0",
|
|
60
|
+
"port": 9443,
|
|
61
|
+
"offlineAfterSeconds": 90,
|
|
62
|
+
"allowedChargerIds": ["garage-wallbox"],
|
|
63
|
+
"logWebSocketTraffic": false,
|
|
64
|
+
"exposeVehicleSensor": true
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Without a `tls` section, the plugin listens using plain `ws://`. The endpoint does not require authentication, so expose its port only on a trusted, isolated network. Use `allowedChargerIds` to reject unexpected identifiers, but do not treat an identifier as a credential.
|
|
69
|
+
|
|
70
|
+
To enable `wss://`, add certificate paths that are readable inside the Homebridge container or process:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
"tls": {
|
|
74
|
+
"certPath": "/var/lib/homebridge/chargecore/fullchain.pem",
|
|
75
|
+
"keyPath": "/var/lib/homebridge/chargecore/privkey.pem"
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Restart Homebridge after changing TLS files.
|
|
80
|
+
|
|
81
|
+
For mutual TLS, set `tls.clientCaPath` to a PEM CA file. The plugin will then require a client certificate signed by that CA.
|
|
82
|
+
|
|
83
|
+
## Docker Compose
|
|
84
|
+
|
|
85
|
+
The included [`docker-compose.test.yml`](./docker-compose.test.yml) is a minimal override for an existing Homebridge Compose deployment. It only mounts this repository at Homebridge's local plugin path.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd /path/to/homebridge-chargecore
|
|
89
|
+
npm ci
|
|
90
|
+
npm run build
|
|
91
|
+
export CHARGECORE_PLUGIN_PATH="$PWD"
|
|
92
|
+
|
|
93
|
+
# Merge the plugin override with your existing Homebridge Compose definition.
|
|
94
|
+
docker compose \
|
|
95
|
+
-f /path/to/existing-homebridge/docker-compose.yml \
|
|
96
|
+
-f /path/to/homebridge-chargecore/docker-compose.test.yml \
|
|
97
|
+
up -d
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If your existing service is not named `homebridge`, change the service name in the override to match it. The host build supplies `dist/` and `node_modules/`, including the `ws` runtime dependency. If TLS is enabled, certificate paths must already be available inside the container.
|
|
101
|
+
|
|
102
|
+
## Charger connection
|
|
103
|
+
|
|
104
|
+
Connect to:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
ws://HOME_BRIDGE_HOST:9443/chargecore/CHARGEBOX_ID
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The WebSocket base path is fixed to `/chargecore` and is not configurable. Configure `ws://HOME_BRIDGE_HOST:9443/chargecore` without a trailing slash; the charger must append its ChargeBox ID. Connections to the base path without an ID are rejected.
|
|
111
|
+
|
|
112
|
+
Compression is disabled and the default maximum message size is 65,536 bytes. Messages must be UTF-8 JSON text, not binary frames.
|
|
113
|
+
|
|
114
|
+
## OCPP 1.6 support
|
|
115
|
+
|
|
116
|
+
The server accepts OCPP 1.6 JSON array frames only. It handles `BootNotification`, `Heartbeat`, `StatusNotification`, `MeterValues`, `Authorize`, `StartTransaction`, `StopTransaction`, `DataTransfer`, `DiagnosticsStatusNotification`, and `FirmwareStatusNotification`.
|
|
117
|
+
|
|
118
|
+
`StatusNotification` drives availability, vehicle connection, charging state, and faults. `MeterValues` maps `Power.Active.Import`, `Energy.Active.Import.Register`, `Current.Import`, and `Voltage` into the HomeKit accessory. HomeKit on/off requests are sent as OCPP `ChangeAvailability` calls.
|
|
119
|
+
|
|
120
|
+
Unsupported charger calls receive an OCPP `NotSupported` CALLERROR.
|
|
121
|
+
|
|
122
|
+
## Operational notes
|
|
123
|
+
|
|
124
|
+
- Set `offlineAfterSeconds` to at least three times the telemetry interval.
|
|
125
|
+
- Enable `logWebSocketTraffic` for full OCPP connection, message, command, acknowledgement, disconnect, and heartbeat diagnostics. Message payloads may contain sensitive charger data.
|
|
126
|
+
- Put charger IDs in `allowedChargerIds` whenever the set of devices is known.
|
|
127
|
+
- Keep the WebSocket port on a trusted LAN or firewall it to charger addresses. The endpoint does not require authentication; TLS is optional.
|
|
128
|
+
- A newer connection for the same charger ID replaces the older one.
|
|
129
|
+
- Accessories are discovered on the first accepted OCPP `BootNotification` and retained by Homebridge across restarts.
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm test
|
|
135
|
+
npm run lint
|
|
136
|
+
npm run build
|
|
137
|
+
```
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pluginAlias": "ChargeCore",
|
|
3
|
+
"pluginType": "platform",
|
|
4
|
+
"singular": true,
|
|
5
|
+
"headerDisplay": "ChargeCore accepts OCPP 1.6 JSON charger connections and exposes each charger as a HomeKit outlet. Restrict network access to the listening port.",
|
|
6
|
+
"footerDisplay": "Configure the charger with `ws://HOME_BRIDGE_HOST:PORT/chargecore` and no trailing slash. It appends `/CHARGEBOX_ID` when connecting. The base path is fixed and cannot be changed. Use `wss://` when TLS is configured.",
|
|
7
|
+
"schema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"name": {
|
|
11
|
+
"title": "Platform Name",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"default": "ChargeCore"
|
|
14
|
+
},
|
|
15
|
+
"host": {
|
|
16
|
+
"title": "Listen Address",
|
|
17
|
+
"type": "string",
|
|
18
|
+
"default": "0.0.0.0",
|
|
19
|
+
"description": "Use 0.0.0.0 for all interfaces or a specific local address."
|
|
20
|
+
},
|
|
21
|
+
"port": {
|
|
22
|
+
"title": "WebSocket Port",
|
|
23
|
+
"type": "integer",
|
|
24
|
+
"default": 9443,
|
|
25
|
+
"minimum": 1,
|
|
26
|
+
"maximum": 65535,
|
|
27
|
+
"x-schema-form": {
|
|
28
|
+
"type": "number"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"tls": {
|
|
32
|
+
"title": "TLS (optional)",
|
|
33
|
+
"type": "object",
|
|
34
|
+
"description": "Leave unset to use plain ws://. When enabled, both certificate and private-key paths are required.",
|
|
35
|
+
"properties": {
|
|
36
|
+
"certPath": {
|
|
37
|
+
"title": "Certificate Path",
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"keyPath": {
|
|
41
|
+
"title": "Private Key Path",
|
|
42
|
+
"type": "string"
|
|
43
|
+
},
|
|
44
|
+
"clientCaPath": {
|
|
45
|
+
"title": "Client CA Path (optional mTLS)",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "When set, clients must present a certificate signed by this CA."
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"offlineAfterSeconds": {
|
|
52
|
+
"title": "Offline Timeout",
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"default": 90,
|
|
55
|
+
"minimum": 5,
|
|
56
|
+
"maximum": 86400,
|
|
57
|
+
"x-schema-form": {
|
|
58
|
+
"type": "number"
|
|
59
|
+
},
|
|
60
|
+
"description": "Mark a charger unavailable after this many seconds without telemetry. Use at least three telemetry intervals."
|
|
61
|
+
},
|
|
62
|
+
"maxPayloadBytes": {
|
|
63
|
+
"title": "Maximum Message Size",
|
|
64
|
+
"type": "integer",
|
|
65
|
+
"default": 65536,
|
|
66
|
+
"minimum": 1024,
|
|
67
|
+
"maximum": 1048576,
|
|
68
|
+
"x-schema-form": {
|
|
69
|
+
"type": "number"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"logWebSocketTraffic": {
|
|
73
|
+
"title": "Log All WebSocket Traffic",
|
|
74
|
+
"type": "boolean",
|
|
75
|
+
"default": false,
|
|
76
|
+
"description": "Log OCPP connection attempts, rejections, raw messages, commands, acknowledgements, disconnects, and heartbeat frames. Payloads may contain sensitive charger data."
|
|
77
|
+
},
|
|
78
|
+
"commandAckTimeoutSeconds": {
|
|
79
|
+
"title": "Command Acknowledgement Timeout",
|
|
80
|
+
"type": "integer",
|
|
81
|
+
"default": 0,
|
|
82
|
+
"minimum": 0,
|
|
83
|
+
"maximum": 60,
|
|
84
|
+
"x-schema-form": {
|
|
85
|
+
"type": "number"
|
|
86
|
+
},
|
|
87
|
+
"description": "0 sends OCPP controls without waiting. A positive value waits for the OCPP CALLRESULT or CALLERROR."
|
|
88
|
+
},
|
|
89
|
+
"allowedChargerIds": {
|
|
90
|
+
"title": "Allowed Charger IDs",
|
|
91
|
+
"type": "array",
|
|
92
|
+
"uniqueItems": true,
|
|
93
|
+
"items": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$"
|
|
96
|
+
},
|
|
97
|
+
"description": "Optional allow-list. If empty, any charger id is accepted. This is an identifier filter, not authentication."
|
|
98
|
+
},
|
|
99
|
+
"exposeVehicleSensor": {
|
|
100
|
+
"title": "Expose Vehicle Sensor",
|
|
101
|
+
"type": "boolean",
|
|
102
|
+
"default": true,
|
|
103
|
+
"description": "Adds a contact sensor that indicates whether a vehicle is plugged in."
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { API, Logger, PlatformAccessory } from 'homebridge';
|
|
2
|
+
import type { ChargerContext, ChargerTelemetry } from './types.js';
|
|
3
|
+
type SetEnabled = (chargerId: string, enabled: boolean) => Promise<void>;
|
|
4
|
+
export declare class ChargerAccessory {
|
|
5
|
+
private readonly api;
|
|
6
|
+
private readonly log;
|
|
7
|
+
private readonly accessory;
|
|
8
|
+
private readonly setEnabled;
|
|
9
|
+
private readonly outlet;
|
|
10
|
+
private readonly vehicleSensor?;
|
|
11
|
+
private readonly custom;
|
|
12
|
+
private readonly onCharacteristic;
|
|
13
|
+
private lastSeen;
|
|
14
|
+
private online;
|
|
15
|
+
constructor(api: API, log: Logger, accessory: PlatformAccessory<ChargerContext>, setEnabled: SetEnabled, exposeVehicleSensor: boolean);
|
|
16
|
+
update(telemetry: ChargerTelemetry): void;
|
|
17
|
+
markOfflineIfStale(now: number, offlineAfterMs: number): void;
|
|
18
|
+
private markOffline;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=charger-accessory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"charger-accessory.d.ts","sourceRoot":"","sources":["../src/charger-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,GAAG,EAGH,MAAM,EACN,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnE,KAAK,UAAU,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEzE,qBAAa,gBAAgB;IASzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAX7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAiB;IAClD,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,MAAM,CAAS;gBAGJ,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,iBAAiB,CAAC,cAAc,CAAC,EAC5C,UAAU,EAAE,UAAU,EACvC,mBAAmB,EAAE,OAAO;IAyC9B,MAAM,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAwCzC,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAI7D,OAAO,CAAC,WAAW;CAgBpB"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { attachChargeCoreCharacteristics } from './custom-characteristics.js';
|
|
2
|
+
export class ChargerAccessory {
|
|
3
|
+
api;
|
|
4
|
+
log;
|
|
5
|
+
accessory;
|
|
6
|
+
setEnabled;
|
|
7
|
+
outlet;
|
|
8
|
+
vehicleSensor;
|
|
9
|
+
custom;
|
|
10
|
+
onCharacteristic;
|
|
11
|
+
lastSeen = 0;
|
|
12
|
+
online = false;
|
|
13
|
+
constructor(api, log, accessory, setEnabled, exposeVehicleSensor) {
|
|
14
|
+
this.api = api;
|
|
15
|
+
this.log = log;
|
|
16
|
+
this.accessory = accessory;
|
|
17
|
+
this.setEnabled = setEnabled;
|
|
18
|
+
const { Service, Characteristic } = api.hap;
|
|
19
|
+
const chargerId = accessory.context.chargerId;
|
|
20
|
+
this.lastSeen = accessory.context.lastSeen ?? 0;
|
|
21
|
+
this.outlet = accessory.getService(Service.Outlet)
|
|
22
|
+
?? accessory.addService(Service.Outlet, accessory.displayName);
|
|
23
|
+
this.outlet.setPrimaryService(true);
|
|
24
|
+
this.outlet.addOptionalCharacteristic(Characteristic.StatusActive);
|
|
25
|
+
this.outlet.addOptionalCharacteristic(Characteristic.StatusFault);
|
|
26
|
+
this.onCharacteristic = this.outlet.getCharacteristic(Characteristic.On)
|
|
27
|
+
.onGet(() => this.onCharacteristic.value ?? false)
|
|
28
|
+
.onSet(async (value) => {
|
|
29
|
+
const enabled = Boolean(value);
|
|
30
|
+
this.log.info(`${enabled ? 'Enabling' : 'Disabling'} charger ${chargerId}`);
|
|
31
|
+
await this.setEnabled(chargerId, enabled);
|
|
32
|
+
});
|
|
33
|
+
this.custom = attachChargeCoreCharacteristics(api, this.outlet);
|
|
34
|
+
const existingVehicleSensor = accessory.getServiceById(Service.ContactSensor, 'vehicle-connected');
|
|
35
|
+
if (exposeVehicleSensor) {
|
|
36
|
+
this.vehicleSensor = existingVehicleSensor
|
|
37
|
+
?? accessory.addService(Service.ContactSensor, `${accessory.displayName} Vehicle`, 'vehicle-connected');
|
|
38
|
+
this.vehicleSensor.addOptionalCharacteristic(Characteristic.StatusActive);
|
|
39
|
+
this.vehicleSensor.addOptionalCharacteristic(Characteristic.StatusFault);
|
|
40
|
+
}
|
|
41
|
+
else if (existingVehicleSensor) {
|
|
42
|
+
accessory.removeService(existingVehicleSensor);
|
|
43
|
+
}
|
|
44
|
+
accessory.getService(Service.AccessoryInformation)
|
|
45
|
+
.setCharacteristic(Characteristic.Manufacturer, 'ChargeCore')
|
|
46
|
+
.setCharacteristic(Characteristic.Model, 'WebSocket EV Wall Charger')
|
|
47
|
+
.setCharacteristic(Characteristic.SerialNumber, chargerId)
|
|
48
|
+
.setCharacteristic(Characteristic.FirmwareRevision, '1.0');
|
|
49
|
+
if (this.lastSeen > 0)
|
|
50
|
+
this.markOffline();
|
|
51
|
+
}
|
|
52
|
+
update(telemetry) {
|
|
53
|
+
const { Characteristic } = this.api.hap;
|
|
54
|
+
this.lastSeen = telemetry.receivedAt;
|
|
55
|
+
this.online = true;
|
|
56
|
+
this.accessory.context.lastSeen = telemetry.receivedAt;
|
|
57
|
+
if (telemetry.enabled !== undefined) {
|
|
58
|
+
this.onCharacteristic.updateValue(telemetry.enabled);
|
|
59
|
+
}
|
|
60
|
+
this.outlet.updateCharacteristic(Characteristic.OutletInUse, telemetry.state === 'charging' || (telemetry.powerW ?? 0) > 50);
|
|
61
|
+
this.outlet.updateCharacteristic(Characteristic.StatusActive, true);
|
|
62
|
+
this.outlet.updateCharacteristic(Characteristic.StatusFault, telemetry.fault ? Characteristic.StatusFault.GENERAL_FAULT : Characteristic.StatusFault.NO_FAULT);
|
|
63
|
+
this.custom.chargerState.updateValue(telemetry.state);
|
|
64
|
+
if (telemetry.powerW !== undefined)
|
|
65
|
+
this.custom.currentConsumption.updateValue(telemetry.powerW);
|
|
66
|
+
if (telemetry.energyKWh !== undefined)
|
|
67
|
+
this.custom.totalConsumption.updateValue(telemetry.energyKWh);
|
|
68
|
+
if (telemetry.voltageV !== undefined)
|
|
69
|
+
this.custom.voltage.updateValue(telemetry.voltageV);
|
|
70
|
+
if (telemetry.currentA !== undefined)
|
|
71
|
+
this.custom.electricCurrent.updateValue(telemetry.currentA);
|
|
72
|
+
if (this.vehicleSensor) {
|
|
73
|
+
this.vehicleSensor.updateCharacteristic(Characteristic.ContactSensorState, telemetry.vehicleConnected
|
|
74
|
+
? Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
75
|
+
: Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);
|
|
76
|
+
this.vehicleSensor.updateCharacteristic(Characteristic.StatusActive, true);
|
|
77
|
+
this.vehicleSensor.updateCharacteristic(Characteristic.StatusFault, telemetry.fault ? Characteristic.StatusFault.GENERAL_FAULT : Characteristic.StatusFault.NO_FAULT);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
markOfflineIfStale(now, offlineAfterMs) {
|
|
81
|
+
if (this.online && now - this.lastSeen > offlineAfterMs)
|
|
82
|
+
this.markOffline();
|
|
83
|
+
}
|
|
84
|
+
markOffline() {
|
|
85
|
+
const { Characteristic } = this.api.hap;
|
|
86
|
+
this.online = false;
|
|
87
|
+
this.outlet.updateCharacteristic(Characteristic.OutletInUse, false);
|
|
88
|
+
this.outlet.updateCharacteristic(Characteristic.StatusActive, false);
|
|
89
|
+
this.outlet.updateCharacteristic(Characteristic.StatusFault, Characteristic.StatusFault.GENERAL_FAULT);
|
|
90
|
+
this.custom.chargerState.updateValue('offline');
|
|
91
|
+
this.custom.currentConsumption.updateValue(0);
|
|
92
|
+
if (this.vehicleSensor) {
|
|
93
|
+
this.vehicleSensor.updateCharacteristic(Characteristic.StatusActive, false);
|
|
94
|
+
this.vehicleSensor.updateCharacteristic(Characteristic.StatusFault, Characteristic.StatusFault.GENERAL_FAULT);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=charger-accessory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"charger-accessory.js","sourceRoot":"","sources":["../src/charger-accessory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { API, Characteristic as HapCharacteristic, Service } from 'homebridge';
|
|
2
|
+
export interface ChargeCoreCharacteristics {
|
|
3
|
+
currentConsumption: HapCharacteristic;
|
|
4
|
+
totalConsumption: HapCharacteristic;
|
|
5
|
+
voltage: HapCharacteristic;
|
|
6
|
+
electricCurrent: HapCharacteristic;
|
|
7
|
+
chargerState: HapCharacteristic;
|
|
8
|
+
}
|
|
9
|
+
export declare function attachChargeCoreCharacteristics(api: API, service: Service): ChargeCoreCharacteristics;
|
|
10
|
+
//# sourceMappingURL=custom-characteristics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-characteristics.d.ts","sourceRoot":"","sources":["../src/custom-characteristics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,GAAG,EACH,cAAc,IAAI,iBAAiB,EAEnC,OAAO,EACR,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,yBAAyB;IACxC,kBAAkB,EAAE,iBAAiB,CAAC;IACtC,gBAAgB,EAAE,iBAAiB,CAAC;IACpC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,eAAe,EAAE,iBAAiB,CAAC;IACnC,YAAY,EAAE,iBAAiB,CAAC;CACjC;AAED,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,yBAAyB,CA0CrG"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function attachChargeCoreCharacteristics(api, service) {
|
|
2
|
+
const { Characteristic, Formats, Perms } = api.hap;
|
|
3
|
+
const readNotify = ["pr" /* Perms.PAIRED_READ */, "ev" /* Perms.NOTIFY */];
|
|
4
|
+
const candidates = {
|
|
5
|
+
currentConsumption: new Characteristic('Current Consumption', 'E863F10D-079E-48FF-8F27-9C2605A29F52', numericProps("float" /* Formats.FLOAT */, 'W', readNotify, 0, 100_000, 0.1)),
|
|
6
|
+
totalConsumption: new Characteristic('Total Consumption', 'E863F10C-079E-48FF-8F27-9C2605A29F52', numericProps("float" /* Formats.FLOAT */, 'kWh', readNotify, 0, 1_000_000, 0.001)),
|
|
7
|
+
voltage: new Characteristic('Voltage', 'E863F10A-079E-48FF-8F27-9C2605A29F52', numericProps("float" /* Formats.FLOAT */, 'V', readNotify, 0, 1_000, 0.1)),
|
|
8
|
+
electricCurrent: new Characteristic('Electric Current', 'E863F126-079E-48FF-8F27-9C2605A29F52', numericProps("float" /* Formats.FLOAT */, 'A', readNotify, 0, 500, 0.01)),
|
|
9
|
+
chargerState: new Characteristic('Charger State', '7A1E0001-4A7C-4C53-9F27-434841524745', {
|
|
10
|
+
format: "string" /* Formats.STRING */,
|
|
11
|
+
perms: readNotify,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
currentConsumption: attach(service, candidates.currentConsumption),
|
|
16
|
+
totalConsumption: attach(service, candidates.totalConsumption),
|
|
17
|
+
voltage: attach(service, candidates.voltage),
|
|
18
|
+
electricCurrent: attach(service, candidates.electricCurrent),
|
|
19
|
+
chargerState: attach(service, candidates.chargerState),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function attach(service, candidate) {
|
|
23
|
+
const existing = service.characteristics.find(characteristic => characteristic.UUID === candidate.UUID);
|
|
24
|
+
return existing ?? service.addCharacteristic(candidate);
|
|
25
|
+
}
|
|
26
|
+
function numericProps(format, unit, perms, minValue, maxValue, minStep) {
|
|
27
|
+
return { format, unit, perms, minValue, maxValue, minStep };
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=custom-characteristics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-characteristics.js","sourceRoot":"","sources":["../src/custom-characteristics.ts"],"names":[],"mappings":"AAeA,MAAM,UAAU,+BAA+B,CAAC,GAAQ,EAAE,OAAgB;IACxE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IACnD,MAAM,UAAU,GAAG,uDAAiC,CAAC;IAErD,MAAM,UAAU,GAA8B;QAC5C,kBAAkB,EAAE,IAAI,cAAc,CACpC,qBAAqB,EACrB,sCAAsC,EACtC,YAAY,8BAAgB,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAC9D;QACD,gBAAgB,EAAE,IAAI,cAAc,CAClC,mBAAmB,EACnB,sCAAsC,EACtC,YAAY,8BAAgB,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,CACpE;QACD,OAAO,EAAE,IAAI,cAAc,CACzB,SAAS,EACT,sCAAsC,EACtC,YAAY,8BAAgB,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAC5D;QACD,eAAe,EAAE,IAAI,cAAc,CACjC,kBAAkB,EAClB,sCAAsC,EACtC,YAAY,8BAAgB,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAC3D;QACD,YAAY,EAAE,IAAI,cAAc,CAC9B,eAAe,EACf,sCAAsC,EACtC;YACE,MAAM,+BAAgB;YACtB,KAAK,EAAE,UAAU;SAClB,CACF;KACF,CAAC;IAEF,OAAO;QACL,kBAAkB,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,kBAAkB,CAAC;QAClE,gBAAgB,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,gBAAgB,CAAC;QAC9D,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC;QAC5C,eAAe,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,eAAe,CAAC;QAC5D,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,YAAY,CAAC;KACvD,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,OAAgB,EAAE,SAA4B;IAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,cAAc,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC;IACxG,OAAO,QAAQ,IAAI,OAAO,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,YAAY,CACnB,MAAqC,EACrC,IAAY,EACZ,KAAmC,EACnC,QAAgB,EAChB,QAAgB,EAChB,OAAe;IAEf,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAyB,CAAC;AACrF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;yBAItB,KAAK,GAAG,KAAG,IAAI;AAA/B,wBAEE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,eAAe,CAAC,GAAQ,EAAQ,EAAE;IAChC,GAAG,CAAC,gBAAgB,CAAC,aAAa,EAAE,kBAAkB,CAAC,CAAC;AAC1D,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { API, DynamicPlatformPlugin, Logger, PlatformAccessory } from 'homebridge';
|
|
2
|
+
import type { ChargeCoreConfig } from './types.js';
|
|
3
|
+
export declare class ChargeCorePlatform implements DynamicPlatformPlugin {
|
|
4
|
+
private readonly log;
|
|
5
|
+
private readonly config;
|
|
6
|
+
private readonly api;
|
|
7
|
+
private readonly cachedAccessories;
|
|
8
|
+
private readonly handlers;
|
|
9
|
+
private readonly server;
|
|
10
|
+
private staleTimer?;
|
|
11
|
+
constructor(log: Logger, config: ChargeCoreConfig, api: API);
|
|
12
|
+
configureAccessory(accessory: PlatformAccessory): void;
|
|
13
|
+
private start;
|
|
14
|
+
private shutdown;
|
|
15
|
+
private handleTelemetry;
|
|
16
|
+
private createHandler;
|
|
17
|
+
private markStaleChargers;
|
|
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,KAAK,EACV,GAAG,EACH,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,gBAAgB,EAAoC,MAAM,YAAY,CAAC;AAErF,qBAAa,kBAAmB,YAAW,qBAAqB;IAO5D,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IARtB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwD;IAC1F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,UAAU,CAAC,CAAiB;gBAGjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE,GAAG;IAS3B,kBAAkB,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;YAWxC,KAAK;YAeL,QAAQ;IAStB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,iBAAiB;CAK1B"}
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ChargerAccessory } from './charger-accessory.js';
|
|
2
|
+
import { ChargeCoreWssServer } from './wss-server.js';
|
|
3
|
+
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
4
|
+
export class ChargeCorePlatform {
|
|
5
|
+
log;
|
|
6
|
+
config;
|
|
7
|
+
api;
|
|
8
|
+
cachedAccessories = new Map();
|
|
9
|
+
handlers = new Map();
|
|
10
|
+
server;
|
|
11
|
+
staleTimer;
|
|
12
|
+
constructor(log, config, api) {
|
|
13
|
+
this.log = log;
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.api = api;
|
|
16
|
+
this.server = new ChargeCoreWssServer(config, log);
|
|
17
|
+
this.server.on('telemetry', telemetry => this.handleTelemetry(telemetry));
|
|
18
|
+
this.api.on('didFinishLaunching', () => void this.start());
|
|
19
|
+
this.api.on('shutdown', () => void this.shutdown());
|
|
20
|
+
}
|
|
21
|
+
configureAccessory(accessory) {
|
|
22
|
+
const typedAccessory = accessory;
|
|
23
|
+
const chargerId = typedAccessory.context.chargerId;
|
|
24
|
+
if (!chargerId) {
|
|
25
|
+
this.log.warn(`Removing cached ChargeCore accessory without a charger id: ${accessory.displayName}`);
|
|
26
|
+
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.cachedAccessories.set(chargerId, typedAccessory);
|
|
30
|
+
}
|
|
31
|
+
async start() {
|
|
32
|
+
for (const [chargerId, accessory] of this.cachedAccessories) {
|
|
33
|
+
this.handlers.set(chargerId, this.createHandler(accessory));
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
await this.server.start();
|
|
37
|
+
const checkEveryMs = Math.min((this.config.offlineAfterSeconds ?? 90) * 500, 30_000);
|
|
38
|
+
this.staleTimer = setInterval(() => this.markStaleChargers(), Math.max(checkEveryMs, 1_000));
|
|
39
|
+
this.staleTimer.unref();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.log.error(`Unable to start ChargeCore: ${error instanceof Error ? error.message : String(error)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async shutdown() {
|
|
46
|
+
if (this.staleTimer)
|
|
47
|
+
clearInterval(this.staleTimer);
|
|
48
|
+
try {
|
|
49
|
+
await this.server.stop();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
this.log.warn(`Error while stopping ChargeCore: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
handleTelemetry(telemetry) {
|
|
56
|
+
let accessory = this.cachedAccessories.get(telemetry.chargerId);
|
|
57
|
+
if (!accessory) {
|
|
58
|
+
const displayName = telemetry.name?.trim().slice(0, 64) || `EV Charger ${telemetry.chargerId}`;
|
|
59
|
+
const uuid = this.api.hap.uuid.generate(`${PLUGIN_NAME}:${telemetry.chargerId}`);
|
|
60
|
+
accessory = new this.api.platformAccessory(displayName, uuid);
|
|
61
|
+
accessory.context.chargerId = telemetry.chargerId;
|
|
62
|
+
this.cachedAccessories.set(telemetry.chargerId, accessory);
|
|
63
|
+
this.handlers.set(telemetry.chargerId, this.createHandler(accessory));
|
|
64
|
+
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
65
|
+
this.log.info(`Added HomeKit accessory for charger ${telemetry.chargerId}`);
|
|
66
|
+
}
|
|
67
|
+
this.handlers.get(telemetry.chargerId)?.update(telemetry);
|
|
68
|
+
}
|
|
69
|
+
createHandler(accessory) {
|
|
70
|
+
return new ChargerAccessory(this.api, this.log, accessory, (chargerId, enabled) => this.server.setEnabled(chargerId, enabled), this.config.exposeVehicleSensor ?? true);
|
|
71
|
+
}
|
|
72
|
+
markStaleChargers() {
|
|
73
|
+
const offlineAfterMs = (this.config.offlineAfterSeconds ?? 90) * 1_000;
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
for (const handler of this.handlers.values())
|
|
76
|
+
handler.markOfflineIfStale(now, offlineAfterMs);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG3D,MAAM,OAAO,kBAAkB;IAOV;IACA;IACA;IARF,iBAAiB,GAAG,IAAI,GAAG,EAA6C,CAAC;IACzE,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IAC/C,MAAM,CAAsB;IACrC,UAAU,CAAkB;IAEpC,YACmB,GAAW,EACX,MAAwB,EACxB,GAAQ;QAFR,QAAG,GAAH,GAAG,CAAQ;QACX,WAAM,GAAN,MAAM,CAAkB;QACxB,QAAG,GAAH,GAAG,CAAK;QAEzB,IAAI,CAAC,MAAM,GAAG,IAAI,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAEnD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1E,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,kBAAkB,CAAC,SAA4B;QAC7C,MAAM,cAAc,GAAG,SAA8C,CAAC;QACtE,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC;QACnD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,8DAA8D,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;YACrG,IAAI,CAAC,GAAG,CAAC,6BAA6B,CAAC,WAAW,EAAE,aAAa,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;YAChF,OAAO;QACT,CAAC;QACD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACxD,CAAC;IAEO,KAAK,CAAC,KAAK;QACjB,KAAK,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC5D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9D,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAC,GAAG,GAAG,EAAE,MAAM,CAAC,CAAC;YACrF,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;YAC7F,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,+BAA+B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1G,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,IAAI,IAAI,CAAC,UAAU;YAAE,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,oCAAoC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC9G,CAAC;IACH,CAAC;IAEO,eAAe,CAAC,SAA2B;QACjD,IAAI,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAChE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,cAAc,SAAS,CAAC,SAAS,EAAE,CAAC;YAC/F,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,WAAW,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;YACjF,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAiB,WAAW,EAAE,IAAI,CAAC,CAAC;YAC9E,SAAS,CAAC,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC;YAClD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YAC3D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;YACtE,IAAI,CAAC,GAAG,CAAC,2BAA2B,CAAC,WAAW,EAAE,aAAa,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;YAC9E,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,uCAAuC,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;QAC9E,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC;IAEO,aAAa,CAAC,SAA4C;QAChE,OAAO,IAAI,gBAAgB,CACzB,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,GAAG,EACR,SAAS,EACT,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,EAClE,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,IAAI,CACxC,CAAC;IACJ,CAAC;IAEO,iBAAiB;QACvB,MAAM,cAAc,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC;QACvE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;YAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAChG,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,0BAA0B,CAAC;AACnD,eAAO,MAAM,aAAa,eAAe,CAAC;AAC1C,eAAO,MAAM,cAAc,gBAAgB,CAAC"}
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,uBAAuB,CAAC;AACnD,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAC;AAC1C,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PlatformConfig } from 'homebridge';
|
|
2
|
+
export type ChargerState = 'available' | 'connected' | 'charging' | 'paused' | 'finishing' | 'faulted' | 'offline' | 'unknown';
|
|
3
|
+
export interface ChargeCoreConfig extends PlatformConfig {
|
|
4
|
+
platform: 'ChargeCore';
|
|
5
|
+
name?: string;
|
|
6
|
+
host?: string;
|
|
7
|
+
port?: number;
|
|
8
|
+
tls?: {
|
|
9
|
+
certPath: string;
|
|
10
|
+
keyPath: string;
|
|
11
|
+
clientCaPath?: string;
|
|
12
|
+
};
|
|
13
|
+
offlineAfterSeconds?: number;
|
|
14
|
+
maxPayloadBytes?: number;
|
|
15
|
+
logWebSocketTraffic?: boolean;
|
|
16
|
+
commandAckTimeoutSeconds?: number;
|
|
17
|
+
allowedChargerIds?: string[];
|
|
18
|
+
exposeVehicleSensor?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ChargerTelemetry {
|
|
21
|
+
chargerId: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
state: ChargerState;
|
|
25
|
+
powerW?: number;
|
|
26
|
+
energyKWh?: number;
|
|
27
|
+
currentA?: number;
|
|
28
|
+
voltageV?: number;
|
|
29
|
+
vehicleConnected?: boolean;
|
|
30
|
+
fault: boolean;
|
|
31
|
+
receivedAt: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ChargerContext {
|
|
34
|
+
chargerId: string;
|
|
35
|
+
lastSeen?: number;
|
|
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,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,WAAW,GACX,UAAU,GACV,QAAQ,GACR,WAAW,GACX,SAAS,GACT,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,QAAQ,EAAE,YAAY,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { Logger } from 'homebridge';
|
|
3
|
+
import type { ChargeCoreConfig, ChargerTelemetry } from './types.js';
|
|
4
|
+
export interface ChargeCoreServerEvents {
|
|
5
|
+
telemetry: [ChargerTelemetry];
|
|
6
|
+
}
|
|
7
|
+
export declare class ChargeCoreWssServer extends EventEmitter<ChargeCoreServerEvents> {
|
|
8
|
+
private readonly config;
|
|
9
|
+
private readonly log;
|
|
10
|
+
private server?;
|
|
11
|
+
private wss?;
|
|
12
|
+
private heartbeat?;
|
|
13
|
+
private readonly sockets;
|
|
14
|
+
private readonly clientState;
|
|
15
|
+
private readonly pendingCommands;
|
|
16
|
+
private readonly path;
|
|
17
|
+
constructor(config: ChargeCoreConfig, log: Logger);
|
|
18
|
+
start(): Promise<void>;
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
setEnabled(chargerId: string, enabled: boolean): Promise<void>;
|
|
21
|
+
private handleConnection;
|
|
22
|
+
private handleMessage;
|
|
23
|
+
private handleOcppMessage;
|
|
24
|
+
private applyOcppStatus;
|
|
25
|
+
private applyOcppMeterValues;
|
|
26
|
+
private emitOcppTelemetry;
|
|
27
|
+
private handleOcppCallResult;
|
|
28
|
+
private handleOcppCallError;
|
|
29
|
+
private sendOcpp;
|
|
30
|
+
private bindSocket;
|
|
31
|
+
private removeSocket;
|
|
32
|
+
private runHeartbeat;
|
|
33
|
+
private logTraffic;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=wss-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wss-server.d.ts","sourceRoot":"","sources":["../src/wss-server.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAkBpB,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,CAAC,gBAAgB,CAAC,CAAC;CAC/B;AAED,qBAAa,mBAAoB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;IAUzE,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAVtB,OAAO,CAAC,MAAM,CAAC,CAA2B;IAC1C,OAAO,CAAC,GAAG,CAAC,CAAkB;IAC9B,OAAO,CAAC,SAAS,CAAC,CAAiB;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyC;IACrE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqC;IACrE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkB;gBAGpB,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE,MAAM;IAKxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2FtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBrB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCpE,OAAO,CAAC,gBAAgB;YAiBV,aAAa;YAwBb,iBAAiB;IAuE/B,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,mBAAmB;YAQb,QAAQ;IAMtB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,UAAU;CAInB"}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { createServer as createHttpServer, } from 'node:http';
|
|
4
|
+
import { createServer } from 'node:https';
|
|
5
|
+
import { EventEmitter } from 'node:events';
|
|
6
|
+
import { WebSocket, WebSocketServer } from 'ws';
|
|
7
|
+
import { WEBSOCKET_PATH } from './settings.js';
|
|
8
|
+
class OcppMessageError extends Error {
|
|
9
|
+
}
|
|
10
|
+
export class ChargeCoreWssServer extends EventEmitter {
|
|
11
|
+
config;
|
|
12
|
+
log;
|
|
13
|
+
server;
|
|
14
|
+
wss;
|
|
15
|
+
heartbeat;
|
|
16
|
+
sockets = new Map();
|
|
17
|
+
clientState = new WeakMap();
|
|
18
|
+
pendingCommands = new Map();
|
|
19
|
+
path = WEBSOCKET_PATH;
|
|
20
|
+
constructor(config, log) {
|
|
21
|
+
super();
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.log = log;
|
|
24
|
+
}
|
|
25
|
+
async start() {
|
|
26
|
+
const tls = this.config.tls;
|
|
27
|
+
if (tls && (!tls.certPath || !tls.keyPath)) {
|
|
28
|
+
throw new Error('tls.certPath and tls.keyPath must both be set when TLS is configured');
|
|
29
|
+
}
|
|
30
|
+
const requestHandler = (_request, response) => {
|
|
31
|
+
response.writeHead(426, { 'Content-Type': 'text/plain', Upgrade: 'websocket' });
|
|
32
|
+
response.end('WebSocket endpoint only\n');
|
|
33
|
+
};
|
|
34
|
+
if (tls) {
|
|
35
|
+
const [cert, key, ca] = await Promise.all([
|
|
36
|
+
readFile(tls.certPath),
|
|
37
|
+
readFile(tls.keyPath),
|
|
38
|
+
tls.clientCaPath ? readFile(tls.clientCaPath) : undefined,
|
|
39
|
+
]);
|
|
40
|
+
this.server = createServer({
|
|
41
|
+
cert,
|
|
42
|
+
key,
|
|
43
|
+
minVersion: 'TLSv1.2',
|
|
44
|
+
ca,
|
|
45
|
+
requestCert: Boolean(ca),
|
|
46
|
+
rejectUnauthorized: Boolean(ca),
|
|
47
|
+
}, requestHandler);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.server = createHttpServer(requestHandler);
|
|
51
|
+
}
|
|
52
|
+
this.wss = new WebSocketServer({
|
|
53
|
+
noServer: true,
|
|
54
|
+
maxPayload: this.config.maxPayloadBytes ?? 65_536,
|
|
55
|
+
perMessageDeflate: false,
|
|
56
|
+
});
|
|
57
|
+
this.server.on('upgrade', (request, socket, head) => {
|
|
58
|
+
const endpointChargerId = chargerIdFromRequestPath(request.url, this.path);
|
|
59
|
+
const peer = request.socket.remoteAddress ?? 'unknown peer';
|
|
60
|
+
const requestPath = request.url?.split('?', 1)[0] ?? '/';
|
|
61
|
+
this.logTraffic(`Upgrade request from ${peer} for ${requestPath}`);
|
|
62
|
+
if (endpointChargerId === null) {
|
|
63
|
+
this.logTraffic(`Rejected upgrade from ${peer}: path not found`);
|
|
64
|
+
rejectUpgrade(socket, 404, 'Not Found');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!endpointChargerId) {
|
|
68
|
+
this.logTraffic(`Rejected upgrade from ${peer}: charger id is missing from the path`);
|
|
69
|
+
rejectUpgrade(socket, 404, 'Charger ID Required');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (endpointChargerId && this.config.allowedChargerIds?.length
|
|
73
|
+
&& !this.config.allowedChargerIds.includes(endpointChargerId)) {
|
|
74
|
+
this.logTraffic(`Rejected upgrade from ${peer}: charger ${endpointChargerId} is not allowed`);
|
|
75
|
+
rejectUpgrade(socket, 403, 'Forbidden');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this.logTraffic(`Accepted upgrade from ${peer}${endpointChargerId ? ` for charger ${endpointChargerId}` : ''}`);
|
|
79
|
+
this.wss?.handleUpgrade(request, socket, head, client => {
|
|
80
|
+
this.wss?.emit('connection', client, request);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
this.wss.on('connection', (socket, request) => {
|
|
84
|
+
const endpointChargerId = chargerIdFromRequestPath(request.url, this.path);
|
|
85
|
+
if (!endpointChargerId) {
|
|
86
|
+
socket.close(1008, 'Charger ID required');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.logTraffic(`Connection opened for charger ${endpointChargerId}`);
|
|
90
|
+
this.handleConnection(socket, endpointChargerId);
|
|
91
|
+
});
|
|
92
|
+
this.wss.on('error', error => this.log.error(`WebSocket server error: ${error.message}`));
|
|
93
|
+
await new Promise((resolve, reject) => {
|
|
94
|
+
const server = this.server;
|
|
95
|
+
const onError = (error) => reject(error);
|
|
96
|
+
server.once('error', onError);
|
|
97
|
+
server.listen(this.config.port ?? 9443, this.config.host ?? '0.0.0.0', () => {
|
|
98
|
+
server.off('error', onError);
|
|
99
|
+
resolve();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
this.heartbeat = setInterval(() => this.runHeartbeat(), 30_000);
|
|
103
|
+
this.heartbeat.unref();
|
|
104
|
+
const scheme = tls ? 'wss' : 'ws';
|
|
105
|
+
this.log.info(`WebSocket server listening on ${scheme}://${this.config.host ?? '0.0.0.0'}:${this.config.port ?? 9443}${this.path}`);
|
|
106
|
+
}
|
|
107
|
+
async stop() {
|
|
108
|
+
if (this.heartbeat)
|
|
109
|
+
clearInterval(this.heartbeat);
|
|
110
|
+
for (const command of this.pendingCommands.values()) {
|
|
111
|
+
clearTimeout(command.timeout);
|
|
112
|
+
command.reject(new Error('ChargeCore server is stopping'));
|
|
113
|
+
}
|
|
114
|
+
this.pendingCommands.clear();
|
|
115
|
+
for (const socket of this.wss?.clients ?? [])
|
|
116
|
+
socket.close(1001, 'Server shutting down');
|
|
117
|
+
await Promise.all([
|
|
118
|
+
new Promise(resolve => this.wss ? this.wss.close(() => resolve()) : resolve()),
|
|
119
|
+
new Promise(resolve => this.server ? this.server.close(() => resolve()) : resolve()),
|
|
120
|
+
]);
|
|
121
|
+
this.sockets.clear();
|
|
122
|
+
}
|
|
123
|
+
async setEnabled(chargerId, enabled) {
|
|
124
|
+
const socket = this.sockets.get(chargerId);
|
|
125
|
+
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
126
|
+
throw new Error(`Charger ${chargerId} is not connected`);
|
|
127
|
+
}
|
|
128
|
+
const requestId = randomUUID();
|
|
129
|
+
const timeoutSeconds = this.config.commandAckTimeoutSeconds ?? 0;
|
|
130
|
+
const payload = JSON.stringify([2, requestId, 'ChangeAvailability', {
|
|
131
|
+
connectorId: 0,
|
|
132
|
+
type: enabled ? 'Operative' : 'Inoperative',
|
|
133
|
+
}]);
|
|
134
|
+
this.logTraffic(`Outgoing WebSocket message [${chargerId}]: ${payload}`);
|
|
135
|
+
if (timeoutSeconds <= 0) {
|
|
136
|
+
await send(socket, payload);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const acknowledgement = new Promise((resolve, reject) => {
|
|
140
|
+
const timeout = setTimeout(() => {
|
|
141
|
+
this.pendingCommands.delete(requestId);
|
|
142
|
+
reject(new Error(`Charger ${chargerId} did not acknowledge the command`));
|
|
143
|
+
}, timeoutSeconds * 1_000);
|
|
144
|
+
timeout.unref();
|
|
145
|
+
this.pendingCommands.set(requestId, { resolve, reject, timeout, socket });
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
await send(socket, payload);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const pending = this.pendingCommands.get(requestId);
|
|
152
|
+
if (pending)
|
|
153
|
+
clearTimeout(pending.timeout);
|
|
154
|
+
this.pendingCommands.delete(requestId);
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
await acknowledgement;
|
|
158
|
+
}
|
|
159
|
+
handleConnection(socket, endpointChargerId) {
|
|
160
|
+
this.clientState.set(socket, { alive: true, chargerId: endpointChargerId });
|
|
161
|
+
this.bindSocket(socket, endpointChargerId);
|
|
162
|
+
socket.on('pong', () => {
|
|
163
|
+
const state = this.clientState.get(socket);
|
|
164
|
+
if (state)
|
|
165
|
+
state.alive = true;
|
|
166
|
+
this.logTraffic(`Pong received [${state?.chargerId ?? 'unidentified'}]`);
|
|
167
|
+
});
|
|
168
|
+
socket.on('message', (data, isBinary) => void this.handleMessage(socket, data, isBinary));
|
|
169
|
+
socket.on('close', (code, reason) => {
|
|
170
|
+
const chargerId = this.clientState.get(socket)?.chargerId ?? 'unidentified';
|
|
171
|
+
this.logTraffic(`Connection closed [${chargerId}]: code=${code} reason=${reason.toString('utf8') || '<none>'}`);
|
|
172
|
+
this.removeSocket(socket);
|
|
173
|
+
});
|
|
174
|
+
socket.on('error', error => this.log.warn(`Charger WebSocket error: ${error.message}`));
|
|
175
|
+
}
|
|
176
|
+
async handleMessage(socket, data, isBinary) {
|
|
177
|
+
if (this.config.logWebSocketTraffic) {
|
|
178
|
+
const chargerId = this.clientState.get(socket)?.chargerId ?? 'unidentified';
|
|
179
|
+
this.log.info(isBinary
|
|
180
|
+
? `Incoming WebSocket message [${chargerId}]: <binary frame>`
|
|
181
|
+
: `Incoming WebSocket message [${chargerId}]: ${data.toString('utf8')}`);
|
|
182
|
+
}
|
|
183
|
+
if (isBinary) {
|
|
184
|
+
socket.close(1003, 'Text JSON messages only');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const rawMessage = JSON.parse(data.toString('utf8'));
|
|
189
|
+
if (!Array.isArray(rawMessage))
|
|
190
|
+
throw new OcppMessageError('OCPP message must be a JSON array');
|
|
191
|
+
await this.handleOcppMessage(socket, rawMessage);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
const detail = error instanceof Error ? error.message : 'unexpected OCPP message error';
|
|
195
|
+
this.log.warn(`Rejected OCPP message: ${detail}`);
|
|
196
|
+
socket.close(1007, 'Invalid OCPP message');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async handleOcppMessage(socket, message) {
|
|
200
|
+
const state = this.clientState.get(socket);
|
|
201
|
+
const chargerId = state?.chargerId;
|
|
202
|
+
if (!state || !chargerId)
|
|
203
|
+
throw new OcppMessageError('OCPP connections require a charger id in the URL');
|
|
204
|
+
const messageType = message[0];
|
|
205
|
+
const requestId = message[1];
|
|
206
|
+
if (typeof requestId !== 'string')
|
|
207
|
+
throw new OcppMessageError('OCPP message requires a string request id');
|
|
208
|
+
if (messageType === 3) {
|
|
209
|
+
this.handleOcppCallResult(socket, requestId, asObject(message[2]));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (messageType === 4) {
|
|
213
|
+
const code = typeof message[2] === 'string' ? message[2] : 'UnknownError';
|
|
214
|
+
const description = typeof message[3] === 'string' ? message[3] : '';
|
|
215
|
+
this.handleOcppCallError(socket, requestId, code, description);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (messageType !== 2 || typeof message[2] !== 'string') {
|
|
219
|
+
throw new OcppMessageError('unsupported OCPP message frame');
|
|
220
|
+
}
|
|
221
|
+
const action = message[2];
|
|
222
|
+
const payload = asObject(message[3]);
|
|
223
|
+
let response;
|
|
224
|
+
switch (action) {
|
|
225
|
+
case 'BootNotification': {
|
|
226
|
+
const vendor = stringValue(payload.chargePointVendor);
|
|
227
|
+
const model = stringValue(payload.chargePointModel);
|
|
228
|
+
const name = [vendor, model].filter(Boolean).join(' ').slice(0, 64) || undefined;
|
|
229
|
+
state.telemetry = { ...state.telemetry, state: 'unknown', fault: false, ...(name ? { name } : {}) };
|
|
230
|
+
this.emitOcppTelemetry(chargerId, state);
|
|
231
|
+
response = { currentTime: new Date().toISOString(), interval: 60, status: 'Accepted' };
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case 'Heartbeat':
|
|
235
|
+
this.emitOcppTelemetry(chargerId, state);
|
|
236
|
+
response = { currentTime: new Date().toISOString() };
|
|
237
|
+
break;
|
|
238
|
+
case 'StatusNotification':
|
|
239
|
+
this.applyOcppStatus(chargerId, state, payload);
|
|
240
|
+
response = {};
|
|
241
|
+
break;
|
|
242
|
+
case 'MeterValues':
|
|
243
|
+
this.applyOcppMeterValues(chargerId, state, payload);
|
|
244
|
+
response = {};
|
|
245
|
+
break;
|
|
246
|
+
case 'Authorize':
|
|
247
|
+
response = { idTagInfo: { status: 'Accepted' } };
|
|
248
|
+
break;
|
|
249
|
+
case 'StartTransaction':
|
|
250
|
+
response = { transactionId: Date.now(), idTagInfo: { status: 'Accepted' } };
|
|
251
|
+
break;
|
|
252
|
+
case 'StopTransaction':
|
|
253
|
+
response = { idTagInfo: { status: 'Accepted' } };
|
|
254
|
+
break;
|
|
255
|
+
case 'DataTransfer':
|
|
256
|
+
response = { status: 'UnknownMessage' };
|
|
257
|
+
break;
|
|
258
|
+
case 'DiagnosticsStatusNotification':
|
|
259
|
+
case 'FirmwareStatusNotification':
|
|
260
|
+
response = {};
|
|
261
|
+
break;
|
|
262
|
+
default:
|
|
263
|
+
await this.sendOcpp(socket, [4, requestId, 'NotSupported', `Action ${action} is not supported`, {}], chargerId);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
await this.sendOcpp(socket, [3, requestId, response], chargerId);
|
|
267
|
+
}
|
|
268
|
+
applyOcppStatus(chargerId, state, payload) {
|
|
269
|
+
const status = stringValue(payload.status);
|
|
270
|
+
const mapped = mapOcppStatus(status);
|
|
271
|
+
state.telemetry = {
|
|
272
|
+
...state.telemetry,
|
|
273
|
+
state: mapped.state,
|
|
274
|
+
fault: mapped.fault,
|
|
275
|
+
vehicleConnected: mapped.vehicleConnected,
|
|
276
|
+
enabled: status !== 'Unavailable',
|
|
277
|
+
};
|
|
278
|
+
this.emitOcppTelemetry(chargerId, state);
|
|
279
|
+
}
|
|
280
|
+
applyOcppMeterValues(chargerId, state, payload) {
|
|
281
|
+
const meterValues = Array.isArray(payload.meterValue) ? payload.meterValue : [];
|
|
282
|
+
const latest = asObject(meterValues.at(-1));
|
|
283
|
+
const samples = Array.isArray(latest.sampledValue) ? latest.sampledValue : [];
|
|
284
|
+
const telemetry = { ...state.telemetry };
|
|
285
|
+
for (const rawSample of samples) {
|
|
286
|
+
const sample = asObject(rawSample);
|
|
287
|
+
const value = Number(sample.value);
|
|
288
|
+
if (!Number.isFinite(value))
|
|
289
|
+
continue;
|
|
290
|
+
const measurand = stringValue(sample.measurand) ?? 'Energy.Active.Import.Register';
|
|
291
|
+
const unit = stringValue(sample.unit);
|
|
292
|
+
if (measurand === 'Power.Active.Import')
|
|
293
|
+
telemetry.powerW = unit === 'kW' ? value * 1_000 : value;
|
|
294
|
+
else if (measurand === 'Energy.Active.Import.Register')
|
|
295
|
+
telemetry.energyKWh = unit === 'Wh' ? value / 1_000 : value;
|
|
296
|
+
else if (measurand === 'Current.Import')
|
|
297
|
+
telemetry.currentA = value;
|
|
298
|
+
else if (measurand === 'Voltage')
|
|
299
|
+
telemetry.voltageV = value;
|
|
300
|
+
}
|
|
301
|
+
state.telemetry = telemetry;
|
|
302
|
+
this.emitOcppTelemetry(chargerId, state);
|
|
303
|
+
}
|
|
304
|
+
emitOcppTelemetry(chargerId, state) {
|
|
305
|
+
const telemetry = state.telemetry;
|
|
306
|
+
if (!telemetry)
|
|
307
|
+
return;
|
|
308
|
+
this.emit('telemetry', {
|
|
309
|
+
chargerId,
|
|
310
|
+
...telemetry,
|
|
311
|
+
state: telemetry.state ?? 'unknown',
|
|
312
|
+
fault: telemetry.fault ?? false,
|
|
313
|
+
receivedAt: Date.now(),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
handleOcppCallResult(socket, requestId, payload) {
|
|
317
|
+
const pending = this.pendingCommands.get(requestId);
|
|
318
|
+
if (!pending || pending.socket !== socket)
|
|
319
|
+
return;
|
|
320
|
+
this.pendingCommands.delete(requestId);
|
|
321
|
+
clearTimeout(pending.timeout);
|
|
322
|
+
const status = stringValue(payload.status);
|
|
323
|
+
if (!status || status === 'Accepted' || status === 'Scheduled')
|
|
324
|
+
pending.resolve();
|
|
325
|
+
else
|
|
326
|
+
pending.reject(new Error(`Charger rejected the OCPP command: ${status}`));
|
|
327
|
+
}
|
|
328
|
+
handleOcppCallError(socket, requestId, code, description) {
|
|
329
|
+
const pending = this.pendingCommands.get(requestId);
|
|
330
|
+
if (!pending || pending.socket !== socket)
|
|
331
|
+
return;
|
|
332
|
+
this.pendingCommands.delete(requestId);
|
|
333
|
+
clearTimeout(pending.timeout);
|
|
334
|
+
pending.reject(new Error(`OCPP ${code}: ${description}`));
|
|
335
|
+
}
|
|
336
|
+
async sendOcpp(socket, message, chargerId) {
|
|
337
|
+
const payload = JSON.stringify(message);
|
|
338
|
+
this.logTraffic(`Outgoing WebSocket message [${chargerId}]: ${payload}`);
|
|
339
|
+
await send(socket, payload);
|
|
340
|
+
}
|
|
341
|
+
bindSocket(socket, chargerId) {
|
|
342
|
+
const state = this.clientState.get(socket);
|
|
343
|
+
if (!state)
|
|
344
|
+
return false;
|
|
345
|
+
if (state.chargerId && state.chargerId !== chargerId) {
|
|
346
|
+
socket.close(1008, 'A connection cannot change charger identity');
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
state.chargerId = chargerId;
|
|
350
|
+
const previous = this.sockets.get(chargerId);
|
|
351
|
+
if (previous && previous !== socket)
|
|
352
|
+
previous.close(4001, 'Replaced by a newer connection');
|
|
353
|
+
this.sockets.set(chargerId, socket);
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
removeSocket(socket) {
|
|
357
|
+
const chargerId = this.clientState.get(socket)?.chargerId;
|
|
358
|
+
if (chargerId && this.sockets.get(chargerId) === socket)
|
|
359
|
+
this.sockets.delete(chargerId);
|
|
360
|
+
for (const [requestId, pending] of this.pendingCommands) {
|
|
361
|
+
if (pending.socket !== socket)
|
|
362
|
+
continue;
|
|
363
|
+
this.pendingCommands.delete(requestId);
|
|
364
|
+
clearTimeout(pending.timeout);
|
|
365
|
+
pending.reject(new Error(`Charger ${chargerId ?? 'connection'} disconnected before acknowledging the command`));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
runHeartbeat() {
|
|
369
|
+
for (const socket of this.wss?.clients ?? []) {
|
|
370
|
+
const state = this.clientState.get(socket);
|
|
371
|
+
if (!state?.alive) {
|
|
372
|
+
socket.terminate();
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
state.alive = false;
|
|
376
|
+
this.logTraffic(`Ping sent [${state.chargerId ?? 'unidentified'}]`);
|
|
377
|
+
socket.ping();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
logTraffic(message) {
|
|
381
|
+
if (this.config.logWebSocketTraffic)
|
|
382
|
+
this.log.info(`[WebSocket traffic] ${message}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function normalizePath(path) {
|
|
386
|
+
const withSlash = path.startsWith('/') ? path : `/${path}`;
|
|
387
|
+
return withSlash.length > 1 ? withSlash.replace(/\/+$/, '') : withSlash;
|
|
388
|
+
}
|
|
389
|
+
function asObject(value) {
|
|
390
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
391
|
+
? value
|
|
392
|
+
: {};
|
|
393
|
+
}
|
|
394
|
+
function stringValue(value) {
|
|
395
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
396
|
+
}
|
|
397
|
+
function mapOcppStatus(status) {
|
|
398
|
+
switch (status) {
|
|
399
|
+
case 'Available': return { state: 'available', fault: false, vehicleConnected: false };
|
|
400
|
+
case 'Preparing': return { state: 'connected', fault: false, vehicleConnected: true };
|
|
401
|
+
case 'Charging': return { state: 'charging', fault: false, vehicleConnected: true };
|
|
402
|
+
case 'SuspendedEV':
|
|
403
|
+
case 'SuspendedEVSE': return { state: 'paused', fault: false, vehicleConnected: true };
|
|
404
|
+
case 'Finishing': return { state: 'finishing', fault: false, vehicleConnected: true };
|
|
405
|
+
case 'Faulted': return { state: 'faulted', fault: true, vehicleConnected: false };
|
|
406
|
+
case 'Unavailable': return { state: 'offline', fault: false, vehicleConnected: false };
|
|
407
|
+
default: return { state: 'unknown', fault: false, vehicleConnected: false };
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function requestUrlPath(url) {
|
|
411
|
+
if (!url)
|
|
412
|
+
return '/';
|
|
413
|
+
return normalizePath(new URL(url, 'https://localhost').pathname);
|
|
414
|
+
}
|
|
415
|
+
function chargerIdFromRequestPath(url, basePath) {
|
|
416
|
+
const path = requestUrlPath(url);
|
|
417
|
+
if (path === basePath)
|
|
418
|
+
return undefined;
|
|
419
|
+
const prefix = basePath === '/' ? '/' : `${basePath}/`;
|
|
420
|
+
if (!path.startsWith(prefix))
|
|
421
|
+
return null;
|
|
422
|
+
const encodedId = path.slice(prefix.length);
|
|
423
|
+
if (!encodedId || encodedId.includes('/'))
|
|
424
|
+
return null;
|
|
425
|
+
try {
|
|
426
|
+
const chargerId = decodeURIComponent(encodedId);
|
|
427
|
+
return /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/.test(chargerId) ? chargerId : null;
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function rejectUpgrade(socket, status, message) {
|
|
434
|
+
socket.end(`HTTP/1.1 ${status} ${message}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`);
|
|
435
|
+
}
|
|
436
|
+
function send(socket, data) {
|
|
437
|
+
return new Promise((resolve, reject) => {
|
|
438
|
+
socket.send(data, error => error ? reject(error) : resolve());
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=wss-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wss-server.js","sourceRoot":"","sources":["../src/wss-server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "homebridge-chargecore",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Homebridge platform and OCPP 1.6 central system for EV wall chargers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"exports": "./dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"config.schema.json",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"clean": "rimraf dist coverage",
|
|
18
|
+
"lint": "eslint src test",
|
|
19
|
+
"prepublishOnly": "npm run clean && npm run build && npm test",
|
|
20
|
+
"test": "vitest run --coverage",
|
|
21
|
+
"test:watch": "vitest"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": "^22.12.0 || ^24.0.0",
|
|
25
|
+
"homebridge": "^2.0.0"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"homebridge-plugin",
|
|
29
|
+
"supports-hap",
|
|
30
|
+
"ev",
|
|
31
|
+
"charger",
|
|
32
|
+
"ocpp",
|
|
33
|
+
"websocket",
|
|
34
|
+
"homekit"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"ws": "8.22.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@eslint/js": "9.38.0",
|
|
42
|
+
"@types/node": "22.18.12",
|
|
43
|
+
"@types/ws": "8.18.1",
|
|
44
|
+
"@vitest/coverage-v8": "4.1.12",
|
|
45
|
+
"eslint": "9.38.0",
|
|
46
|
+
"homebridge": "2.4.0",
|
|
47
|
+
"rimraf": "6.0.1",
|
|
48
|
+
"typescript": "5.9.3",
|
|
49
|
+
"typescript-eslint": "8.46.2",
|
|
50
|
+
"vitest": "4.1.12"
|
|
51
|
+
}
|
|
52
|
+
}
|