-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathcontroller.ts
More file actions
313 lines (254 loc) · 9.23 KB
/
controller.ts
File metadata and controls
313 lines (254 loc) · 9.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
import { EventEmitter } from 'events'
import { Constants as C } from './constants'
import { ViscaCommand } from "./command"
import { Camera } from "./camera"
import { SerialTransport } from './visca-serial'
import { UDPData, UDPTransport, ViscaServer } from "./visca-ip"
// VISCA cameras are identified by an id which in hard-wired cameras is counted
// upward from the first camera in the chain being #1. IP cameras are accessed
// directly with their IP address and therefore always have an id of 1.
//
// [name] is a user-readable name for the camera.
export interface ViscaCameraConfig {
name: string,
id: number,
ip: string,
port: number,
}
export interface ViscaControllerConfig {
// serial port details for talking to visca cameras
viscaSerial: {
port: string, // 'COM8' or /dev/ttyUSB0 etc
baud: number, // usually 9600 or 38400
},
// configuration for the visca ip translation server
// the http server will reside at the basePort
// udp servers will exist at basePort + cameraIndex
viscaServer: {
basePort: number,
},
}
// the controller keeps track of the cameras connected by serial
// it also communicates with cameras over IP
// and it exposes a UDP server for each serially connected camera
export class ViscaController extends EventEmitter {
serialConnection: SerialTransport;
ipServers: ViscaServer[] = [];
serialBroadcastCommands: ViscaCommand[] = []; // FIFO stack of serial commands sent
cameras: {[index:string]: Camera} = {}; // will be indexed with uuid strings for ip cameras
cameraCount = 0;
constructor(public config: ViscaControllerConfig) {
super();
}
init() {
this.cameras = {};
this.cameraCount = 0;
}
// uuid will be generated when the data comes from an IP camera
addIPCamera(c: ViscaCameraConfig, doInquire: boolean = true) : Camera {
let transport = new UDPTransport(c.ip, c.port);
transport.on('data', ({uuid, viscaCommand}) => this.onUDPData({uuid, viscaCommand}));
let camera = new Camera(1, transport, c.name); // IP cameras all have index 1
this.cameras[transport.uuid] = camera;
camera.sendCommand(ViscaCommand.cmdInterfaceClearAll(1));
if (doInquire) camera.inquireAll();
return camera;
}
// manage the serial transport
restartSerial() { this.closeSerial(); this.init(); this.startSerial(); }
closeSerial() { this.serialConnection.close(); }
startSerial(portname = "/dev/ttyUSB0", baudRate = 9600, timeout = 1, debug = false) {
this.serialConnection = new SerialTransport(portname, timeout, baudRate, debug);
//this.serialConnection.start();
// create callbacks
this.serialConnection.on('open', this.onSerialOpen.bind(this));
this.serialConnection.on('close', this.onSerialClose.bind(this));
this.serialConnection.on('error', this.onSerialError.bind(this));
this.serialConnection.on('data', this.onSerialData.bind(this));
// send enumeration command (on reply, we will send the IF clear command)
this.enumerateSerial();
}
onSerialOpen() { }
onSerialClose() { }
onSerialError(e:string) { console.log(e); }
onSerialData(viscaCommand:ViscaCommand) {
let v = viscaCommand;
// make sure we have this camera as an object if it came from a camera
// but leave the camera null if it was a broadcast command
let camera = null;
if (v.source != 0) {
if (!(v.source in this.cameras)) {
camera = new Camera(v.source, this.serialConnection);
camera.uuid = v.source.toString();
this.cameras[v.source] = camera;
} else {
camera = this.cameras[v.source];
}
}
if (camera != null) {
return this.onCameraData(camera, v);
}
// the following commands are 'passthrough' commands that
// go through the whole serial chain as broadcast commands
switch (v.msgType) {
case C.MSGTYPE_IF_CLEAR:
// reset data for all serial port cameras
for (let cam of Object.values(this.cameras)) {
if (cam.uuid == cam.index.toString()) cam._clear();
}
this.inquireAllSerial();
break;
// address set message, reset all serial port cameras
case C.MSGTYPE_ADDRESS_SET:
let highestIndex = v.data[0] - 1;
for (let i = 1; i <= highestIndex; i++) this.cameras[i] = new Camera(i, this.serialConnection);
for (let i = highestIndex + 1; i < 8; i++) delete (this.cameras[i]);
this.ifClearAllSerial();
this.setupIPProxies();
break;
default:
break;
}
this.emit('update');
}
onUDPData({ uuid, viscaCommand }:UDPData) {
let camera = this.cameras[uuid];
return this.onCameraData(camera, viscaCommand);
}
onCameraData(camera:Camera, v:ViscaCommand) {
switch (v.msgType) {
case C.MSGTYPE_IF_CLEAR:
camera._clear();
break;
// network change messages are unprompted
case C.MSGTYPE_NETCHANGE:
// a camera issues this when it detects a change on the serial line,
// and if we get it, we should re-assign all serial port cameras.
this.enumerateSerial();
break;
// ack message, one of our commands was accepted and put in a buffer
case C.MSGTYPE_ACK:
camera.ack(v);
return;
// completion message
case C.MSGTYPE_COMPLETE:
camera.complete(v);
break;
// error messages
case C.MSGTYPE_ERROR:
camera.error(v);
break;
default:
break;
}
this.emit('update');
}
sendSerial(viscaCommand:ViscaCommand) {
this.serialConnection.write(viscaCommand);
}
// forces a command to be a broadcast command (only applies to serial)
broadcastSerial(viscaCommand:ViscaCommand) {
viscaCommand.broadcast = true;
this.serialConnection.write(viscaCommand);
}
// forces a command to go to a specific camera
sendToCamera(camera:Camera, viscaCommand:ViscaCommand) {
camera.sendCommand(viscaCommand);
}
// system-level commands... only relevant to serial connections
enumerateSerial() {
this.sendSerial(ViscaCommand.addressSet());
}
ifClearAllSerial() {
this.sendSerial(ViscaCommand.cmdInterfaceClearAll());
}
// for each camera queue all the inquiry commands
// to get a full set of camera status data
inquireAllSerial() {
for (let camera of Object.values(this.cameras)) {
if (camera.transport == this.serialConnection) {
camera.inquireAll();
}
}
}
inquireAllIP() {
for (let camera of Object.values(this.cameras)) {
if (camera.transport.uuid) {
camera.inquireAll();
}
}
}
inquireAll() { this.inquireAllSerial(); this.inquireAllIP(); }
setupIPProxies() {
for (let server of this.ipServers) server.close();
this.ipServers = [];
for (let camera of Object.values(this.cameras)) {
if (camera.transport.uuid) continue;
let port = this.config.viscaServer.basePort + camera.index;
let server = new ViscaServer(port);
server.on('data', (viscaCommand: ViscaCommand) => {
this.onCameraData(camera, viscaCommand);
});
this.ipServers.push(server);
}
}
// for debugging
dump(packet:number[], title:string = null) {
if (!packet || packet.length == 0) return;
let header = packet[0];
let term = packet[packet.length - 2]; // last item
let qq = packet[1];
let sender = (header & 0b01110000) >> 4;
let broadcast = (header & 0b1000) >> 3;
let recipient = header & 0b0111;
let recipient_s;
if (broadcast) recipient_s = "*";
else recipient_s = recipient.toString();
console.log("-----");
if (title) console.log(`packet (${title}) [${sender} => ${recipient_s}] len=${packet.length}: ${packet}`);
else console.log(`packet [${sender} => ${recipient_s}] len=${packet.length}: ${packet}`);
console.log(` QQ.........: ${qq}`);
if (qq == 0x01) console.log(" (Command)");
if (qq == 0x09) console.log(" (Inquiry)");
if (packet.length > 3) {
let rr = packet[2];
console.log(` RR.........: ${rr}`);
if (rr == 0x00) console.log(" (Interface)");
if (rr == 0x04) console.log(" (Camera [1])");
if (rr == 0x06) console.log(" (Pan/Tilter)");
}
if (packet.length > 4) {
let data = packet.slice(3);
console.log(` Data.......: ${data}`);
} else console.log(" Data.......: null");
if (term !== 0xff) {
console.log("ERROR: Packet not terminated correctly");
return;
}
if (packet.length == 3 && (qq & 0b11110000) >> 4 == 4) {
let socketno = qq & 0b1111;
console.log(` packet: ACK for socket ${socketno}`);
}
if (packet.length == 3 && (qq & 0b11110000) >> 4 == 5) {
let socketno = qq & 0b1111;
console.log(` packet: COMPLETION for socket ${socketno}`);
}
if (packet.length > 3 && (qq & 0b11110000) >> 4 == 5) {
let socketno = qq & 0b1111;
let ret = packet.slice(2);
console.log(` packet: COMPLETION for socket ${socketno}, data=${ret}`);
}
if (packet.length == 4 && (qq & 0b11110000) >> 4 == 6) {
console.log(" packet: ERROR!");
let socketno = qq & 0b00001111;
let errcode = packet[2];
//these two are special, socket is zero && has no meaning:
if (errcode == 0x02 && socketno == 0) console.log(" : Syntax Error");
if (errcode == 0x03 && socketno == 0) console.log(" : Command Buffer Full");
if (errcode == 0x04) console.log(` : Socket ${socketno}: Command canceled`);
if (errcode == 0x05) console.log(` : Socket ${socketno}: Invalid socket selected`);
if (errcode == 0x41) console.log(` : Socket ${socketno}: Command not executable`);
}
if (packet.length == 3 && qq == 0x38) console.log("Network Change - we should immediately issue a renumbering!");
}
}