tciv-client 0.0.4 → 0.0.6
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 +43 -0
- package/dist/cli.js +35 -0
- package/dist/client.d.ts +27 -1
- package/dist/client.js +85 -18
- package/package.json +30 -8
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ This library is intended for **system integrators and IT administrators** managi
|
|
|
29
29
|
- [Provisioning](#full-device-provisioning)
|
|
30
30
|
- [Video](#video)
|
|
31
31
|
- [Factory Reset](#factory-reset)
|
|
32
|
+
- [Mode (SIP/Edge)](#mode-sipedge)
|
|
32
33
|
- [Reboot](#device-reboot)
|
|
33
34
|
- [API](#api)
|
|
34
35
|
- [Connectivity](#connectivity)
|
|
@@ -240,6 +241,27 @@ tciv factory-reset -h 192.168.1.143 --keep-ip
|
|
|
240
241
|
tciv factory-reset -h 192.168.1.143 --dhcp
|
|
241
242
|
```
|
|
242
243
|
|
|
244
|
+
### Mode (SIP/Edge)
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Read current mode
|
|
248
|
+
tciv mode -h 192.168.1.143
|
|
249
|
+
# 📡 Current mode: SIP (sip)
|
|
250
|
+
|
|
251
|
+
# Switch to SIP mode (reboots device, waits ~40s)
|
|
252
|
+
tciv mode set sip -h 192.168.1.143
|
|
253
|
+
|
|
254
|
+
# Switch to Edge mode
|
|
255
|
+
tciv mode set exc -h 192.168.1.143
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
| Mode value | Description |
|
|
259
|
+
|------------|-------------|
|
|
260
|
+
| `sip` | SIP mode (required for Portia) |
|
|
261
|
+
| `dip` | ICX-AlphaCom |
|
|
262
|
+
| `exc` | Edge |
|
|
263
|
+
| `srv` | Edge Controller |
|
|
264
|
+
|
|
243
265
|
### CLI Options
|
|
244
266
|
|
|
245
267
|
| Flag | Short | Description | Default |
|
|
@@ -434,6 +456,27 @@ await z.factoryReset('dhcp');
|
|
|
434
456
|
| `dhcp` | All defaults, DHCP enabled |
|
|
435
457
|
| `keep-ip` | All defaults, current IP preserved |
|
|
436
458
|
|
|
459
|
+
#### Mode
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// Read current mode
|
|
463
|
+
const mode = await z.getMode();
|
|
464
|
+
// 'sip' | 'dip' | 'pulse' (Edge) | 'srv'
|
|
465
|
+
|
|
466
|
+
// Switch to SIP mode
|
|
467
|
+
await z.setMode('sip');
|
|
468
|
+
await z.applyChanges(); // triggers reboot
|
|
469
|
+
await z.waitForReboot(); // polls until online (~40s)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### Wait for Reboot
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
// Poll until device comes back online (default 60s timeout)
|
|
476
|
+
const online = await z.waitForReboot(60000, 3000);
|
|
477
|
+
// true = device is back, false = timeout
|
|
478
|
+
```
|
|
479
|
+
|
|
437
480
|
---
|
|
438
481
|
|
|
439
482
|
### `scanNetwork()`
|
package/dist/cli.js
CHANGED
|
@@ -229,6 +229,41 @@ async function main() {
|
|
|
229
229
|
console.log(`⚠️ Factory reset (${labels[mode]}) — all settings will be lost!`);
|
|
230
230
|
await client.factoryReset(mode);
|
|
231
231
|
console.log('✅ Factory reset sent. Device will reboot with default settings.');
|
|
232
|
+
if (!hasFlag('no-wait')) {
|
|
233
|
+
console.log('⏳ Waiting for device to come back online...');
|
|
234
|
+
const ok = await client.waitForReboot(90000);
|
|
235
|
+
console.log(ok ? '✅ Device is back online!' : '⚠️ Timeout — device may still be rebooting.');
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case 'mode': {
|
|
240
|
+
const client = getClient();
|
|
241
|
+
const subCmd = args[1]; // get | set
|
|
242
|
+
if (subCmd === 'set') {
|
|
243
|
+
const target = args[2];
|
|
244
|
+
if (!target || !['sip', 'dip', 'exc', 'srv'].includes(target)) {
|
|
245
|
+
console.error('Usage: tciv mode set <sip|dip|exc|srv>');
|
|
246
|
+
console.log(' sip = SIP mode');
|
|
247
|
+
console.log(' dip = ICX-AlphaCom');
|
|
248
|
+
console.log(' exc = Edge');
|
|
249
|
+
console.log(' srv = Edge Controller');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
console.log(`🔄 Setting mode to ${target}...`);
|
|
253
|
+
await client.setMode(target);
|
|
254
|
+
console.log('📤 Applying changes (device will reboot)...');
|
|
255
|
+
await client.applyChanges();
|
|
256
|
+
if (!hasFlag('no-wait')) {
|
|
257
|
+
console.log('⏳ Waiting for device to come back online...');
|
|
258
|
+
const ok = await client.waitForReboot(90000);
|
|
259
|
+
console.log(ok ? '✅ Device is back online!' : '⚠️ Timeout — device may still be rebooting.');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
const mode = await client.getMode();
|
|
264
|
+
const labels = { sip: 'SIP', dip: 'ICX-AlphaCom', exc: 'Edge', srv: 'Edge Controller', pulse: 'Edge' };
|
|
265
|
+
console.log(`📡 Current mode: ${labels[mode] || mode} (${mode})`);
|
|
266
|
+
}
|
|
232
267
|
break;
|
|
233
268
|
}
|
|
234
269
|
case 'video': {
|
package/dist/client.d.ts
CHANGED
|
@@ -43,7 +43,11 @@ export declare class TcivClient {
|
|
|
43
43
|
};
|
|
44
44
|
/** Read current SIP config by scraping the form fields */
|
|
45
45
|
getSIPConfig(): Promise<SIPConfig>;
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Write SIP config via POST to zForm_save_changes.
|
|
48
|
+
* The Zenitel form requires ALL fields to be present — partial submissions are silently ignored.
|
|
49
|
+
* Submit field is `sipconfig=SAVE` (not signallingMode).
|
|
50
|
+
*/
|
|
47
51
|
setSIPConfig(config: SIPConfig): Promise<void>;
|
|
48
52
|
/** Enable webcall + relay HTTP API (required for FW ≥4.11.3.1) */
|
|
49
53
|
enableWebcall(): Promise<void>;
|
|
@@ -137,6 +141,28 @@ export declare class TcivClient {
|
|
|
137
141
|
* - 'keep-ip' → Factory defaults but keep current IP settings
|
|
138
142
|
*/
|
|
139
143
|
factoryReset(mode?: 'full' | 'dhcp' | 'keep-ip'): Promise<void>;
|
|
144
|
+
/**
|
|
145
|
+
* Get the current device mode.
|
|
146
|
+
* Returns 'sip', 'dip' (ICX-AlphaCom), 'exc' (Edge), or 'srv' (Edge Controller).
|
|
147
|
+
*/
|
|
148
|
+
getMode(): Promise<string>;
|
|
149
|
+
/**
|
|
150
|
+
* Set device mode. Requires applyChanges() + reboot afterwards.
|
|
151
|
+
* @param mode - 'sip' | 'dip' (ICX-AlphaCom) | 'exc' (Edge) | 'srv' (Edge Controller)
|
|
152
|
+
*/
|
|
153
|
+
setMode(mode: 'sip' | 'dip' | 'exc' | 'srv'): Promise<void>;
|
|
154
|
+
/**
|
|
155
|
+
* Send APPLY command to trigger reboot after config changes.
|
|
156
|
+
* The device responds with "System is rebooting..." and goes offline.
|
|
157
|
+
*/
|
|
158
|
+
applyChanges(): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Poll the device until it comes back online after a reboot.
|
|
161
|
+
* @param timeoutMs - Max wait time (default 60s)
|
|
162
|
+
* @param intervalMs - Poll interval (default 3s)
|
|
163
|
+
* @returns true if device came back, false if timeout
|
|
164
|
+
*/
|
|
165
|
+
waitForReboot(timeoutMs?: number, intervalMs?: number): Promise<boolean>;
|
|
140
166
|
private _fetch;
|
|
141
167
|
private _html;
|
|
142
168
|
private _post;
|
package/dist/client.js
CHANGED
|
@@ -188,24 +188,41 @@ export class TcivClient {
|
|
|
188
188
|
transport: select('sip_outbound_transport'),
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
|
-
/**
|
|
191
|
+
/**
|
|
192
|
+
* Write SIP config via POST to zForm_save_changes.
|
|
193
|
+
* The Zenitel form requires ALL fields to be present — partial submissions are silently ignored.
|
|
194
|
+
* Submit field is `sipconfig=SAVE` (not signallingMode).
|
|
195
|
+
*/
|
|
192
196
|
async setSIPConfig(config) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
197
|
+
// Read current values first (read-modify-write)
|
|
198
|
+
const current = await this.getSIPConfig();
|
|
199
|
+
const fields = {
|
|
200
|
+
sip_nick: config.displayName ?? current.displayName ?? '',
|
|
201
|
+
sip_id: config.directoryNumber ?? current.directoryNumber ?? '',
|
|
202
|
+
sip_domain: config.domain ?? current.domain ?? '',
|
|
203
|
+
sip_domain2: config.domain ?? current.domain ?? '',
|
|
204
|
+
sip_domain3: config.domain ?? current.domain ?? '',
|
|
205
|
+
registration_method: '0',
|
|
206
|
+
sip_auth_user: config.authUsername ?? current.authUsername ?? '',
|
|
207
|
+
sip_auth_pwd: config.authPassword ?? current.authPassword ?? '',
|
|
208
|
+
register_interval: '100',
|
|
209
|
+
fail_interval: '60',
|
|
210
|
+
sip_ppa: config.outboundProxy ?? current.outboundProxy ?? '',
|
|
211
|
+
sip_ppp: '5060',
|
|
212
|
+
sip_outbound_proxy_backup_address: '',
|
|
213
|
+
sip_outbound_proxy_backup_port: '5060',
|
|
214
|
+
sip_outbound_proxy_backup_address2: '',
|
|
215
|
+
sip_outbound_proxy_backup_port2: '5060',
|
|
216
|
+
sip_outbound_transport: (config.transport ?? current.transport ?? 'udp').toUpperCase(),
|
|
217
|
+
sip_scheme: 'sip',
|
|
218
|
+
rtp_encryption: 'disabled',
|
|
219
|
+
srtp_crypto_type: 'AES_CM_128_HMAC_SHA1_80',
|
|
220
|
+
multicast_relay: '',
|
|
221
|
+
auto_answer_mode: 'on',
|
|
222
|
+
auto_resume_call: 'on',
|
|
223
|
+
mkey_dtmf: 'on',
|
|
224
|
+
sipconfig: 'SAVE',
|
|
225
|
+
};
|
|
209
226
|
await this._post('/goform/zForm_save_changes', fields);
|
|
210
227
|
}
|
|
211
228
|
// ── Webcall Enable/Disable ─────────────────────────────────────────────
|
|
@@ -242,7 +259,8 @@ export class TcivClient {
|
|
|
242
259
|
async getDAK(buttonIndex = 0) {
|
|
243
260
|
const res = await this._fetch('/goform/zForm_speeddial_configuration_basic_auth');
|
|
244
261
|
const html = await res.text();
|
|
245
|
-
|
|
262
|
+
// Attribute order varies: value may come before or after name
|
|
263
|
+
const match = html.match(new RegExp(`dak_value${buttonIndex}[^>]*value='([^']*)'`));
|
|
246
264
|
return match?.[1] || '';
|
|
247
265
|
}
|
|
248
266
|
// ── Config Backup / Restore ────────────────────────────────────────────
|
|
@@ -673,6 +691,55 @@ export class TcivClient {
|
|
|
673
691
|
// Device disconnects during reset — expected
|
|
674
692
|
}
|
|
675
693
|
}
|
|
694
|
+
// ── Mode (SIP / Edge / ICX-AlphaCom) ───────────────────────────────────
|
|
695
|
+
/**
|
|
696
|
+
* Get the current device mode.
|
|
697
|
+
* Returns 'sip', 'dip' (ICX-AlphaCom), 'exc' (Edge), or 'srv' (Edge Controller).
|
|
698
|
+
*/
|
|
699
|
+
async getMode() {
|
|
700
|
+
const info = await this.getDeviceInfo();
|
|
701
|
+
return info.mode || 'sip';
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Set device mode. Requires applyChanges() + reboot afterwards.
|
|
705
|
+
* @param mode - 'sip' | 'dip' (ICX-AlphaCom) | 'exc' (Edge) | 'srv' (Edge Controller)
|
|
706
|
+
*/
|
|
707
|
+
async setMode(mode) {
|
|
708
|
+
await this._post('/goform/zForm_save_changes', {
|
|
709
|
+
sigmode: mode,
|
|
710
|
+
signallingMode: 'SAVE',
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Send APPLY command to trigger reboot after config changes.
|
|
715
|
+
* The device responds with "System is rebooting..." and goes offline.
|
|
716
|
+
*/
|
|
717
|
+
async applyChanges() {
|
|
718
|
+
try {
|
|
719
|
+
await this._fetch('/goform/zForm_send_cmd?message=APPLY', 'GET', 5000);
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
// Device disconnects during reboot — expected
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
// ── Wait for reboot ────────────────────────────────────────────────────
|
|
726
|
+
/**
|
|
727
|
+
* Poll the device until it comes back online after a reboot.
|
|
728
|
+
* @param timeoutMs - Max wait time (default 60s)
|
|
729
|
+
* @param intervalMs - Poll interval (default 3s)
|
|
730
|
+
* @returns true if device came back, false if timeout
|
|
731
|
+
*/
|
|
732
|
+
async waitForReboot(timeoutMs = 60000, intervalMs = 3000) {
|
|
733
|
+
const start = Date.now();
|
|
734
|
+
// Wait a bit for device to actually go offline first
|
|
735
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
736
|
+
while (Date.now() - start < timeoutMs) {
|
|
737
|
+
if (await this.isReachable())
|
|
738
|
+
return true;
|
|
739
|
+
await new Promise(r => setTimeout(r, intervalMs));
|
|
740
|
+
}
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
676
743
|
// ── Internal helpers ────────────────────────────────────────────────────
|
|
677
744
|
async _fetch(path, method = 'GET', timeout, body, contentType) {
|
|
678
745
|
const controller = new AbortController();
|
package/package.json
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tciv-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "HTTP client for TCIV-series intercom systems (TCIV-2+, TCIV-3). Control relays, SIP configuration, DAK provisioning, webcall, audio settings, and camera feeds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"import": {
|
|
11
|
-
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
12
18
|
},
|
|
13
19
|
"./scanner": {
|
|
14
|
-
"import": {
|
|
15
|
-
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/scanner.d.ts",
|
|
22
|
+
"default": "./dist/scanner.js"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/scanner.d.ts",
|
|
26
|
+
"default": "./dist/scanner.js"
|
|
27
|
+
}
|
|
16
28
|
}
|
|
17
29
|
},
|
|
18
30
|
"bin": {
|
|
@@ -30,10 +42,20 @@
|
|
|
30
42
|
"tsx": "^4.21.0",
|
|
31
43
|
"@types/node": "^22.0.0"
|
|
32
44
|
},
|
|
33
|
-
"files": [
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"README.md"
|
|
48
|
+
],
|
|
34
49
|
"keywords": [
|
|
35
|
-
"intercom",
|
|
36
|
-
"
|
|
50
|
+
"intercom",
|
|
51
|
+
"TCIV",
|
|
52
|
+
"SIP",
|
|
53
|
+
"door-access",
|
|
54
|
+
"relay",
|
|
55
|
+
"DTMF",
|
|
56
|
+
"webcall",
|
|
57
|
+
"building-automation",
|
|
58
|
+
"tciv-client"
|
|
37
59
|
],
|
|
38
60
|
"license": "Apache-2.0",
|
|
39
61
|
"repository": {
|