node-chargepoint 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Marc Billow
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 ADDED
@@ -0,0 +1,460 @@
1
+ # node-chargepoint
2
+
3
+ A simple, async Node.js/TypeScript wrapper around the ChargePoint EV Charging Network API.
4
+
5
+ > Based on [python-chargepoint](https://github.com/mbillow/python-chargepoint) by Marc Billow (MIT).
6
+
7
+ ## Disclaimer
8
+
9
+ This project is not affiliated with or endorsed by ChargePoint in any way. Use at your own risk.
10
+ ChargePoint is a registered trademark of ChargePoint, Inc.
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm add node-chargepoint
18
+ ```
19
+
20
+ Requires **Node.js ≥ 24**.
21
+
22
+ ---
23
+
24
+ ## Library Usage
25
+
26
+ All client methods are `async` and return Promises.
27
+
28
+ ### Authentication
29
+
30
+ Three authentication methods are supported. The client is created via the async factory `ChargePoint.create()`.
31
+
32
+ **Password:**
33
+ ```typescript
34
+ import { ChargePoint } from 'node-chargepoint';
35
+
36
+ const client = await ChargePoint.create('user@example.com');
37
+ await client.loginWithPassword('password');
38
+ // ...
39
+ await client.logout();
40
+ ```
41
+
42
+ **Long-lived session token** (recommended for automation):
43
+ ```typescript
44
+ const client = await ChargePoint.create('user@example.com', {
45
+ coulombToken: '<coulomb_sess cookie value>',
46
+ });
47
+ ```
48
+
49
+ **SSO JWT:**
50
+ ```typescript
51
+ const client = await ChargePoint.create('user@example.com');
52
+ await client.loginWithSsoSession('<sso jwt>');
53
+ ```
54
+
55
+ ---
56
+
57
+ ### Obtaining Tokens Manually
58
+
59
+ Password-based login may be blocked by bot-protection (Datadome). When that happens,
60
+ you can capture a token directly from your browser and pass it to the client.
61
+
62
+ 1. Open [https://driver.chargepoint.com](https://driver.chargepoint.com) in your browser and log in normally.
63
+ 2. Open Developer Tools and navigate to **Application > Cookies > https://driver.chargepoint.com**.
64
+ 3. Copy the value of one of the following cookies:
65
+
66
+ | Cookie | Use as |
67
+ |---|---|
68
+ | `coulomb_sess` | `coulombToken:` option (recommended — long-lived) |
69
+ | `auth-session` | `loginWithSsoSession()` (shorter-lived JWT) |
70
+
71
+ > **Note:** The `coulomb_sess` value contains `#` and `?` characters. When setting it as a
72
+ > shell environment variable, always wrap the value in **double quotes** to prevent the shell
73
+ > from interpreting `#` as a comment:
74
+ >
75
+ > ```bash
76
+ > export CP_TOKEN="Ab3dEf...token...#D???????#RNA-US"
77
+ > ```
78
+
79
+ ---
80
+
81
+ ### Account
82
+
83
+ ```typescript
84
+ const account = await client.getAccount();
85
+ console.log(account.user.fullName); // "Jane Smith"
86
+ console.log(account.accountBalance.amount); // 12.34
87
+
88
+ const vehicles = await client.getVehicles();
89
+ for (const ev of vehicles) {
90
+ console.log(`${ev.year} ${ev.make} ${ev.model}`); // "2023 Polestar 2"
91
+ console.log(` AC: ${ev.chargingSpeed} kW DC: ${ev.dcChargingSpeed} kW`);
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ### Home Charger
98
+
99
+ ```typescript
100
+ const chargerIds = await client.getHomeChargers();
101
+ // [12345678]
102
+
103
+ const chargerId = chargerIds[0];
104
+
105
+ const status = await client.getHomeChargerStatus(chargerId);
106
+ // {
107
+ // chargerId: 12345678,
108
+ // brand: 'CP',
109
+ // model: 'HOME FLEX',
110
+ // chargingStatus: 'AVAILABLE',
111
+ // isPluggedIn: true,
112
+ // isConnected: true,
113
+ // amperageLimit: 28,
114
+ // possibleAmperageLimits: [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]
115
+ // }
116
+
117
+ const tech = await client.getHomeChargerTechnicalInfo(chargerId);
118
+ // {
119
+ // modelNumber: 'CPH50-NEMA6-50-L23',
120
+ // serialNumber: '...',
121
+ // softwareVersion: '1.2.3.4',
122
+ // lastConnectedAt: '2024-06-01T08:30:00Z'
123
+ // }
124
+
125
+ const config = await client.getHomeChargerConfig(chargerId);
126
+ // {
127
+ // stationNickname: 'Home Flex',
128
+ // ledBrightness: { level: 5, supportedLevels: [0,1,2,3,4,5] },
129
+ // utility: { name: 'Austin Energy', ... }
130
+ // }
131
+ ```
132
+
133
+ #### Amperage limit
134
+
135
+ ```typescript
136
+ // Print valid amperage values
137
+ console.log(status.possibleAmperageLimits);
138
+ // [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]
139
+
140
+ await client.setAmperageLimit(chargerId, 24);
141
+ ```
142
+
143
+ #### LED brightness
144
+
145
+ Levels map to: `0`=off, `1`=20%, `2`=40%, `3`=60%, `4`=80%, `5`=100%.
146
+ Available levels are returned by `getHomeChargerConfig()`.
147
+
148
+ ```typescript
149
+ await client.setLedBrightness(chargerId, 3); // 60%
150
+ ```
151
+
152
+ #### Restart
153
+
154
+ ```typescript
155
+ await client.restartHomeCharger(chargerId);
156
+ ```
157
+
158
+ #### Charging schedule
159
+
160
+ ```typescript
161
+ const schedule = await client.getHomeChargerSchedule(chargerId);
162
+ console.log(schedule.scheduleEnabled); // false
163
+ console.log(schedule.defaultSchedule.weekdays.startTime); // "23:00"
164
+ console.log(schedule.defaultSchedule.weekdays.endTime); // "07:00"
165
+ console.log(schedule.defaultSchedule.weekends.startTime); // "19:00"
166
+ console.log(schedule.defaultSchedule.weekends.endTime); // "15:00"
167
+
168
+ // Enable a schedule
169
+ const updated = await client.setHomeChargerSchedule(
170
+ chargerId,
171
+ '23:00', '07:00', // weekday start, weekday end
172
+ '19:00', '15:00', // weekend start, weekend end
173
+ );
174
+ console.log(updated.scheduleEnabled); // true
175
+
176
+ // Disable the schedule
177
+ await client.disableHomeChargerSchedule(chargerId);
178
+ ```
179
+
180
+ ---
181
+
182
+ ### Charging Status and Sessions
183
+
184
+ ```typescript
185
+ const status = await client.getUserChargingStatus();
186
+ if (status) {
187
+ console.log(status.state); // "CHARGING"
188
+ console.log(status.sessionId); // 1234567890
189
+
190
+ const session = await client.getChargingSession(status.sessionId);
191
+ console.log(session.chargingState); // "CHARGING"
192
+ console.log(session.energyKwh); // 6.42
193
+ console.log(session.milesAdded); // 22.3
194
+ }
195
+ ```
196
+
197
+ #### Starting and stopping a session
198
+
199
+ ```typescript
200
+ // Stop the current session
201
+ const session = await client.getChargingSession(status.sessionId);
202
+ await session.stop();
203
+
204
+ // Start a new session on any device
205
+ const newSession = await client.startChargingSession(chargerId);
206
+ console.log(newSession.sessionId);
207
+ ```
208
+
209
+ ---
210
+
211
+ ### Station Info
212
+
213
+ Fetch detailed information about any station by device ID — ports, pricing, connector types, and real-time status.
214
+
215
+ ```typescript
216
+ const info = await client.getStation(13055991);
217
+ console.log(info.name.join(' / ')); // "DOMAIN TOWER 2 / LVL 2_STATION 2"
218
+ console.log(info.address.address1); // "10025 Alterra Pkwy"
219
+ console.log(info.stationStatusV2); // "available"
220
+ console.log(info.portsInfo.totalCount); // 2
221
+
222
+ for (const port of info.portsInfo.ports) {
223
+ console.log(`Port ${port.outletNumber}: ${port.statusV2} (Level ${port.level})`);
224
+ for (const c of port.connectorList) {
225
+ console.log(` ${c.displayPlugType}: ${c.statusV2}`);
226
+ }
227
+ }
228
+
229
+ if (info.stationPrice) {
230
+ for (const tou of info.stationPrice.touFees) {
231
+ console.log(`Rate: ${tou.price} ${info.stationPrice.currencyCode}`);
232
+ }
233
+ }
234
+ ```
235
+
236
+ ---
237
+
238
+ ### Nearby Stations
239
+
240
+ Fetch all charging stations visible within a geographic bounding box.
241
+
242
+ ```typescript
243
+ import type { MapFilter, ZoomBounds } from 'node-chargepoint';
244
+
245
+ const bounds: ZoomBounds = {
246
+ swLat: 30.37, swLon: -97.66,
247
+ neLat: 30.40, neLon: -97.64,
248
+ };
249
+
250
+ // No filter — return all stations
251
+ const stations = await client.getNearbyStations(bounds);
252
+
253
+ // Optional: filter by connector type or status
254
+ const filter: MapFilter = {
255
+ connectorL2: true,
256
+ connectorCombo: true,
257
+ statusAvailable: true,
258
+ };
259
+ const filtered = await client.getNearbyStations(bounds, filter);
260
+
261
+ for (const s of filtered) {
262
+ console.log(`${s.name1} — ${s.stationStatusV2}`);
263
+ if (s.isHome && s.chargingInfo) {
264
+ console.log(` Charging: ${s.chargingStatus}`);
265
+ }
266
+ }
267
+ ```
268
+
269
+ **`MapFilter` fields** (all `boolean`, all optional):
270
+
271
+ | Field | Description |
272
+ |---|---|
273
+ | `connectorL2` | Level 2 AC |
274
+ | `connectorCombo` | CCS combo (DC) |
275
+ | `connectorChademo` | CHAdeMO (DC) |
276
+ | `connectorTesla` | Tesla proprietary |
277
+ | `connectorL1` | Level 1 AC |
278
+ | `connectorL2Tesla` | Tesla Level 2 |
279
+ | `connectorL2Nema1450` | NEMA 14-50 |
280
+ | `dcFastCharging` | Any DC fast charger |
281
+ | `statusAvailable` | Only available stations |
282
+ | `priceFree` | Only free stations |
283
+ | `vanAccessible` | Van-accessible spaces |
284
+ | `disabledParking` | Disability-accessible parking |
285
+ | `networkChargepoint` | ChargePoint network |
286
+ | `networkEvgo` | EVgo network |
287
+
288
+ ---
289
+
290
+ ## CLI
291
+
292
+ After installation, a `chargepoint` command is available.
293
+
294
+ ### Authentication
295
+
296
+ Credentials are read from environment variables:
297
+
298
+ ```bash
299
+ export CP_USERNAME="user@example.com"
300
+ export CP_TOKEN="<coulomb_sess cookie value>"
301
+ # or use --password / CP_PASSWORD for password-based login
302
+ ```
303
+
304
+ ### Commands
305
+
306
+ ```bash
307
+ # Show account info
308
+ chargepoint -u user@example.com -t $CP_TOKEN account
309
+
310
+ # List registered vehicles
311
+ chargepoint -u user@example.com -t $CP_TOKEN vehicles
312
+
313
+ # Show current charging session status
314
+ chargepoint -u user@example.com -t $CP_TOKEN status
315
+
316
+ # List home charger IDs
317
+ chargepoint -u user@example.com -t $CP_TOKEN chargers
318
+
319
+ # Start a charging session on a device
320
+ chargepoint -u user@example.com -t $CP_TOKEN start <deviceId>
321
+
322
+ # Show details for a charging station
323
+ chargepoint -u user@example.com -t $CP_TOKEN station <deviceId>
324
+ ```
325
+
326
+ Global options:
327
+
328
+ ```
329
+ -u, --username <username> ChargePoint username (or set CP_USERNAME)
330
+ -t, --token <token> Coulomb session token (or set CP_TOKEN)
331
+ -p, --password <password> Password login fallback (or set CP_PASSWORD)
332
+ -V, --version Print version
333
+ -h, --help Display help
334
+ ```
335
+
336
+ ---
337
+
338
+ ## Error Handling
339
+
340
+ ```typescript
341
+ import {
342
+ ChargePoint,
343
+ LoginError,
344
+ InvalidSession,
345
+ DatadomeCaptcha,
346
+ CommunicationError,
347
+ APIError,
348
+ } from 'node-chargepoint';
349
+
350
+ try {
351
+ await client.loginWithPassword('bad-password');
352
+ } catch (err) {
353
+ if (err instanceof LoginError) {
354
+ console.error('Wrong credentials');
355
+ } else if (err instanceof InvalidSession) {
356
+ console.error('Session expired — re-authenticate');
357
+ } else if (err instanceof DatadomeCaptcha) {
358
+ console.error('Bot protection triggered:', err.captchaUrl);
359
+ } else if (err instanceof CommunicationError) {
360
+ console.error(`API error ${err.statusCode}:`, err.message);
361
+ }
362
+ }
363
+ ```
364
+
365
+ **Error hierarchy:**
366
+
367
+ ```
368
+ APIError
369
+ ├── CommunicationError (non-2xx response)
370
+ │ ├── LoginError (authentication failed)
371
+ │ └── InvalidSession (session expired — HTTP 401)
372
+ └── DatadomeCaptcha (Datadome bot protection — HTTP 403)
373
+ ```
374
+
375
+ ---
376
+
377
+ ## Development
378
+
379
+ ### Setup
380
+
381
+ ```bash
382
+ git clone https://github.com/musicbender/node-chargepoint.git
383
+ cd node-chargepoint
384
+ pnpm install
385
+ ```
386
+
387
+ ### Type checking
388
+
389
+ ```bash
390
+ pnpm typecheck
391
+ ```
392
+
393
+ ### Tests
394
+
395
+ ```bash
396
+ pnpm test
397
+ ```
398
+
399
+ Tests use [Vitest](https://vitest.dev/) and [MSW](https://mswjs.io/) to intercept `fetch` calls. No real network traffic is made during tests.
400
+
401
+ ### E2E Tests
402
+
403
+ An optional E2E suite runs against the live ChargePoint API using a real home charger. It requires valid credentials and is not run as part of `pnpm test`.
404
+
405
+ **Setup:**
406
+
407
+ Create a `.env.e2e` file in the project root (it is gitignored):
408
+
409
+ ```
410
+ CP_USERNAME=your-chargepoint-email@example.com
411
+ CP_PASSWORD=your-chargepoint-password
412
+
413
+ # After first run, paste the printed token here to avoid re-logging in:
414
+ # CP_TOKEN=
415
+ ```
416
+
417
+ > **Tip:** `coulomb_sess` tokens contain special characters (`#`, `?`). Always wrap the value in
418
+ > double quotes when exporting to your shell:
419
+ > ```bash
420
+ > export CP_TOKEN="abc...#D???"
421
+ > ```
422
+
423
+ **Run (read-only — safe with charger in any state):**
424
+
425
+ ```bash
426
+ pnpm test:e2e
427
+ ```
428
+
429
+ On first run the session token is printed. Paste it into `CP_TOKEN` in `.env.e2e` to skip
430
+ password re-authentication on subsequent runs.
431
+
432
+ **Run with mutation tests** (schedule, amperage limit, LED brightness — each test restores
433
+ original values):
434
+
435
+ ```bash
436
+ E2E_MUTATIONS=true pnpm test:e2e
437
+ ```
438
+
439
+ The following operations are intentionally excluded from the automated suite due to their
440
+ disruptive nature: `restartHomeCharger()`, `startChargingSession()`, `stopChargingSession()`.
441
+ Use the CLI to invoke these manually.
442
+
443
+ ### Build
444
+
445
+ ```bash
446
+ pnpm build
447
+ ```
448
+
449
+ Produces `dist/index.js` (ESM), `dist/index.cjs` (CommonJS), and `dist/index.d.ts` (type declarations) via [tsup](https://tsup.egoist.dev/).
450
+
451
+ ### Checks
452
+
453
+ | Command | Purpose |
454
+ |---|---|
455
+ | `pnpm typecheck` | TypeScript strict type checking |
456
+ | `pnpm test` | Run all tests |
457
+ | `pnpm build` | Build CJS + ESM + `.d.ts` |
458
+ | `pnpm prepublishOnly` | Build + typecheck (runs automatically before `pnpm publish`) |
459
+
460
+ ---