zenitel-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,526 @@
1
+ # @pinecall/zenitel-client
2
+
3
+ > TypeScript client for Zenitel intercom systems β€” HTTP scraper, network scanner, and CLI.
4
+
5
+ Zero runtime dependencies. Uses native `fetch` (Node 22+).
6
+
7
+ Tested and validated against a real **TCIV-2+** (Turbine Compact - Video Plus), firmware **9.2.3.0**.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @pinecall/zenitel-client
13
+ ```
14
+
15
+ ---
16
+
17
+ ## CLI
18
+
19
+ The `zenitel` CLI is included for hardware testing and debugging.
20
+
21
+ ```bash
22
+ npx zenitel <command> [options]
23
+ ```
24
+
25
+ ### Device Discovery
26
+
27
+ ```bash
28
+ # Auto-scan network via ARP table (OUI 00:13:CB = Zenitel) + HTTP fingerprinting
29
+ zenitel scan
30
+
31
+ # πŸ” Scanning network for Zenitel devices...
32
+ #
33
+ # 🟒 192.168.1.143
34
+ # MAC: 00:13:cb:28:35:ca
35
+ # Model: TCIV-2+
36
+ # Firmware: 9.2.3.0
37
+ # Hostname: zenitel01
38
+ # Mode: sip
39
+ # Camera: Yes
40
+ #
41
+ # Found 1 device(s).
42
+ ```
43
+
44
+ ### Device Information
45
+
46
+ ```bash
47
+ zenitel info -h 192.168.1.143
48
+
49
+ # πŸ“‹ Getting device info from 192.168.1.143...
50
+ #
51
+ # Model: TCIV-2+
52
+ # System: Turbine Compact - Video Plus
53
+ # Firmware: 9.2.3.0
54
+ # MAC: 00:13:cb:28:35:ca
55
+ # Serial: 22040000
56
+ # Hostname: zenitel01
57
+ # Mode: sip
58
+ # Camera: Yes
59
+ # Platform: turbine
60
+ # HW Type: 8801
61
+ # Uptime: up 2 days, 20 hours, 3 minutes
62
+ # Webcall: Enabled
63
+ # SIP Domain: testing-mo16m3gw.sip.twilio.com
64
+ # SIP Registered: Yes
65
+ # SIP Number: zenitel01
66
+ # Outbound Proxy: testing-mo16m3gw.sip.twilio.com
67
+ ```
68
+
69
+ ### Call Status & Relays
70
+
71
+ ```bash
72
+ # Full status (call + all relays)
73
+ zenitel status -h 192.168.1.143
74
+
75
+ # Activate relay 1 for 3 seconds (opens door)
76
+ zenitel relay -h 192.168.1.143
77
+
78
+ # Activate GPIO output 2 for 5 seconds
79
+ zenitel relay -h 192.168.1.143 --id gpio2 --timer 5
80
+
81
+ # Deactivate relay
82
+ zenitel relay -h 192.168.1.143 --off
83
+
84
+ # Place a SIP call
85
+ zenitel call 122 -h 192.168.1.143
86
+
87
+ # Stop current call
88
+ zenitel stop -h 192.168.1.143
89
+ ```
90
+
91
+ ### SIP Configuration
92
+
93
+ ```bash
94
+ # Read current SIP config
95
+ zenitel sip get -h 192.168.1.143
96
+
97
+ # πŸ“‘ SIP config from 192.168.1.143:
98
+ # Name: zenitel01
99
+ # Number: zenitel01
100
+ # Domain: testing-mo16m3gw.sip.twilio.com
101
+ # Auth User: zenitel01
102
+ # Auth Pass: ********
103
+ # Proxy: testing-mo16m3gw.sip.twilio.com
104
+ # Transport: UDP
105
+
106
+ # Write SIP config (requires reboot to take effect)
107
+ zenitel sip set -h 192.168.1.143 \
108
+ --domain my-trunk.sip.twilio.com \
109
+ --number station01 \
110
+ --proxy my-trunk.sip.twilio.com \
111
+ --transport udp
112
+ ```
113
+
114
+ ### Webcall API Management
115
+
116
+ ```bash
117
+ # Check webcall status
118
+ zenitel webcall -h 192.168.1.143
119
+
120
+ # Enable webcall + relay HTTP API
121
+ zenitel webcall enable -h 192.168.1.143
122
+
123
+ # Disable webcall + relay HTTP API
124
+ zenitel webcall disable -h 192.168.1.143
125
+ ```
126
+
127
+ ### Config Backup & Restore
128
+
129
+ ```bash
130
+ # Download full config as tar.gz
131
+ zenitel backup -h 192.168.1.143 -o my_backup.tar.gz
132
+
133
+ # Contents: ipst_config.xml, zapconfig.json, snmpd.conf, certs/, logos/
134
+
135
+ # Restore config from backup
136
+ zenitel restore my_backup.tar.gz -h 192.168.1.143
137
+ ```
138
+
139
+ ### Video
140
+
141
+ ```bash
142
+ zenitel video -h 192.168.1.143
143
+
144
+ # πŸ“· Video URLs for 192.168.1.143:
145
+ # MJPG: http://192.168.1.143/mjpg/video.mjpg
146
+ # RTSP: rtsp://192.168.1.143:554/1/RTSP
147
+ ```
148
+
149
+ ### Call Button (DAK) Configuration
150
+
151
+ The DAK (Direct Access Key) controls what number the intercom dials when a visitor presses the call button. The value is stored inside `ipst_config.xml` in the device's config backup.
152
+
153
+ ```bash
154
+ # Read current call button config
155
+ zenitel dak get -h 192.168.1.143
156
+
157
+ # πŸ“ž Call button (DAK1) config from 192.168.1.143:
158
+ # Dial Number: 222
159
+ # SIP Domain: testing-mo16m3gw.sip.twilio.com
160
+ # Full URI: 222@testing-mo16m3gw.sip.twilio.com
161
+
162
+ # Set call button to dial a different number
163
+ # (downloads backup β†’ modifies XML β†’ re-uploads β†’ reboots)
164
+ zenitel dak set --number portia-ae3c -h 192.168.1.143
165
+
166
+ # Set without auto-reboot
167
+ zenitel dak set --number portia-ae3c --no-reboot -h 192.168.1.143
168
+
169
+ # Set with explicit SIP domain
170
+ zenitel dak set --number portia-ae3c --domain my.sip.twilio.com -h 192.168.1.143
171
+ ```
172
+
173
+ > **How it works:** The command downloads the device's full config backup (`ipst_config.tar.gz`), extracts `ipst_config.xml`, replaces the `<dak1><val>` field with the new SIP URI, re-packs the tar.gz (with correct checksums), uploads it, and reboots the device. Zero external dependencies β€” tar parsing is done in pure Node.js using `node:zlib`.
174
+
175
+ ### Full Device Provisioning
176
+
177
+ Configures a **factory-reset** Zenitel in a single command. Sets SIP credentials, call button target, enables webcall + auto-answer, and reboots.
178
+
179
+ ```bash
180
+ zenitel provision -h 192.168.1.143 \
181
+ --domain testing-mo16m3gw.sip.twilio.com \
182
+ --sip-user zenitel01 \
183
+ --sip-pass 'your-sip-password' \
184
+ --number portia-ae3c
185
+
186
+ # πŸ”§ Provisioning 192.168.1.143 from factory reset...
187
+ #
188
+ # SIP Domain: testing-mo16m3gw.sip.twilio.com
189
+ # SIP Auth User: zenitel01
190
+ # Call Button β†’: portia-ae3c@testing-mo16m3gw.sip.twilio.com
191
+ # Webcall: Enabled
192
+ # Auto-answer: Enabled
193
+ #
194
+ # βœ… Provisioned! Device is rebooting (~30s).
195
+ ```
196
+
197
+ > **Under the hood:** Downloads config backup, modifies 9 XML fields in `ipst_config.xml` (SIP identity, domain, auth credentials, transport, outbound proxy, DAK1 value, webcall enable, auto-answer mode), re-packs with correct tar checksums, uploads, and reboots. One operation, one reboot, zero manual steps.
198
+
199
+ ### Device Reboot
200
+
201
+ ```bash
202
+ zenitel reboot -h 192.168.1.143
203
+ # Device will be offline for ~30 seconds
204
+ ```
205
+
206
+ ### CLI Options
207
+
208
+ | Flag | Short | Description | Default |
209
+ |------|-------|-------------|---------|
210
+ | `--host` | `-h` | IP address of the Zenitel | required |
211
+ | `--user` | `-u` | Web UI username | `admin` |
212
+ | `--pass` | `-p` | Web UI password | (device default) |
213
+ | `--id` | | Relay ID (`relay1`, `gpio1`–`gpio6`) | `relay1` |
214
+ | `--timer` | | Relay timer in seconds | `3` |
215
+ | `--timeout` | | Scan timeout in ms | `5000` |
216
+ | `--off` | | Deactivate relay | |
217
+ | `--domain` | | SIP domain | |
218
+ | `--number` | | SIP directory number / agent number | |
219
+ | `--proxy` | | Outbound proxy | |
220
+ | `--transport` | | SIP transport (`udp`/`tcp`/`tls`) | |
221
+ | `--name` | | Display name | |
222
+ | `--out` | `-o` | Backup output filename | `ipst_config.tar.gz` |
223
+ | `--no-reboot` | | Skip reboot after DAK set | |
224
+ | `--sip-user` | | SIP auth username (provision) | |
225
+ | `--sip-pass` | | SIP auth password (provision) | |
226
+
227
+ ---
228
+
229
+ ## API
230
+
231
+ ### `ZenitelClient`
232
+
233
+ The main class for interacting with a single Zenitel intercom via its web UI (HTTP).
234
+
235
+ ```typescript
236
+ import { ZenitelClient } from '@pinecall/zenitel-client';
237
+
238
+ const z = new ZenitelClient({
239
+ host: '192.168.1.143',
240
+ user: 'admin', // default
241
+ password: 'your-password'
242
+ });
243
+ ```
244
+
245
+ #### Connectivity
246
+
247
+ ```typescript
248
+ const reachable = await z.isReachable(); // true | false
249
+ ```
250
+
251
+ #### Device Info
252
+
253
+ ```typescript
254
+ const info = await z.getDeviceInfo();
255
+ // {
256
+ // model: 'TCIV-2+',
257
+ // firmware: '9.2.3.0',
258
+ // mac: '00:13:cb:28:35:ca',
259
+ // ip: '192.168.1.143',
260
+ // hostname: 'zenitel01',
261
+ // serialNumber: '22040000',
262
+ // hardwareType: '8801',
263
+ // mode: 'sip',
264
+ // hasCamera: true,
265
+ // sipDomain: 'testing-mo16m3gw.sip.twilio.com',
266
+ // sipRegistered: true,
267
+ // sipNumber: 'zenitel01',
268
+ // outboundProxy: 'testing-mo16m3gw.sip.twilio.com',
269
+ // uptime: 'up 2 days, 20 hours, 3 minutes',
270
+ // webcallEnabled: true,
271
+ // platform: 'turbine',
272
+ // systemModelName: 'Turbine Compact - Video Plus'
273
+ // }
274
+ ```
275
+
276
+ #### Webcall (Place / Stop / Answer calls)
277
+
278
+ ```typescript
279
+ await z.placeCall('122'); // Dial a number
280
+ await z.placeCall('sip:user@domain');
281
+ await z.stopCall(); // Hang up
282
+ await z.answerCall(); // Answer incoming call
283
+ const status = await z.getCallStatus(); // 'Idle' | 'Calling' | 'Connected' | 'Ringing'
284
+ ```
285
+
286
+ #### Relay / Door Control
287
+
288
+ ```typescript
289
+ // Open door (relay1, 3 seconds)
290
+ await z.activateRelay({ relayId: 'relay1', timer: 3 });
291
+
292
+ // Activate GPIO output 2 for 5 seconds
293
+ await z.activateRelay({ relayId: 'gpio2', timer: 5 });
294
+
295
+ // Deactivate relay
296
+ await z.deactivateRelay('relay1');
297
+
298
+ // Get all relay statuses
299
+ const relays = await z.getRelayStatus();
300
+ // { relay1: 'Deactivated', gpio1: 'Deactivated', ..., gpio6: 'Deactivated' }
301
+ ```
302
+
303
+ Available relay IDs: `relay1`, `gpio1`, `gpio2`, `gpio3`, `gpio4`, `gpio5`, `gpio6`.
304
+
305
+ #### SIP Configuration
306
+
307
+ ```typescript
308
+ // Read
309
+ const sip = await z.getSIPConfig();
310
+ // {
311
+ // displayName: 'zenitel01',
312
+ // directoryNumber: 'zenitel01',
313
+ // domain: 'testing-mo16m3gw.sip.twilio.com',
314
+ // authUsername: 'zenitel01',
315
+ // authPassword: '********',
316
+ // outboundProxy: 'testing-mo16m3gw.sip.twilio.com',
317
+ // transport: 'UDP'
318
+ // }
319
+
320
+ // Write (partial updates supported)
321
+ await z.setSIPConfig({
322
+ domain: 'my-trunk.sip.twilio.com',
323
+ directoryNumber: 'station01',
324
+ outboundProxy: 'my-trunk.sip.twilio.com',
325
+ });
326
+
327
+ // Reboot required for SIP changes to take effect
328
+ await z.reboot();
329
+ ```
330
+
331
+ #### Webcall Management
332
+
333
+ ```typescript
334
+ await z.enableWebcall(); // Enable HTTP webcall + relay API
335
+ await z.disableWebcall(); // Disable it
336
+ ```
337
+
338
+ > **Note:** Firmware β‰₯4.11.3.1 disables webcall by default. You must enable it for relay and call control to work via HTTP.
339
+
340
+ #### Config Backup & Restore
341
+
342
+ ```typescript
343
+ // Download full config as tar.gz
344
+ const backup = await z.downloadConfig();
345
+ fs.writeFileSync('backup.tar.gz', backup);
346
+
347
+ // Upload config (apply on next reboot)
348
+ const tarGz = fs.readFileSync('backup.tar.gz');
349
+ await z.uploadConfig(tarGz);
350
+ await z.reboot();
351
+ ```
352
+
353
+ The backup archive contains:
354
+ - `config/ipst_config.xml` β€” Main configuration (SIP, relays, audio, network)
355
+ - `config/zapconfig.json` β€” ZAP configuration
356
+ - `config/snmpd.conf` β€” SNMP configuration
357
+ - `config/certs/` β€” TLS certificates
358
+ - `config/ui/logos/` β€” Custom logos
359
+ - `config/ui/background_images/` β€” Background images
360
+
361
+ #### Video
362
+
363
+ ```typescript
364
+ const mjpgUrl = z.getMJPGUrl();
365
+ // "http://192.168.1.143/mjpg/video.mjpg"
366
+ // Use in <img> tags for live feed (Basic Auth required)
367
+
368
+ const rtspUrl = z.getRTSPUrl();
369
+ // "rtsp://192.168.1.143:554/1/RTSP"
370
+
371
+ const auth = z.getVideoAuth();
372
+ // { user: 'admin', password: '***' }
373
+ ```
374
+
375
+ **MJPG in a browser/Electron:**
376
+ ```html
377
+ <!-- Direct (if no auth required or same-origin) -->
378
+ <img src="http://192.168.1.143/mjpg/video.mjpg" />
379
+
380
+ <!-- In Electron with protocol handler (recommended) -->
381
+ <img src="portia-cam:///?ip=192.168.1.143&user=admin&pass=YOUR_PASS" />
382
+ ```
383
+
384
+ #### Call Button (DAK) Configuration
385
+
386
+ The call button is the physical button on the intercom that triggers a SIP call. Its target number is stored in the `ipst_config.xml` inside the device firmware.
387
+
388
+ ```typescript
389
+ // Read current call button configuration
390
+ const dak = await z.readDAK();
391
+ // {
392
+ // number: '222',
393
+ // domain: 'testing-mo16m3gw.sip.twilio.com',
394
+ // raw: '222@testing-mo16m3gw.sip.twilio.com'
395
+ // }
396
+
397
+ // Configure call button to dial a random Portia agent
398
+ // Flow: download backup β†’ modify XML β†’ upload β†’ reboot
399
+ await z.configureCallButton('portia-ae3c');
400
+ // Domain auto-detected from current SIP config
401
+
402
+ // With explicit domain + skip reboot
403
+ await z.configureCallButton('portia-ae3c', 'my.sip.twilio.com', false);
404
+ ```
405
+
406
+ > **Under the hood:** `configureCallButton()` downloads the full config as `tar.gz`, uses a built-in tar parser (zero dependencies) to locate and modify `ipst_config.xml`, recalculates tar checksums, re-compresses, uploads, and optionally reboots. The entire process takes ~2 seconds.
407
+
408
+ #### Full Device Provisioning
409
+
410
+ One-shot setup for a factory-reset Zenitel. Configures SIP credentials, call button, webcall, and auto-answer in a single backup→upload→reboot cycle.
411
+
412
+ ```typescript
413
+ import type { ProvisionConfig } from '@pinecall/zenitel-client';
414
+
415
+ await z.provisionDevice({
416
+ sipDomain: 'testing-mo16m3gw.sip.twilio.com',
417
+ sipAuthUser: 'your-sip-user',
418
+ sipAuthPassword: 'your-sip-password',
419
+ agentNumber: 'portia-ae3c', // Call button will dial this
420
+ // Optional:
421
+ sipProxy: 'proxy.sip.twilio.com', // Default: sipDomain
422
+ sipTransport: 'UDP', // Default: UDP
423
+ stationName: 'Lobby Intercom', // Default: agentNumber
424
+ enableWebcall: true, // Default: true
425
+ autoAnswer: true, // Default: true
426
+ });
427
+ // Device reboots automatically after upload
428
+ ```
429
+
430
+ **XML fields modified** (9 total):
431
+ | XML Element | Value |
432
+ |---|---|
433
+ | `<sip_nick>` | stationName |
434
+ | `<sip_id>` | stationName |
435
+ | `<sip_domain>` | sipDomain |
436
+ | `<sip_auth_user>` | sipAuthUser |
437
+ | `<sip_auth_password>` | sipAuthPassword |
438
+ | `<sip_outbound_transport>` | sipTransport |
439
+ | `<sip_outbound_proxy_address>` | sipProxy |
440
+ | `<dak1><val>` | agentNumber@sipDomain |
441
+ | `<enable_wc_r>` | 1 |
442
+ | `<auto_answer_mode>` | 1 |
443
+
444
+ #### Reboot
445
+
446
+ ```typescript
447
+ await z.reboot();
448
+ // Device will be offline for approximately 30 seconds
449
+ ```
450
+
451
+ ---
452
+
453
+ ### `scanNetwork()`
454
+
455
+ Discovers Zenitel intercoms on the local network using multiple strategies.
456
+
457
+ ```typescript
458
+ import { scanNetwork } from '@pinecall/zenitel-client';
459
+
460
+ const devices = await scanNetwork();
461
+ // [
462
+ // {
463
+ // ip: '192.168.1.143',
464
+ // mac: '00:13:cb:28:35:ca',
465
+ // model: 'TCIV-2+',
466
+ // firmware: '9.2.3.0',
467
+ // hasCamera: true,
468
+ // hostname: 'zenitel01',
469
+ // mode: 'sip'
470
+ // }
471
+ // ]
472
+ ```
473
+
474
+ #### Scan Options
475
+
476
+ ```typescript
477
+ const devices = await scanNetwork({
478
+ timeout: 5000, // Scan timeout (ms)
479
+ subnet: '192.168.1.0/24', // Auto-detected if omitted
480
+ strategies: ['arp-oui', 'http-probe'] // Default: both
481
+ });
482
+ ```
483
+
484
+ #### Discovery Strategies
485
+
486
+ | Strategy | Method | Pros | Cons |
487
+ |----------|--------|------|------|
488
+ | **`arp-oui`** | Parse ARP table, filter by MAC prefix `00:13:CB` | Fast, no scanning | Requires recent ARP entry |
489
+ | **`http-probe`** | Probe every IP in subnet, look for `zenitel.js` | Works without ARP cache | Slow on large subnets |
490
+
491
+ Results from all strategies are **merged by IP** β€” if the same device is found by multiple strategies, the entry with the most data wins.
492
+
493
+ ---
494
+
495
+ ## Supported Hardware
496
+
497
+ Confirmed on Zenitel Turbine platform (hardware type `8801`):
498
+
499
+ | Model | Camera | Relay | Webcall | SIP Config | DAK | Firmware |
500
+ |-------|--------|-------|---------|------------|-----|----------|
501
+ | **TCIV-2+** | βœ… MJPG/RTSP | βœ… relay1 + gpio1–6 | βœ… | βœ… | βœ… | 9.2.3.0 |
502
+ | TCIV-3+ | βœ… | βœ… | βœ… | βœ… | βœ… | Expected compatible |
503
+ | TCIS-2 | ❌ | βœ… | βœ… | βœ… | βœ… | Expected compatible |
504
+
505
+ ## Goform Endpoint Map
506
+
507
+ All communication uses the Zenitel web UI's goform API:
508
+
509
+ | Endpoint | Method | Purpose |
510
+ |----------|--------|---------|
511
+ | `/` | GET | Login page (302 redirect) |
512
+ | `/goform/zForm_header` | GET | Device hidden fields (mode, platform, hwtype) |
513
+ | `/goform/zForm_stn_info` | GET | Station info table (model, MAC, firmware, SIP status) |
514
+ | `/goform/zForm_webcall` | GET/POST | Webcall + relay control |
515
+ | `/goform/zForm_sip_configuration` | GET | SIP config form fields |
516
+ | `/goform/zForm_save_changes` | POST | Write SIP configuration |
517
+ | `/goform/zForm_config_backup` | POST | Config restore (multipart upload) |
518
+ | `/ipst_config.tar.gz` | GET | Config backup download |
519
+ | `/goform/zForm_system_prefs` | POST | Reboot device |
520
+ | `/mjpg/video.mjpg` | GET | Live MJPG video stream (port 80) |
521
+
522
+ Authentication: **HTTP Basic Auth** on all endpoints.
523
+
524
+ ## License
525
+
526
+ Apache-2.0
package/dist/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * zenitel β€” CLI for testing Zenitel intercom interaction
4
+ *
5
+ * Usage:
6
+ * zenitel scan Discover Zenitel devices on the network
7
+ * zenitel info -h 192.168.1.143 Get device info
8
+ * zenitel relay -h 192.168.1.143 Activate relay1 for 3 seconds
9
+ * zenitel relay -h ... --id gpio1 --timer 5
10
+ * zenitel call 122 -h 192.168.1.143 Place a call
11
+ * zenitel stop -h 192.168.1.143 Stop current call
12
+ * zenitel status -h 192.168.1.143 Get relay + call status
13
+ */
14
+ export {};