Skip to content

Commit b2f1383

Browse files
committed
docs(implementations/js/client): document client
1 parent 2250767 commit b2f1383

1 file changed

Lines changed: 255 additions & 0 deletions

File tree

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# WSC (Web Show Control) Client
2+
3+
A WebRTC-based client that connects to a [WSC-compatible gateway](https://github.com/ASLS-org/WSC/tree/main/implementations/js), and handles the creation, transmission, and parsing of WSC packets.
4+
5+
>**About WSC:**<br>
6+
[WSC](https://github.com/ASLS-org/WSC) defines a unified wire format that transports DMX data, linear timecode, cue triggers, structured parameter updates, and arbitrary binary payloads. A gateway can then relay this data to downstream protocols such as Art-Net, sACN, OSC, MIDI, Modbus, and others.
7+
8+
## Quick Start
9+
10+
Install the following dependencies in your project:
11+
```bash
12+
npm install -S @asls/wsc-sdk
13+
npm install -S @asls/wsc-client
14+
```
15+
16+
Import the client and the SDK binding:
17+
18+
```js
19+
import { WscClient, WSC_REMOTE_STATE, KEEPALIVE_INTERVAL } from '@asls/wsc-client';
20+
import { WscPacket, WscTransport, WscFlags, WscAddress } from '@asls/wsc-sdk';
21+
```
22+
23+
### 1. Connect to a gateway
24+
25+
```js
26+
const client = new WscClient(
27+
'192.168.1.50', // gateway host or IP
28+
4515, // signaling WebSocket port
29+
onOpen,
30+
onMessage,
31+
onClose,
32+
onError,
33+
);
34+
35+
client.connect();
36+
37+
function onOpen() {
38+
console.log('Connected — WSC ready');
39+
client.startKeepAliveSession();
40+
}
41+
42+
function onMessage(packet) {
43+
const decoded = WscPacket.decode(packet);
44+
console.log(`${packet.typeName()}`, decoded);
45+
}
46+
47+
function onClose() {
48+
console.log('Disconnected');
49+
}
50+
51+
function onError(err) {
52+
console.error('Error:', err);
53+
}
54+
```
55+
56+
### 2. Stream DMX channels via Art-Net
57+
58+
```js
59+
const values = new Uint8Array(512).fill(0);
60+
values[0] = 255; // channel 1 full
61+
values[1] = 128; // channel 2 half
62+
63+
const packet = WscPacket.create(
64+
WscPacket.Type.STREAM_CHANNELS,
65+
{ universe: 0, startChannel: 1, values },
66+
{
67+
flags: new WscFlags(true, true), // TR + GW
68+
transport: WscTransport.udp(
69+
WscTransport.Protocol.ARTNET,
70+
'127.0.0.1',
71+
6454,
72+
),
73+
},
74+
);
75+
76+
client.send(packet);
77+
```
78+
79+
### 3. Fire a cue via OSC
80+
81+
```js
82+
const packet = WscPacket.create(
83+
WscPacket.Type.CONTROL_CUE,
84+
{
85+
address: WscAddress.parse('lighting.cue.42'),
86+
action: WscPacket.CueAction.START,
87+
},
88+
{
89+
flags: new WscFlags(true, true), // TR + GW
90+
transport: WscTransport.udp(
91+
WscTransport.Protocol.OSC,
92+
'127.0.0.1',
93+
8000,
94+
),
95+
},
96+
);
97+
98+
client.send(packet);
99+
```
100+
101+
### 4. Set a named parameter via OSC
102+
103+
```js
104+
const packet = WscPacket.create(
105+
WscPacket.Type.CONTROL_PARAM,
106+
{
107+
address: WscAddress.parse('lighting.layer.2.intensity'),
108+
valueType: WscPacket.ValueType.F32,
109+
value: 0.75,
110+
},
111+
{
112+
flags: new WscFlags(true, true),
113+
transport: WscTransport.udp(
114+
WscTransport.Protocol.OSC,
115+
'127.0.0.1',
116+
8000,
117+
),
118+
},
119+
);
120+
121+
client.send(packet);
122+
```
123+
124+
### 5. Stream linear timecode
125+
126+
```js
127+
const packet = WscPacket.create(
128+
WscPacket.Type.STREAM_TIMECODE,
129+
{ hours: 1, minutes: 0, seconds: 30, frames: 0, rate: 30 },
130+
);
131+
132+
client.send(packet);
133+
```
134+
135+
### 6. Tunnel raw bytes
136+
137+
```js
138+
const raw = new Uint8Array([0xF0, 0x42, 0x40, 0x7F, 0xF7]);
139+
140+
const packet = WscPacket.create(
141+
WscPacket.Type.TUNNEL_RAW,
142+
{ raw },
143+
{
144+
flags: new WscFlags(true, true),
145+
transport: WscTransport.raw(),
146+
},
147+
);
148+
149+
client.send(packet);
150+
```
151+
152+
---
153+
154+
## Client — API Reference
155+
156+
### `new WscClient(host, port, onOpen, onMessage, onClose, onError)`
157+
158+
| Parameter | Type | Description |
159+
|---|---|---|
160+
| `host` | `string` | Gateway hostname or IP address |
161+
| `port` | `number` | Signaling WebSocket port |
162+
| `onOpen` | `() => void` | Called when the first DataChannel is open |
163+
| `onMessage` | `(packet: WscPacket) => void` | Called for each received packet |
164+
| `onClose` | `() => void` | Called on disconnection |
165+
| `onError` | `(err: any) => void` | Called on transport or signaling error |
166+
167+
### Methods
168+
169+
| Method | Description |
170+
|---|---|
171+
| `connect()` | Initiate WebSocket signaling and establish the DataChannel |
172+
| `send(packet)` | Serialize and write a `WscPacket` to the appropriate DataChannel; silently drops if the channel is not open |
173+
| `close()` | Disconnect cleanly and transition to `IDLE` |
174+
| `startKeepAliveSession()` | Begin sending `STATE_QUERY(KEEPALIVE)` every 1 000 ms |
175+
| `stopKeepaliveSession()` | Stop the keepalive timer |
176+
177+
### Properties
178+
179+
| Property | Type | Description |
180+
|---|---|---|
181+
| `state` | `WSC_REMOTE_STATE` | Current connection state |
182+
| `debug` | `DebugEntry[]` | Rolling debug log, last 100 entries |
183+
184+
### `WSC_REMOTE_STATE`
185+
186+
| Value | Meaning |
187+
|---|---|
188+
| `IDLE` (`0`) | Not connected |
189+
| `CONNECTING` (`1`) | Signaling in progress |
190+
| `CONNECTED` (`2`) | DataChannel open |
191+
| `ERROR` (`-1`) | Unrecoverable transport error |
192+
193+
---
194+
195+
## DataChannel naming
196+
197+
The client creates one DataChannel per message type, named:
198+
199+
```
200+
WSC!DC:<TYPE_NAME>
201+
```
202+
203+
For example: `WSC!DC:STREAM_CHANNELS`, `WSC!DC:CONTROL_CUE`.
204+
205+
The gateway identifies WSC channels by the `WSC!DC` prefix. Response channels used by the server:
206+
207+
| Channel | Purpose |
208+
|---|---|
209+
| `WSC!DC:STATE_QUERY` | Carries `STATE_ANSWER` responses |
210+
| `WSC!DC:STATE_ERROR` | Carries `STATE_ERROR` responses |
211+
212+
---
213+
214+
## Validation
215+
216+
Always validate packets in development to catch flag and compatibility errors early:
217+
218+
```js
219+
const { valid, errors, warnings } = WscPacket.validate(packet);
220+
221+
if (!valid) {
222+
console.error('Invalid packet:', errors);
223+
return;
224+
}
225+
if (warnings.length) {
226+
console.warn('Packet warnings:', warnings);
227+
}
228+
229+
client.send(packet);
230+
```
231+
232+
---
233+
234+
## Connection lifecycle example
235+
236+
```js
237+
let client = null;
238+
239+
function connect(host, port) {
240+
client = new WscClient(host, port,
241+
() => {
242+
client.startKeepAliveSession();
243+
},
244+
(packet) => {
245+
// handle incoming STATE_ANSWER, STATE_ERROR, etc.
246+
},
247+
() => {
248+
client = null;
249+
setTimeout(() => connect(host, port), 2000);
250+
},
251+
(err) => console.error(err),
252+
);
253+
client.connect();
254+
}
255+
```

0 commit comments

Comments
 (0)