pelcod-decoder
Version:
CCTV PTZ Telemetry Decoder for Pelco D, Pelco P, BBV422, Bosch/Philips, Forward Vision, Vicon protocols and AD/Sensormatic protocols
1,155 lines (998 loc) • 45.7 kB
JavaScript
/*
* Read and decode Pelco D, Pelco P, BBV422, Bosch/Philips, Forward Vision, Vicon, American Dynamics/Sensormatic CCTV commands
* This code is not designed to decode every command.
* The purpose is to monitor the output from various CCTV systems to confirm the protocols and camera addresses in use.
* The meanings of certain Aux signals and special Preset values is not shown
*
* (c) Copyright 2016 Roger Hardiman
*
* Processes NodeJS Buffer() data.
* Buffer() objects may have multiple PTZ messages or just part of a message so bytes are cached if needed.
*
*/
var EventEmitter = require('events');
class PelcoD_Decoder extends EventEmitter {
constructor() {
super();
// A Buffer used to cache partial commands
this.pelco_command_buffer = new Buffer(7);
// Number of bytes in the current Buffer
this.pelco_command_index = 0;
// A Buffer used to cache partial commands for Pelco P
this.pelco_p_command_buffer = new Buffer(8);
// Number of bytes in the current Buffer
this.pelco_p_command_index = 0;
// A Buffer used for byte Bosch/Philips BiPhase
// Max length is 128 as length is bits 0 to 6 of header
this.bosch_command_buffer = new Buffer(128);
// Number of bytes in the current Buffer
this.bosch_command_index = 0;
// A Buffer used for byte Forward Vision Protocol (FV Protocol)
// Min length is 8. Max length is 255
this.fv_command_buffer = new Buffer(255);
// Number of bytes in the current Buffer
this.fv_command_index = 0;
// A Buffer used for Vicon
this.vicon_command_buffer = new Buffer(10);
// Number of bytes in the current Buffer
this.vicon_command_index = 0;
// A Buffer used for American Dynamics/Sensormatic (variable length message)
this.ad_command_buffer = new Buffer(128);
// Number of bytes in the current Buffer
this.ad_command_index = 0;
// A Buffer used for Panasonic (variable length message)
this.panasonic_command_buffer = new Buffer(128);
// Number of bytes in the current Buffer
this.panasonic_command_index = 0;
}
// new_data_buffer can be a NodeJS Buffer or a Javascript array
// as the only methods called are .length and the array index '[]' operator
processBuffer(new_data_buffer) {
// this.emit("log",'received ' + this.bytes_to_string(new_data_buffer,new_data_buffer.length) );
// process each byte from new_data_buffer in turn
for (var i = 0; i < new_data_buffer.length; i++) {
// Get the next new byte
var new_byte = new_data_buffer[i];
// Add to the end of the Pelco D buffer
// We cannot simply look for 0xFF as this could be
// part of the payload as well as the header
if (this.pelco_command_index < this.pelco_command_buffer.length) {
// Add the new_byte to the end of the pelco_command_buffer
this.pelco_command_buffer[this.pelco_command_index] = new_byte;
this.pelco_command_index++;
} else {
// Shift the bytes to make room for the new_byte at the end
for (var x = 0; x < (this.pelco_command_buffer.length - 1); x++) {
this.pelco_command_buffer[x] = this.pelco_command_buffer[x + 1];
}
// Then add the new_byte to the end
this.pelco_command_buffer[this.pelco_command_buffer.length-1] = new_byte;
}
// Add to the end of Pelco P buffer
// We cannot simply look for 0xA0 as this could be
// part of the payload as well as the header value
if (this.pelco_p_command_index < this.pelco_p_command_buffer.length) {
// Add the new_byte to the end of the pelco_p_command_buffer
this.pelco_p_command_buffer[this.pelco_p_command_index] = new_byte;
this.pelco_p_command_index++;
} else {
// Shift the bytes to make room for the new_byte at the end
for (var x = 0; x < (this.pelco_p_command_buffer.length - 1); x++) {
this.pelco_p_command_buffer[x] = this.pelco_p_command_buffer[x + 1];
}
// Then add the new_byte to the end
this.pelco_p_command_buffer[this.pelco_p_command_buffer.length-1] = new_byte;
}
// Add to Bosch byte buffer
if (new_byte & 0x80) {
// MSB is set to 1. This marks the start of a Bosch command so reset buffer counter
this.bosch_command_index = 0;
}
if (this.bosch_command_index < this.bosch_command_buffer.length) {
// Add the new_byte to the end of the bosch_command_buffer
this.bosch_command_buffer[this.bosch_command_index] = new_byte;
this.bosch_command_index++;
}
// Add to Forward Vision (FV) byte buffer
if (new_byte == 0x0A) {
// Always starts with 0x0A (LineFeed). Other bytes are 'ascii range'. Checksum is >= 128 (0x80 to 0xFF) so 0x0A is unique.
this.fv_command_index = 0;
}
if (this.fv_command_index < this.fv_command_buffer.length) {
// Add the new_byte to the end of the fv_command_buffer
this.fv_command_buffer[this.fv_command_index] = new_byte;
this.fv_command_index++;
}
// Add to Vicon byte buffer
if (new_byte & 0x80) {
// MSB is set to 1. This marks the start of a Vicon command so reset buffer counter
this.vicon_command_index = 0;
}
if (this.vicon_command_index < this.vicon_command_buffer.length) {
// Add the new_byte to the end of the vicon_command_buffer
this.vicon_command_buffer[this.vicon_command_index] = new_byte;
this.vicon_command_index++;
}
// Add to American Dynamics byte buffer accumulating bytes
// AD422 is minimum of 3 bytes.
// It starts with an address (1..99 [0x01..0x63] where 64[0x40] is for broadcast) followed by a command (0x81 to 0xFA)
// and then either the Checksum OR a variable length payload and Checksum
// Ensure first 2 bytes meet the range criteria
if (this.ad_command_index == 0 && new_byte>=0x01 && new_byte <=0x63) {
// Add the new_byte to the end of the ad_command_buffer
this.ad_command_buffer[this.ad_command_index] = new_byte;
this.ad_command_index++;
}
else if (this.ad_command_index == 1 && new_byte >= 0x81 && new_byte <= 0xFA) {
// Add the new_byte to the end of the ad_command_buffer
this.ad_command_buffer[this.ad_command_index] = new_byte;
this.ad_command_index++;
}
else if (this.ad_command_index == 2) {
// Add the new_byte to the end of the ad_command_buffer
this.ad_command_buffer[this.ad_command_index] = new_byte;
this.ad_command_index++;
}
else if (this.ad_command_index > 2 && this.ad_command_index < this.ad_message_length(this.ad_command_buffer[1],this.ad_command_buffer[2])) {
// Add the new_byte to the end of the ad_command_buffer
this.ad_command_buffer[this.ad_command_index] = new_byte;
this.ad_command_index++;
} else {
// We have not met the critera. Reset the buffer
this.ad_command_index = 0;
}
// Add to Panasonic byte buffer accumulating bytes
// It starts with 0x02 (STX) and ends with 0x03 (ETX)
// The rest of the data bytes are ASCII character bytes (0..9, A-Z etc)
if (new_byte == 0x02) {
// Always starts with 0x02 (STX)
this.panasonic_command_index = 0;
}
if (this.panasonic_command_index < this.panasonic_command_buffer.length) {
// Add the new_byte to the end of the panasonic_command_buffer
this.panasonic_command_buffer[this.panasonic_command_index] = new_byte;
this.panasonic_command_index++;
}
// Pelco D Test. Check if we have 7 bytes with byte 0 = 0xFF and with a valid SUM checksum
if (this.pelco_command_index === 7 && this.pelco_command_buffer[0] === 0xFF
&& this.checksum_valid(this.pelco_command_buffer)) {
// Looks like we have a Pelco command. Try and process it
this.decode(this.pelco_command_buffer);
this.pelco_command_index = 0; // empty the buffer
}
// Pelco P Test. Check if we have 8 bytes with byte 0 = 0xA0, byte 6 = 0xAF and with a valid XOR checksum
if (this.pelco_p_command_index === 8 && this.pelco_p_command_buffer[0] === 0xA0
&& this.pelco_p_command_buffer[6] === 0xAF
&& this.checksum_p_valid(this.pelco_p_command_buffer)) {
// Looks like we have a Pelco command. Try and process it
this.decode(this.pelco_p_command_buffer);
this.pelco_p_command_index = 0; // empty the buffer
}
// BBV422 Protocol Test. Check if we have 8 bytes with byte 0 = 0xB0, byte 6 = 0xBF and with a valid XOR checksum
if (this.pelco_p_command_index === 8 && this.pelco_p_command_buffer[0] === 0xB0
&& this.pelco_p_command_buffer[6] === 0xBF
&& this.checksum_p_valid(this.pelco_p_command_buffer)) {
// Looks like we have a Pelco command. Try and process it
this.decode(this.pelco_p_command_buffer);
this.pelco_p_command_index = 0; // empty the buffer
}
// Bosch Test. First byte has MSB of 1. First byte is the message size (excluding the checksum)
var bosch_len = this.bosch_command_buffer[0] & 0x7F;
if ((this.bosch_command_buffer[0] & 0x80)
&& this.bosch_command_index == (bosch_len + 1)
&& this.checksum_bosch_valid(this.bosch_command_buffer, this.bosch_command_index)) {
// Looks like we have a Bosch command. Try and process it
this.decode_bosch(this.bosch_command_buffer);
this.bosch_command_index = 0; // empty the buffer
}
// Forward Vision. Byte 0 is 0x0A. Checksum is only byte with MSB set to 1
if ((this.fv_command_buffer[0] == 0x0A)
&& this.fv_command_index >= 8
&& (this.fv_command_buffer[this.fv_command_index-1] >= 128) // checksum
&& this.checksum_fv_valid(this.fv_command_buffer, this.fv_command_index)) {
// Looks like we have a Forward Vision command. Try and process it
this.decode_forward_vision(this.fv_command_buffer, this.fv_command_index);
this.fv_command_index = 0; // empty the buffer
}
// Vicon. 10 bytes where Byte 1 upper nibble is 1000
// and Byte 2 has bit 6 set (meaning extended command)
if (((this.vicon_command_buffer[0] & 0xF0) === 0x80)
&& (this.vicon_command_buffer[1] & 0x40)
&& (this.vicon_command_index == 10 )) {
// Looks like we have a Vicon command. Try and process it
this.decode_vicon(this.vicon_command_buffer, this.vicon_command_index);
this.vicon_command_index = 0; // empty the buffer
}
// American Dynamics AD422 / Sensormatic
// First Byte = Address 0x01 to 0x63
// Second Byte = Command 0x81 to 0xFA
// Third Byte = Checksum OR a Payload and Checksum
// Then a variable length payload (zero, 1, 2 or more bytes)
// This code does not handle the 0xDE SET TEXT command as the payload size is in the 5th byte
if ((this.ad_command_buffer[0] >= 0x01 && this.ad_command_buffer[0] <= 0x63)
&& (this.ad_command_buffer[1] >= 0x81 && this.ad_command_buffer[1] <= 0xFA)
&& (this.ad_command_index == this.ad_message_length(this.ad_command_buffer[1],this.ad_command_buffer[2]))
&& (this.checksum_ad_valid(this.ad_command_buffer, this.ad_command_index))) {
// Looks like we have an American Dynamics AD422 / Sensormatic command. Try and process it
this.decode_ad422(this.ad_command_buffer);
this.ad_command_index = 0; // empty the buffer
}
// Panasonic is STX (0x02) then payload and then ETX (0x03)
// The Payload is at least 11 bytes eg GC7:9034002 so the shortest message is 13 bytes
if ((this.panasonic_command_buffer[0] == 0x02)
&& (this.panasonic_command_buffer[this.panasonic_command_index - 1] == 0x03)
&& (this.panasonic_command_index >= 13)) {
// Looks like we have a Panasonic command. Try and process it
this.decode_panasonic(this.panasonic_command_buffer, this.panasonic_command_index);
this.panasonic_command_index = 0; // empty the buffer
}
}
};
checksum_valid(buffer) {
var total = 0;
// The 0xFF start byte is not included in the checksum
for (var x = 1; x < (buffer.length - 1); x++) {
total += buffer[x];
}
var computed_checksum = total % 256;
// Check if computed_checksum matches the last byte in the buffer
if (computed_checksum === buffer[buffer.length - 1]) {
return true;
} else {
return false;
}
};
checksum_p_valid(buffer) {
var computed_checksum = 0x00;
for (var x = 0; x < (buffer.length - 1); x++) {
computed_checksum = computed_checksum ^ buffer[x]; // xor
}
// Check if computed_checksum matches the last byte in the buffer
if (computed_checksum === buffer[buffer.length - 1]) {
return true;
} else {
return false;
}
};
checksum_bosch_valid(buffer,message_length) {
var total = 0;
for (var x = 0; x < (message_length - 1); x++) {
total += buffer[x];
}
var computed_checksum = total & 0x7F; // Checksum has MSB of zero. MSB of 1 is reserved for first byte of message
// Check if computed_checksum matches the last byte in the buffer
if (computed_checksum === buffer[message_length - 1]) {
return true;
} else {
return false;
}
};
checksum_fv_valid(buffer,message_length) {
var computed_checksum = 0x00;
for (var x = 0; x < (message_length -1 ); x++) {
computed_checksum = computed_checksum ^ buffer[x]; // xor
}
computed_checksum = computed_checksum | 0x80; // set MSB to 1
// Check if computed_checksum matches the last byte in the buffer
if (computed_checksum === buffer[message_length - 1]) {
return true;
} else {
return false;
}
};
checksum_ad_valid(buffer,message_length) {
var total = 0;
for (var x = 0; x < (message_length - 1); x++) {
total += buffer[x];
}
var computed_checksum = (0 - total) & 0xFF;
// Check if computed_checksum matches the last byte in the buffer
if (computed_checksum === buffer[message_length - 1]) {
return true;
} else {
return false;
}
};
decode(pelco_command_buffer) {
var pelco_d = false;
var pelco_p = false;
var msg_string ='';
if (pelco_command_buffer.length == 7) pelco_d = true;
if (pelco_command_buffer.length == 8) pelco_p = true;
if (pelco_d) {
//var sync = pelco_command_buffer[0];
var camera_id = pelco_command_buffer[1];
var command_1 = pelco_command_buffer[2];
var command_2 = pelco_command_buffer[3];
var data_1 = pelco_command_buffer[4];
var data_2 = pelco_command_buffer[5];
//var checksum = pelco_command_buffer[6];
var extended_command = ((command_2 & 0x01)==1);
msg_string += 'D ';
}
if (pelco_p) {
var stx = pelco_command_buffer[0];
var camera_id = pelco_command_buffer[1] + 1; // Pelco P sends Cam1 as 0x00
var command_1 = pelco_command_buffer[2];
var command_2 = pelco_command_buffer[3];
var data_1 = pelco_command_buffer[4];
var data_2 = pelco_command_buffer[5];
//var etx = pelco_command_buffer[6];
//var checksum = pelco_command_buffer[7];
var extended_command = ((command_2 & 0x01)==1);
if (stx === 0xA0) msg_string += 'P ';
if (stx === 0xB0) msg_string += 'BBV ';
}
msg_string += 'Camera ' + camera_id + ' ';
if (extended_command) {
// Process extended commands
// Command 1 (byte3) and Command 2 (byte 4) identifies the Extended Command
// byte 5 and 6 contain additional data used by the extended commands
if (command_2 === 0x03 && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[SET PRESET ' + data_2 + ']';
} else if (command_2 === 0x05 && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[CLEAR PRESET ' + data_2 + ']';
} else if (command_2 === 0x07 && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[GOTO PRESET ' + data_2 + ']';
} else if (command_2 === 0x09 && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[SET AUX ' + data_2 + ']';
} else if (command_2 === 0x0B && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[CLEAR AUX ' + data_2 + ']';
} else if (command_2 === 0x1F && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[START RECORDING TOUR ' + data_2 + ']';
} else if (command_2 === 0x21 && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[STOP RECORDING TOUR]';
} else if (command_2 === 0x23 && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[START TOUR ' + data_2 + ']';
} else if (command_2 === 0x25 && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[SET ZOOM SPEED ' + data_2 + ']';
} else if (command_2 === 0x27 && command_1 === 0x00 && data_1 === 0x00) {
msg_string += '[SET FOCUS SPEED ' + data_2 + ']';
} else if (command_2 === 0x2B && command_1 === 0x00 && data_1 === 0x00 && data_2 == 0x00) {
msg_string += '[AUTO FOCUS SET TO AUTOMATIC]';
} else if (command_2 === 0x2B && command_1 === 0x00 && data_1 === 0x00 && data_2 != 0x00) {
// 2012 spec says 0 = Automatic Operation. 1 = Auto Focus Off
// 1999 spec says range 0..2 Automatic,On,Off
msg_string += '[AUTO FOCUS SETTING ' + data_2 + ']';
} else if (command_2 === 0x2D && command_1 === 0x00 && data_1 === 0x00 && data_2 == 0x00) {
msg_string += '[AUTO IRIS SET TO AUTOMATIC]';
} else if (command_2 === 0x2D && command_1 === 0x00 && data_1 === 0x00 && data_2 != 0x00) {
// 2012 spec says 0 = Automatic Operation. 1 = Auto Iris Off
// 1999 spec says range 0..2 Automatic,On,Off
msg_string += '[AUTO IRIS SETTING ' + data_2 + ']';
} else if (command_2 === 0x31 && command_1 === 0x00 && data_1 === 0x00 && data_2 == 0x01) {
msg_string += '[BACKLIGHT COMPENSATION OFF]';
} else if (command_2 === 0x31 && command_1 === 0x00 && data_1 === 0x00 && data_2 == 0x02) {
msg_string += '[BACKLIGHT COMPENSATION ON]';
} else {
msg_string += 'Unknown extended command 0x' + this.DecToHexPad(command_2, 2);
}
} else {
// Process a normal Pan, Tilt, Zoom, Focus and Iris command
if (pelco_d) {
var iris_close = (command_1 >> 2) & 0x01;
var iris_open = (command_1 >> 1) & 0x01;
var focus_near = (command_1 >> 0) & 0x01;
var focus_far = (command_2 >> 7) & 0x01;
var zoom_out = (command_2 >> 6) & 0x01;
var zoom_in = (command_2 >> 5) & 0x01;
var down = (command_2 >> 4) & 0x01;
var up = (command_2 >> 3) & 0x01;
var left = (command_2 >> 2) & 0x01;
var right = (command_2 >> 1) & 0x01;
}
if (pelco_p) {
var iris_close = (command_1 >> 3) & 0x01;
var iris_open = (command_1 >> 2) & 0x01;
var focus_near = (command_1 >> 1) & 0x01;
var focus_far = (command_1 >> 0) & 0x01;
var zoom_out = (command_2 >> 6) & 0x01;
var zoom_in = (command_2 >> 5) & 0x01;
var down = (command_2 >> 4) & 0x01;
var up = (command_2 >> 3) & 0x01;
var left = (command_2 >> 2) & 0x01;
var right = (command_2 >> 1) & 0x01;
}
if (left === 0 && right === 0) {
msg_string += '[pan stop ]';
} else if (left === 1 && right === 0) {
msg_string += '[PAN LEFT ('+data_1+')]';
} else if (left === 0 && right === 1) {
msg_string += '[PAN RIGHT('+data_1+')]';
} else { // left === 1 && right === 1)
msg_string += '[PAN ???? ('+data_1+')]';
}
if (up === 0 && down === 0) {
msg_string += '[tilt stop ]';
} else if (up === 1 && down === 0) {
msg_string += '[TILT UP ('+data_2+')]';
} else if (up === 0 && down === 1) {
msg_string += '[TILT DOWN('+data_2+')]';
} else { // (up === 1 && down === 1)
msg_string += '[TILT ????('+data_2+')]';
}
if (zoom_in === 0 && zoom_out === 0) {
msg_string += '[zoom stop]';
} else if (zoom_in === 1 && zoom_out === 0) {
msg_string += '[ZOOM IN ]';
} else if (zoom_in === 0 && zoom_out === 1) {
msg_string += '[ZOOM OUT ]';
} else { // (zoom_in === 1 && zoom_out === 1)
msg_string += '[ZOOM ????]';
}
if (iris_open === 0 && iris_close === 0) {
msg_string += '[iris stop ]';
} else if (iris_open === 1 && iris_close === 0) {
msg_string += '[IRIS OPEN ]';
} else if (iris_open === 0 && iris_close === 1) {
msg_string += '[IRIS CLOSE]';
} else { // (iris_open === 1 && iris_close === 1)
msg_string += '[IRIS ???? ]';
}
if (focus_near === 0 && focus_far === 0) {
msg_string += '[focus stop]';
} else if (focus_near === 1 && focus_far === 0) {
msg_string += '[FOCUS NEAR]';
} else if (focus_near === 0 && focus_far === 1) {
msg_string += '[FOCUS FAR ]';
} else { // (focus_near === 1 && focus_far === 1)
msg_string += '[FOCUS ????]';
}
}
this.emit("log",this.bytes_to_string(pelco_command_buffer, pelco_command_buffer.length) + ' ' + msg_string);
};
decode_bosch(bosch_command_buffer) {
// Note Bosch is 9600 8-N-1
var msg_string ='';
msg_string += 'Bosch ';
var length = bosch_command_buffer[0] & 0x7F;
var high_order_address = bosch_command_buffer[1];
var low_order_address = bosch_command_buffer[2];
var op_code = bosch_command_buffer[3];
//var data_byte_1 = bosch_command_buffer[4];
//var data_byte_2 = bosch_command_buffer[5];
//var data_byte_3 = bosch_command_buffer[6];
//var data_byte_X = bosch_command_buffer[xxx];
//var checksum = bosch_command_buffer[the last byte];
var camera_id = (high_order_address << 7) + low_order_address + 1;
msg_string += 'Camera ' + camera_id + ' ';
//
// OSRD Commands
//
if (op_code == 0x02) {
msg_string += 'Start/Stop Fixed Speed PTZ, Focus and Iris';
}
else if (op_code == 0x03) {
msg_string += 'Fixed Speed PTZ for a specified period';
}
else if (op_code == 0x04) {
msg_string += 'Repetitive Fixed Speed PTZ';
}
else if (op_code == 0x05) {
msg_string += 'Start/Stop Variable Speed PTZ = ';
// 3 data bytes used with this Op Code
var data_1 = bosch_command_buffer[4];
var data_2 = bosch_command_buffer[5];
var data_3 = bosch_command_buffer[6];
var zoom_speed = (data_1 >> 4) & 0x07;
var tilt_speed = (data_1 >> 0) & 0x0F;
var pan_speed = (data_2 >> 3) & 0x0F;
var iris_open = (data_2 >> 2) & 0x01;
var iris_close = (data_2 >> 1) & 0x01;
var focus_far = (data_2 >> 0) & 0x01;
var focus_near = (data_3 >> 6) & 0x01;
var zoom_in = (data_3 >> 5) & 0x01;
var zoom_out = (data_3 >> 4) & 0x01;
var up = (data_3 >> 3) & 0x01;
var down = (data_3 >> 2) & 0x01;
var left = (data_3 >> 1) & 0x01;
var right = (data_3 >> 0) & 0x01;
if (left === 0 && right === 0) {
msg_string += '[pan stop ]';
} else if (left === 1 && right === 0) {
msg_string += '[PAN LEFT ('+pan_speed+')]';
} else if (left === 0 && right === 1) {
msg_string += '[PAN RIGHT('+pan_speed+')]';
} else { // left === 1 && right === 1)
msg_string += '[PAN ???? ('+pan_speed+')]';
}
if (up === 0 && down === 0) {
msg_string += '[tilt stop ]';
} else if (up === 1 && down === 0) {
msg_string += '[TILT UP ('+tilt_speed+')]';
} else if (up === 0 && down === 1) {
msg_string += '[TILT DOWN('+tilt_speed+')]';
} else { // (up === 1 && down === 1)
msg_string += '[TILT ????('+tilt_speed+')]';
}
if (zoom_in === 0 && zoom_out === 0) {
msg_string += '[zoom stop]';
} else if (zoom_in === 1 && zoom_out === 0) {
msg_string += '[ZOOM IN('+zoom_speed+')]';
} else if (zoom_in === 0 && zoom_out === 1) {
msg_string += '[ZOOM OUT('+zoom_speed+')]';
} else { // (zoom_in === 1 && zoom_out === 1)
msg_string += '[ZOOM ????]';
}
if (iris_open === 0 && iris_close === 0) {
msg_string += '[iris stop ]';
} else if (iris_open === 1 && iris_close === 0) {
msg_string += '[IRIS OPEN ]';
} else if (iris_open === 0 && iris_close === 1) {
msg_string += '[IRIS CLOSE]';
} else { // (iris_open === 1 && iris_close === 1)
msg_string += '[IRIS ???? ]';
}
if (focus_near === 0 && focus_far === 0) {
msg_string += '[focus stop]';
} else if (focus_near === 1 && focus_far === 0) {
msg_string += '[FOCUS NEAR]';
} else if (focus_near === 0 && focus_far === 1) {
msg_string += '[FOCUS FAR ]';
} else { // (focus_near === 1 && focus_far === 1)
msg_string += '[FOCUS ????]';
}
}
else if (op_code == 0x06) {
msg_string += 'Repetitive Fixed speed Zoom, Focus and Iris';
}
else if (op_code == 0x07) {
msg_string += 'Auxiliary On/Off and Preposition Set/Shot = ';
// 2 data bytes used with this Op Code
var data_1 = bosch_command_buffer[4];
var data_2 = bosch_command_buffer[5];
var function_code = data_1 & 0x0F;
var data = ((data_1 & 0x70)<< 3) + data_2;
if (function_code == 1) msg_string += 'Aux On ' + data;
else if (function_code == 2) msg_string += 'Aux Off ' + data;
else if (function_code == 4) msg_string += 'Pre-position SET ' + data;
else if (function_code == 5) msg_string += 'Pre-position SHOT ' + data;
else if (function_code == 8) msg_string += 'Cancel Latching Aux ' + data;
else if (function_code == 9) msg_string += 'Latching Aux On ' + data;
else if (function_code == 10) msg_string += 'Latching Aux Off ' + data;
else msg_string += 'unknown aux or pre-position command ' + function_code + ' with value ' + data;
}
else if (op_code == 0x08) {
msg_string += 'Repetitive Variable-speed PTZ, Focus and Iris';
}
//
// OSRD Extended Commands
//
else if (op_code == 0x09) {
msg_string += 'Fine Speed PTZ';
}
else if (op_code == 0x0A) {
msg_string += 'Position Report and Replay / Position Commands';
}
else if (op_code == 0x0C) {
msg_string += 'Ping Command';
}
else if (op_code == 0x0F) {
msg_string += 'Information Requested / Reply';
}
else if (op_code == 0x10) {
msg_string += 'Title set';
}
else if (op_code == 0x12) {
msg_string += 'Auxiliary Commands with Data';
}
else if (op_code == 0x13) {
msg_string += 'Set Position / Get Position';
}
else if (op_code == 0x14) {
msg_string += 'BiCom message';
}
//
// BiCom within OSRD
//
else {
msg_string += 'Unknown Op Code ' + op_code;
}
this.emit("log",this.bytes_to_string(bosch_command_buffer,length+1) + ' ' + msg_string);
};
fv_hex_ascii(byte_1,byte_2) {
// Byte 1 could be 0x31 = ASCII "1"
// Byte 2 could be 0x46 = ASCII "F"
// Convert Byte 1 and Byte 2 from 'bytes' into Chars, eg to "1" and "F"
// Combine the chars to a Hex String eg "0x1F"
// Convert the Hex String into an integer value
var hex_string = '0x' + String.fromCharCode(byte_1,byte_2);
var value = parseInt(hex_string);
return value;
}
decode_forward_vision(fv_command_buffer,fv_command_length) {
// Note Forward Vision is 9600 8-O-1 *** ODD PARITY ***
var msg_string ='';
msg_string += 'FV ';
var camera_id = this.fv_hex_ascii(fv_command_buffer[1], fv_command_buffer[2]);
var length = this.fv_hex_ascii(fv_command_buffer[3], fv_command_buffer[4]);
var control_flag_char = String.fromCharCode(fv_command_buffer[5]);
var control_code_char = String.fromCharCode(fv_command_buffer[6]);
msg_string += 'Camera ' + camera_id + ' ';
if (control_code_char === 'G') {
var data1 = this.fv_hex_ascii(fv_command_buffer[7], fv_command_buffer[8]);
var data2 = this.fv_hex_ascii(fv_command_buffer[9], fv_command_buffer[10]);
var data3 = this.fv_hex_ascii(fv_command_buffer[11], fv_command_buffer[12]);
var pan_speed = this.fv_hex_ascii(fv_command_buffer[13], fv_command_buffer[14]);
var tilt_speed = this.fv_hex_ascii(fv_command_buffer[15], fv_command_buffer[16]);
var focus_off_on = (data1 >> 7) & 0x01;
var focus_near_far = (data1 >> 6) & 0x01;
var zoom_off_on = (data1 >> 5) & 0x01;
var zoom_tele_wide = (data1 >> 4) & 0x01;
var tilt_off_on = (data1 >> 3) & 0x01;
var tilt_up_down = (data1 >> 2) & 0x01;
var pan_off_on = (data1 >> 1) & 0x01;
var pan_left_right = (data1 >> 0) & 0x01;
var iris_sense_peak = (data2 >> 7) & 0x01;
var iris_control = (data2 >> 4) & 0x07;
var iris_slow_fast = (data2 >> 3) & 0x01;
var focus_slow_fast = (data2 >> 2) & 0x01;
var zoom_slow_fast = (data2 >> 1) & 0x01;
var autopan_off_on = (data2 >> 0) & 0x01;
var pan_tilt_scale_off_on = (data3 >> 7) & 0x01;
var wiper_off_on = (data3 >> 6) & 0x01;
var washer_off_on = (data3 >> 5) & 0x01;
var lamp_control = (data3 >> 3) & 0x03; // 2 bit value
var aux_3_off_on = (data3 >> 2) & 0x01;
var aux_2_off_on = (data3 >> 1) & 0x01;
var aux_1_off_on = (data3 >> 0) & 0x01;
if (pan_off_on === 0) {
msg_string += '[pan stop ]';
} else if (pan_off_on === 1 && pan_left_right === 0) {
msg_string += '[PAN LEFT ('+pan_speed+')]';
} else if (pan_off_on === 1 && pan_left_right === 1) {
msg_string += '[PAN RIGHT('+pan_speed+')]';
} else {
msg_string += '[PAN ???? ('+pan_speed+')]';
}
if (tilt_off_on === 0) {
msg_string += '[tilt stop ]';
} else if (tilt_off_on === 1 && tilt_up_down === 0) {
msg_string += '[TILT UP ('+tilt_speed+')]';
} else if (tilt_off_on === 1 && tilt_up_down === 1) {
msg_string += '[TILT DOWN('+tilt_speed+')]';
} else {
msg_string += '[TILT ????('+tilt_speed+')]';
}
if (zoom_off_on === 0) {
msg_string += '[zoom stop]';
} else if (zoom_off_on === 1 && zoom_tele_wide === 0) {
msg_string += '[ZOOM IN ('+zoom_slow_fast+')]';
} else if (zoom_off_on === 1 && zoom_tele_wide === 1) {
msg_string += '[ZOOM OUT('+zoom_slow_fast+')]';
} else {
msg_string += '[ZOOM ???]';
}
if (iris_control === 0) {
msg_string += '[iris stop]';
} else if (iris_control === 1) {
msg_string += '[IRIS CLOSE]';
} else if (iris_control === 2) {
msg_string += '[IRIS OPEN]';
} else if (iris_control === 3) {
msg_string += '[IRIS AUTO]';
} else {
msg_string += '[IRIS ????]';
}
if (focus_off_on === 0) {
msg_string += '[focus stop]';
} else if (focus_off_on === 1 && focus_near_far === 0) {
msg_string += '[FOCUS NEAR]';
} else if (focus_off_on === 1 && focus_near_far === 1) {
msg_string += '[FOCUS FAR ]';
} else {
msg_string += '[FOCUS ????]';
}
msg_string += ' Aux1='+aux_1_off_on;
msg_string += ' Aux2='+aux_2_off_on;
msg_string += ' Aux3='+aux_3_off_on;
msg_string += ' Wipe='+wiper_off_on;
msg_string += ' Wash='+washer_off_on;
msg_string += ' Lamp='+lamp_control;
msg_string += ' PropZoom='+pan_tilt_scale_off_on;
}
else if (control_code_char === 'L') {
var preset = this.fv_hex_ascii(fv_command_buffer[7], fv_command_buffer[8]);
msg_string += 'Goto Preset ' + preset;
}
else if (control_code_char === 'M') {
var preset = this.fv_hex_ascii(fv_command_buffer[7], fv_command_buffer[8]);
msg_string += 'Store Preset ' + preset;
}
else if (control_code_char === 'O') {
msg_string += 'Get Current Position';
}
else if (control_code_char === 'W') {
var data1 = this.fv_hex_ascii(fv_command_buffer[7], fv_command_buffer[8]);
msg_string += 'Reset Value=' + data1;
}
else if (control_code_char === 'Y') {
msg_string += 'Get SW Version';
}
else {
msg_string += 'Unknown Command Code ' + control_code_char;
}
this.emit("log",this.bytes_to_string(fv_command_buffer,fv_command_length) + ' ' + msg_string);
};
decode_vicon(vicon_command_buffer,vicon_command_length) {
// Does not appear to be any checksum
// Byte 1. MSB set to 1.
var msg_string ='';
msg_string += 'Vicon ';
var camera_id = ((vicon_command_buffer[0] & 0x0F)*16) + (vicon_command_buffer[1] & 0x0F);
var left = (vicon_command_buffer[2] >> 6) & 0x01; // 0x40
var right = (vicon_command_buffer[2] >> 5) & 0x01; // 0x20
var up = (vicon_command_buffer[2] >> 4) & 0x01; // 0x10
var down = (vicon_command_buffer[2] >> 3) & 0x01; // 0x08
var auto_pan = (vicon_command_buffer[2] >> 2) & 0x01; // 0x04
var zoom_out = (vicon_command_buffer[3] >> 6) & 0x01; // 0x40
var zoom_in = (vicon_command_buffer[3] >> 5) & 0x01; // 0x20
var focus_far = (vicon_command_buffer[3] >> 4) & 0x01; // 0x10
var focus_near = (vicon_command_buffer[3] >> 3) & 0x01; // 0x08
var iris_open = (vicon_command_buffer[3] >> 2) & 0x01; // 0x04
var iris_close = (vicon_command_buffer[3] >> 1) & 0x01; // 0x02
var goto_preset = (vicon_command_buffer[6] === 0x10 ? 1 : 0); // >> 4) & 0x01; // 0x10
var preset_value = (vicon_command_buffer[7]);
var pan_speed = (vicon_command_buffer[6] << 7) | (vicon_command_buffer[7]);
var tilt_speed = (vicon_command_buffer[8] << 7) | (vicon_command_buffer[9]);
msg_string += 'Camera ' + camera_id + ' ';
if (left === 0 && right === 0) {
msg_string += '[pan stop ]';
} else if (left === 1 && right === 0) {
msg_string += '[PAN LEFT ('+pan_speed+')]';
} else if (left === 0 && right === 1) {
msg_string += '[PAN RIGHT('+pan_speed+')]';
} else { // left === 1 && right === 1)
msg_string += '[PAN ???? ('+pan_speed+')]';
}
if (up === 0 && down === 0) {
msg_string += '[tilt stop ]';
} else if (up === 1 && down === 0) {
msg_string += '[TILT UP ('+tilt_speed+')]';
} else if (up === 0 && down === 1) {
msg_string += '[TILT DOWN('+tilt_speed+')]';
} else { // (up === 1 && down === 1)
msg_string += '[TILT ????('+tilt_speed+')]';
}
if (zoom_in === 0 && zoom_out === 0) {
msg_string += '[zoom stop]';
} else if (zoom_in === 1 && zoom_out === 0) {
msg_string += '[ZOOM IN]';
} else if (zoom_in === 0 && zoom_out === 1) {
msg_string += '[ZOOM OUT]';
} else { // (zoom_in === 1 && zoom_out === 1)
msg_string += '[ZOOM ???]';
}
if (iris_open === 0 && iris_close === 0) {
msg_string += '[iris stop ]';
} else if (iris_open === 1 && iris_close === 0) {
msg_string += '[IRIS OPEN ]';
} else if (iris_open === 0 && iris_close === 1) {
msg_string += '[IRIS CLOSE]';
} else { // (iris_open === 1 && iris_close === 1)
msg_string += '[IRIS ???? ]';
}
if (focus_near === 0 && focus_far === 0) {
msg_string += '[focus stop]';
} else if (focus_near === 1 && focus_far === 0) {
msg_string += '[FOCUS NEAR]';
} else if (focus_near === 0 && focus_far === 1) {
msg_string += '[FOCUS FAR ]';
} else { // (focus_near === 1 && focus_far === 1)
msg_string += '[FOCUS ????]';
}
if (auto_pan === 1) {
msg_string += '[AutoPan]';
}
if (goto_preset === 1) {
msg_string += '[Goto Preset '+ preset_value + ']';
}
this.emit("log",this.bytes_to_string(vicon_command_buffer,vicon_command_length) + ' ' + msg_string);
};
// Returns the length of a command (as this protocol uses variable length messages)
ad_message_length(command,byte3) { // ,byte4,byte5) {
if (command == 0xA6) return 13; // Goto Abs Position
if (command == 0xC0) return 5; // Proportional Speed
if (command == 0xC4) return 6; // Get Config
if (command == 0xC7) return 5; // Set and Goto Preset
if (command == 0xCC) return 4; // Various config commands
if (command == 0xCD) return 5; // QuickSet
if (command == 0xDE) return 0; // Variable Length ASCII message. 5th Byte tells us ASCII string length
// Check for 0xFA 'get' command has bit 7 clear. Goes not use command length bits
if (command == 0xFA && (byte3>>7 == 0)) return 4 + 1; // Variable Length Network Position Command. 4 bytes plus checsksum
// Check for 0xFA 'set' command has bit 7 set. Obey command length bits
if (command == 0xFA && (byte3>>7 == 1)) return (byte3 & 0x1F) + 1; // Variable Length Network Position Command plus checsksum
return 3; // all other commands are 3 bytes long (address, command, checksum)
};
decode_ad422(ad_command_buffer) {
var msg_string ='';
msg_string += 'AD/Sens ';
var camera_id = ad_command_buffer[0];
var command_code = ad_command_buffer[1];
var length = this.ad_message_length(ad_command_buffer[1],ad_command_buffer[2]);
msg_string += 'Camera ' + camera_id + ' ';
if (command_code == 0x81) {
msg_string += 'Pan Left';
}
else if (command_code == 0x82) {
msg_string += 'Pan Right';
}
else if (command_code == 0x83) {
msg_string += 'Pan Stop';
}
else if (command_code == 0x84) {
msg_string += 'Tilt Up';
}
else if (command_code == 0x85) {
msg_string += 'Tilt Down';
}
else if (command_code == 0x86) {
msg_string += 'Tilt Stop';
}
else if (command_code == 0x87) {
msg_string += 'Focus Near';
}
else if (command_code == 0x88) {
msg_string += 'Focus Far';
}
else if (command_code == 0x89) {
msg_string += 'Focus Stop';
}
else if (command_code == 0x8A) {
msg_string += 'Zoom In';
}
else if (command_code == 0x8B) {
msg_string += 'Zoom Out';
}
else if (command_code == 0x8C) {
msg_string += 'Zoom Stop';
}
else if (command_code == 0x90) {
msg_string += 'Iris Open';
}
else if (command_code == 0x91) {
msg_string += 'Iris Close';
}
else if (command_code == 0x92) {
msg_string += 'Iris Stop';
}
else if (command_code == 0x93) {
msg_string += 'All Stop';
}
else if (command_code == 0x98) {
msg_string += 'Suspend replies from camera';
}
else if (command_code == 0x99) {
msg_string += 'Resume replies from camera';
}
else if (command_code == 0xA5) {
msg_string += 'Request Position';
}
else if (command_code == 0xA8) {
msg_string += 'Store Target 1';
}
else if (command_code == 0xA9) {
msg_string += 'Store Target 2';
}
else if (command_code == 0xAA) {
msg_string += 'Store Target 3';
}
else if (command_code == 0xAB) {
msg_string += 'Store Target 4';
}
else if (command_code == 0xB4) {
msg_string += 'Goto Target 1';
}
else if (command_code == 0xB5) {
msg_string += 'Goto Target 2';
}
else if (command_code == 0xB6) {
msg_string += 'Goto Target 3';
}
else if (command_code == 0xB7) {
msg_string += 'Goto Target 4';
}
else if (command_code == 0xB9) {
msg_string += 'Store Target 5';
}
else if (command_code == 0xBA) {
msg_string += 'Store Target 6';
}
else if (command_code == 0xBB) {
msg_string += 'Store Target 7';
}
else if (command_code == 0xBC) {
msg_string += 'Goto Target 5';
}
else if (command_code == 0xBD) {
msg_string += 'Goto Target 6';
}
else if (command_code == 0xBE) {
msg_string += 'Goto Target 7';
}
else if (command_code == 0xC0) {
var direction = ad_command_buffer[2];
var speed = ad_command_buffer[3];
if (direction == 0x81) msg_string += 'Pan Left (' + speed + ')';
else if (direction == 0x82) msg_string += 'Pan Right (' + speed + ')';
else if (direction == 0x84) msg_string += 'Tilt Up (' + speed + ')';
else if (direction == 0x85) msg_string += 'Tilt Down (' + speed + ')';
}
else if (command_code == 0xC4) {
msg_string += 'Get Configuration Buffer';
}
else if (command_code == 0xCC) {
var additional_command = ad_command_buffer[2];
if (additional_command == 0x08) msg_string += 'Auto Focus Auto Iris';
}
else if (command_code == 0xC7) {
var additional_command = ad_command_buffer[2];
var preset = ad_command_buffer[3];
if (additional_command == 0x01) msg_string += 'Set Preset ' + preset;
if (additional_command == 0x02) msg_string += 'Goto Preset ' + preset;
}
else if (command_code >= 0xE0 && command_code <= 0xEF) {
msg_string += 'Controlling Output Pins 0x' + this.DecToHexPad(command_code & 0x0F,1)
}
else if (command_code == 0xFA) {
var cmd_bit7 = (ad_command_buffer[2] >> 7) & 0x01; // get/set
var cmd_bit5 = (ad_command_buffer[2] >> 5) & 0x01; // abs/rel
var units_bit6 = (ad_command_buffer[3] >> 6) & 0x01; // auto focus
var units_bit7 = (ad_command_buffer[3] >> 7) & 0x01; // auto iris
if (cmd_bit7 == 0 && cmd_bit5 == 0) {
msg_string += 'Get Absolute Position';
}
if (cmd_bit7 == 0 && cmd_bit5 == 1) {
msg_string += 'Get Relative Position';
}
if (cmd_bit7 == 1 && cmd_bit5 == 0) {
msg_string += 'Set Absolute Position';
}
if (cmd_bit7 == 1 && cmd_bit5 == 1) {
msg_string += 'Set Relative Position';
}
if (cmd_bit7 == 1 && units_bit6 == 0) {
msg_string += ' AutoFocus=Off';
}
if (cmd_bit7 == 1 && units_bit6 == 1) {
msg_string += ' AutoFocus=On';
}
if (cmd_bit7 == 1 && units_bit7 == 0) {
msg_string += ' AutoIris=Off';
}
if (cmd_bit7 == 1 && units_bit7 == 1) {
msg_string += ' AutoIris=On';
}
}
else {
msg_string += 'Unknown Command Code 0x' + this.DecToHexPad(command_code,2);
command_code;
}
this.emit("log",this.bytes_to_string(ad_command_buffer,length) + ' ' + msg_string);
return;
};
decode_panasonic(buffer,length) {
var msg_string = "";
msg_string += "Panasonic ";
for (var i = 1; i < length -1; i++) {
msg_string += String.fromCharCode(buffer[i]);
}
this.emit("log",msg_string);
return;
};
bytes_to_string(buffer, length) {
var byte_string = '';
for (var i = 0; i < length; i++) {
byte_string += '[' + this.DecToHexPad(buffer[i],2) + ']';
}
return byte_string;
};
DecToHexPad(decimal,size) {
var ret_string = decimal.toString('16');
while (ret_string.length < size) {
ret_string = '0' + ret_string;
}
return ret_string;
};
} // end class
module.exports = { PelcoD_Decoder };