UNPKG

@br8kppooint/visca

Version:

Advanced nodejs library for working with VISCA-based PTZ cameras over IP and over serial connections.

260 lines 10.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ViscaController = void 0; const events_1 = require("events"); const constants_1 = require("./constants"); const command_1 = require("./command"); const camera_1 = require("./camera"); const visca_serial_1 = require("./visca-serial"); const visca_ip_1 = require("./visca-ip"); // 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 class ViscaController extends events_1.EventEmitter { constructor(config) { super(); this.config = config; this.ipServers = []; this.serialBroadcastCommands = []; // FIFO stack of serial commands sent this.cameras = {}; // will be indexed with uuid strings for ip cameras this.cameraCount = 0; } init() { this.cameras = {}; this.cameraCount = 0; } // uuid will be generated when the data comes from an IP camera addIPCamera(c, doInquire = true) { let transport = new visca_ip_1.UDPTransport(c.ip, c.port); transport.on('data', ({ uuid, viscaCommand }) => this.onUDPData({ uuid, viscaCommand })); let camera = new camera_1.Camera(1, transport, c.name); // IP cameras all have index 1 this.cameras[transport.uuid] = camera; camera.sendCommand(command_1.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 visca_serial_1.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) { console.log(e); } onSerialData(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_1.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 constants_1.Constants.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 constants_1.Constants.MSGTYPE_ADDRESS_SET: let highestIndex = v.data[0] - 1; for (let i = 1; i <= highestIndex; i++) this.cameras[i] = new camera_1.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 }) { let camera = this.cameras[uuid]; return this.onCameraData(camera, viscaCommand); } onCameraData(camera, v) { switch (v.msgType) { case constants_1.Constants.MSGTYPE_IF_CLEAR: camera._clear(); break; // network change messages are unprompted case constants_1.Constants.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 constants_1.Constants.MSGTYPE_ACK: camera.ack(v); return; // completion message case constants_1.Constants.MSGTYPE_COMPLETE: camera.complete(v); break; // error messages case constants_1.Constants.MSGTYPE_ERROR: camera.error(v); break; default: break; } this.emit('update'); } sendSerial(viscaCommand) { this.serialConnection.write(viscaCommand); } // forces a command to be a broadcast command (only applies to serial) broadcastSerial(viscaCommand) { viscaCommand.broadcast = true; this.serialConnection.write(viscaCommand); } // forces a command to go to a specific camera sendToCamera(camera, viscaCommand) { camera.sendCommand(viscaCommand); } // system-level commands... only relevant to serial connections enumerateSerial() { this.sendSerial(command_1.ViscaCommand.addressSet()); } ifClearAllSerial() { this.sendSerial(command_1.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 visca_ip_1.ViscaServer(port); server.on('data', (viscaCommand) => { this.onCameraData(camera, viscaCommand); }); this.ipServers.push(server); } } // for debugging dump(packet, title = 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!"); } } exports.ViscaController = ViscaController; //# sourceMappingURL=controller.js.map