diff --git a/decoders/connector/volley-boast/vobo-gp-1/assets/logo.png b/decoders/connector/volley-boast/vobo-gp-1/assets/logo.png new file mode 100644 index 0000000..469feca Binary files /dev/null and b/decoders/connector/volley-boast/vobo-gp-1/assets/logo.png differ diff --git a/decoders/connector/volley-boast/vobo-gp-1/connector.jsonc b/decoders/connector/volley-boast/vobo-gp-1/connector.jsonc new file mode 100644 index 0000000..aeac05a --- /dev/null +++ b/decoders/connector/volley-boast/vobo-gp-1/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "VoBo GP-1", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-gp-1/description.md b/decoders/connector/volley-boast/vobo-gp-1/description.md new file mode 100644 index 0000000..9c72f98 --- /dev/null +++ b/decoders/connector/volley-boast/vobo-gp-1/description.md @@ -0,0 +1 @@ +Sensor agnostic safe area LoRaWAN bridge endpoint. \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload-config.jsonc b/decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload-config.jsonc new file mode 100644 index 0000000..8bb4c1c --- /dev/null +++ b/decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload-config.jsonc @@ -0,0 +1,24 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The VoBo GP-1 is a Safe Area LoRaWAN Class A endpoint which bridges Analog, Digital and RS485 Modbus RTU transmitters to LoRaWAN", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-loriot/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload.js b/decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload.js new file mode 100644 index 0000000..2244caf --- /dev/null +++ b/decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload.js @@ -0,0 +1,1512 @@ +/* istanbul ignore next */ +function customDecoder(bytes, fport) { + var decoded = Decoder(bytes, fport); + //======================================================= + // Insert your customization here + + + //======================================================= + return decoded; +} + +//=========================================================== +// Volley Boast Decoder +// +// Volley Boast maintains the below code, therefore +// DO NOT EDIT BELOW THIS POINT!! +//=========================================================== +const DECODER_MAJOR_VERSION = 2; +const DECODER_MINOR_VERSION = 1; +const DECODER_PATCH_VERSION = 1; + +function decodeUplink(input) { + const decoded = Decoder(input.bytes, input.fPort); + return decoded; +} + +function Decoder(bytes, fport) { + var decoded = {}; + var decodedData = {}; + var warnings = []; + var errors = []; + + if (fport == 1) { + decodedData = parseStandardPayload(bytes); + } + else if ((fport >= 2) && (fport <= 9)) { + decodedData = parseModbusStandardPayload(bytes, fport); + } + else if (fport == 10) { + decodedData = parseHeartbeat1p0Payload(bytes); + } + else if ((fport >= 20) && (fport <= 29)) { + decodedData = parseHeartbeat2p0Payload(bytes); + } + else if ((fport >= 30) && (fport <= 39)) { + decodedData = parseOneAnalogSensorPayload(bytes); + } + else if ((fport >= 40) && (fport <= 49)) { + decodedData = parseTwoAnalogSensorsPayload(bytes); + } + else if ((fport >= 50) && (fport <= 59)) { + decodedData = parseDigitalSensorsPayload(bytes); + } + else if ((fport >= 60) && (fport <= 69)) { + decodedData = parseEventLogPayload(bytes); + } + else if ((fport >= 70) && (fport <= 79)) { + decodedData = parseConfigurationPayload(bytes, fport); + } + else if ((fport >= 100) && (fport <= 109)) { + decodedData = parseModbusGenericPayload(bytes, fport); + } + else if ((fport >= 110) && (fport <= 119)) { + decodedData = parseAnalogInputVariableLengthPayload(bytes, fport); + } + else if ((fport >= 120) && (fport <= 129)) { + decodedData = parseModbusStandardVariableLengthPayload(bytes, fport); + } + else { + errors.push('unknown FPort') + } + + decoded = addVoboMetadata(decodedData, fport); + decoded.warnings = warnings; + decoded.errors = errors; + + return decoded; +} + +function addVoboMetadata(decodedData, fport) { + var payload = {}; + payload.data = decodedData; + + var voboType = ""; + var payloadType = ""; + var portLastDigit = fport % 10; + + if ((portLastDigit == 0) || ((fport >= 1) && (fport < 10))) voboType = "VoBoXX"; + else if (portLastDigit == 1) voboType = "VoBoTC"; + else if (portLastDigit == 2) voboType = "VoBoXP"; + + if (fport == 1) payloadType = "Standard"; + else if ((fport >= 2) && (fport <= 9)) { + payloadType = "Modbus Standard"; + } + else if (fport == 10) payloadType = "Heartbeat 1.0"; + else if ((fport >= 20) && (fport <= 29)) { + payloadType = "Heartbeat 2.0"; + } + else if ((fport >= 30) && (fport <= 39)) { + payloadType = "One Analog Input"; + payload.data.analogSensorString0 = lookupAnalogSensorName(voboType, payload.data.sensorNum0); + payload.data.engUnitsString0 = lookupUnits(1, payload.data.sensorUnits0); + } + else if ((fport >= 40) && (fport <= 49)) { + payloadType = "Two Analog Inputs"; + payload.data.analogSensorString0 = lookupAnalogSensorName(voboType, payload.data.sensorNum0); + payload.data.engUnitsString0 = lookupUnits(1, payload.data.sensorUnits0); + payload.data.analogSensorString1 = lookupAnalogSensorName(voboType, payload.data.sensorNum1); + payload.data.engUnitsString1 = lookupUnits(1, payload.data.sensorUnits1); + } + else if ((fport >= 50) && (fport <= 59)) { + payloadType = "Digital Inputs"; + payload.data.digitalSensorStrings = []; + payload.data.digitalSensorData = []; + for (let i = 0; i < 16; i++) { + let valid = (payload.data.sensorValid0 >> i) & 1; + if (valid == 1) { + payload.data.digitalSensorStrings.push(lookupDigitalSensorName(voboType, i)); + let data = (payload.data.sensorData0 >> i) & 1; + payload.data.digitalSensorData.push(data); + } + } + } + else if ((fport >= 60) && (fport <= 69)) payloadType = "Event Log"; + else if ((fport >= 70) && (fport <= 79)) payloadType = "Configuration"; + else if ((fport >= 100) && (fport <= 109)) payloadType = "Modbus Generic"; + else if ((fport >= 110) && (fport <= 119)) { + payloadType = "Analog Input Variable Length"; + for (let i = 0; i < payload.data.numOfAinPayloads; i++) { + payload.data["analogSensorString" + i] = lookupAnalogSensorName(voboType, payload.data.ainPayloads[i].sensorNum0); + payload.data["engUnitsString" + i] = lookupUnits(1, payload.data.ainPayloads[i].sensorUnits0); + } + } + else if ((fport >= 120) && (fport <= 129)) payloadType = "Modbus Standard Variable Length"; + + payload.data.fport = fport; + payload.data.voboType = voboType; + payload.data.payloadType = payloadType; + + return payload; +} + +function parseStandardPayload(bytes) { + var decoded = {}; + decoded.DIN1 = bytes[0] & 0x01; // Discrete digital 1 (1-bit) + decoded.DIN2 = bytes[0] >> 1 & 0x01; // Discrete digital 2 (1-bit) + decoded.DIN3 = bytes[0] >> 2 & 0x01; // Discrete digital 3 (1-bit) + decoded.WKUP = bytes[0] >> 3 & 0x01; // Discrete digital wakeup (1-bit) + decoded.ADC1 = ((bytes[0] & 0xf0) >> 4) | (bytes[1] << 4); // ADC 1 (12-bit) + decoded.ADC2 = (bytes[3] & 0x0f) << 8 | bytes[2]; // ADC 2 (12-bit) + decoded.ADC3 = ((bytes[3] & 0xf0) >> 4) | (bytes[4] << 4); // ADC 3 (12-bit) + decoded.Battery = ((bytes[6] & 0x0f) << 8 | bytes[5]) * 4; // ADC battery (12-bit) + if ((bytes[7] >> 7 & 0x01) == 0) { + decoded.Temperature = (((bytes[6] & 0xf0) >> 4) | (bytes[7] << 4)) * 0.125; // ADC temperature (12-bit) above 0 degrees C + } else { + decoded.Temperature = (4096 - (((bytes[6] & 0xf0) >> 4) | (bytes[7] << 4))) * 0.125 * (-1); // ADC temperature (12-bit) below 0 degrees C + } + decoded.Modbus0 = bytes[9] << 8 | bytes[8]; // Modbus-RS485 (16-bit) + return decoded; +} + +function parseModbusStandardPayload(bytes, fport) { + var startIdx = (fport - 1) * 5 - 4; + var decoded = {}; + decoded["Modbus" + startIdx++] = (bytes[1] << 8) | bytes[0]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[3] << 8) | bytes[2]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[5] << 8) | bytes[4]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[7] << 8) | bytes[6]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx] = (bytes[9] << 8) | bytes[8]; // Modbus-RS485 (16-bit) + return decoded; +} + +function parseHeartbeat1p0Payload(bytes) { + var decoded = {}; + decoded.batteryLevelMV = ((bytes[1] & 0x0f) << 8 | bytes[0]) * 4; // ADC battery (12-bit) + decoded.fwVersionMajor = (bytes[2] & 0x0f) | (bytes[1] & 0xf0) >> 4; // FW Version Major (8-bit) + decoded.fwVersionMinor = (bytes[3] & 0x0f) | (bytes[2] & 0xf0) >> 4; // FW Version Minor (8-bit) + decoded.fwVersionPatch = (bytes[4] & 0x0f) | (bytes[3] & 0xf0) >> 4; // FW Version Patch (8-bit) + decoded.fwVersionCustom = (bytes[4] & 0xf0) >> 4; // FW Version Custom (4-bit) + decoded.recSignalLevels = bytes[5] * -1; // Last RSSI (8 bits) + decoded.analogVoltageConfig = (bytes[6] & 0x1f); // Analog Voltage Config (5 bits) + decoded.analogPowerTimeConfig = parseFloat((((bytes[7] & 0x1F) << 3 | (bytes[6] & 0xE0) >> 5) / 10).toFixed(1)); // Analog Power Time Config (8 bits) + decoded.failedTransmissionBeforeRejoinConfig = (bytes[8] & 0x01) << 3 | (bytes[7] & 0xE0) >> 5; // Failed Transmissions Before Rejoin Config (4 bits) + decoded.cycleThroughFSB = (bytes[8] & 0x02) >> 1; // Cycle Through FSB (1 bit) + decoded.ackEnable = (bytes[8] & 0x04) >> 2; // ACK Enable (1 bit) + decoded.ackFreq = (bytes[8] & 0x78) >> 3; // ACK Frequency (4 bits) + decoded.ackReq = (bytes[9] & 0x07) << 1 | (bytes[8] & 0x80) >> 7; // ACK Request (4 bits) + decoded.batteryLevelThreshold = (((bytes[10] & 0x7F) << 5) | (bytes[9] & 0xF8) >> 3) * 4; // Battery Level Threshold (12-bit) + + return decoded; +} + +function parseHeartbeat2p0Payload(bytes) { + var decoded = {}; + decoded.batteryLevel = (bytes[1] & 0x0F) << 8 | bytes[0]; // Current Battery level in mV + decoded.fatalErrorsTotal = (bytes[1] & 0xF0) >> 4; // Total number of Fatal Error detected by the handler (triggering reboot) for the last HeartBeat interval + decoded.rssiAvg = bytes[2]; // RSSI of received signal from LoRaWAN GW - average for the last HeartBeat interval + decoded.failedJoinAttemptsTotal = bytes[3] & 0x7F; // Total Number of Failed Join Attempts to LoRaWAN network for the last HeartBeat interval + decoded.configUpdateOccurred = (bytes[3] & 0x80) >> 7; // Indicates if one or more config updates occurred for the last HeartBeat interval + decoded.firmwareRevision = (bytes[5] & 0x03) << 8 | bytes[4]; // Firmware revision encoding. + decoded.rebootsTotal = (bytes[5] & 0x1C) >> 2; // Total number of VoBo reboots not including Fatal Error for the last HeartBeat interval + decoded.failedTransmitsTotal = (bytes[5] & 0xE0) >> 5; // Total Number of Failed LoRaWAN Transmits for the last HeartBeat interval + decoded.errorEventLogsTotal = bytes[6]; // Total number of Error Event Logs written for the last HeartBeat interval + decoded.warningEventLogsTotal = bytes[7]; // Total number of Warning Event Logs written for the last HeartBeat interval + decoded.infoEventLogsTotal = bytes[8]; // Total number of Info Event Logs written for the last HeartBeat interval + decoded.measurementPacketsTotal = (bytes[10]) << 8 | bytes[9]; // Number of Measurements Packets successfully sent for the last HeartBeat interval + + return decoded; +} + +function bytesToFloat32(bytesArray) { + var buffer = new ArrayBuffer(4); + var bytesRaw = new Uint8Array(buffer); + bytesRaw[0] = bytesArray[0]; + bytesRaw[1] = bytesArray[1]; + bytesRaw[2] = bytesArray[2]; + bytesRaw[3] = bytesArray[3]; + var float32Res = new DataView(buffer).getFloat32(0, false); + + return float32Res; +} + +function parseOneAnalogSensorPayload(bytes) { + var decoded = {}; + decoded.sensorNum0 = bytes[0]; // Sensor Num 0 (8-bit) + decoded.sensorUnits0 = bytes[1]; // Sensor Units 0 (8-bit) + decoded.sensorData0 = bytesToFloat32(bytes.slice(2, 6)); // Sensor Data 0 (32-bit) + + return decoded; +} + +function parseTwoAnalogSensorsPayload(bytes) { + var decoded = {}; + decoded.sensorNum0 = (bytes[0] & 0xF0) >> 4; // Sensor Num 0 (4-bit) + decoded.sensorUnits0 = bytes[1]; // Sensor Units 0 (8-bit) + decoded.sensorData0 = bytesToFloat32(bytes.slice(2, 6)); // Sensor Data 0 (32-bit) + decoded.sensorNum1 = bytes[0] & 0x0F; // Sensor Num 1 (4-bit) + decoded.sensorUnits1 = bytes[6]; // Sensor Units 1 (8-bit) + decoded.sensorData1 = bytesToFloat32(bytes.slice(7, 11)); // Sensor Data 1 (32-bit) + + return decoded; +} + +function parseDigitalSensorsPayload(bytes) { + var decoded = {}; + decoded.sensorValid0 = (bytes[1] << 8) | bytes[0]; // Sensor Valid 0 (16-bit) + decoded.sensorData0 = (bytes[3] << 8) | bytes[2]; // Sensor Data 0 (16-bit) + + return decoded; +} + +function parseEventLogPayload(bytes) { + var decoded = {}; + decoded.eventTimestamp = bytes[0] + bytes[1] * Math.pow(2, 8) + bytes[2] * Math.pow(2, 16) + bytes[3] * Math.pow(2, 24); + decoded.eventCode = bytes[4] + bytes[5] * Math.pow(2, 8); + decoded.metadata = Array.prototype.slice.call(bytes.slice(6, 11), 0); + + return decoded; +} + +function parseVoboLibGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.transRejoin = bytes[1] & 0x0F; // Transmission Rejoin (4-bit) + decoded.ackFrequency = (bytes[1] & 0xF0) >> 4; // Acknowledgement Frequency for Data (4-bit) + decoded.lowBattery = parseFloat(((bytes[2] & 0x0F) / 10.0 + 2.5).toFixed(1)); // Low Battery Threshold (4-bit) + decoded.reserved1 = (bytes[2] & 0x10) >> 4; // Reserved 1 Field (1-bit) + decoded.heartbeatAckEnable = Boolean((bytes[2] & 0x20) >> 5); // Acknowledgement Enable for Heartbeat (1-bit) + decoded.operationMode = (bytes[2] & 0x40) >> 6; // Operation Mode (1-bit) + decoded.cycleSubBands = Boolean((bytes[2] & 0x80) >> 7); // Cycle Sub Bands Enable (1-bit) + decoded.ackRetries = (bytes[3] & 0x07); // Acknowledgement Retries (3-bit) + decoded.reservedLL = (bytes[3] & 0x38) >> 3; // Reserved LL (3-bit) + decoded.ackEnable = Boolean((bytes[3] & 0x40) >> 6); // Acknowledgement Enable for Data (1-bit) + decoded.heartbeatEnable = Boolean((bytes[3] & 0x80) >> 7); // Heartbeat Enable (1-bit) + decoded.cycleTime = ((bytes[6] & 0x03) << 16) | (bytes[5] << 8) | bytes[4]; // Cycle Time (18-bit) + decoded.backOffReset = (bytes[6] & 0xFC) >> 2; // BackOff Reset (6-bit) + decoded.reservedRD = bytes[7] & 0x3F; // Reserved RD (6-bit) + decoded.reserved2 = (bytes[7] & 0xC0) >> 6; // Reserved 2 Field (2-bit) + decoded.resendAttempts = bytes[8] & 0x0F; // Resend Attempts (4-bit) + decoded.freqSubBand = (bytes[8] & 0xF0) >> 4; // Frequency Sub Band (4-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.timeSyncInterval = bytes[1]; // Time Sync Interval in 30 minutes quantities (8-bit) + decoded.joinEUI = ""; // LoRaWAN Join EUI (64-bit) + for (let i = 9; i >= 2; i--) { + decoded.joinEUI += bytes[i].toString(16).toUpperCase().padStart(2, 0); + if (i != 2) decoded.joinEUI += "-"; + } + decoded.joinNonceResetEnable = Boolean(bytes[10] & 0x01); // Join Nonce Reset Enable (1-bit) + decoded.timeSyncWakeupEnable = Boolean((bytes[10] & 0x02) >> 1); // Time Sync Wakeup Enable (1-bit) + decoded.reserved1 = (bytes[10] & 0xFC) >> 2; // Reserved 1 Field (1-bit) + } + + return decoded; +} + +function parseVoboLibVoboSyncConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.vbsNodeNumber = (bytes[2] << 8) | bytes[1]; // VoboSync Node Number (16-bit) + decoded.vbsTimeReference = bytes.slice(3, 7).readUInt32LE(); // VoboSync Time Reference (32-bit) + decoded.reservedVCPDS = bytes[7] & 0x0F; // Reserved VCPDS (4-bit) + decoded.reservedVMPDS = (bytes[7] & 0xF0) >> 4; // Reserved VMPDS (4-bit) + decoded.reservedVUDS = bytes[8] & 0x0F; // Reserved VUDS (4-bit) + decoded.reserved1 = (bytes[8] & 0x30) >> 4; // Reserved 1 Field (2-bit) + decoded.reservedVSAE = Boolean((bytes[8] & 0x40) >> 6); // Reserved VSAE (1-bit) + decoded.vbsEnable = Boolean((bytes[8] & 0x80) >> 7); // VoboSync Enable (1-bit) + decoded.reserved3 = bytes[9] & 0x3F; // Reserved 3 (6-bit) + decoded.reserved2 = (bytes[9] & 0xC0) >> 6; // Reserved 2 Field (2-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.vbsMeasurementDelaySec = bytes[1] & 0x7F; // VoboSync Measurement Delay (7-bit) + decoded.reserved1 = (bytes[1] & 0x80) >> 7; // Reserved 1 Field (1-bit) + decoded.vbsUplinkDelaySec = bytes[2] & 0x7F; // VoboSync Uplink Delay (7-bit) + decoded.reserved2 = (bytes[2] & 0x80) >> 7; // Reserved 2 Field (1-bit) + } + + return decoded; +} + +function parseVoboXXGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + decoded.analogVoltage = parseFloat((bytes[1] / 10).toFixed(1)); // Analog Voltage (8-bit) + decoded.powerTime = parseFloat((bytes[2] / 10).toFixed(1)); // Analog Power Time (8-bit) + decoded.mbEnable = Boolean(bytes[3] & 0x01); // Modbus Enable (1-bit) + decoded.engUnitsEnable = Boolean((bytes[3] >> 1) & 0x01); // Engineering Units Enable (1-bit) + decoded.din1TransmitEnable = Boolean((bytes[3] >> 2) & 0x01); // DIN1 Transmit Enable (1-bit) + decoded.din2TransmitEnable = Boolean((bytes[3] >> 3) & 0x01); // DIN2 Transmit Enable (1-bit) + decoded.din3TransmitEnable = Boolean((bytes[3] >> 4) & 0x01); // DIN3 Transmit Enable (1-bit) + decoded.wkupTransmitEnable = Boolean((bytes[3] >> 5) & 0x01); // WKUP Transmit Enable (1-bit) + decoded.ain1TransmitEnable = Boolean((bytes[3] >> 6) & 0x01); // AIN1 Transmit Enable (1-bit) + decoded.ain2TransmitEnable = Boolean((bytes[3] >> 7) & 0x01); // AIN2 Transmit Enable (1-bit) + decoded.ain3TransmitEnable = Boolean(bytes[4] & 0x01); // AIN3 Transmit Enable (1-bit) + decoded.batteryLevelTransmitEnable = Boolean((bytes[4] >> 1) & 0x01); // Battery Level Transmit Enable (1-bit) + decoded.adcTemperatureTransmitEnable = Boolean((bytes[4] >> 2) & 0x01); // ADC Temperature Transmit Enable (1-bit) + decoded.mbTransmitEnable = Boolean((bytes[4] >> 3) & 0x01); // Modbus Transmit Enable (1-bit) + decoded.ainPayloadType = (bytes[4] >> 4) & 0x01; // Ain Payload Type (1-bit) + decoded.reservedMAWE = Boolean((bytes[4] >> 5) & 0x01); // Reserved MAWE Field (1-bit) + decoded.reservedMADE = Boolean((bytes[4] >> 6) & 0x01); // Reserved MADE Field (1-bit) + decoded.reservedMAME = Boolean((bytes[4] >> 7) & 0x01); // Reserved MAME Field (1-bit) + + return decoded; +} + +function parseVoboXXModbusGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + decoded.mbTimeout = (bytes[2] << 8) | bytes[1]; // Modbus Timeout in milliseconds (16-bit) + decoded.mbBaud = bytes[3] & 0x0F; // Modbus Baud Rate - encoded (4-bit) + decoded.mbStopBits = (bytes[3] >> 4) & 0x03; // Modbus Stop Bits (2-bit) + decoded.mbParity = (bytes[3] >> 6) & 0x03; // Modbus Parity (2-bit) + decoded.mbPayloadType = bytes[4] & 0x03; // Modbus Payload Type (2-bit) + decoded.reserved1 = (bytes[4] >> 2) & 0x3F; // Reserved 1 Field (6-bit) + + return decoded; +} + +function parseVoboXXModbusGroupsEnableConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.subgroupID == 6) { + decoded.mbFirstCycleG1 = Boolean(bytes[1] & 0x01); // Group 1 First Cycle Enable + decoded.mbFirstCycleG2 = Boolean((bytes[1] >> 1) & 0x01); // Group 2 First Cycle Enable + decoded.mbFirstCycleG3 = Boolean((bytes[1] >> 2) & 0x01); // Group 3 First Cycle Enable + decoded.mbFirstCycleG4 = Boolean((bytes[1] >> 3) & 0x01); // Group 4 First Cycle Enable + decoded.mbFirstCycleG5 = Boolean((bytes[1] >> 4) & 0x01); // Group 5 First Cycle Enable + decoded.mbFirstCycleG6 = Boolean((bytes[1] >> 5) & 0x01); // Group 6 First Cycle Enable + decoded.mbFirstCycleG7 = Boolean((bytes[1] >> 6) & 0x01); // Group 7 First Cycle Enable + decoded.mbFirstCycleG8 = Boolean((bytes[1] >> 7) & 0x01); // Group 8 First Cycle Enable + decoded.mbFirstCycleG9 = Boolean(bytes[2] & 0x01); // Group 9 First Cycle Enable + decoded.mbFirstCycleG10 = Boolean((bytes[2] >> 1) & 0x01); // Group 10 First Cycle Enable + decoded.mbFirstCycleG11 = Boolean((bytes[2] >> 2) & 0x01); // Group 11 First Cycle Enable + decoded.mbFirstCycleG12 = Boolean((bytes[2] >> 3) & 0x01); // Group 12 First Cycle Enable + decoded.mbFirstCycleG13 = Boolean((bytes[2] >> 4) & 0x01); // Group 13 First Cycle Enable + decoded.mbFirstCycleG14 = Boolean((bytes[2] >> 5) & 0x01); // Group 14 First Cycle Enable + decoded.mbFirstCycleG15 = Boolean((bytes[2] >> 6) & 0x01); // Group 15 First Cycle Enable + decoded.mbFirstCycleG16 = Boolean((bytes[2] >> 7) & 0x01); // Group 16 First Cycle Enable + decoded.mbFirstCycleG17 = Boolean(bytes[3] & 0x01); // Group 17 First Cycle Enable + decoded.mbFirstCycleG18 = Boolean((bytes[3] >> 1) & 0x01); // Group 18 First Cycle Enable + decoded.mbFirstCycleG19 = Boolean((bytes[3] >> 2) & 0x01); // Group 19 First Cycle Enable + decoded.mbFirstCycleG20 = Boolean((bytes[3] >> 3) & 0x01); // Group 20 First Cycle Enable + decoded.mbFirstCycleG21 = Boolean((bytes[3] >> 4) & 0x01); // Group 21 First Cycle Enable + decoded.mbFirstCycleG22 = Boolean((bytes[3] >> 5) & 0x01); // Group 22 First Cycle Enable + decoded.mbFirstCycleG23 = Boolean((bytes[3] >> 6) & 0x01); // Group 23 First Cycle Enable + decoded.mbFirstCycleG24 = Boolean((bytes[3] >> 7) & 0x01); // Group 24 First Cycle Enable + decoded.mbFirstCycleG25 = Boolean(bytes[4] & 0x01); // Group 25 First Cycle Enable + decoded.mbFirstCycleG26 = Boolean((bytes[4] >> 1) & 0x01); // Group 26 First Cycle Enable + decoded.mbFirstCycleG27 = Boolean((bytes[4] >> 2) & 0x01); // Group 27 First Cycle Enable + decoded.mbFirstCycleG28 = Boolean((bytes[4] >> 3) & 0x01); // Group 28 First Cycle Enable + decoded.mbFirstCycleG29 = Boolean((bytes[4] >> 4) & 0x01); // Group 29 First Cycle Enable + decoded.mbFirstCycleG30 = Boolean((bytes[4] >> 5) & 0x01); // Group 30 First Cycle Enable + decoded.mbFirstCycleG31 = Boolean((bytes[4] >> 6) & 0x01); // Group 31 First Cycle Enable + decoded.mbFirstCycleG32 = Boolean((bytes[4] >> 7) & 0x01); // Group 32 First Cycle Enable + decoded.mbFirstCycleG33 = Boolean(bytes[5] & 0x01); // Group 33 First Cycle Enable + decoded.mbFirstCycleG34 = Boolean((bytes[5] >> 1) & 0x01); // Group 34 First Cycle Enable + decoded.mbFirstCycleG35 = Boolean((bytes[5] >> 2) & 0x01); // Group 35 First Cycle Enable + decoded.mbFirstCycleG36 = Boolean((bytes[5] >> 3) & 0x01); // Group 36 First Cycle Enable + decoded.mbFirstCycleG37 = Boolean((bytes[5] >> 4) & 0x01); // Group 37 First Cycle Enable + decoded.mbFirstCycleG38 = Boolean((bytes[5] >> 5) & 0x01); // Group 38 First Cycle Enable + decoded.mbFirstCycleG39 = Boolean((bytes[5] >> 6) & 0x01); // Group 39 First Cycle Enable + decoded.mbFirstCycleG40 = Boolean((bytes[5] >> 7) & 0x01); // Group 40 First Cycle Enable + decoded.mbFirstCycleG41 = Boolean(bytes[6] & 0x01); // Group 41 First Cycle Enable + decoded.reserved1 = (bytes[6] >> 1) & 0x7F; // Reserved 1 Field (7-bit) + } + if (decoded.subgroupID == 7) { + decoded.mbSubseqCyclesG1 = Boolean(bytes[1] & 0x01); // Group 1 Subsequent Cycle Enable + decoded.mbSubseqCyclesG2 = Boolean((bytes[1] >> 1) & 0x01); // Group 2 Subsequent Cycle Enable + decoded.mbSubseqCyclesG3 = Boolean((bytes[1] >> 2) & 0x01); // Group 3 Subsequent Cycle Enable + decoded.mbSubseqCyclesG4 = Boolean((bytes[1] >> 3) & 0x01); // Group 4 Subsequent Cycle Enable + decoded.mbSubseqCyclesG5 = Boolean((bytes[1] >> 4) & 0x01); // Group 5 Subsequent Cycle Enable + decoded.mbSubseqCyclesG6 = Boolean((bytes[1] >> 5) & 0x01); // Group 6 Subsequent Cycle Enable + decoded.mbSubseqCyclesG7 = Boolean((bytes[1] >> 6) & 0x01); // Group 7 Subsequent Cycle Enable + decoded.mbSubseqCyclesG8 = Boolean((bytes[1] >> 7) & 0x01); // Group 8 Subsequent Cycle Enable + decoded.mbSubseqCyclesG9 = Boolean(bytes[2] & 0x01); // Group 9 Subsequent Cycle Enable + decoded.mbSubseqCyclesG10 = Boolean((bytes[2] >> 1) & 0x01); // Group 10 Subsequent Cycle Enable + decoded.mbSubseqCyclesG11 = Boolean((bytes[2] >> 2) & 0x01); // Group 11 Subsequent Cycle Enable + decoded.mbSubseqCyclesG12 = Boolean((bytes[2] >> 3) & 0x01); // Group 12 Subsequent Cycle Enable + decoded.mbSubseqCyclesG13 = Boolean((bytes[2] >> 4) & 0x01); // Group 13 Subsequent Cycle Enable + decoded.mbSubseqCyclesG14 = Boolean((bytes[2] >> 5) & 0x01); // Group 14 Subsequent Cycle Enable + decoded.mbSubseqCyclesG15 = Boolean((bytes[2] >> 6) & 0x01); // Group 15 Subsequent Cycle Enable + decoded.mbSubseqCyclesG16 = Boolean((bytes[2] >> 7) & 0x01); // Group 16 Subsequent Cycle Enable + decoded.mbSubseqCyclesG17 = Boolean(bytes[3] & 0x01); // Group 17 Subsequent Cycle Enable + decoded.mbSubseqCyclesG18 = Boolean((bytes[3] >> 1) & 0x01); // Group 18 Subsequent Cycle Enable + decoded.mbSubseqCyclesG19 = Boolean((bytes[3] >> 2) & 0x01); // Group 19 Subsequent Cycle Enable + decoded.mbSubseqCyclesG20 = Boolean((bytes[3] >> 3) & 0x01); // Group 20 Subsequent Cycle Enable + decoded.mbSubseqCyclesG21 = Boolean((bytes[3] >> 4) & 0x01); // Group 21 Subsequent Cycle Enable + decoded.mbSubseqCyclesG22 = Boolean((bytes[3] >> 5) & 0x01); // Group 22 Subsequent Cycle Enable + decoded.mbSubseqCyclesG23 = Boolean((bytes[3] >> 6) & 0x01); // Group 23 Subsequent Cycle Enable + decoded.mbSubseqCyclesG24 = Boolean((bytes[3] >> 7) & 0x01); // Group 24 Subsequent Cycle Enable + decoded.mbSubseqCyclesG25 = Boolean(bytes[4] & 0x01); // Group 25 Subsequent Cycle Enable + decoded.mbSubseqCyclesG26 = Boolean((bytes[4] >> 1) & 0x01); // Group 26 Subsequent Cycle Enable + decoded.mbSubseqCyclesG27 = Boolean((bytes[4] >> 2) & 0x01); // Group 27 Subsequent Cycle Enable + decoded.mbSubseqCyclesG28 = Boolean((bytes[4] >> 3) & 0x01); // Group 28 Subsequent Cycle Enable + decoded.mbSubseqCyclesG29 = Boolean((bytes[4] >> 4) & 0x01); // Group 29 Subsequent Cycle Enable + decoded.mbSubseqCyclesG30 = Boolean((bytes[4] >> 5) & 0x01); // Group 30 Subsequent Cycle Enable + decoded.mbSubseqCyclesG31 = Boolean((bytes[4] >> 6) & 0x01); // Group 31 Subsequent Cycle Enable + decoded.mbSubseqCyclesG32 = Boolean((bytes[4] >> 7) & 0x01); // Group 32 Subsequent Cycle Enable + decoded.mbSubseqCyclesG33 = Boolean(bytes[5] & 0x01); // Group 33 Subsequent Cycle Enable + decoded.mbSubseqCyclesG34 = Boolean((bytes[5] >> 1) & 0x01); // Group 34 Subsequent Cycle Enable + decoded.mbSubseqCyclesG35 = Boolean((bytes[5] >> 2) & 0x01); // Group 35 Subsequent Cycle Enable + decoded.mbSubseqCyclesG36 = Boolean((bytes[5] >> 3) & 0x01); // Group 36 Subsequent Cycle Enable + decoded.mbSubseqCyclesG37 = Boolean((bytes[5] >> 4) & 0x01); // Group 37 Subsequent Cycle Enable + decoded.mbSubseqCyclesG38 = Boolean((bytes[5] >> 5) & 0x01); // Group 38 Subsequent Cycle Enable + decoded.mbSubseqCyclesG39 = Boolean((bytes[5] >> 6) & 0x01); // Group 39 Subsequent Cycle Enable + decoded.mbSubseqCyclesG40 = Boolean((bytes[5] >> 7) & 0x01); // Group 40 Subsequent Cycle Enable + decoded.mbSubseqCyclesG41 = Boolean(bytes[6] & 0x01); // Group 41 Subsequent Cycle Enable + decoded.reserved1 = (bytes[6] >> 1) & 0x7F; // Reserved 1 Field (7-bit) + } + + return decoded; +} + +function parseVoboXXModbusGroupsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + var groupIdx = ((decoded.subgroupID - 8) * 16) + decoded.sequenceNumber + 1; // Sub-Groups 8, 9 and 10 carry the groups configurations. Group index is determined by Sub-Group ID and Sequence Number. + var groupIdxStr = (groupIdx).toString(); + decoded["mbFirstCycleG" + groupIdxStr] = Boolean(bytes[1] & 0x01); // Modbus Group First Cycle Enable (1-bit) + decoded["mbSubseqCyclesG" + groupIdxStr] = Boolean((bytes[1] >> 1) & 0x01); // Modbus Group Subsequent Cycle Enable (1-bit) + decoded["mbByteSwapG" + groupIdxStr] = Boolean((bytes[1] >> 2) & 0x01); // Modbus Group Byte Swap Enable (1-bit) + decoded["mbWordSwapG" + groupIdxStr] = Boolean((bytes[1] >> 3) & 0x01); // Modbus Group Word Swap Enable (1-bit) + decoded["mbNumTypeG" + groupIdxStr] = (bytes[1] >> 4) & 0x03; // Modbus Group Numerical Type (2-bit) + decoded["reserved1"] = (bytes[1] >> 6) & 0x03; // Reserved 1 Field (2-bit) + decoded["mbSlaveAddrG" + groupIdxStr] = bytes[2]; // Modbus Group Slave Address (8-bit) + decoded["mbStartAddrG" + groupIdxStr] = (bytes[4] << 8) | bytes[3]; // Modbus Group Start Address (16-bit) + decoded["mbNumRegsG" + groupIdxStr] = bytes[5]; // Modbus Group Number of Registers (8-bit) + decoded["mbWdataG" + groupIdxStr] = (bytes[7] << 8) | bytes[6]; // Modbus Group Write Data (16-bit) + decoded["mbGrpDelayG" + groupIdxStr] = (bytes[9] << 8) | bytes[8]; // Modbus Group Delay in milliseconds (16-bit) + decoded["mbFuncCodeG" + groupIdxStr] = bytes[10] & 0x3F; // Modbus Group Function Code (6-bit) + decoded["reserved2"] = (bytes[10] >> 6) & 0x03; // Reserved 2 Field (2-bit) + + return decoded; +} + +function parseVoboXXModbusPayloadsSlotsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.mbGroupPaySlot0 = bytes[1]; // Modbus Payload Slot 0 Group Idx (8-bit) + decoded.mbRegPaySlot0 = bytes[2]; // Modbus Payload Slot 0 Register Idx (8-bit) + decoded.mbGroupPaySlot1 = bytes[3]; // Modbus Payload Slot 1 Group Idx (8-bit) + decoded.mbRegPaySlot1 = bytes[4]; // Modbus Payload Slot 1 Register Idx (8-bit) + decoded.mbGroupPaySlot2 = bytes[5]; // Modbus Payload Slot 2 Group Idx (8-bit) + decoded.mbRegPaySlot2 = bytes[6]; // Modbus Payload Slot 2 Register Idx (8-bit) + decoded.mbGroupPaySlot3 = bytes[7]; // Modbus Payload Slot 3 Group Idx (8-bit) + decoded.mbRegPaySlot3 = bytes[8]; // Modbus Payload Slot 3 Register Idx (8-bit) + decoded.mbGroupPaySlot4 = bytes[9]; // Modbus Payload Slot 4 Group Idx (8-bit) + decoded.mbRegPaySlot4 = bytes[10]; // Modbus Payload Slot 4 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.mbGroupPaySlot5 = bytes[1]; // Modbus Payload Slot 5 Group Idx (8-bit) + decoded.mbRegPaySlot5 = bytes[2]; // Modbus Payload Slot 5 Register Idx (8-bit) + decoded.mbGroupPaySlot6 = bytes[3]; // Modbus Payload Slot 6 Group Idx (8-bit) + decoded.mbRegPaySlot6 = bytes[4]; // Modbus Payload Slot 6 Register Idx (8-bit) + decoded.mbGroupPaySlot7 = bytes[5]; // Modbus Payload Slot 7 Group Idx (8-bit) + decoded.mbRegPaySlot7 = bytes[6]; // Modbus Payload Slot 7 Register Idx (8-bit) + decoded.mbGroupPaySlot8 = bytes[7]; // Modbus Payload Slot 8 Group Idx (8-bit) + decoded.mbRegPaySlot8 = bytes[8]; // Modbus Payload Slot 8 Register Idx (8-bit) + decoded.mbGroupPaySlot9 = bytes[9]; // Modbus Payload Slot 9 Group Idx (8-bit) + decoded.mbRegPaySlot9 = bytes[10]; // Modbus Payload Slot 9 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 2) { + decoded.mbGroupPaySlot10 = bytes[1]; // Modbus Payload Slot 10 Group Idx (8-bit) + decoded.mbRegPaySlot10 = bytes[2]; // Modbus Payload Slot 10 Register Idx (8-bit) + decoded.mbGroupPaySlot11 = bytes[3]; // Modbus Payload Slot 11 Group Idx (8-bit) + decoded.mbRegPaySlot11 = bytes[4]; // Modbus Payload Slot 11 Register Idx (8-bit) + decoded.mbGroupPaySlot12 = bytes[5]; // Modbus Payload Slot 12 Group Idx (8-bit) + decoded.mbRegPaySlot12 = bytes[6]; // Modbus Payload Slot 12 Register Idx (8-bit) + decoded.mbGroupPaySlot13 = bytes[7]; // Modbus Payload Slot 13 Group Idx (8-bit) + decoded.mbRegPaySlot13 = bytes[8]; // Modbus Payload Slot 13 Register Idx (8-bit) + decoded.mbGroupPaySlot14 = bytes[9]; // Modbus Payload Slot 14 Group Idx (8-bit) + decoded.mbRegPaySlot14 = bytes[10]; // Modbus Payload Slot 14 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 3) { + decoded.mbGroupPaySlot15 = bytes[1]; // Modbus Payload Slot 15 Group Idx (8-bit) + decoded.mbRegPaySlot15 = bytes[2]; // Modbus Payload Slot 15 Register Idx (8-bit) + decoded.mbGroupPaySlot16 = bytes[3]; // Modbus Payload Slot 16 Group Idx (8-bit) + decoded.mbRegPaySlot16 = bytes[4]; // Modbus Payload Slot 16 Register Idx (8-bit) + decoded.mbGroupPaySlot17 = bytes[5]; // Modbus Payload Slot 17 Group Idx (8-bit) + decoded.mbRegPaySlot17 = bytes[6]; // Modbus Payload Slot 17 Register Idx (8-bit) + decoded.mbGroupPaySlot18 = bytes[7]; // Modbus Payload Slot 18 Group Idx (8-bit) + decoded.mbRegPaySlot18 = bytes[8]; // Modbus Payload Slot 18 Register Idx (8-bit) + decoded.mbGroupPaySlot19 = bytes[9]; // Modbus Payload Slot 19 Group Idx (8-bit) + decoded.mbRegPaySlot19 = bytes[10]; // Modbus Payload Slot 19 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 4) { + decoded.mbGroupPaySlot20 = bytes[1]; // Modbus Payload Slot 20 Group Idx (8-bit) + decoded.mbRegPaySlot20 = bytes[2]; // Modbus Payload Slot 20 Register Idx (8-bit) + decoded.mbGroupPaySlot21 = bytes[3]; // Modbus Payload Slot 21 Group Idx (8-bit) + decoded.mbRegPaySlot21 = bytes[4]; // Modbus Payload Slot 21 Register Idx (8-bit) + decoded.mbGroupPaySlot22 = bytes[5]; // Modbus Payload Slot 22 Group Idx (8-bit) + decoded.mbRegPaySlot22 = bytes[6]; // Modbus Payload Slot 22 Register Idx (8-bit) + decoded.mbGroupPaySlot23 = bytes[7]; // Modbus Payload Slot 23 Group Idx (8-bit) + decoded.mbRegPaySlot23 = bytes[8]; // Modbus Payload Slot 23 Register Idx (8-bit) + decoded.mbGroupPaySlot24 = bytes[9]; // Modbus Payload Slot 24 Group Idx (8-bit) + decoded.mbRegPaySlot24 = bytes[10]; // Modbus Payload Slot 24 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 5) { + decoded.mbGroupPaySlot25 = bytes[1]; // Modbus Payload Slot 25 Group Idx (8-bit) + decoded.mbRegPaySlot25 = bytes[2]; // Modbus Payload Slot 25 Register Idx (8-bit) + decoded.mbGroupPaySlot26 = bytes[3]; // Modbus Payload Slot 26 Group Idx (8-bit) + decoded.mbRegPaySlot26 = bytes[4]; // Modbus Payload Slot 26 Register Idx (8-bit) + decoded.mbGroupPaySlot27 = bytes[5]; // Modbus Payload Slot 27 Group Idx (8-bit) + decoded.mbRegPaySlot27 = bytes[6]; // Modbus Payload Slot 27 Register Idx (8-bit) + decoded.mbGroupPaySlot28 = bytes[7]; // Modbus Payload Slot 28 Group Idx (8-bit) + decoded.mbRegPaySlot28 = bytes[8]; // Modbus Payload Slot 28 Register Idx (8-bit) + decoded.mbGroupPaySlot29 = bytes[9]; // Modbus Payload Slot 29 Group Idx (8-bit) + decoded.mbRegPaySlot29 = bytes[10]; // Modbus Payload Slot 29 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 6) { + decoded.mbGroupPaySlot30 = bytes[1]; // Modbus Payload Slot 30 Group Idx (8-bit) + decoded.mbRegPaySlot30 = bytes[2]; // Modbus Payload Slot 30 Register Idx (8-bit) + decoded.mbGroupPaySlot31 = bytes[3]; // Modbus Payload Slot 31 Group Idx (8-bit) + decoded.mbRegPaySlot31 = bytes[4]; // Modbus Payload Slot 31 Register Idx (8-bit) + decoded.mbGroupPaySlot32 = bytes[5]; // Modbus Payload Slot 32 Group Idx (8-bit) + decoded.mbRegPaySlot32 = bytes[6]; // Modbus Payload Slot 32 Register Idx (8-bit) + decoded.mbGroupPaySlot33 = bytes[7]; // Modbus Payload Slot 33 Group Idx (8-bit) + decoded.mbRegPaySlot33 = bytes[8]; // Modbus Payload Slot 33 Register Idx (8-bit) + decoded.mbGroupPaySlot34 = bytes[9]; // Modbus Payload Slot 34 Group Idx (8-bit) + decoded.mbRegPaySlot34 = bytes[10]; // Modbus Payload Slot 34 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 7) { + decoded.mbGroupPaySlot35 = bytes[1]; // Modbus Payload Slot 35 Group Idx (8-bit) + decoded.mbRegPaySlot35 = bytes[2]; // Modbus Payload Slot 35 Register Idx (8-bit) + decoded.mbGroupPaySlot36 = bytes[3]; // Modbus Payload Slot 36 Group Idx (8-bit) + decoded.mbRegPaySlot36 = bytes[4]; // Modbus Payload Slot 36 Register Idx (8-bit) + decoded.mbGroupPaySlot37 = bytes[5]; // Modbus Payload Slot 37 Group Idx (8-bit) + decoded.mbRegPaySlot37 = bytes[6]; // Modbus Payload Slot 37 Register Idx (8-bit) + decoded.mbGroupPaySlot38 = bytes[7]; // Modbus Payload Slot 38 Group Idx (8-bit) + decoded.mbRegPaySlot38 = bytes[8]; // Modbus Payload Slot 38 Register Idx (8-bit) + decoded.mbGroupPaySlot39 = bytes[9]; // Modbus Payload Slot 39 Group Idx (8-bit) + decoded.mbRegPaySlot39 = bytes[10]; // Modbus Payload Slot 39 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 8) { + decoded.mbGroupPaySlot40 = bytes[1]; // Modbus Payload Slot 40 Group Idx (8-bit) + decoded.mbRegPaySlot40 = bytes[2]; // Modbus Payload Slot 40 Register Idx (8-bit) + } + + return decoded; +} + +function parseVoboXXEngineeringUnitsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + // AIN1 General + if (decoded.sequenceNumber == 0) { + decoded.ain1UnitsCode = bytes[1]; // AIN1 Units Code (8-bit) + decoded.ain1MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN1 Minimum Value (32-bit) + decoded.ain1MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN1 Maximum Value (32-bit) + decoded.ain1Type = bytes[10] & 0x03; // AIN1 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN1 Series Resistance + if (decoded.sequenceNumber == 1) { + decoded.ain1SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN1 Series Resistance (32-bit) + } + // AIN1 Calibration + if (decoded.sequenceNumber == 2) { + decoded.ain1Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN1 Calibration Gain (32-bit) + decoded.ain1Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN1 Calibration Offset (32-bit) + } + + // AIN2 General + if (decoded.sequenceNumber == 3) { + decoded.ain2UnitsCode = bytes[1]; // AIN2 Units Code (8-bit) + decoded.ain2MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN2 Minimum Value (32-bit) + decoded.ain2MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN2 Maximum Value (32-bit) + decoded.ain2Type = bytes[10] & 0x03; // AIN2 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN2 Series Resistance + if (decoded.sequenceNumber == 4) { + decoded.ain2SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN2 Series Resistance (32-bit) + } + // AIN2 Calibration + if (decoded.sequenceNumber == 5) { + decoded.ain2Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN2 Calibration Gain (32-bit) + decoded.ain2Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN2 Calibration Offset (32-bit) + } + + // AIN3 General + if (decoded.sequenceNumber == 6) { + decoded.ain3UnitsCode = bytes[1]; // AIN3 Units Code (8-bit) + decoded.ain3MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN3 Minimum Value (32-bit) + decoded.ain3MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN3 Maximum Value (32-bit) + decoded.ain3Type = bytes[10] & 0x03; // AIN3 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN3 Series Resistance + if (decoded.sequenceNumber == 7) { + decoded.ain3SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN3 Series Resistance (32-bit) + } + // AIN3 Calibration + if (decoded.sequenceNumber == 8) { + decoded.ain3Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN3 Calibration Gain (32-bit) + decoded.ain3Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN3 Calibration Offset (32-bit) + } + + return decoded; +} + +function parseVoboTCGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.deviceType1 = bytes[1] & 0x07; // Device Type 1 (3-bit) + decoded.enable1 = Boolean((bytes[1] & 0x08) >> 3); // Enable 1 (1-bit) + decoded.deviceType2 = (bytes[1] & 0x70) >> 4; // Device Type 2 (3-bit) + decoded.enable2 = Boolean((bytes[1] & 0x80) >> 7); // Enable 2 (1-bit) + decoded.deviceType3 = bytes[2] & 0x07; // Device Type 3 (3-bit) + decoded.enable3 = Boolean((bytes[2] & 0x08) >> 3); // Enable 3 (1-bit) + decoded.deviceType4 = (bytes[2] & 0x70) >> 4; // Device Type 4 (3-bit) + decoded.enable4 = Boolean((bytes[2] & 0x80) >> 7); // Enable 4 (1-bit) + decoded.deviceType5 = bytes[3] & 0x07; // Device Type 5 (3-bit) + decoded.enable5 = Boolean((bytes[3] & 0x08) >> 3); // Enable 5 (1-bit) + decoded.deviceType6 = (bytes[3] & 0x70) >> 4; // Device Type 6 (3-bit) + decoded.enable6 = Boolean((bytes[3] & 0x80) >> 7); // Enable 6 (1-bit) + decoded.deviceType7 = bytes[4] & 0x07; // Device Type 7 (3-bit) + decoded.enable7 = Boolean((bytes[4] & 0x08) >> 3); // Enable 7 (1-bit) + decoded.deviceType8 = (bytes[4] & 0x70) >> 4; // Device Type 8 (3-bit) + decoded.enable8 = Boolean((bytes[4] & 0x80) >> 7); // Enable 8 (1-bit) + decoded.deviceType9 = bytes[5] & 0x07; // Device Type 9 (3-bit) + decoded.enable9 = Boolean((bytes[5] & 0x08) >> 3); // Enable 9 (1-bit) + decoded.deviceType10 = (bytes[5] & 0x70) >> 4; // Device Type 10 (3-bit) + decoded.enable10 = Boolean((bytes[5] & 0x80) >> 7); // Enable 10 (1-bit) + decoded.deviceType11 = bytes[6] & 0x07; // Device Type 11 (3-bit) + decoded.enable11 = Boolean((bytes[6] & 0x08) >> 3); // Enable 11 (1-bit) + decoded.deviceType12 = (bytes[6] & 0x70) >> 4; // Device Type 12 (3-bit) + decoded.enable12 = Boolean((bytes[6] & 0x80) >> 7); // Enable 12 (1-bit) + decoded.deviceUnits1 = bytes[7] & 0x01; // Device Units 1 (1-bit) + decoded.deviceUnits2 = (bytes[7] & 0x02) >> 1; // Device Units 2 (1-bit) + decoded.deviceUnits3 = (bytes[7] & 0x04) >> 2; // Device Units 3 (1-bit) + decoded.deviceUnits4 = (bytes[7] & 0x08) >> 3; // Device Units 4 (1-bit) + decoded.deviceUnits5 = (bytes[7] & 0x10) >> 4; // Device Units 5 (1-bit) + decoded.deviceUnits6 = (bytes[7] & 0x20) >> 5; // Device Units 6 (1-bit) + decoded.deviceUnits7 = (bytes[7] & 0x40) >> 6; // Device Units 7 (1-bit) + decoded.deviceUnits8 = (bytes[7] & 0x80) >> 7; // Device Units 8 (1-bit) + decoded.deviceUnits9 = bytes[8] & 0x01; // Device Units 9 (1-bit) + decoded.deviceUnits10 = (bytes[8] & 0x02) >> 1; // Device Units 10 (1-bit) + decoded.deviceUnits11 = (bytes[8] & 0x04) >> 2; // Device Units 11 (1-bit) + decoded.deviceUnits12 = (bytes[8] & 0x08) >> 3; // Device Units 12 (1-bit) + decoded.wkupSensorTransmitEnable = Boolean((bytes[8] & 0x10) >> 4); // WKUP Sensor Transmit Enable (1-bit) + decoded.batterySensorTransmitEnable = Boolean((bytes[8] & 0x20) >> 5); // Battery Sensor Transmit Enable (1-bit) + decoded.coldJointSensorTransmitEnable = Boolean((bytes[8] & 0x40) >> 6); // Cold Joint Sensor Transmit Enable (1-bit) + decoded.reserved1 = (bytes[8] & 0x80) >> 7; // Reserved 1 Field (1-bit) + } + else if (decoded.sequenceNumber == 1) { + decoded.deviceType1 = bytes[1] & 0x07; // Device Type 1 (3-bit) + decoded.enable1 = Boolean((bytes[1] & 0x08) >> 3); // Enable 1 (1-bit) + decoded.deviceType2 = (bytes[1] & 0x70) >> 4; // Device Type 2 (3-bit) + decoded.enable2 = Boolean((bytes[1] & 0x80) >> 7); // Enable 2 (1-bit) + decoded.deviceType3 = bytes[2] & 0x07; // Device Type 3 (3-bit) + decoded.enable3 = Boolean((bytes[2] & 0x08) >> 3); // Enable 3 (1-bit) + decoded.deviceType4 = (bytes[2] & 0x70) >> 4; // Device Type 4 (3-bit) + decoded.enable4 = Boolean((bytes[2] & 0x80) >> 7); // Enable 4 (1-bit) + decoded.deviceType5 = bytes[3] & 0x07; // Device Type 5 (3-bit) + decoded.enable5 = Boolean((bytes[3] & 0x08) >> 3); // Enable 5 (1-bit) + decoded.deviceType6 = (bytes[3] & 0x70) >> 4; // Device Type 6 (3-bit) + decoded.enable6 = Boolean((bytes[3] & 0x80) >> 7); // Enable 6 (1-bit) + decoded.deviceType7 = bytes[4] & 0x07; // Device Type 7 (3-bit) + decoded.enable7 = Boolean((bytes[4] & 0x08) >> 3); // Enable 7 (1-bit) + decoded.deviceType8 = (bytes[4] & 0x70) >> 4; // Device Type 8 (3-bit) + decoded.enable8 = Boolean((bytes[4] & 0x80) >> 7); // Enable 8 (1-bit) + decoded.deviceType9 = bytes[5] & 0x07; // Device Type 9 (3-bit) + decoded.enable9 = Boolean((bytes[5] & 0x08) >> 3); // Enable 9 (1-bit) + decoded.deviceType10 = (bytes[5] & 0x70) >> 4; // Device Type 10 (3-bit) + decoded.enable10 = Boolean((bytes[5] & 0x80) >> 7); // Enable 10 (1-bit) + decoded.deviceType11 = bytes[6] & 0x07; // Device Type 11 (3-bit) + decoded.enable11 = Boolean((bytes[6] & 0x08) >> 3); // Enable 11 (1-bit) + decoded.deviceType12 = (bytes[6] & 0x70) >> 4; // Device Type 12 (3-bit) + decoded.enable12 = Boolean((bytes[6] & 0x80) >> 7); // Enable 12 (1-bit) + decoded.deviceUnits1 = bytes[7] & 0x03; // Device Units 1 (2-bit) + decoded.deviceUnits2 = (bytes[7] & 0x0C) >> 2; // Device Units 2 (2-bit) + decoded.deviceUnits3 = (bytes[7] & 0x30) >> 4; // Device Units 3 (2-bit) + decoded.deviceUnits4 = (bytes[7] & 0xC0) >> 6; // Device Units 4 (2-bit) + decoded.deviceUnits5 = bytes[8] & 0x03; // Device Units 5 (2-bit) + decoded.deviceUnits6 = (bytes[8] & 0x0C) >> 2; // Device Units 6 (2-bit) + decoded.deviceUnits7 = (bytes[8] & 0x30) >> 4; // Device Units 7 (2-bit) + decoded.deviceUnits8 = (bytes[8] & 0xC0) >> 6; // Device Units 8 (2-bit) + decoded.deviceUnits9 = bytes[9] & 0x03; // Device Units 9 (2-bit) + decoded.deviceUnits10 = (bytes[9] & 0x0C) >> 2; // Device Units 10 (2-bit) + decoded.deviceUnits11 = (bytes[9] & 0x30) >> 4; // Device Units 11 (2-bit) + decoded.deviceUnits12 = (bytes[9] & 0xC0) >> 6; // Device Units 12 (2-bit) + decoded.coldJointSensorUnits = bytes[10] & 0x03; // Cold Joint Sensor Units (2-bit) + decoded.wkupSensorTransmitEnable = Boolean((bytes[10] & 0x04) >> 2); // WKUP Sensor Transmit Enable (1-bit) + decoded.batterySensorTransmitEnable = Boolean((bytes[10] & 0x08) >> 3); // Battery Sensor Transmit Enable (1-bit) + decoded.coldJointSensorTransmitEnable = Boolean((bytes[10] & 0x10) >> 4); // Cold Joint Sensor Transmit Enable (1-bit) + decoded.tcSensorsTransmitEnable = Boolean((bytes[10] & 0x20) >> 5);; // TC Sensors Transmit Enable (1-bit) + decoded.ainPayloadType = (bytes[10] & 0x40) >> 6; // Ain Payload Type (1-bit) + decoded.reserved1 = (bytes[10] & 0x80) >> 7; // Reserved 1 Field (1-bit) + } + + return decoded; +} + +function parseVoboTCCalibrationConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.gain1 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 1 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset1 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 1 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain2 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 2 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset2 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 2 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.gain3 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 3 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset3 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 3 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain4 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 4 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset4 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 4 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 2) { + decoded.gain5 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 5 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset5 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 5 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain6 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 6 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset6 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 6 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 3) { + decoded.gain7 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 7 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset7 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 7 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain8 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 8 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset8 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 8 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 4) { + decoded.gain9 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 9 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset9 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 9 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain10 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 10 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset10 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3));// Offset 10 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 5) { + decoded.gain11 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 11 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset11 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3));// Offset 11 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain12 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 12 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset12 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3));// Offset 12 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + + return decoded; +} + +function parseConfigurationPayload(bytes, fport) { + var decoded = {}; + var subgroupID = bytes[0] & 0x0F; + + // VoBo Lib Configuration Payloads + if (subgroupID == 0) decoded = parseVoboLibGeneralConfigurationPayload(bytes); + else if (subgroupID == 1) decoded = parseVoboLibVoboSyncConfigurationPayload(bytes); + // VoBo XX Configuration Payloads + else if (fport == 70 && subgroupID == 4) decoded = parseVoboXXGeneralConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 5) decoded = parseVoboXXModbusGeneralConfigurationPayload(bytes); + else if (fport == 70 && (subgroupID == 6 || subgroupID == 7)) decoded = parseVoboXXModbusGroupsEnableConfigurationPayload(bytes); + else if (fport == 70 && (subgroupID == 8 || subgroupID == 9 || subgroupID == 10)) decoded = parseVoboXXModbusGroupsConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 11) decoded = parseVoboXXModbusPayloadsSlotsConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 12) decoded = parseVoboXXEngineeringUnitsConfigurationPayload(bytes); + // VoBo TC Configuration Payloads + else if (fport == 71 && subgroupID == 4) decoded = parseVoboTCGeneralConfigurationPayload(bytes); + else if (fport == 71 && subgroupID == 5) decoded = parseVoboTCCalibrationConfigurationPayload(bytes); + + return decoded; +} + +function parseModbusGenericPayload(bytes, fport) { + var decoded = {}; + var payloadSize = bytes.length; + var byteIdx = 0; + while (byteIdx < (payloadSize - 1)) { + var groupIdx = bytes[byteIdx] & 0x3F; + if (groupIdx > 0) { + var blockType = (bytes[byteIdx] >> 6) & 0x03; + byteIdx++; + + if (blockType == 0x00) // One register block + { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register1"] = registerValue; + byteIdx += 2; + } + else if (blockType == 0x01) // Two registers block + { + var register1Value = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register1"] = register1Value; + var register2Value = (bytes[byteIdx + 3] << 8) | (bytes[byteIdx + 2]); + decoded["group" + groupIdx + "register2"] = register2Value; + byteIdx += (2 * 2); + } + else if (blockType == 0x02) // Non fragmented block + { + var registersValues = []; + var numOfRegisters = bytes[byteIdx] & 0x7F; + byteIdx++; + for (let regIdx = 0; regIdx < numOfRegisters; regIdx++) { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register" + (regIdx + 1)] = registerValue; + registersValues.push(registerValue); + byteIdx += 2; + } + } + else if (blockType == 0x03) // Fragmented block + { + var registersValues = []; + var numOfRegisters = bytes[byteIdx] & 0x7F; + byteIdx++; + var registerOffset = bytes[byteIdx] & 0x7F; + byteIdx++; + + var numOfRegToRead = numOfRegisters - registerOffset; + var freeRegToRead = Math.floor((payloadSize - byteIdx) / 2); + if (numOfRegToRead > freeRegToRead) numOfRegToRead = freeRegToRead; + for (let regIdx = 0; regIdx < numOfRegToRead; regIdx++) { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register" + (registerOffset + regIdx + 1)] = registerValue; + registersValues.push(registerValue); + byteIdx += 2; + } + } + } + else break; + } + return decoded; +} + +function parseAnalogInputVariableLengthPayload(bytes, fport) { + var decoded = {}; + var decodedAinPayloads = []; + const AIN_PAYLOAD_SIZE = 6; + + var payloadLength = bytes.length + var byteIdx = 0; + while (byteIdx < payloadLength) { + var ainPayload = bytes.slice(byteIdx, byteIdx + AIN_PAYLOAD_SIZE) + var decodedAinPayload = parseOneAnalogSensorPayload(ainPayload) + decodedAinPayloads.push(decodedAinPayload) + byteIdx += AIN_PAYLOAD_SIZE; + } + + decoded.ainPayloads = decodedAinPayloads; + decoded.numOfAinPayloads = decodedAinPayloads.length + + return decoded; +} + +function parseModbusStandardVariableLengthPayload(bytes, fport) { + var decoded = {}; + var decodedModbusSlots = {}; + + var payloadLength = bytes.length + var firstSlotNum = bytes[payloadLength - 1] + const REGISTER_SIZE = 2 + var byteIdx = 0; + while (byteIdx < payloadLength - 1) { + var registerIdx = byteIdx / 2; + var slotIdx = firstSlotNum + registerIdx; + decodedModbusSlots["Modbus" + slotIdx] = (bytes[byteIdx + 1] << 8) | bytes[byteIdx]; + byteIdx += REGISTER_SIZE; + } + + decoded.modbusSlots = decodedModbusSlots; + decoded.firstSlotNum = firstSlotNum; + decoded.numModbusSlots = (payloadLength - 1) / 2; + + return decoded; +} + +function lookupAnalogSensorName(voboType, sensorNum) { + const analogSensorsTableXX = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "Battery Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "AIN1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "AIN2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "AIN3" }, + { "Analog Sensor Number": "15", "Analog Sensor Name": "ADC Temperature" } + ]; + + const analogSensorsTableTC = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "Battery Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "TC1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "TC2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "TC3" }, + { "Analog Sensor Number": "4", "Analog Sensor Name": "TC4" }, + { "Analog Sensor Number": "5", "Analog Sensor Name": "TC5" }, + { "Analog Sensor Number": "6", "Analog Sensor Name": "TC6" }, + { "Analog Sensor Number": "7", "Analog Sensor Name": "TC7" }, + { "Analog Sensor Number": "8", "Analog Sensor Name": "TC8" }, + { "Analog Sensor Number": "9", "Analog Sensor Name": "TC9" }, + { "Analog Sensor Number": "10", "Analog Sensor Name": "TC10" }, + { "Analog Sensor Number": "11", "Analog Sensor Name": "TC11" }, + { "Analog Sensor Number": "12", "Analog Sensor Name": "TC12" }, + { "Analog Sensor Number": "13", "Analog Sensor Name": "Cold Joint Temperature" } + ]; + + const analogSensorsTableXP = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "3.3V Supply Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "AIN1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "AIN2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "AIN3" }, + { "Analog Sensor Number": "4", "Analog Sensor Name": "DIN1" }, + { "Analog Sensor Number": "5", "Analog Sensor Name": "DIN1 Pulse Count" }, + { "Analog Sensor Number": "6", "Analog Sensor Name": "DIN2" }, + { "Analog Sensor Number": "7", "Analog Sensor Name": "DIN2 Pulse Count" }, + { "Analog Sensor Number": "8", "Analog Sensor Name": "RLY1 Voltage" }, + { "Analog Sensor Number": "9", "Analog Sensor Name": "RLY2 Voltage" }, + { "Analog Sensor Number": "10", "Analog Sensor Name": "RLY3 Voltage" }, + { "Analog Sensor Number": "11", "Analog Sensor Name": "RLY4 Voltage" }, + { "Analog Sensor Number": "12", "Analog Sensor Name": "WKUP" }, + { "Analog Sensor Number": "13", "Analog Sensor Name": "VPP Voltage" }, + { "Analog Sensor Number": "14", "Analog Sensor Name": "VIN Voltage" }, + { "Analog Sensor Number": "15", "Analog Sensor Name": "ADC Temperature" }, + { "Analog Sensor Number": "16", "Analog Sensor Name": "Cont Meas Period" }, + { "Analog Sensor Number": "17", "Analog Sensor Name": "Cont Meas Count" } + ]; + + var analogSensorsTable = []; + var analogSensorNameSuffix = ""; + if (voboType == "VoBoXX") { + analogSensorsTable = analogSensorsTableXX; + } + else if (voboType == "VoBoTC") { + analogSensorsTable = analogSensorsTableTC; + } + else if (voboType == "VoBoXP") { + analogSensorsTable = analogSensorsTableXP; + var sensorNumEncoding = (sensorNum & 0xC0) >> 6; + if (sensorNumEncoding == 0x01) analogSensorNameSuffix = "(Max)"; + else if (sensorNumEncoding == 0x02) analogSensorNameSuffix = "(Min)"; + else if (sensorNumEncoding == 0x03) analogSensorNameSuffix = "(Avg)"; + sensorNum = sensorNum & 0x3F; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\", \"VoBoTC\" or \"VoBoXP\""; + throw new Error(errorMsg); + } + + var analogSensorRow = analogSensorsTable.find(element => element["Analog Sensor Number"] == sensorNum.toString()); + if (typeof analogSensorRow == 'undefined') { + var genericAnalogSensorName = "AnalogSensor" + sensorNum.toString(); + return genericAnalogSensorName; + } + + var analogSensorName = analogSensorRow["Analog Sensor Name"]; + if (voboType == "VoBoXP") { + analogSensorName = analogSensorName + " " + analogSensorNameSuffix; + } + return analogSensorName; +} + +function lookupDigitalSensorName(voboType, sensorNum) { + const digitalSensorsTableXX = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" }, + { "Digital Sensor Number": "1", "Digital Sensor Name": "DIN1" }, + { "Digital Sensor Number": "2", "Digital Sensor Name": "DIN2" }, + { "Digital Sensor Number": "3", "Digital Sensor Name": "DIN3" } + ]; + + const digitalSensorsTableTC = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" } + ]; + + const digitalSensorsTableXP = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" }, + { "Digital Sensor Number": "1", "Digital Sensor Name": "DIN1" }, + { "Digital Sensor Number": "2", "Digital Sensor Name": "DIN2" } + ]; + + var digitalSensorsTable = []; + if (voboType == "VoBoXX") { + digitalSensorsTable = digitalSensorsTableXX; + } + else if (voboType == "VoBoTC") { + digitalSensorsTable = digitalSensorsTableTC; + } + else if (voboType == "VoBoXP") { + digitalSensorsTable = digitalSensorsTableXP; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\", \"VoBoTC\" or \"VoBoXP\""; + throw new Error(errorMsg); + } + + var digitalSensorRow = digitalSensorsTable.find(element => element["Digital Sensor Number"] == sensorNum.toString()); + if (typeof digitalSensorRow == 'undefined') { + var genericDigitalSensorName = "DigitalSensor" + sensorNum.toString(); + return genericDigitalSensorName; + } + + var digitalSensorName = digitalSensorRow["Digital Sensor Name"]; + return digitalSensorName; +} + +function lookupUnits(outputType, unitCode) { + const engUnitsTable = + [ + { "Units Code": "1", "Description": "inches of water at 20 degC (68 degF)", "Abbreviated Units": "inH2O (20 degC or 68 degF)" }, + { "Units Code": "2", "Description": "inches of mercury at 0 degC (32 degF)", "Abbreviated Units": "inHg (20 degC or 68 degF)" }, + { "Units Code": "3", "Description": "feet of water at 20 degC (68 degF)", "Abbreviated Units": "ftH2O (20 degC or 68 degF)" }, + { "Units Code": "4", "Description": "millimeters of water at 20 degC (68 degF)", "Abbreviated Units": "mmH2O (20 degC or 68 degF)" }, + { "Units Code": "5", "Description": "millimeters of mercury at 0 degC (32 degF)", "Abbreviated Units": "mmHg (0 degC or 32 degF)" }, + { "Units Code": "6", "Description": "pounds per square inch", "Abbreviated Units": "psi" }, + { "Units Code": "7", "Description": "bars", "Abbreviated Units": "bar" }, + { "Units Code": "8", "Description": "millibars", "Abbreviated Units": "mbar" }, + { "Units Code": "9", "Description": "grams per square centimeter", "Abbreviated Units": "g/cm^2" }, + { "Units Code": "10", "Description": "kilograms per square centimeter", "Abbreviated Units": "kg/cm^2" }, + { "Units Code": "11", "Description": "pascals", "Abbreviated Units": "Pa" }, + { "Units Code": "12", "Description": "kilopascals", "Abbreviated Units": "kPa" }, + { "Units Code": "13", "Description": "torr", "Abbreviated Units": "torr" }, + { "Units Code": "14", "Description": "atmospheres", "Abbreviated Units": "atm" }, + { "Units Code": "15", "Description": "cubic feet per minute", "Abbreviated Units": "ft^3/min" }, + { "Units Code": "16", "Description": "gallons per minute", "Abbreviated Units": "usg/min" }, + { "Units Code": "17", "Description": "liters per minute", "Abbreviated Units": "L/min" }, + { "Units Code": "18", "Description": "imperial gallons per minute", "Abbreviated Units": "impgal/min" }, + { "Units Code": "19", "Description": "cubic meter per hour", "Abbreviated Units": "m^3/h" }, + { "Units Code": "20", "Description": "feet per second", "Abbreviated Units": "ft/s" }, + { "Units Code": "21", "Description": "meters per seond", "Abbreviated Units": "m/s" }, + { "Units Code": "22", "Description": "gallons per second", "Abbreviated Units": "usg/s" }, + { "Units Code": "23", "Description": "million gallons per day", "Abbreviated Units": "Musg/d" }, + { "Units Code": "24", "Description": "liters per second", "Abbreviated Units": "L/s" }, + { "Units Code": "25", "Description": "million liters per day", "Abbreviated Units": "ML/d" }, + { "Units Code": "26", "Description": "cubic feet per second", "Abbreviated Units": "ft^3/s" }, + { "Units Code": "27", "Description": "cubic feet per day", "Abbreviated Units": "ft^3/d" }, + { "Units Code": "28", "Description": "cubic meters per second", "Abbreviated Units": "m^3/s" }, + { "Units Code": "29", "Description": "cubic meters per day", "Abbreviated Units": "m^3/d" }, + { "Units Code": "30", "Description": "imperial gallons per hour", "Abbreviated Units": "impgal/h" }, + { "Units Code": "31", "Description": "imperial gallons per day", "Abbreviated Units": "impgal/d" }, + { "Units Code": "32", "Description": "Degrees Celsius", "Abbreviated Units": "C" }, + { "Units Code": "33", "Description": "Degrees Fahrenheit", "Abbreviated Units": "F" }, + { "Units Code": "34", "Description": "Degrees Rankine", "Abbreviated Units": "R" }, + { "Units Code": "35", "Description": "Kelvin", "Abbreviated Units": "K" }, + { "Units Code": "36", "Description": "millivolts", "Abbreviated Units": "mV" }, + { "Units Code": "37", "Description": "ohms", "Abbreviated Units": "ohm" }, + { "Units Code": "38", "Description": "hertz", "Abbreviated Units": "hz" }, + { "Units Code": "39", "Description": "milliamperes", "Abbreviated Units": "mA" }, + { "Units Code": "40", "Description": "gallons", "Abbreviated Units": "usg" }, + { "Units Code": "41", "Description": "liters", "Abbreviated Units": "L" }, + { "Units Code": "42", "Description": "imperial gallons", "Abbreviated Units": "impgal" }, + { "Units Code": "43", "Description": "cubic meters", "Abbreviated Units": "m^3" }, + { "Units Code": "44", "Description": "feet", "Abbreviated Units": "ft" }, + { "Units Code": "45", "Description": "meters", "Abbreviated Units": "m" }, + { "Units Code": "46", "Description": "barrels", "Abbreviated Units": "bbl" }, + { "Units Code": "47", "Description": "inches", "Abbreviated Units": "in" }, + { "Units Code": "48", "Description": "centimeters", "Abbreviated Units": "cm" }, + { "Units Code": "49", "Description": "millimeters", "Abbreviated Units": "mm" }, + { "Units Code": "50", "Description": "minutes", "Abbreviated Units": "min" }, + { "Units Code": "51", "Description": "seconds", "Abbreviated Units": "s" }, + { "Units Code": "52", "Description": "hours", "Abbreviated Units": "h" }, + { "Units Code": "53", "Description": "days", "Abbreviated Units": "d" }, + { "Units Code": "54", "Description": "centistokes", "Abbreviated Units": "centistokes" }, + { "Units Code": "55", "Description": "centipoise", "Abbreviated Units": "cP" }, + { "Units Code": "56", "Description": "microsiemens", "Abbreviated Units": "microsiemens" }, + { "Units Code": "57", "Description": "percent", "Abbreviated Units": "%" }, + { "Units Code": "58", "Description": "volts", "Abbreviated Units": "V" }, + { "Units Code": "59", "Description": "pH", "Abbreviated Units": "pH" }, + { "Units Code": "60", "Description": "grams", "Abbreviated Units": "g" }, + { "Units Code": "61", "Description": "kilograms", "Abbreviated Units": "kg" }, + { "Units Code": "62", "Description": "metric tons", "Abbreviated Units": "t" }, + { "Units Code": "63", "Description": "pounds", "Abbreviated Units": "lb" }, + { "Units Code": "64", "Description": "short tons", "Abbreviated Units": "short ton" }, + { "Units Code": "65", "Description": "long tons", "Abbreviated Units": "long ton" }, + { "Units Code": "66", "Description": "millisiemens per centimeter", "Abbreviated Units": "millisiemens/cm" }, + { "Units Code": "67", "Description": "microsiemens per centimeter", "Abbreviated Units": "microsiemens/cm" }, + { "Units Code": "68", "Description": "newton", "Abbreviated Units": "N" }, + { "Units Code": "69", "Description": "newton meter", "Abbreviated Units": "N m" }, + { "Units Code": "70", "Description": "grams per second", "Abbreviated Units": "g/s" }, + { "Units Code": "71", "Description": "grams per minute", "Abbreviated Units": "g/min" }, + { "Units Code": "72", "Description": "grams per hour", "Abbreviated Units": "g/h" }, + { "Units Code": "73", "Description": "kilograms per second", "Abbreviated Units": "kg/s" }, + { "Units Code": "74", "Description": "kilograms per minute", "Abbreviated Units": "kg/min" }, + { "Units Code": "75", "Description": "kilograms per hour", "Abbreviated Units": "kg/h" }, + { "Units Code": "76", "Description": "kilograms per day", "Abbreviated Units": "kg/d" }, + { "Units Code": "77", "Description": "metric tons per minute", "Abbreviated Units": "t/min" }, + { "Units Code": "78", "Description": "metric tons per hour", "Abbreviated Units": "t/h" }, + { "Units Code": "79", "Description": "metric tons per day", "Abbreviated Units": "t/d" }, + { "Units Code": "80", "Description": "pounds per second", "Abbreviated Units": "lb/s" }, + { "Units Code": "81", "Description": "pounds per minute", "Abbreviated Units": "lb/min" }, + { "Units Code": "82", "Description": "pounds per hour", "Abbreviated Units": "lb/h" }, + { "Units Code": "83", "Description": "pounds per day", "Abbreviated Units": "lb/d" }, + { "Units Code": "84", "Description": "short tons per minute", "Abbreviated Units": "short ton/min" }, + { "Units Code": "85", "Description": "short tons per hour", "Abbreviated Units": "short ton/h" }, + { "Units Code": "86", "Description": "short tons per day", "Abbreviated Units": "short ton/d" }, + { "Units Code": "87", "Description": "long tons per hour", "Abbreviated Units": "long ton/h" }, + { "Units Code": "88", "Description": "long tons per day", "Abbreviated Units": "long ton/d" }, + { "Units Code": "89", "Description": "deka therm", "Abbreviated Units": "Dth" }, + { "Units Code": "90", "Description": "specific gravity units", "Abbreviated Units": "specific gravity units" }, + { "Units Code": "91", "Description": "grams per cubic centimeter", "Abbreviated Units": "g/cm^3" }, + { "Units Code": "92", "Description": "kilograms per cubic meter", "Abbreviated Units": "kg/m^3" }, + { "Units Code": "93", "Description": "pounds per gallon", "Abbreviated Units": "lb/usg" }, + { "Units Code": "94", "Description": "pounds per cubic feet", "Abbreviated Units": "lb/ft^3" }, + { "Units Code": "95", "Description": "grams per milliliter", "Abbreviated Units": "g/mL" }, + { "Units Code": "96", "Description": "kilograms per liter", "Abbreviated Units": "kg/L" }, + { "Units Code": "97", "Description": "grams per liter", "Abbreviated Units": "g/L" }, + { "Units Code": "98", "Description": "pounds per cubic inch", "Abbreviated Units": "lb/in^3" }, + { "Units Code": "99", "Description": "short tons per cubic yard", "Abbreviated Units": "short ton/yd^3" }, + { "Units Code": "100", "Description": "degrees twaddell", "Abbreviated Units": "degTw" }, + { "Units Code": "101", "Description": "degrees brix", "Abbreviated Units": "degBx" }, + { "Units Code": "102", "Description": "degrees baume heavy", "Abbreviated Units": "BH" }, + { "Units Code": "103", "Description": "degrees baume light", "Abbreviated Units": "BL" }, + { "Units Code": "104", "Description": "degrees API", "Abbreviated Units": "degAPI" }, + { "Units Code": "105", "Description": "percent solids per weight", "Abbreviated Units": "% solid/weight" }, + { "Units Code": "106", "Description": "percent solids per volume", "Abbreviated Units": "% solid/volume" }, + { "Units Code": "107", "Description": "degrees balling", "Abbreviated Units": "degrees balling" }, + { "Units Code": "108", "Description": "proof per volume", "Abbreviated Units": "proof/volume" }, + { "Units Code": "109", "Description": "proof per mass", "Abbreviated Units": "proof/mass" }, + { "Units Code": "110", "Description": "bushels", "Abbreviated Units": "bushel" }, + { "Units Code": "111", "Description": "cubic yards", "Abbreviated Units": "yd^3" }, + { "Units Code": "112", "Description": "cubic feet", "Abbreviated Units": "ft^3" }, + { "Units Code": "113", "Description": "cubic inches", "Abbreviated Units": "in^3" }, + { "Units Code": "114", "Description": "inches per second", "Abbreviated Units": "in/s" }, + { "Units Code": "115", "Description": "inches per minute", "Abbreviated Units": "in/min" }, + { "Units Code": "116", "Description": "feet per minute", "Abbreviated Units": "ft/min" }, + { "Units Code": "117", "Description": "degrees per second", "Abbreviated Units": "deg/s" }, + { "Units Code": "118", "Description": "revolutions per second", "Abbreviated Units": "rev/s" }, + { "Units Code": "119", "Description": "revolutions per minute", "Abbreviated Units": "rpm" }, + { "Units Code": "120", "Description": "meters per hour", "Abbreviated Units": "m/hr" }, + { "Units Code": "121", "Description": "normal cubic meters per hour", "Abbreviated Units": "normal m^3/h" }, + { "Units Code": "122", "Description": "normal liter per hour", "Abbreviated Units": "normal L/h" }, + { "Units Code": "123", "Description": "standard cubic feet per minute", "Abbreviated Units": "standard ft^3/min" }, + { "Units Code": "124", "Description": "bbl liq", "Abbreviated Units": "bbl liq" }, + { "Units Code": "125", "Description": "ounce", "Abbreviated Units": "oz" }, + { "Units Code": "126", "Description": "foot pound force", "Abbreviated Units": "ft lb force" }, + { "Units Code": "127", "Description": "kilowatt", "Abbreviated Units": "kW" }, + { "Units Code": "128", "Description": "kilowatt hour", "Abbreviated Units": "KWh" }, + { "Units Code": "129", "Description": "horsepower", "Abbreviated Units": "hp" }, + { "Units Code": "130", "Description": "cubic feet per hour", "Abbreviated Units": "ft^3/h" }, + { "Units Code": "131", "Description": "cubic meters per minute", "Abbreviated Units": "m^3/min" }, + { "Units Code": "132", "Description": "barrels per second", "Abbreviated Units": "bbl/s" }, + { "Units Code": "133", "Description": "barrels per minute", "Abbreviated Units": "bbl/min" }, + { "Units Code": "134", "Description": "barrels per hour", "Abbreviated Units": "bbl/h" }, + { "Units Code": "135", "Description": "barrels per day", "Abbreviated Units": "bbl/d" }, + { "Units Code": "136", "Description": "gallons per hour", "Abbreviated Units": "usg/h" }, + { "Units Code": "137", "Description": "imperial gallons per second", "Abbreviated Units": "impgal/s" }, + { "Units Code": "138", "Description": "liters per hour", "Abbreviated Units": "L/h" }, + { "Units Code": "139", "Description": "parts per million", "Abbreviated Units": "ppm" }, + { "Units Code": "140", "Description": "megacalorie per hour", "Abbreviated Units": "Mcal/h" }, + { "Units Code": "141", "Description": "megajoule per hour", "Abbreviated Units": "MJ/h" }, + { "Units Code": "142", "Description": "British thermal unit per hour", "Abbreviated Units": "BTU/h" }, + { "Units Code": "143", "Description": "degrees", "Abbreviated Units": "degrees" }, + { "Units Code": "144", "Description": "radian", "Abbreviated Units": "rad" }, + { "Units Code": "145", "Description": "inches of water at 15.6 degC (60 degF)", "Abbreviated Units": "inH20 (15.6 degC or 60 degF)" }, + { "Units Code": "146", "Description": "micrograms per liter", "Abbreviated Units": "micrograms/L" }, + { "Units Code": "147", "Description": "micrograms per cubic meter", "Abbreviated Units": "micrograms/m^3" }, + { "Units Code": "148", "Description": "percent consitency", "Abbreviated Units": "% consistency" }, + { "Units Code": "149", "Description": "volume percent", "Abbreviated Units": "volume %" }, + { "Units Code": "150", "Description": "percent steam quality", "Abbreviated Units": "% steam quality" }, + { "Units Code": "151", "Description": "feet in sixteenths", "Abbreviated Units": "ft in sixteenths" }, + { "Units Code": "152", "Description": "cubic feet per pound", "Abbreviated Units": "ft^3/lb" }, + { "Units Code": "153", "Description": "picofarads", "Abbreviated Units": "pF" }, + { "Units Code": "154", "Description": "milliliters per liter", "Abbreviated Units": "mL/L" }, + { "Units Code": "155", "Description": "microliters per liter", "Abbreviated Units": "microliters/L" }, + { "Units Code": "156", "Description": "percent plato", "Abbreviated Units": "% plato" }, + { "Units Code": "157", "Description": "percent lower explosion level", "Abbreviated Units": "% lower explosion level" }, + { "Units Code": "158", "Description": "mega calorie", "Abbreviated Units": "Mcal" }, + { "Units Code": "159", "Description": "kiloohms", "Abbreviated Units": "kohm" }, + { "Units Code": "160", "Description": "megajoule", "Abbreviated Units": "MJ" }, + { "Units Code": "161", "Description": "British thermal unit", "Abbreviated Units": "BTU" }, + { "Units Code": "162", "Description": "normal cubic meter", "Abbreviated Units": "normal m^3" }, + { "Units Code": "163", "Description": "normal liter", "Abbreviated Units": "normal L" }, + { "Units Code": "164", "Description": "standard cubic feet", "Abbreviated Units": "normal ft^3" }, + { "Units Code": "165", "Description": "parts per billion", "Abbreviated Units": "parts/billion" }, + { "Units Code": "166", "Description": "ampere", "Abbreviated Units": "A" }, + { "Units Code": "167", "Description": "millimeters per meter", "Abbreviated Units": "mm/m" }, + { "Units Code": "168", "Description": "seconds since epoch", "Abbreviated Units": "epoch sec" }, + { "Units Code": "169", "Description": "no units", "Abbreviated Units": "no units" }, + { "Units Code": "170", "Description": "percentage", "Abbreviated Units": "%" }, + { "Units Code": "171", "Description": "kilohertz", "Abbreviated Units": "khz" }, + { "Units Code": "235", "Description": "gallons per day", "Abbreviated Units": "usg/d" }, + { "Units Code": "236", "Description": "hectoliters", "Abbreviated Units": "hL" }, + { "Units Code": "237", "Description": "megapascals", "Abbreviated Units": "MPa" }, + { "Units Code": "238", "Description": "inches of water at 4 degC (39.2 degF)", "Abbreviated Units": "inH2O (4 degC or 39.2 degF)" }, + { "Units Code": "239", "Description": "millimeters of water at 4 degC (39.2 degF)", "Abbreviated Units": "mmH2O (4 degC or 39.2 degF)" }, + { "Units Code": "253", "Description": "edge count or pulse count", "Abbreviated Units": "counts" }, + { "Units Code": "254", "Description": "raw ADC code", "Abbreviated Units": "ADC code" }, + { "Units Code": "255", "Description": "data not valid", "Abbreviated Units": "data not valid" } + ]; + + var outputString = ""; + var engUnitsRow = engUnitsTable.find(element => element["Units Code"] == unitCode.toString()); + + if (typeof engUnitsRow == 'undefined') { + return "Unknown Units"; + } + + if (outputType == 0) { + outputString = engUnitsRow["Description"]; + } + else if (outputType == 1) { + outputString = engUnitsRow["Abbreviated Units"]; + } + else { + errorMsg = outputType + " -- " + "Invalid Output Type. Use 0 for Description or 1 for Abbreviated Units."; + throw new Error(errorMsg); + } + + return outputString; +} + +//=========================================================== +// Utility functions +// Decoder doesn't call these functions +// Functions provided for customer usage +// +// List of functions: +// hexToFloat - Conversion from Hex to Float Number +// modbusToFloat_AB_CD_to_ABCD - Takes two Modbus registers, does no swapping, and converts them to 32 bit floating point number +// modbusToFloat_AB_CD_to_DCBA - Takes two Modbus registers, does byte and word swapping, and converts them to 32 bit floating point number +// modbusToFloat_AB_CD_to_BADC - Takes two Modbus registers, does byte swapping, and converts them to 32bit floating point number +// modbusToFloat_AB_CD_to_CDAB - Takes two Modbus registers, does word swapping, and converts them to 32bit floating point number +// printPayload - Prints Decoded payloads in formatted fashion +//=========================================================== + +/* istanbul ignore next */ +function hexToFloat(hex) { + var s = hex >> 31 ? -1 : 1; + var e = (hex >> 23) & 0xFF; + return s * (hex & 0x7fffff | 0x800000) * 1.0 / Math.pow(2, 23) * Math.pow(2, (e - 127)) +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_ABCD(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = (modbusReg1 & 0xFF00) >> 8; + bytes[1] = modbusReg1 & 0xFF; + bytes[2] = (modbusReg0 & 0xFF00) >> 8; + bytes[3] = modbusReg0 & 0xFF; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_DCBA(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = modbusReg0 & 0xFF; + bytes[1] = (modbusReg0 & 0xFF00) >> 8; + bytes[2] = modbusReg1 & 0xFF; + bytes[3] = (modbusReg1 & 0xFF00) >> 8; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_BADC(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = modbusReg1 & 0xFF; + bytes[1] = (modbusReg1 & 0xFF00) >> 8; + bytes[2] = modbusReg0 & 0xFF; + bytes[3] = (modbusReg0 & 0xFF00) >> 8; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_CDAB(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = (modbusReg0 & 0xFF00) >> 8; + bytes[1] = modbusReg0 & 0xFF; + bytes[2] = (modbusReg1 & 0xFF00) >> 8; + bytes[3] = modbusReg1 & 0xFF; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function printPayload(payload) { + if (payload.data.payloadType == "Standard") { + let modbusSlot0HexString = payload.data.Modbus0.toString(16).toUpperCase().padStart(4, 0); + console.log("%s | %s | %s | %s | " + + "DIN1 = %d | DIN2 = %d | DIN3 = %d | WKUP = %d | ADC1 = %d | ADC2 = %d | ADC3 = %d | " + + "Battery = %d | Temperature = %f | Modbus0 = 0x%s |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.DIN1, payload.data.DIN2, payload.data.DIN3, payload.data.WKUP, payload.data.ADC1, payload.data.ADC2, payload.data.ADC3, + payload.data.Battery, payload.data.Temperature, modbusSlot0HexString); + return; + } + + if (payload.data.payloadType == "Modbus Standard") { + dataKeys = Object.keys(payload.data); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + if ((dataKeys[idx] != "fport") && (dataKeys[idx] != "voboType") && (dataKeys[idx] != "payloadType")) { + let fieldName = dataKeys[idx]; + let valueHex = "0x" + payload.data[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + valueHex + " | "; + } + } + console.log("%s\n", stringToPrint); + return; + } + + if (payload.data.payloadType == "Heartbeat 1.0") { + let firmwareRevisionHexString = payload.data.fwVersionMajor.toString(10) + "." + payload.data.fwVersionMinor.toString(10) + "." + payload.data.fwVersionPatch.toString(10) + "." + payload.data.fwVersionCustom.toString(10); + console.log("%s | %s | %s | %s | " + + "Battery Level MV = %d | FW Version = %s | Last RSSI = %d | Analog Voltage Config = %d | Analog Power Time Config = %d | Failed Transmissions Before Rejoin Config = %d | " + + "Cycle Through FSB Config = %d | ACK Enable Config = %d | ACK Frequency Config = %d | ACK Request Config = %d | Battery Level Threshold Config = %d |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.batteryLevelMV, firmwareRevisionHexString, payload.data.recSignalLevels, payload.data.analogVoltageConfig, payload.data.analogPowerTimeConfig, payload.data.failedTransmissionBeforeRejoinConfig, + payload.data.cycleThroughFSB, payload.data.ackEnable, payload.data.ackFreq, payload.data.ackReq, payload.data.batteryLevelThreshold); + return; + } + + if (payload.data.payloadType == "Heartbeat 2.0") { + let firmwareRevisionHexString = payload.data.firmwareRevision.toString(16).toUpperCase().padStart(3, 0); + console.log("%s | %s | %s | %s | " + + "Battery Level = %d | Fatal Errors Total = %d | RSSI Avg = %d | Failed Join Attempts Total = %d | Config Update Occurred = %d | Firmware Revision = 0x%s | " + + "Reboots Total = %d | Failed Transmits Total = %d | Error Event Logs Total = %d | Warning Event Logs Total = %d | Info Event Logs Total = %d | Measurement Packets Total = %d |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.batteryLevel, payload.data.fatalErrorsTotal, payload.data.rssiAvg, payload.data.failedJoinAttemptsTotal, payload.data.configUpdateOccurred, firmwareRevisionHexString, + payload.data.rebootsTotal, payload.data.failedTransmitsTotal, payload.data.errorEventLogsTotal, payload.data.warningEventLogsTotal, payload.data.infoEventLogsTotal, payload.data.measurementPacketsTotal); + return; + } + + if (payload.data.payloadType == "One Analog Input") { + let fractionDigitsData0 = 1; + if (payload.data.engUnitsString0 == "mV" || payload.data.engUnitsString0 == "V") fractionDigitsData0 = 3; + if (payload.data.engUnitsString0 == "ADC code") fractionDigitsData0 = 0; + console.log("%s | %s | %s | %s | %s = %f %s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, payload.data.analogSensorString0, parseFloat(payload.data.sensorData0).toFixed(fractionDigitsData0), payload.data.engUnitsString0); + return; + } + + if (payload.data.payloadType == "Two Analog Inputs") { + let fractionDigitsData0 = 1; + if (payload.data.engUnitsString0 == "mV" || payload.data.engUnitsString0 == "V" || payload.data.engUnitsString0 == "mA" || payload.data.engUnitsString0 == "A") fractionDigitsData0 = 3; + if (payload.data.engUnitsString0 == "ADC code") fractionDigitsData0 = 0; + let fractionDigitsData1 = 1; + if (payload.data.engUnitsString1 == "mV" || payload.data.engUnitsString1 == "V" || payload.data.engUnitsString1 == "mA" || payload.data.engUnitsString1 == "A") fractionDigitsData1 = 3; + if (payload.data.engUnitsString1 == "ADC code") fractionDigitsData1 = 0; + console.log("%s | %s | %s | %s | %s = %f %s | %s = %f %s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, payload.data.analogSensorString0, parseFloat(payload.data.sensorData0).toFixed(fractionDigitsData0), payload.data.engUnitsString0, payload.data.analogSensorString1, parseFloat(payload.data.sensorData1).toFixed(fractionDigitsData1), payload.data.engUnitsString1); + return; + } + + if (payload.data.payloadType == "Analog Input Variable Length") { + var stringToPrint = ""; + for (let i = 0; i < payload.data.numOfAinPayloads; i++) { + analogSensorString = payload.data["analogSensorString" + i]; + engUnitsString = payload.data["engUnitsString" + i]; + sensorData = payload.data.ainPayloads[i].sensorData0; + + let fractionDigitsData = 1; + if (engUnitsString == "mV" || engUnitsString == "V" || engUnitsString == "mA" || engUnitsString == "A") fractionDigitsData = 3; + if (engUnitsString == "ADC code") fractionDigitsData = 0; + stringToPrint = stringToPrint + analogSensorString + " = " + parseFloat(sensorData).toFixed(fractionDigitsData) + " " + engUnitsString + " | "; + } + console.log("%s | %s | %s | %s | %s\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, stringToPrint); + return; + } + + if (payload.data.payloadType == "Modbus Standard Variable Length") { + dataKeys = Object.keys(payload.data.modbusSlots); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + let fieldName = dataKeys[idx]; + let valueHex = "0x" + payload.data.modbusSlots[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + valueHex + " | "; + } + console.log("%s\n", stringToPrint); + return; + } + + if (payload.data.payloadType == "Digital Inputs") { + var stringToPrint = "" + for (let i = 0; i < payload.data.digitalSensorData.length; i++) { + stringToPrint = stringToPrint + payload.data.digitalSensorStrings[i] + " = " + payload.data.digitalSensorData[i].toString() + " | "; + } + console.log("%s | %s | %s | %s | %s\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, stringToPrint); + return; + } + + if (payload.data.payloadType == "Event Log") { + let eventTimestampDateTime = new Date(payload.data.eventTimestamp * 1000).toISOString(); + let eventCodeHexString = payload.data.eventCode.toString(16).toUpperCase().padStart(4, 0); + let metadataHexString = Buffer.from(payload.data.metadata).toString("hex").toUpperCase(); + console.log("%s | %s | %s | %s | Timestamp = %s | Code = 0x%s | Metadata = 0x%s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, eventTimestampDateTime, eventCodeHexString, metadataHexString); + return; + } + + if (payload.data.payloadType == "Modbus Generic") { + let dataKeys = Object.keys(payload.data); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + if ((dataKeys[idx] != "fport") && (dataKeys[idx] != "voboType") && (dataKeys[idx] != "payloadType")) { + let fieldName = dataKeys[idx]; + let regValueHexStr = "0x" + payload.data[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + regValueHexStr + " | "; + } + } + console.log("%s\n", stringToPrint); + return; + } + + console.log(JSON.stringify(payload, null, 4)); + console.log(""); +} + +//=========================================================== +// Changes made to decoder for TagoIO below this point. +// TagoIO executes the decoder file (payload.js) rather than +// calling the Decoder function, so code must be added here +// to map the arguments to the Decoder function, and the +// exports had to be commented out. +//=========================================================== + +const bytes_vb = payload.find((x) => x.variable === "bytes"); +const fPort_vb = payload.find((x) => x.variable === "port" || x.variable === "fPort")?.value; + +if (bytes_vb) { + try { + const buffer = Buffer.from(bytes_vb.value, "hex") + const decoded = Decoder(buffer, fPort_vb); + payload = decoded; + } catch (error: any) { + console.error(error); + payload = [{ variable: "parse_error", value: error.message }]; + } +} + + +// module.exports = { +// customDecoder, decodeUplink, Decoder, addVoboMetadata, lookupAnalogSensorName, lookupDigitalSensorName, lookupUnits, +// hexToFloat, modbusToFloat_AB_CD_to_ABCD, modbusToFloat_AB_CD_to_DCBA, modbusToFloat_AB_CD_to_BADC, modbusToFloat_AB_CD_to_CDAB, printPayload +// } \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload.test.ts b/decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload.test.ts new file mode 100644 index 0000000..273f22c --- /dev/null +++ b/decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload.test.ts @@ -0,0 +1,574 @@ +import { describe, test, expect } from "vitest"; +import { decoderRun } from "../../../../../src/functions/decoder-run"; + +const file_path = "decoders/connector/volley-boast/vobo-gp-1/v1.0.0/payload.js" + +const testPayloads = { + "data": [ + { "fport": 1, "bytes": "273ef1336472e30c000000", "name": "Standard Payload" }, + { "fport": 2, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 3, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 4, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 5, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 6, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 7, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 8, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 9, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 20, "bytes": "cc0d3900000c0000000400", "name": "Heartbeat 2.0 Payload" }, + { "fport": 30, "bytes": "0f2041d30000", "name": "One Analog Input Payload" }, + { "fport": 40, "bytes": "1227411ffefa3a4022147b", "name": "Two Analog Input Payload" }, + { "fport": 50, "bytes": "0f000e00", "name": "Digital Input Payload" }, + { "fport": 60, "bytes": "681ebd64ffff0000000000", "name": "Event Log Payload" }, + { "fport": 70, "bytes": "004ae6e23c002405200000", "name": "Configuration Payload" }, + { "fport": 100, "bytes": "0313020000000000000000", "name": "Modbus Generic Payload - One Register Group" }, + { "fport": 110, "bytes": "01fe3f80000002fe4000000003fe3f800000003a405810620f2041b60000", "name": "VoBo-XX Analog Input Variable Length Payload" }, + { "fport": 120, "bytes": "0f270f270f270f270f270f270f2701", "name": "VoBo-XX Modbus Standard Variable Length Payload" }, + ] +} + +describe(`fPort: ${testPayloads.data[0].fport} - ${testPayloads.data[0].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[0].bytes }, + { variable: "fPort", value: testPayloads.data[0].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "DIN1": 1, + "DIN2": 1, + "DIN3": 1, + "WKUP": 0, + "ADC1": 994, + "ADC2": 1009, + "ADC3": 1603, + "Battery": 3528, + "Temperature": 25.75, + "Modbus0": 0, + "fport": 1, + "voboType": "VoBoXX", + "payloadType": "Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[1].fport} - ${testPayloads.data[1].name}`, () => { // fPort 2 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[1].bytes }, + { variable: "fPort", value: testPayloads.data[1].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus1": 8705, + "Modbus2": 8706, + "Modbus3": 8707, + "Modbus4": 8708, + "Modbus5": 8709, + "fport": 2, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[2].fport} - ${testPayloads.data[2].name}`, () => { // fPort 3 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[2].bytes }, + { variable: "fPort", value: testPayloads.data[2].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus6": 8705, + "Modbus7": 8706, + "Modbus8": 8707, + "Modbus9": 8708, + "Modbus10": 8709, + "fport": 3, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[3].fport} - ${testPayloads.data[3].name}`, () => { // fPort 4 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[3].bytes }, + { variable: "fPort", value: testPayloads.data[3].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus11": 8705, + "Modbus12": 8706, + "Modbus13": 8707, + "Modbus14": 8708, + "Modbus15": 8709, + "fport": 4, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[4].fport} - ${testPayloads.data[4].name}`, () => { // fPort 5 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[4].bytes }, + { variable: "fPort", value: testPayloads.data[4].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus16": 8705, + "Modbus17": 8706, + "Modbus18": 8707, + "Modbus19": 8708, + "Modbus20": 8709, + "fport": 5, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[5].fport} - ${testPayloads.data[5].name}`, () => { // fPort 6 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[5].bytes }, + { variable: "fPort", value: testPayloads.data[5].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus21": 8705, + "Modbus22": 8706, + "Modbus23": 8707, + "Modbus24": 8708, + "Modbus25": 8709, + "fport": 6, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[6].fport} - ${testPayloads.data[6].name}`, () => { // fPort 7 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[6].bytes }, + { variable: "fPort", value: testPayloads.data[6].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus26": 8705, + "Modbus27": 8706, + "Modbus28": 8707, + "Modbus29": 8708, + "Modbus30": 8709, + "fport": 7, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[7].fport} - ${testPayloads.data[7].name}`, () => { // fPort 8 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[7].bytes }, + { variable: "fPort", value: testPayloads.data[7].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus31": 8705, + "Modbus32": 8706, + "Modbus33": 8707, + "Modbus34": 8708, + "Modbus35": 8709, + "fport": 8, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[8].fport} - ${testPayloads.data[8].name}`, () => { // fPort 9 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[8].bytes }, + { variable: "fPort", value: testPayloads.data[8].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus36": 8705, + "Modbus37": 8706, + "Modbus38": 8707, + "Modbus39": 8708, + "Modbus40": 8709, + "fport": 9, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[9].fport} - ${testPayloads.data[9].name}`, () => { // fPort 20 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[9].bytes }, + { variable: "fPort", value: testPayloads.data[9].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "batteryLevel": 3532, + "fatalErrorsTotal": 0, + "rssiAvg": 57, + "failedJoinAttemptsTotal": 0, + "configUpdateOccurred": 0, + "firmwareRevision": 0, + "rebootsTotal": 3, + "failedTransmitsTotal": 0, + "errorEventLogsTotal": 0, + "warningEventLogsTotal": 0, + "infoEventLogsTotal": 0, + "measurementPacketsTotal": 4, + "fport": 20, + "voboType": "VoBoXX", + "payloadType": "Heartbeat 2.0" + }, + "warnings": [], + "errors": [] + }); + }); +}); +describe(`fPort: ${testPayloads.data[10].fport} - ${testPayloads.data[10].name}`, () => { // fPort 30 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[10].bytes }, + { variable: "fPort", value: testPayloads.data[10].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorNum0": 15, + "sensorUnits0": 32, + "sensorData0": 26.375, + "analogSensorString0": "ADC Temperature", + "engUnitsString0": "C", + "fport": 30, + "voboType": "VoBoXX", + "payloadType": "One Analog Input" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[11].fport} - ${testPayloads.data[11].name}`, () => { // fPort 40 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[11].bytes }, + { variable: "fPort", value: testPayloads.data[11].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorNum0": 1, + "sensorUnits0": 39, + "sensorData0": 9.999750137329102, + "sensorNum1": 2, + "sensorUnits1": 58, + "sensorData1": 2.5325000286102295, + "analogSensorString0": "AIN1", + "engUnitsString0": "mA", + "analogSensorString1": "AIN2", + "engUnitsString1": "V", + "fport": 40, + "voboType": "VoBoXX", + "payloadType": "Two Analog Inputs" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[12].fport} - ${testPayloads.data[12].name}`, () => { // fPort 50 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[12].bytes }, + { variable: "fPort", value: testPayloads.data[12].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorValid0": 15, + "sensorData0": 14, + "digitalSensorStrings": [ + "WKUP", + "DIN1", + "DIN2", + "DIN3" + ], + "digitalSensorData": [ + 0, + 1, + 1, + 1 + ], + "fport": 50, + "voboType": "VoBoXX", + "payloadType": "Digital Inputs" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[13].fport} - ${testPayloads.data[13].name}`, () => { // fPort 60 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[13].bytes }, + { variable: "fPort", value: testPayloads.data[13].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "eventTimestamp": 1690115688, + "eventCode": 65535, + "metadata": [ + 0, + 0, + 0, + 0, + 0 + ], + "fport": 60, + "voboType": "VoBoXX", + "payloadType": "Event Log" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[14].fport} - ${testPayloads.data[14].name}`, () => { // fPort 70 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[14].bytes }, + { variable: "fPort", value: testPayloads.data[14].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "subgroupID": 0, + "sequenceNumber": 0, + "transRejoin": 10, + "ackFrequency": 4, + "lowBattery": 3.1, + "reserved1": 0, + "heartbeatAckEnable": true, + "operationMode": 1, + "cycleSubBands": true, + "ackRetries": 2, + "reservedLL": 4, + "ackEnable": true, + "heartbeatEnable": true, + "cycleTime": 60, + "backOffReset": 9, + "reservedRD": 5, + "reserved2": 0, + "resendAttempts": 0, + "freqSubBand": 2, + "fport": 70, + "voboType": "VoBoXX", + "payloadType": "Configuration" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[15].fport} - ${testPayloads.data[15].name}`, () => { // fPort 100 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[15].bytes }, + { variable: "fPort", value: testPayloads.data[15].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "group3register1": 531, + "fport": 100, + "voboType": "VoBoXX", + "payloadType": "Modbus Generic" + }, + "warnings": [], + "errors": [] + }); + }); +}); + + +describe(`fPort: ${testPayloads.data[16].fport} - ${testPayloads.data[16].name}`, () => { // fPort 110 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[16].bytes }, + { variable: "fPort", value: testPayloads.data[16].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "ainPayloads": [ + { + "sensorNum0": 1, + "sensorUnits0": 254, + "sensorData0": 1 + }, + { + "sensorNum0": 2, + "sensorUnits0": 254, + "sensorData0": 2 + }, + { + "sensorNum0": 3, + "sensorUnits0": 254, + "sensorData0": 1 + }, + { + "sensorNum0": 0, + "sensorUnits0": 58, + "sensorData0": 3.375999927520752 + }, + { + "sensorNum0": 15, + "sensorUnits0": 32, + "sensorData0": 22.75 + } + ], + "numOfAinPayloads": 5, + "analogSensorString0": "AIN1", + "engUnitsString0": "ADC code", + "analogSensorString1": "AIN2", + "engUnitsString1": "ADC code", + "analogSensorString2": "AIN3", + "engUnitsString2": "ADC code", + "analogSensorString3": "Battery Voltage", + "engUnitsString3": "V", + "analogSensorString4": "ADC Temperature", + "engUnitsString4": "C", + "fport": 110, + "voboType": "VoBoXX", + "payloadType": "Analog Input Variable Length" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[17].fport} - ${testPayloads.data[17].name}`, () => { // fPort 120 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[17].bytes }, + { variable: "fPort", value: testPayloads.data[17].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "modbusSlots": { + "Modbus1": 9999, + "Modbus2": 9999, + "Modbus3": 9999, + "Modbus4": 9999, + "Modbus5": 9999, + "Modbus6": 9999, + "Modbus7": 9999 + }, + "firstSlotNum": 1, + "numModbusSlots": 7, + "fport": 120, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard Variable Length" + }, + "warnings": [], + "errors": [] + }); + }); +}); \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-hl-1/assets/logo.png b/decoders/connector/volley-boast/vobo-hl-1/assets/logo.png new file mode 100644 index 0000000..0fed9b0 Binary files /dev/null and b/decoders/connector/volley-boast/vobo-hl-1/assets/logo.png differ diff --git a/decoders/connector/volley-boast/vobo-hl-1/connector.jsonc b/decoders/connector/volley-boast/vobo-hl-1/connector.jsonc new file mode 100644 index 0000000..7e65067 --- /dev/null +++ b/decoders/connector/volley-boast/vobo-hl-1/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "VoBo HL-1", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-hl-1/description.md b/decoders/connector/volley-boast/vobo-hl-1/description.md new file mode 100644 index 0000000..5468e9e --- /dev/null +++ b/decoders/connector/volley-boast/vobo-hl-1/description.md @@ -0,0 +1 @@ +Sensor agnostic hazardous area LoRaWAN bridge endpoint. \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload-config.jsonc b/decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload-config.jsonc new file mode 100644 index 0000000..b6d36f1 --- /dev/null +++ b/decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload-config.jsonc @@ -0,0 +1,24 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The VoBo HL-1 is a Hazardous Area LoRaWAN Class A endpoint which bridges Analog, Digital and RS485 Modbus RTU transmitters to LoRaWAN", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-loriot/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload.js b/decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload.js new file mode 100644 index 0000000..2244caf --- /dev/null +++ b/decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload.js @@ -0,0 +1,1512 @@ +/* istanbul ignore next */ +function customDecoder(bytes, fport) { + var decoded = Decoder(bytes, fport); + //======================================================= + // Insert your customization here + + + //======================================================= + return decoded; +} + +//=========================================================== +// Volley Boast Decoder +// +// Volley Boast maintains the below code, therefore +// DO NOT EDIT BELOW THIS POINT!! +//=========================================================== +const DECODER_MAJOR_VERSION = 2; +const DECODER_MINOR_VERSION = 1; +const DECODER_PATCH_VERSION = 1; + +function decodeUplink(input) { + const decoded = Decoder(input.bytes, input.fPort); + return decoded; +} + +function Decoder(bytes, fport) { + var decoded = {}; + var decodedData = {}; + var warnings = []; + var errors = []; + + if (fport == 1) { + decodedData = parseStandardPayload(bytes); + } + else if ((fport >= 2) && (fport <= 9)) { + decodedData = parseModbusStandardPayload(bytes, fport); + } + else if (fport == 10) { + decodedData = parseHeartbeat1p0Payload(bytes); + } + else if ((fport >= 20) && (fport <= 29)) { + decodedData = parseHeartbeat2p0Payload(bytes); + } + else if ((fport >= 30) && (fport <= 39)) { + decodedData = parseOneAnalogSensorPayload(bytes); + } + else if ((fport >= 40) && (fport <= 49)) { + decodedData = parseTwoAnalogSensorsPayload(bytes); + } + else if ((fport >= 50) && (fport <= 59)) { + decodedData = parseDigitalSensorsPayload(bytes); + } + else if ((fport >= 60) && (fport <= 69)) { + decodedData = parseEventLogPayload(bytes); + } + else if ((fport >= 70) && (fport <= 79)) { + decodedData = parseConfigurationPayload(bytes, fport); + } + else if ((fport >= 100) && (fport <= 109)) { + decodedData = parseModbusGenericPayload(bytes, fport); + } + else if ((fport >= 110) && (fport <= 119)) { + decodedData = parseAnalogInputVariableLengthPayload(bytes, fport); + } + else if ((fport >= 120) && (fport <= 129)) { + decodedData = parseModbusStandardVariableLengthPayload(bytes, fport); + } + else { + errors.push('unknown FPort') + } + + decoded = addVoboMetadata(decodedData, fport); + decoded.warnings = warnings; + decoded.errors = errors; + + return decoded; +} + +function addVoboMetadata(decodedData, fport) { + var payload = {}; + payload.data = decodedData; + + var voboType = ""; + var payloadType = ""; + var portLastDigit = fport % 10; + + if ((portLastDigit == 0) || ((fport >= 1) && (fport < 10))) voboType = "VoBoXX"; + else if (portLastDigit == 1) voboType = "VoBoTC"; + else if (portLastDigit == 2) voboType = "VoBoXP"; + + if (fport == 1) payloadType = "Standard"; + else if ((fport >= 2) && (fport <= 9)) { + payloadType = "Modbus Standard"; + } + else if (fport == 10) payloadType = "Heartbeat 1.0"; + else if ((fport >= 20) && (fport <= 29)) { + payloadType = "Heartbeat 2.0"; + } + else if ((fport >= 30) && (fport <= 39)) { + payloadType = "One Analog Input"; + payload.data.analogSensorString0 = lookupAnalogSensorName(voboType, payload.data.sensorNum0); + payload.data.engUnitsString0 = lookupUnits(1, payload.data.sensorUnits0); + } + else if ((fport >= 40) && (fport <= 49)) { + payloadType = "Two Analog Inputs"; + payload.data.analogSensorString0 = lookupAnalogSensorName(voboType, payload.data.sensorNum0); + payload.data.engUnitsString0 = lookupUnits(1, payload.data.sensorUnits0); + payload.data.analogSensorString1 = lookupAnalogSensorName(voboType, payload.data.sensorNum1); + payload.data.engUnitsString1 = lookupUnits(1, payload.data.sensorUnits1); + } + else if ((fport >= 50) && (fport <= 59)) { + payloadType = "Digital Inputs"; + payload.data.digitalSensorStrings = []; + payload.data.digitalSensorData = []; + for (let i = 0; i < 16; i++) { + let valid = (payload.data.sensorValid0 >> i) & 1; + if (valid == 1) { + payload.data.digitalSensorStrings.push(lookupDigitalSensorName(voboType, i)); + let data = (payload.data.sensorData0 >> i) & 1; + payload.data.digitalSensorData.push(data); + } + } + } + else if ((fport >= 60) && (fport <= 69)) payloadType = "Event Log"; + else if ((fport >= 70) && (fport <= 79)) payloadType = "Configuration"; + else if ((fport >= 100) && (fport <= 109)) payloadType = "Modbus Generic"; + else if ((fport >= 110) && (fport <= 119)) { + payloadType = "Analog Input Variable Length"; + for (let i = 0; i < payload.data.numOfAinPayloads; i++) { + payload.data["analogSensorString" + i] = lookupAnalogSensorName(voboType, payload.data.ainPayloads[i].sensorNum0); + payload.data["engUnitsString" + i] = lookupUnits(1, payload.data.ainPayloads[i].sensorUnits0); + } + } + else if ((fport >= 120) && (fport <= 129)) payloadType = "Modbus Standard Variable Length"; + + payload.data.fport = fport; + payload.data.voboType = voboType; + payload.data.payloadType = payloadType; + + return payload; +} + +function parseStandardPayload(bytes) { + var decoded = {}; + decoded.DIN1 = bytes[0] & 0x01; // Discrete digital 1 (1-bit) + decoded.DIN2 = bytes[0] >> 1 & 0x01; // Discrete digital 2 (1-bit) + decoded.DIN3 = bytes[0] >> 2 & 0x01; // Discrete digital 3 (1-bit) + decoded.WKUP = bytes[0] >> 3 & 0x01; // Discrete digital wakeup (1-bit) + decoded.ADC1 = ((bytes[0] & 0xf0) >> 4) | (bytes[1] << 4); // ADC 1 (12-bit) + decoded.ADC2 = (bytes[3] & 0x0f) << 8 | bytes[2]; // ADC 2 (12-bit) + decoded.ADC3 = ((bytes[3] & 0xf0) >> 4) | (bytes[4] << 4); // ADC 3 (12-bit) + decoded.Battery = ((bytes[6] & 0x0f) << 8 | bytes[5]) * 4; // ADC battery (12-bit) + if ((bytes[7] >> 7 & 0x01) == 0) { + decoded.Temperature = (((bytes[6] & 0xf0) >> 4) | (bytes[7] << 4)) * 0.125; // ADC temperature (12-bit) above 0 degrees C + } else { + decoded.Temperature = (4096 - (((bytes[6] & 0xf0) >> 4) | (bytes[7] << 4))) * 0.125 * (-1); // ADC temperature (12-bit) below 0 degrees C + } + decoded.Modbus0 = bytes[9] << 8 | bytes[8]; // Modbus-RS485 (16-bit) + return decoded; +} + +function parseModbusStandardPayload(bytes, fport) { + var startIdx = (fport - 1) * 5 - 4; + var decoded = {}; + decoded["Modbus" + startIdx++] = (bytes[1] << 8) | bytes[0]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[3] << 8) | bytes[2]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[5] << 8) | bytes[4]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[7] << 8) | bytes[6]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx] = (bytes[9] << 8) | bytes[8]; // Modbus-RS485 (16-bit) + return decoded; +} + +function parseHeartbeat1p0Payload(bytes) { + var decoded = {}; + decoded.batteryLevelMV = ((bytes[1] & 0x0f) << 8 | bytes[0]) * 4; // ADC battery (12-bit) + decoded.fwVersionMajor = (bytes[2] & 0x0f) | (bytes[1] & 0xf0) >> 4; // FW Version Major (8-bit) + decoded.fwVersionMinor = (bytes[3] & 0x0f) | (bytes[2] & 0xf0) >> 4; // FW Version Minor (8-bit) + decoded.fwVersionPatch = (bytes[4] & 0x0f) | (bytes[3] & 0xf0) >> 4; // FW Version Patch (8-bit) + decoded.fwVersionCustom = (bytes[4] & 0xf0) >> 4; // FW Version Custom (4-bit) + decoded.recSignalLevels = bytes[5] * -1; // Last RSSI (8 bits) + decoded.analogVoltageConfig = (bytes[6] & 0x1f); // Analog Voltage Config (5 bits) + decoded.analogPowerTimeConfig = parseFloat((((bytes[7] & 0x1F) << 3 | (bytes[6] & 0xE0) >> 5) / 10).toFixed(1)); // Analog Power Time Config (8 bits) + decoded.failedTransmissionBeforeRejoinConfig = (bytes[8] & 0x01) << 3 | (bytes[7] & 0xE0) >> 5; // Failed Transmissions Before Rejoin Config (4 bits) + decoded.cycleThroughFSB = (bytes[8] & 0x02) >> 1; // Cycle Through FSB (1 bit) + decoded.ackEnable = (bytes[8] & 0x04) >> 2; // ACK Enable (1 bit) + decoded.ackFreq = (bytes[8] & 0x78) >> 3; // ACK Frequency (4 bits) + decoded.ackReq = (bytes[9] & 0x07) << 1 | (bytes[8] & 0x80) >> 7; // ACK Request (4 bits) + decoded.batteryLevelThreshold = (((bytes[10] & 0x7F) << 5) | (bytes[9] & 0xF8) >> 3) * 4; // Battery Level Threshold (12-bit) + + return decoded; +} + +function parseHeartbeat2p0Payload(bytes) { + var decoded = {}; + decoded.batteryLevel = (bytes[1] & 0x0F) << 8 | bytes[0]; // Current Battery level in mV + decoded.fatalErrorsTotal = (bytes[1] & 0xF0) >> 4; // Total number of Fatal Error detected by the handler (triggering reboot) for the last HeartBeat interval + decoded.rssiAvg = bytes[2]; // RSSI of received signal from LoRaWAN GW - average for the last HeartBeat interval + decoded.failedJoinAttemptsTotal = bytes[3] & 0x7F; // Total Number of Failed Join Attempts to LoRaWAN network for the last HeartBeat interval + decoded.configUpdateOccurred = (bytes[3] & 0x80) >> 7; // Indicates if one or more config updates occurred for the last HeartBeat interval + decoded.firmwareRevision = (bytes[5] & 0x03) << 8 | bytes[4]; // Firmware revision encoding. + decoded.rebootsTotal = (bytes[5] & 0x1C) >> 2; // Total number of VoBo reboots not including Fatal Error for the last HeartBeat interval + decoded.failedTransmitsTotal = (bytes[5] & 0xE0) >> 5; // Total Number of Failed LoRaWAN Transmits for the last HeartBeat interval + decoded.errorEventLogsTotal = bytes[6]; // Total number of Error Event Logs written for the last HeartBeat interval + decoded.warningEventLogsTotal = bytes[7]; // Total number of Warning Event Logs written for the last HeartBeat interval + decoded.infoEventLogsTotal = bytes[8]; // Total number of Info Event Logs written for the last HeartBeat interval + decoded.measurementPacketsTotal = (bytes[10]) << 8 | bytes[9]; // Number of Measurements Packets successfully sent for the last HeartBeat interval + + return decoded; +} + +function bytesToFloat32(bytesArray) { + var buffer = new ArrayBuffer(4); + var bytesRaw = new Uint8Array(buffer); + bytesRaw[0] = bytesArray[0]; + bytesRaw[1] = bytesArray[1]; + bytesRaw[2] = bytesArray[2]; + bytesRaw[3] = bytesArray[3]; + var float32Res = new DataView(buffer).getFloat32(0, false); + + return float32Res; +} + +function parseOneAnalogSensorPayload(bytes) { + var decoded = {}; + decoded.sensorNum0 = bytes[0]; // Sensor Num 0 (8-bit) + decoded.sensorUnits0 = bytes[1]; // Sensor Units 0 (8-bit) + decoded.sensorData0 = bytesToFloat32(bytes.slice(2, 6)); // Sensor Data 0 (32-bit) + + return decoded; +} + +function parseTwoAnalogSensorsPayload(bytes) { + var decoded = {}; + decoded.sensorNum0 = (bytes[0] & 0xF0) >> 4; // Sensor Num 0 (4-bit) + decoded.sensorUnits0 = bytes[1]; // Sensor Units 0 (8-bit) + decoded.sensorData0 = bytesToFloat32(bytes.slice(2, 6)); // Sensor Data 0 (32-bit) + decoded.sensorNum1 = bytes[0] & 0x0F; // Sensor Num 1 (4-bit) + decoded.sensorUnits1 = bytes[6]; // Sensor Units 1 (8-bit) + decoded.sensorData1 = bytesToFloat32(bytes.slice(7, 11)); // Sensor Data 1 (32-bit) + + return decoded; +} + +function parseDigitalSensorsPayload(bytes) { + var decoded = {}; + decoded.sensorValid0 = (bytes[1] << 8) | bytes[0]; // Sensor Valid 0 (16-bit) + decoded.sensorData0 = (bytes[3] << 8) | bytes[2]; // Sensor Data 0 (16-bit) + + return decoded; +} + +function parseEventLogPayload(bytes) { + var decoded = {}; + decoded.eventTimestamp = bytes[0] + bytes[1] * Math.pow(2, 8) + bytes[2] * Math.pow(2, 16) + bytes[3] * Math.pow(2, 24); + decoded.eventCode = bytes[4] + bytes[5] * Math.pow(2, 8); + decoded.metadata = Array.prototype.slice.call(bytes.slice(6, 11), 0); + + return decoded; +} + +function parseVoboLibGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.transRejoin = bytes[1] & 0x0F; // Transmission Rejoin (4-bit) + decoded.ackFrequency = (bytes[1] & 0xF0) >> 4; // Acknowledgement Frequency for Data (4-bit) + decoded.lowBattery = parseFloat(((bytes[2] & 0x0F) / 10.0 + 2.5).toFixed(1)); // Low Battery Threshold (4-bit) + decoded.reserved1 = (bytes[2] & 0x10) >> 4; // Reserved 1 Field (1-bit) + decoded.heartbeatAckEnable = Boolean((bytes[2] & 0x20) >> 5); // Acknowledgement Enable for Heartbeat (1-bit) + decoded.operationMode = (bytes[2] & 0x40) >> 6; // Operation Mode (1-bit) + decoded.cycleSubBands = Boolean((bytes[2] & 0x80) >> 7); // Cycle Sub Bands Enable (1-bit) + decoded.ackRetries = (bytes[3] & 0x07); // Acknowledgement Retries (3-bit) + decoded.reservedLL = (bytes[3] & 0x38) >> 3; // Reserved LL (3-bit) + decoded.ackEnable = Boolean((bytes[3] & 0x40) >> 6); // Acknowledgement Enable for Data (1-bit) + decoded.heartbeatEnable = Boolean((bytes[3] & 0x80) >> 7); // Heartbeat Enable (1-bit) + decoded.cycleTime = ((bytes[6] & 0x03) << 16) | (bytes[5] << 8) | bytes[4]; // Cycle Time (18-bit) + decoded.backOffReset = (bytes[6] & 0xFC) >> 2; // BackOff Reset (6-bit) + decoded.reservedRD = bytes[7] & 0x3F; // Reserved RD (6-bit) + decoded.reserved2 = (bytes[7] & 0xC0) >> 6; // Reserved 2 Field (2-bit) + decoded.resendAttempts = bytes[8] & 0x0F; // Resend Attempts (4-bit) + decoded.freqSubBand = (bytes[8] & 0xF0) >> 4; // Frequency Sub Band (4-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.timeSyncInterval = bytes[1]; // Time Sync Interval in 30 minutes quantities (8-bit) + decoded.joinEUI = ""; // LoRaWAN Join EUI (64-bit) + for (let i = 9; i >= 2; i--) { + decoded.joinEUI += bytes[i].toString(16).toUpperCase().padStart(2, 0); + if (i != 2) decoded.joinEUI += "-"; + } + decoded.joinNonceResetEnable = Boolean(bytes[10] & 0x01); // Join Nonce Reset Enable (1-bit) + decoded.timeSyncWakeupEnable = Boolean((bytes[10] & 0x02) >> 1); // Time Sync Wakeup Enable (1-bit) + decoded.reserved1 = (bytes[10] & 0xFC) >> 2; // Reserved 1 Field (1-bit) + } + + return decoded; +} + +function parseVoboLibVoboSyncConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.vbsNodeNumber = (bytes[2] << 8) | bytes[1]; // VoboSync Node Number (16-bit) + decoded.vbsTimeReference = bytes.slice(3, 7).readUInt32LE(); // VoboSync Time Reference (32-bit) + decoded.reservedVCPDS = bytes[7] & 0x0F; // Reserved VCPDS (4-bit) + decoded.reservedVMPDS = (bytes[7] & 0xF0) >> 4; // Reserved VMPDS (4-bit) + decoded.reservedVUDS = bytes[8] & 0x0F; // Reserved VUDS (4-bit) + decoded.reserved1 = (bytes[8] & 0x30) >> 4; // Reserved 1 Field (2-bit) + decoded.reservedVSAE = Boolean((bytes[8] & 0x40) >> 6); // Reserved VSAE (1-bit) + decoded.vbsEnable = Boolean((bytes[8] & 0x80) >> 7); // VoboSync Enable (1-bit) + decoded.reserved3 = bytes[9] & 0x3F; // Reserved 3 (6-bit) + decoded.reserved2 = (bytes[9] & 0xC0) >> 6; // Reserved 2 Field (2-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.vbsMeasurementDelaySec = bytes[1] & 0x7F; // VoboSync Measurement Delay (7-bit) + decoded.reserved1 = (bytes[1] & 0x80) >> 7; // Reserved 1 Field (1-bit) + decoded.vbsUplinkDelaySec = bytes[2] & 0x7F; // VoboSync Uplink Delay (7-bit) + decoded.reserved2 = (bytes[2] & 0x80) >> 7; // Reserved 2 Field (1-bit) + } + + return decoded; +} + +function parseVoboXXGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + decoded.analogVoltage = parseFloat((bytes[1] / 10).toFixed(1)); // Analog Voltage (8-bit) + decoded.powerTime = parseFloat((bytes[2] / 10).toFixed(1)); // Analog Power Time (8-bit) + decoded.mbEnable = Boolean(bytes[3] & 0x01); // Modbus Enable (1-bit) + decoded.engUnitsEnable = Boolean((bytes[3] >> 1) & 0x01); // Engineering Units Enable (1-bit) + decoded.din1TransmitEnable = Boolean((bytes[3] >> 2) & 0x01); // DIN1 Transmit Enable (1-bit) + decoded.din2TransmitEnable = Boolean((bytes[3] >> 3) & 0x01); // DIN2 Transmit Enable (1-bit) + decoded.din3TransmitEnable = Boolean((bytes[3] >> 4) & 0x01); // DIN3 Transmit Enable (1-bit) + decoded.wkupTransmitEnable = Boolean((bytes[3] >> 5) & 0x01); // WKUP Transmit Enable (1-bit) + decoded.ain1TransmitEnable = Boolean((bytes[3] >> 6) & 0x01); // AIN1 Transmit Enable (1-bit) + decoded.ain2TransmitEnable = Boolean((bytes[3] >> 7) & 0x01); // AIN2 Transmit Enable (1-bit) + decoded.ain3TransmitEnable = Boolean(bytes[4] & 0x01); // AIN3 Transmit Enable (1-bit) + decoded.batteryLevelTransmitEnable = Boolean((bytes[4] >> 1) & 0x01); // Battery Level Transmit Enable (1-bit) + decoded.adcTemperatureTransmitEnable = Boolean((bytes[4] >> 2) & 0x01); // ADC Temperature Transmit Enable (1-bit) + decoded.mbTransmitEnable = Boolean((bytes[4] >> 3) & 0x01); // Modbus Transmit Enable (1-bit) + decoded.ainPayloadType = (bytes[4] >> 4) & 0x01; // Ain Payload Type (1-bit) + decoded.reservedMAWE = Boolean((bytes[4] >> 5) & 0x01); // Reserved MAWE Field (1-bit) + decoded.reservedMADE = Boolean((bytes[4] >> 6) & 0x01); // Reserved MADE Field (1-bit) + decoded.reservedMAME = Boolean((bytes[4] >> 7) & 0x01); // Reserved MAME Field (1-bit) + + return decoded; +} + +function parseVoboXXModbusGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + decoded.mbTimeout = (bytes[2] << 8) | bytes[1]; // Modbus Timeout in milliseconds (16-bit) + decoded.mbBaud = bytes[3] & 0x0F; // Modbus Baud Rate - encoded (4-bit) + decoded.mbStopBits = (bytes[3] >> 4) & 0x03; // Modbus Stop Bits (2-bit) + decoded.mbParity = (bytes[3] >> 6) & 0x03; // Modbus Parity (2-bit) + decoded.mbPayloadType = bytes[4] & 0x03; // Modbus Payload Type (2-bit) + decoded.reserved1 = (bytes[4] >> 2) & 0x3F; // Reserved 1 Field (6-bit) + + return decoded; +} + +function parseVoboXXModbusGroupsEnableConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.subgroupID == 6) { + decoded.mbFirstCycleG1 = Boolean(bytes[1] & 0x01); // Group 1 First Cycle Enable + decoded.mbFirstCycleG2 = Boolean((bytes[1] >> 1) & 0x01); // Group 2 First Cycle Enable + decoded.mbFirstCycleG3 = Boolean((bytes[1] >> 2) & 0x01); // Group 3 First Cycle Enable + decoded.mbFirstCycleG4 = Boolean((bytes[1] >> 3) & 0x01); // Group 4 First Cycle Enable + decoded.mbFirstCycleG5 = Boolean((bytes[1] >> 4) & 0x01); // Group 5 First Cycle Enable + decoded.mbFirstCycleG6 = Boolean((bytes[1] >> 5) & 0x01); // Group 6 First Cycle Enable + decoded.mbFirstCycleG7 = Boolean((bytes[1] >> 6) & 0x01); // Group 7 First Cycle Enable + decoded.mbFirstCycleG8 = Boolean((bytes[1] >> 7) & 0x01); // Group 8 First Cycle Enable + decoded.mbFirstCycleG9 = Boolean(bytes[2] & 0x01); // Group 9 First Cycle Enable + decoded.mbFirstCycleG10 = Boolean((bytes[2] >> 1) & 0x01); // Group 10 First Cycle Enable + decoded.mbFirstCycleG11 = Boolean((bytes[2] >> 2) & 0x01); // Group 11 First Cycle Enable + decoded.mbFirstCycleG12 = Boolean((bytes[2] >> 3) & 0x01); // Group 12 First Cycle Enable + decoded.mbFirstCycleG13 = Boolean((bytes[2] >> 4) & 0x01); // Group 13 First Cycle Enable + decoded.mbFirstCycleG14 = Boolean((bytes[2] >> 5) & 0x01); // Group 14 First Cycle Enable + decoded.mbFirstCycleG15 = Boolean((bytes[2] >> 6) & 0x01); // Group 15 First Cycle Enable + decoded.mbFirstCycleG16 = Boolean((bytes[2] >> 7) & 0x01); // Group 16 First Cycle Enable + decoded.mbFirstCycleG17 = Boolean(bytes[3] & 0x01); // Group 17 First Cycle Enable + decoded.mbFirstCycleG18 = Boolean((bytes[3] >> 1) & 0x01); // Group 18 First Cycle Enable + decoded.mbFirstCycleG19 = Boolean((bytes[3] >> 2) & 0x01); // Group 19 First Cycle Enable + decoded.mbFirstCycleG20 = Boolean((bytes[3] >> 3) & 0x01); // Group 20 First Cycle Enable + decoded.mbFirstCycleG21 = Boolean((bytes[3] >> 4) & 0x01); // Group 21 First Cycle Enable + decoded.mbFirstCycleG22 = Boolean((bytes[3] >> 5) & 0x01); // Group 22 First Cycle Enable + decoded.mbFirstCycleG23 = Boolean((bytes[3] >> 6) & 0x01); // Group 23 First Cycle Enable + decoded.mbFirstCycleG24 = Boolean((bytes[3] >> 7) & 0x01); // Group 24 First Cycle Enable + decoded.mbFirstCycleG25 = Boolean(bytes[4] & 0x01); // Group 25 First Cycle Enable + decoded.mbFirstCycleG26 = Boolean((bytes[4] >> 1) & 0x01); // Group 26 First Cycle Enable + decoded.mbFirstCycleG27 = Boolean((bytes[4] >> 2) & 0x01); // Group 27 First Cycle Enable + decoded.mbFirstCycleG28 = Boolean((bytes[4] >> 3) & 0x01); // Group 28 First Cycle Enable + decoded.mbFirstCycleG29 = Boolean((bytes[4] >> 4) & 0x01); // Group 29 First Cycle Enable + decoded.mbFirstCycleG30 = Boolean((bytes[4] >> 5) & 0x01); // Group 30 First Cycle Enable + decoded.mbFirstCycleG31 = Boolean((bytes[4] >> 6) & 0x01); // Group 31 First Cycle Enable + decoded.mbFirstCycleG32 = Boolean((bytes[4] >> 7) & 0x01); // Group 32 First Cycle Enable + decoded.mbFirstCycleG33 = Boolean(bytes[5] & 0x01); // Group 33 First Cycle Enable + decoded.mbFirstCycleG34 = Boolean((bytes[5] >> 1) & 0x01); // Group 34 First Cycle Enable + decoded.mbFirstCycleG35 = Boolean((bytes[5] >> 2) & 0x01); // Group 35 First Cycle Enable + decoded.mbFirstCycleG36 = Boolean((bytes[5] >> 3) & 0x01); // Group 36 First Cycle Enable + decoded.mbFirstCycleG37 = Boolean((bytes[5] >> 4) & 0x01); // Group 37 First Cycle Enable + decoded.mbFirstCycleG38 = Boolean((bytes[5] >> 5) & 0x01); // Group 38 First Cycle Enable + decoded.mbFirstCycleG39 = Boolean((bytes[5] >> 6) & 0x01); // Group 39 First Cycle Enable + decoded.mbFirstCycleG40 = Boolean((bytes[5] >> 7) & 0x01); // Group 40 First Cycle Enable + decoded.mbFirstCycleG41 = Boolean(bytes[6] & 0x01); // Group 41 First Cycle Enable + decoded.reserved1 = (bytes[6] >> 1) & 0x7F; // Reserved 1 Field (7-bit) + } + if (decoded.subgroupID == 7) { + decoded.mbSubseqCyclesG1 = Boolean(bytes[1] & 0x01); // Group 1 Subsequent Cycle Enable + decoded.mbSubseqCyclesG2 = Boolean((bytes[1] >> 1) & 0x01); // Group 2 Subsequent Cycle Enable + decoded.mbSubseqCyclesG3 = Boolean((bytes[1] >> 2) & 0x01); // Group 3 Subsequent Cycle Enable + decoded.mbSubseqCyclesG4 = Boolean((bytes[1] >> 3) & 0x01); // Group 4 Subsequent Cycle Enable + decoded.mbSubseqCyclesG5 = Boolean((bytes[1] >> 4) & 0x01); // Group 5 Subsequent Cycle Enable + decoded.mbSubseqCyclesG6 = Boolean((bytes[1] >> 5) & 0x01); // Group 6 Subsequent Cycle Enable + decoded.mbSubseqCyclesG7 = Boolean((bytes[1] >> 6) & 0x01); // Group 7 Subsequent Cycle Enable + decoded.mbSubseqCyclesG8 = Boolean((bytes[1] >> 7) & 0x01); // Group 8 Subsequent Cycle Enable + decoded.mbSubseqCyclesG9 = Boolean(bytes[2] & 0x01); // Group 9 Subsequent Cycle Enable + decoded.mbSubseqCyclesG10 = Boolean((bytes[2] >> 1) & 0x01); // Group 10 Subsequent Cycle Enable + decoded.mbSubseqCyclesG11 = Boolean((bytes[2] >> 2) & 0x01); // Group 11 Subsequent Cycle Enable + decoded.mbSubseqCyclesG12 = Boolean((bytes[2] >> 3) & 0x01); // Group 12 Subsequent Cycle Enable + decoded.mbSubseqCyclesG13 = Boolean((bytes[2] >> 4) & 0x01); // Group 13 Subsequent Cycle Enable + decoded.mbSubseqCyclesG14 = Boolean((bytes[2] >> 5) & 0x01); // Group 14 Subsequent Cycle Enable + decoded.mbSubseqCyclesG15 = Boolean((bytes[2] >> 6) & 0x01); // Group 15 Subsequent Cycle Enable + decoded.mbSubseqCyclesG16 = Boolean((bytes[2] >> 7) & 0x01); // Group 16 Subsequent Cycle Enable + decoded.mbSubseqCyclesG17 = Boolean(bytes[3] & 0x01); // Group 17 Subsequent Cycle Enable + decoded.mbSubseqCyclesG18 = Boolean((bytes[3] >> 1) & 0x01); // Group 18 Subsequent Cycle Enable + decoded.mbSubseqCyclesG19 = Boolean((bytes[3] >> 2) & 0x01); // Group 19 Subsequent Cycle Enable + decoded.mbSubseqCyclesG20 = Boolean((bytes[3] >> 3) & 0x01); // Group 20 Subsequent Cycle Enable + decoded.mbSubseqCyclesG21 = Boolean((bytes[3] >> 4) & 0x01); // Group 21 Subsequent Cycle Enable + decoded.mbSubseqCyclesG22 = Boolean((bytes[3] >> 5) & 0x01); // Group 22 Subsequent Cycle Enable + decoded.mbSubseqCyclesG23 = Boolean((bytes[3] >> 6) & 0x01); // Group 23 Subsequent Cycle Enable + decoded.mbSubseqCyclesG24 = Boolean((bytes[3] >> 7) & 0x01); // Group 24 Subsequent Cycle Enable + decoded.mbSubseqCyclesG25 = Boolean(bytes[4] & 0x01); // Group 25 Subsequent Cycle Enable + decoded.mbSubseqCyclesG26 = Boolean((bytes[4] >> 1) & 0x01); // Group 26 Subsequent Cycle Enable + decoded.mbSubseqCyclesG27 = Boolean((bytes[4] >> 2) & 0x01); // Group 27 Subsequent Cycle Enable + decoded.mbSubseqCyclesG28 = Boolean((bytes[4] >> 3) & 0x01); // Group 28 Subsequent Cycle Enable + decoded.mbSubseqCyclesG29 = Boolean((bytes[4] >> 4) & 0x01); // Group 29 Subsequent Cycle Enable + decoded.mbSubseqCyclesG30 = Boolean((bytes[4] >> 5) & 0x01); // Group 30 Subsequent Cycle Enable + decoded.mbSubseqCyclesG31 = Boolean((bytes[4] >> 6) & 0x01); // Group 31 Subsequent Cycle Enable + decoded.mbSubseqCyclesG32 = Boolean((bytes[4] >> 7) & 0x01); // Group 32 Subsequent Cycle Enable + decoded.mbSubseqCyclesG33 = Boolean(bytes[5] & 0x01); // Group 33 Subsequent Cycle Enable + decoded.mbSubseqCyclesG34 = Boolean((bytes[5] >> 1) & 0x01); // Group 34 Subsequent Cycle Enable + decoded.mbSubseqCyclesG35 = Boolean((bytes[5] >> 2) & 0x01); // Group 35 Subsequent Cycle Enable + decoded.mbSubseqCyclesG36 = Boolean((bytes[5] >> 3) & 0x01); // Group 36 Subsequent Cycle Enable + decoded.mbSubseqCyclesG37 = Boolean((bytes[5] >> 4) & 0x01); // Group 37 Subsequent Cycle Enable + decoded.mbSubseqCyclesG38 = Boolean((bytes[5] >> 5) & 0x01); // Group 38 Subsequent Cycle Enable + decoded.mbSubseqCyclesG39 = Boolean((bytes[5] >> 6) & 0x01); // Group 39 Subsequent Cycle Enable + decoded.mbSubseqCyclesG40 = Boolean((bytes[5] >> 7) & 0x01); // Group 40 Subsequent Cycle Enable + decoded.mbSubseqCyclesG41 = Boolean(bytes[6] & 0x01); // Group 41 Subsequent Cycle Enable + decoded.reserved1 = (bytes[6] >> 1) & 0x7F; // Reserved 1 Field (7-bit) + } + + return decoded; +} + +function parseVoboXXModbusGroupsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + var groupIdx = ((decoded.subgroupID - 8) * 16) + decoded.sequenceNumber + 1; // Sub-Groups 8, 9 and 10 carry the groups configurations. Group index is determined by Sub-Group ID and Sequence Number. + var groupIdxStr = (groupIdx).toString(); + decoded["mbFirstCycleG" + groupIdxStr] = Boolean(bytes[1] & 0x01); // Modbus Group First Cycle Enable (1-bit) + decoded["mbSubseqCyclesG" + groupIdxStr] = Boolean((bytes[1] >> 1) & 0x01); // Modbus Group Subsequent Cycle Enable (1-bit) + decoded["mbByteSwapG" + groupIdxStr] = Boolean((bytes[1] >> 2) & 0x01); // Modbus Group Byte Swap Enable (1-bit) + decoded["mbWordSwapG" + groupIdxStr] = Boolean((bytes[1] >> 3) & 0x01); // Modbus Group Word Swap Enable (1-bit) + decoded["mbNumTypeG" + groupIdxStr] = (bytes[1] >> 4) & 0x03; // Modbus Group Numerical Type (2-bit) + decoded["reserved1"] = (bytes[1] >> 6) & 0x03; // Reserved 1 Field (2-bit) + decoded["mbSlaveAddrG" + groupIdxStr] = bytes[2]; // Modbus Group Slave Address (8-bit) + decoded["mbStartAddrG" + groupIdxStr] = (bytes[4] << 8) | bytes[3]; // Modbus Group Start Address (16-bit) + decoded["mbNumRegsG" + groupIdxStr] = bytes[5]; // Modbus Group Number of Registers (8-bit) + decoded["mbWdataG" + groupIdxStr] = (bytes[7] << 8) | bytes[6]; // Modbus Group Write Data (16-bit) + decoded["mbGrpDelayG" + groupIdxStr] = (bytes[9] << 8) | bytes[8]; // Modbus Group Delay in milliseconds (16-bit) + decoded["mbFuncCodeG" + groupIdxStr] = bytes[10] & 0x3F; // Modbus Group Function Code (6-bit) + decoded["reserved2"] = (bytes[10] >> 6) & 0x03; // Reserved 2 Field (2-bit) + + return decoded; +} + +function parseVoboXXModbusPayloadsSlotsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.mbGroupPaySlot0 = bytes[1]; // Modbus Payload Slot 0 Group Idx (8-bit) + decoded.mbRegPaySlot0 = bytes[2]; // Modbus Payload Slot 0 Register Idx (8-bit) + decoded.mbGroupPaySlot1 = bytes[3]; // Modbus Payload Slot 1 Group Idx (8-bit) + decoded.mbRegPaySlot1 = bytes[4]; // Modbus Payload Slot 1 Register Idx (8-bit) + decoded.mbGroupPaySlot2 = bytes[5]; // Modbus Payload Slot 2 Group Idx (8-bit) + decoded.mbRegPaySlot2 = bytes[6]; // Modbus Payload Slot 2 Register Idx (8-bit) + decoded.mbGroupPaySlot3 = bytes[7]; // Modbus Payload Slot 3 Group Idx (8-bit) + decoded.mbRegPaySlot3 = bytes[8]; // Modbus Payload Slot 3 Register Idx (8-bit) + decoded.mbGroupPaySlot4 = bytes[9]; // Modbus Payload Slot 4 Group Idx (8-bit) + decoded.mbRegPaySlot4 = bytes[10]; // Modbus Payload Slot 4 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.mbGroupPaySlot5 = bytes[1]; // Modbus Payload Slot 5 Group Idx (8-bit) + decoded.mbRegPaySlot5 = bytes[2]; // Modbus Payload Slot 5 Register Idx (8-bit) + decoded.mbGroupPaySlot6 = bytes[3]; // Modbus Payload Slot 6 Group Idx (8-bit) + decoded.mbRegPaySlot6 = bytes[4]; // Modbus Payload Slot 6 Register Idx (8-bit) + decoded.mbGroupPaySlot7 = bytes[5]; // Modbus Payload Slot 7 Group Idx (8-bit) + decoded.mbRegPaySlot7 = bytes[6]; // Modbus Payload Slot 7 Register Idx (8-bit) + decoded.mbGroupPaySlot8 = bytes[7]; // Modbus Payload Slot 8 Group Idx (8-bit) + decoded.mbRegPaySlot8 = bytes[8]; // Modbus Payload Slot 8 Register Idx (8-bit) + decoded.mbGroupPaySlot9 = bytes[9]; // Modbus Payload Slot 9 Group Idx (8-bit) + decoded.mbRegPaySlot9 = bytes[10]; // Modbus Payload Slot 9 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 2) { + decoded.mbGroupPaySlot10 = bytes[1]; // Modbus Payload Slot 10 Group Idx (8-bit) + decoded.mbRegPaySlot10 = bytes[2]; // Modbus Payload Slot 10 Register Idx (8-bit) + decoded.mbGroupPaySlot11 = bytes[3]; // Modbus Payload Slot 11 Group Idx (8-bit) + decoded.mbRegPaySlot11 = bytes[4]; // Modbus Payload Slot 11 Register Idx (8-bit) + decoded.mbGroupPaySlot12 = bytes[5]; // Modbus Payload Slot 12 Group Idx (8-bit) + decoded.mbRegPaySlot12 = bytes[6]; // Modbus Payload Slot 12 Register Idx (8-bit) + decoded.mbGroupPaySlot13 = bytes[7]; // Modbus Payload Slot 13 Group Idx (8-bit) + decoded.mbRegPaySlot13 = bytes[8]; // Modbus Payload Slot 13 Register Idx (8-bit) + decoded.mbGroupPaySlot14 = bytes[9]; // Modbus Payload Slot 14 Group Idx (8-bit) + decoded.mbRegPaySlot14 = bytes[10]; // Modbus Payload Slot 14 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 3) { + decoded.mbGroupPaySlot15 = bytes[1]; // Modbus Payload Slot 15 Group Idx (8-bit) + decoded.mbRegPaySlot15 = bytes[2]; // Modbus Payload Slot 15 Register Idx (8-bit) + decoded.mbGroupPaySlot16 = bytes[3]; // Modbus Payload Slot 16 Group Idx (8-bit) + decoded.mbRegPaySlot16 = bytes[4]; // Modbus Payload Slot 16 Register Idx (8-bit) + decoded.mbGroupPaySlot17 = bytes[5]; // Modbus Payload Slot 17 Group Idx (8-bit) + decoded.mbRegPaySlot17 = bytes[6]; // Modbus Payload Slot 17 Register Idx (8-bit) + decoded.mbGroupPaySlot18 = bytes[7]; // Modbus Payload Slot 18 Group Idx (8-bit) + decoded.mbRegPaySlot18 = bytes[8]; // Modbus Payload Slot 18 Register Idx (8-bit) + decoded.mbGroupPaySlot19 = bytes[9]; // Modbus Payload Slot 19 Group Idx (8-bit) + decoded.mbRegPaySlot19 = bytes[10]; // Modbus Payload Slot 19 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 4) { + decoded.mbGroupPaySlot20 = bytes[1]; // Modbus Payload Slot 20 Group Idx (8-bit) + decoded.mbRegPaySlot20 = bytes[2]; // Modbus Payload Slot 20 Register Idx (8-bit) + decoded.mbGroupPaySlot21 = bytes[3]; // Modbus Payload Slot 21 Group Idx (8-bit) + decoded.mbRegPaySlot21 = bytes[4]; // Modbus Payload Slot 21 Register Idx (8-bit) + decoded.mbGroupPaySlot22 = bytes[5]; // Modbus Payload Slot 22 Group Idx (8-bit) + decoded.mbRegPaySlot22 = bytes[6]; // Modbus Payload Slot 22 Register Idx (8-bit) + decoded.mbGroupPaySlot23 = bytes[7]; // Modbus Payload Slot 23 Group Idx (8-bit) + decoded.mbRegPaySlot23 = bytes[8]; // Modbus Payload Slot 23 Register Idx (8-bit) + decoded.mbGroupPaySlot24 = bytes[9]; // Modbus Payload Slot 24 Group Idx (8-bit) + decoded.mbRegPaySlot24 = bytes[10]; // Modbus Payload Slot 24 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 5) { + decoded.mbGroupPaySlot25 = bytes[1]; // Modbus Payload Slot 25 Group Idx (8-bit) + decoded.mbRegPaySlot25 = bytes[2]; // Modbus Payload Slot 25 Register Idx (8-bit) + decoded.mbGroupPaySlot26 = bytes[3]; // Modbus Payload Slot 26 Group Idx (8-bit) + decoded.mbRegPaySlot26 = bytes[4]; // Modbus Payload Slot 26 Register Idx (8-bit) + decoded.mbGroupPaySlot27 = bytes[5]; // Modbus Payload Slot 27 Group Idx (8-bit) + decoded.mbRegPaySlot27 = bytes[6]; // Modbus Payload Slot 27 Register Idx (8-bit) + decoded.mbGroupPaySlot28 = bytes[7]; // Modbus Payload Slot 28 Group Idx (8-bit) + decoded.mbRegPaySlot28 = bytes[8]; // Modbus Payload Slot 28 Register Idx (8-bit) + decoded.mbGroupPaySlot29 = bytes[9]; // Modbus Payload Slot 29 Group Idx (8-bit) + decoded.mbRegPaySlot29 = bytes[10]; // Modbus Payload Slot 29 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 6) { + decoded.mbGroupPaySlot30 = bytes[1]; // Modbus Payload Slot 30 Group Idx (8-bit) + decoded.mbRegPaySlot30 = bytes[2]; // Modbus Payload Slot 30 Register Idx (8-bit) + decoded.mbGroupPaySlot31 = bytes[3]; // Modbus Payload Slot 31 Group Idx (8-bit) + decoded.mbRegPaySlot31 = bytes[4]; // Modbus Payload Slot 31 Register Idx (8-bit) + decoded.mbGroupPaySlot32 = bytes[5]; // Modbus Payload Slot 32 Group Idx (8-bit) + decoded.mbRegPaySlot32 = bytes[6]; // Modbus Payload Slot 32 Register Idx (8-bit) + decoded.mbGroupPaySlot33 = bytes[7]; // Modbus Payload Slot 33 Group Idx (8-bit) + decoded.mbRegPaySlot33 = bytes[8]; // Modbus Payload Slot 33 Register Idx (8-bit) + decoded.mbGroupPaySlot34 = bytes[9]; // Modbus Payload Slot 34 Group Idx (8-bit) + decoded.mbRegPaySlot34 = bytes[10]; // Modbus Payload Slot 34 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 7) { + decoded.mbGroupPaySlot35 = bytes[1]; // Modbus Payload Slot 35 Group Idx (8-bit) + decoded.mbRegPaySlot35 = bytes[2]; // Modbus Payload Slot 35 Register Idx (8-bit) + decoded.mbGroupPaySlot36 = bytes[3]; // Modbus Payload Slot 36 Group Idx (8-bit) + decoded.mbRegPaySlot36 = bytes[4]; // Modbus Payload Slot 36 Register Idx (8-bit) + decoded.mbGroupPaySlot37 = bytes[5]; // Modbus Payload Slot 37 Group Idx (8-bit) + decoded.mbRegPaySlot37 = bytes[6]; // Modbus Payload Slot 37 Register Idx (8-bit) + decoded.mbGroupPaySlot38 = bytes[7]; // Modbus Payload Slot 38 Group Idx (8-bit) + decoded.mbRegPaySlot38 = bytes[8]; // Modbus Payload Slot 38 Register Idx (8-bit) + decoded.mbGroupPaySlot39 = bytes[9]; // Modbus Payload Slot 39 Group Idx (8-bit) + decoded.mbRegPaySlot39 = bytes[10]; // Modbus Payload Slot 39 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 8) { + decoded.mbGroupPaySlot40 = bytes[1]; // Modbus Payload Slot 40 Group Idx (8-bit) + decoded.mbRegPaySlot40 = bytes[2]; // Modbus Payload Slot 40 Register Idx (8-bit) + } + + return decoded; +} + +function parseVoboXXEngineeringUnitsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + // AIN1 General + if (decoded.sequenceNumber == 0) { + decoded.ain1UnitsCode = bytes[1]; // AIN1 Units Code (8-bit) + decoded.ain1MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN1 Minimum Value (32-bit) + decoded.ain1MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN1 Maximum Value (32-bit) + decoded.ain1Type = bytes[10] & 0x03; // AIN1 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN1 Series Resistance + if (decoded.sequenceNumber == 1) { + decoded.ain1SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN1 Series Resistance (32-bit) + } + // AIN1 Calibration + if (decoded.sequenceNumber == 2) { + decoded.ain1Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN1 Calibration Gain (32-bit) + decoded.ain1Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN1 Calibration Offset (32-bit) + } + + // AIN2 General + if (decoded.sequenceNumber == 3) { + decoded.ain2UnitsCode = bytes[1]; // AIN2 Units Code (8-bit) + decoded.ain2MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN2 Minimum Value (32-bit) + decoded.ain2MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN2 Maximum Value (32-bit) + decoded.ain2Type = bytes[10] & 0x03; // AIN2 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN2 Series Resistance + if (decoded.sequenceNumber == 4) { + decoded.ain2SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN2 Series Resistance (32-bit) + } + // AIN2 Calibration + if (decoded.sequenceNumber == 5) { + decoded.ain2Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN2 Calibration Gain (32-bit) + decoded.ain2Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN2 Calibration Offset (32-bit) + } + + // AIN3 General + if (decoded.sequenceNumber == 6) { + decoded.ain3UnitsCode = bytes[1]; // AIN3 Units Code (8-bit) + decoded.ain3MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN3 Minimum Value (32-bit) + decoded.ain3MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN3 Maximum Value (32-bit) + decoded.ain3Type = bytes[10] & 0x03; // AIN3 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN3 Series Resistance + if (decoded.sequenceNumber == 7) { + decoded.ain3SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN3 Series Resistance (32-bit) + } + // AIN3 Calibration + if (decoded.sequenceNumber == 8) { + decoded.ain3Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN3 Calibration Gain (32-bit) + decoded.ain3Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN3 Calibration Offset (32-bit) + } + + return decoded; +} + +function parseVoboTCGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.deviceType1 = bytes[1] & 0x07; // Device Type 1 (3-bit) + decoded.enable1 = Boolean((bytes[1] & 0x08) >> 3); // Enable 1 (1-bit) + decoded.deviceType2 = (bytes[1] & 0x70) >> 4; // Device Type 2 (3-bit) + decoded.enable2 = Boolean((bytes[1] & 0x80) >> 7); // Enable 2 (1-bit) + decoded.deviceType3 = bytes[2] & 0x07; // Device Type 3 (3-bit) + decoded.enable3 = Boolean((bytes[2] & 0x08) >> 3); // Enable 3 (1-bit) + decoded.deviceType4 = (bytes[2] & 0x70) >> 4; // Device Type 4 (3-bit) + decoded.enable4 = Boolean((bytes[2] & 0x80) >> 7); // Enable 4 (1-bit) + decoded.deviceType5 = bytes[3] & 0x07; // Device Type 5 (3-bit) + decoded.enable5 = Boolean((bytes[3] & 0x08) >> 3); // Enable 5 (1-bit) + decoded.deviceType6 = (bytes[3] & 0x70) >> 4; // Device Type 6 (3-bit) + decoded.enable6 = Boolean((bytes[3] & 0x80) >> 7); // Enable 6 (1-bit) + decoded.deviceType7 = bytes[4] & 0x07; // Device Type 7 (3-bit) + decoded.enable7 = Boolean((bytes[4] & 0x08) >> 3); // Enable 7 (1-bit) + decoded.deviceType8 = (bytes[4] & 0x70) >> 4; // Device Type 8 (3-bit) + decoded.enable8 = Boolean((bytes[4] & 0x80) >> 7); // Enable 8 (1-bit) + decoded.deviceType9 = bytes[5] & 0x07; // Device Type 9 (3-bit) + decoded.enable9 = Boolean((bytes[5] & 0x08) >> 3); // Enable 9 (1-bit) + decoded.deviceType10 = (bytes[5] & 0x70) >> 4; // Device Type 10 (3-bit) + decoded.enable10 = Boolean((bytes[5] & 0x80) >> 7); // Enable 10 (1-bit) + decoded.deviceType11 = bytes[6] & 0x07; // Device Type 11 (3-bit) + decoded.enable11 = Boolean((bytes[6] & 0x08) >> 3); // Enable 11 (1-bit) + decoded.deviceType12 = (bytes[6] & 0x70) >> 4; // Device Type 12 (3-bit) + decoded.enable12 = Boolean((bytes[6] & 0x80) >> 7); // Enable 12 (1-bit) + decoded.deviceUnits1 = bytes[7] & 0x01; // Device Units 1 (1-bit) + decoded.deviceUnits2 = (bytes[7] & 0x02) >> 1; // Device Units 2 (1-bit) + decoded.deviceUnits3 = (bytes[7] & 0x04) >> 2; // Device Units 3 (1-bit) + decoded.deviceUnits4 = (bytes[7] & 0x08) >> 3; // Device Units 4 (1-bit) + decoded.deviceUnits5 = (bytes[7] & 0x10) >> 4; // Device Units 5 (1-bit) + decoded.deviceUnits6 = (bytes[7] & 0x20) >> 5; // Device Units 6 (1-bit) + decoded.deviceUnits7 = (bytes[7] & 0x40) >> 6; // Device Units 7 (1-bit) + decoded.deviceUnits8 = (bytes[7] & 0x80) >> 7; // Device Units 8 (1-bit) + decoded.deviceUnits9 = bytes[8] & 0x01; // Device Units 9 (1-bit) + decoded.deviceUnits10 = (bytes[8] & 0x02) >> 1; // Device Units 10 (1-bit) + decoded.deviceUnits11 = (bytes[8] & 0x04) >> 2; // Device Units 11 (1-bit) + decoded.deviceUnits12 = (bytes[8] & 0x08) >> 3; // Device Units 12 (1-bit) + decoded.wkupSensorTransmitEnable = Boolean((bytes[8] & 0x10) >> 4); // WKUP Sensor Transmit Enable (1-bit) + decoded.batterySensorTransmitEnable = Boolean((bytes[8] & 0x20) >> 5); // Battery Sensor Transmit Enable (1-bit) + decoded.coldJointSensorTransmitEnable = Boolean((bytes[8] & 0x40) >> 6); // Cold Joint Sensor Transmit Enable (1-bit) + decoded.reserved1 = (bytes[8] & 0x80) >> 7; // Reserved 1 Field (1-bit) + } + else if (decoded.sequenceNumber == 1) { + decoded.deviceType1 = bytes[1] & 0x07; // Device Type 1 (3-bit) + decoded.enable1 = Boolean((bytes[1] & 0x08) >> 3); // Enable 1 (1-bit) + decoded.deviceType2 = (bytes[1] & 0x70) >> 4; // Device Type 2 (3-bit) + decoded.enable2 = Boolean((bytes[1] & 0x80) >> 7); // Enable 2 (1-bit) + decoded.deviceType3 = bytes[2] & 0x07; // Device Type 3 (3-bit) + decoded.enable3 = Boolean((bytes[2] & 0x08) >> 3); // Enable 3 (1-bit) + decoded.deviceType4 = (bytes[2] & 0x70) >> 4; // Device Type 4 (3-bit) + decoded.enable4 = Boolean((bytes[2] & 0x80) >> 7); // Enable 4 (1-bit) + decoded.deviceType5 = bytes[3] & 0x07; // Device Type 5 (3-bit) + decoded.enable5 = Boolean((bytes[3] & 0x08) >> 3); // Enable 5 (1-bit) + decoded.deviceType6 = (bytes[3] & 0x70) >> 4; // Device Type 6 (3-bit) + decoded.enable6 = Boolean((bytes[3] & 0x80) >> 7); // Enable 6 (1-bit) + decoded.deviceType7 = bytes[4] & 0x07; // Device Type 7 (3-bit) + decoded.enable7 = Boolean((bytes[4] & 0x08) >> 3); // Enable 7 (1-bit) + decoded.deviceType8 = (bytes[4] & 0x70) >> 4; // Device Type 8 (3-bit) + decoded.enable8 = Boolean((bytes[4] & 0x80) >> 7); // Enable 8 (1-bit) + decoded.deviceType9 = bytes[5] & 0x07; // Device Type 9 (3-bit) + decoded.enable9 = Boolean((bytes[5] & 0x08) >> 3); // Enable 9 (1-bit) + decoded.deviceType10 = (bytes[5] & 0x70) >> 4; // Device Type 10 (3-bit) + decoded.enable10 = Boolean((bytes[5] & 0x80) >> 7); // Enable 10 (1-bit) + decoded.deviceType11 = bytes[6] & 0x07; // Device Type 11 (3-bit) + decoded.enable11 = Boolean((bytes[6] & 0x08) >> 3); // Enable 11 (1-bit) + decoded.deviceType12 = (bytes[6] & 0x70) >> 4; // Device Type 12 (3-bit) + decoded.enable12 = Boolean((bytes[6] & 0x80) >> 7); // Enable 12 (1-bit) + decoded.deviceUnits1 = bytes[7] & 0x03; // Device Units 1 (2-bit) + decoded.deviceUnits2 = (bytes[7] & 0x0C) >> 2; // Device Units 2 (2-bit) + decoded.deviceUnits3 = (bytes[7] & 0x30) >> 4; // Device Units 3 (2-bit) + decoded.deviceUnits4 = (bytes[7] & 0xC0) >> 6; // Device Units 4 (2-bit) + decoded.deviceUnits5 = bytes[8] & 0x03; // Device Units 5 (2-bit) + decoded.deviceUnits6 = (bytes[8] & 0x0C) >> 2; // Device Units 6 (2-bit) + decoded.deviceUnits7 = (bytes[8] & 0x30) >> 4; // Device Units 7 (2-bit) + decoded.deviceUnits8 = (bytes[8] & 0xC0) >> 6; // Device Units 8 (2-bit) + decoded.deviceUnits9 = bytes[9] & 0x03; // Device Units 9 (2-bit) + decoded.deviceUnits10 = (bytes[9] & 0x0C) >> 2; // Device Units 10 (2-bit) + decoded.deviceUnits11 = (bytes[9] & 0x30) >> 4; // Device Units 11 (2-bit) + decoded.deviceUnits12 = (bytes[9] & 0xC0) >> 6; // Device Units 12 (2-bit) + decoded.coldJointSensorUnits = bytes[10] & 0x03; // Cold Joint Sensor Units (2-bit) + decoded.wkupSensorTransmitEnable = Boolean((bytes[10] & 0x04) >> 2); // WKUP Sensor Transmit Enable (1-bit) + decoded.batterySensorTransmitEnable = Boolean((bytes[10] & 0x08) >> 3); // Battery Sensor Transmit Enable (1-bit) + decoded.coldJointSensorTransmitEnable = Boolean((bytes[10] & 0x10) >> 4); // Cold Joint Sensor Transmit Enable (1-bit) + decoded.tcSensorsTransmitEnable = Boolean((bytes[10] & 0x20) >> 5);; // TC Sensors Transmit Enable (1-bit) + decoded.ainPayloadType = (bytes[10] & 0x40) >> 6; // Ain Payload Type (1-bit) + decoded.reserved1 = (bytes[10] & 0x80) >> 7; // Reserved 1 Field (1-bit) + } + + return decoded; +} + +function parseVoboTCCalibrationConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.gain1 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 1 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset1 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 1 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain2 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 2 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset2 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 2 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.gain3 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 3 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset3 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 3 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain4 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 4 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset4 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 4 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 2) { + decoded.gain5 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 5 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset5 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 5 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain6 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 6 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset6 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 6 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 3) { + decoded.gain7 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 7 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset7 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 7 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain8 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 8 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset8 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 8 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 4) { + decoded.gain9 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 9 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset9 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 9 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain10 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 10 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset10 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3));// Offset 10 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 5) { + decoded.gain11 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 11 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset11 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3));// Offset 11 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain12 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 12 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset12 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3));// Offset 12 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + + return decoded; +} + +function parseConfigurationPayload(bytes, fport) { + var decoded = {}; + var subgroupID = bytes[0] & 0x0F; + + // VoBo Lib Configuration Payloads + if (subgroupID == 0) decoded = parseVoboLibGeneralConfigurationPayload(bytes); + else if (subgroupID == 1) decoded = parseVoboLibVoboSyncConfigurationPayload(bytes); + // VoBo XX Configuration Payloads + else if (fport == 70 && subgroupID == 4) decoded = parseVoboXXGeneralConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 5) decoded = parseVoboXXModbusGeneralConfigurationPayload(bytes); + else if (fport == 70 && (subgroupID == 6 || subgroupID == 7)) decoded = parseVoboXXModbusGroupsEnableConfigurationPayload(bytes); + else if (fport == 70 && (subgroupID == 8 || subgroupID == 9 || subgroupID == 10)) decoded = parseVoboXXModbusGroupsConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 11) decoded = parseVoboXXModbusPayloadsSlotsConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 12) decoded = parseVoboXXEngineeringUnitsConfigurationPayload(bytes); + // VoBo TC Configuration Payloads + else if (fport == 71 && subgroupID == 4) decoded = parseVoboTCGeneralConfigurationPayload(bytes); + else if (fport == 71 && subgroupID == 5) decoded = parseVoboTCCalibrationConfigurationPayload(bytes); + + return decoded; +} + +function parseModbusGenericPayload(bytes, fport) { + var decoded = {}; + var payloadSize = bytes.length; + var byteIdx = 0; + while (byteIdx < (payloadSize - 1)) { + var groupIdx = bytes[byteIdx] & 0x3F; + if (groupIdx > 0) { + var blockType = (bytes[byteIdx] >> 6) & 0x03; + byteIdx++; + + if (blockType == 0x00) // One register block + { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register1"] = registerValue; + byteIdx += 2; + } + else if (blockType == 0x01) // Two registers block + { + var register1Value = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register1"] = register1Value; + var register2Value = (bytes[byteIdx + 3] << 8) | (bytes[byteIdx + 2]); + decoded["group" + groupIdx + "register2"] = register2Value; + byteIdx += (2 * 2); + } + else if (blockType == 0x02) // Non fragmented block + { + var registersValues = []; + var numOfRegisters = bytes[byteIdx] & 0x7F; + byteIdx++; + for (let regIdx = 0; regIdx < numOfRegisters; regIdx++) { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register" + (regIdx + 1)] = registerValue; + registersValues.push(registerValue); + byteIdx += 2; + } + } + else if (blockType == 0x03) // Fragmented block + { + var registersValues = []; + var numOfRegisters = bytes[byteIdx] & 0x7F; + byteIdx++; + var registerOffset = bytes[byteIdx] & 0x7F; + byteIdx++; + + var numOfRegToRead = numOfRegisters - registerOffset; + var freeRegToRead = Math.floor((payloadSize - byteIdx) / 2); + if (numOfRegToRead > freeRegToRead) numOfRegToRead = freeRegToRead; + for (let regIdx = 0; regIdx < numOfRegToRead; regIdx++) { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register" + (registerOffset + regIdx + 1)] = registerValue; + registersValues.push(registerValue); + byteIdx += 2; + } + } + } + else break; + } + return decoded; +} + +function parseAnalogInputVariableLengthPayload(bytes, fport) { + var decoded = {}; + var decodedAinPayloads = []; + const AIN_PAYLOAD_SIZE = 6; + + var payloadLength = bytes.length + var byteIdx = 0; + while (byteIdx < payloadLength) { + var ainPayload = bytes.slice(byteIdx, byteIdx + AIN_PAYLOAD_SIZE) + var decodedAinPayload = parseOneAnalogSensorPayload(ainPayload) + decodedAinPayloads.push(decodedAinPayload) + byteIdx += AIN_PAYLOAD_SIZE; + } + + decoded.ainPayloads = decodedAinPayloads; + decoded.numOfAinPayloads = decodedAinPayloads.length + + return decoded; +} + +function parseModbusStandardVariableLengthPayload(bytes, fport) { + var decoded = {}; + var decodedModbusSlots = {}; + + var payloadLength = bytes.length + var firstSlotNum = bytes[payloadLength - 1] + const REGISTER_SIZE = 2 + var byteIdx = 0; + while (byteIdx < payloadLength - 1) { + var registerIdx = byteIdx / 2; + var slotIdx = firstSlotNum + registerIdx; + decodedModbusSlots["Modbus" + slotIdx] = (bytes[byteIdx + 1] << 8) | bytes[byteIdx]; + byteIdx += REGISTER_SIZE; + } + + decoded.modbusSlots = decodedModbusSlots; + decoded.firstSlotNum = firstSlotNum; + decoded.numModbusSlots = (payloadLength - 1) / 2; + + return decoded; +} + +function lookupAnalogSensorName(voboType, sensorNum) { + const analogSensorsTableXX = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "Battery Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "AIN1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "AIN2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "AIN3" }, + { "Analog Sensor Number": "15", "Analog Sensor Name": "ADC Temperature" } + ]; + + const analogSensorsTableTC = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "Battery Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "TC1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "TC2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "TC3" }, + { "Analog Sensor Number": "4", "Analog Sensor Name": "TC4" }, + { "Analog Sensor Number": "5", "Analog Sensor Name": "TC5" }, + { "Analog Sensor Number": "6", "Analog Sensor Name": "TC6" }, + { "Analog Sensor Number": "7", "Analog Sensor Name": "TC7" }, + { "Analog Sensor Number": "8", "Analog Sensor Name": "TC8" }, + { "Analog Sensor Number": "9", "Analog Sensor Name": "TC9" }, + { "Analog Sensor Number": "10", "Analog Sensor Name": "TC10" }, + { "Analog Sensor Number": "11", "Analog Sensor Name": "TC11" }, + { "Analog Sensor Number": "12", "Analog Sensor Name": "TC12" }, + { "Analog Sensor Number": "13", "Analog Sensor Name": "Cold Joint Temperature" } + ]; + + const analogSensorsTableXP = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "3.3V Supply Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "AIN1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "AIN2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "AIN3" }, + { "Analog Sensor Number": "4", "Analog Sensor Name": "DIN1" }, + { "Analog Sensor Number": "5", "Analog Sensor Name": "DIN1 Pulse Count" }, + { "Analog Sensor Number": "6", "Analog Sensor Name": "DIN2" }, + { "Analog Sensor Number": "7", "Analog Sensor Name": "DIN2 Pulse Count" }, + { "Analog Sensor Number": "8", "Analog Sensor Name": "RLY1 Voltage" }, + { "Analog Sensor Number": "9", "Analog Sensor Name": "RLY2 Voltage" }, + { "Analog Sensor Number": "10", "Analog Sensor Name": "RLY3 Voltage" }, + { "Analog Sensor Number": "11", "Analog Sensor Name": "RLY4 Voltage" }, + { "Analog Sensor Number": "12", "Analog Sensor Name": "WKUP" }, + { "Analog Sensor Number": "13", "Analog Sensor Name": "VPP Voltage" }, + { "Analog Sensor Number": "14", "Analog Sensor Name": "VIN Voltage" }, + { "Analog Sensor Number": "15", "Analog Sensor Name": "ADC Temperature" }, + { "Analog Sensor Number": "16", "Analog Sensor Name": "Cont Meas Period" }, + { "Analog Sensor Number": "17", "Analog Sensor Name": "Cont Meas Count" } + ]; + + var analogSensorsTable = []; + var analogSensorNameSuffix = ""; + if (voboType == "VoBoXX") { + analogSensorsTable = analogSensorsTableXX; + } + else if (voboType == "VoBoTC") { + analogSensorsTable = analogSensorsTableTC; + } + else if (voboType == "VoBoXP") { + analogSensorsTable = analogSensorsTableXP; + var sensorNumEncoding = (sensorNum & 0xC0) >> 6; + if (sensorNumEncoding == 0x01) analogSensorNameSuffix = "(Max)"; + else if (sensorNumEncoding == 0x02) analogSensorNameSuffix = "(Min)"; + else if (sensorNumEncoding == 0x03) analogSensorNameSuffix = "(Avg)"; + sensorNum = sensorNum & 0x3F; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\", \"VoBoTC\" or \"VoBoXP\""; + throw new Error(errorMsg); + } + + var analogSensorRow = analogSensorsTable.find(element => element["Analog Sensor Number"] == sensorNum.toString()); + if (typeof analogSensorRow == 'undefined') { + var genericAnalogSensorName = "AnalogSensor" + sensorNum.toString(); + return genericAnalogSensorName; + } + + var analogSensorName = analogSensorRow["Analog Sensor Name"]; + if (voboType == "VoBoXP") { + analogSensorName = analogSensorName + " " + analogSensorNameSuffix; + } + return analogSensorName; +} + +function lookupDigitalSensorName(voboType, sensorNum) { + const digitalSensorsTableXX = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" }, + { "Digital Sensor Number": "1", "Digital Sensor Name": "DIN1" }, + { "Digital Sensor Number": "2", "Digital Sensor Name": "DIN2" }, + { "Digital Sensor Number": "3", "Digital Sensor Name": "DIN3" } + ]; + + const digitalSensorsTableTC = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" } + ]; + + const digitalSensorsTableXP = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" }, + { "Digital Sensor Number": "1", "Digital Sensor Name": "DIN1" }, + { "Digital Sensor Number": "2", "Digital Sensor Name": "DIN2" } + ]; + + var digitalSensorsTable = []; + if (voboType == "VoBoXX") { + digitalSensorsTable = digitalSensorsTableXX; + } + else if (voboType == "VoBoTC") { + digitalSensorsTable = digitalSensorsTableTC; + } + else if (voboType == "VoBoXP") { + digitalSensorsTable = digitalSensorsTableXP; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\", \"VoBoTC\" or \"VoBoXP\""; + throw new Error(errorMsg); + } + + var digitalSensorRow = digitalSensorsTable.find(element => element["Digital Sensor Number"] == sensorNum.toString()); + if (typeof digitalSensorRow == 'undefined') { + var genericDigitalSensorName = "DigitalSensor" + sensorNum.toString(); + return genericDigitalSensorName; + } + + var digitalSensorName = digitalSensorRow["Digital Sensor Name"]; + return digitalSensorName; +} + +function lookupUnits(outputType, unitCode) { + const engUnitsTable = + [ + { "Units Code": "1", "Description": "inches of water at 20 degC (68 degF)", "Abbreviated Units": "inH2O (20 degC or 68 degF)" }, + { "Units Code": "2", "Description": "inches of mercury at 0 degC (32 degF)", "Abbreviated Units": "inHg (20 degC or 68 degF)" }, + { "Units Code": "3", "Description": "feet of water at 20 degC (68 degF)", "Abbreviated Units": "ftH2O (20 degC or 68 degF)" }, + { "Units Code": "4", "Description": "millimeters of water at 20 degC (68 degF)", "Abbreviated Units": "mmH2O (20 degC or 68 degF)" }, + { "Units Code": "5", "Description": "millimeters of mercury at 0 degC (32 degF)", "Abbreviated Units": "mmHg (0 degC or 32 degF)" }, + { "Units Code": "6", "Description": "pounds per square inch", "Abbreviated Units": "psi" }, + { "Units Code": "7", "Description": "bars", "Abbreviated Units": "bar" }, + { "Units Code": "8", "Description": "millibars", "Abbreviated Units": "mbar" }, + { "Units Code": "9", "Description": "grams per square centimeter", "Abbreviated Units": "g/cm^2" }, + { "Units Code": "10", "Description": "kilograms per square centimeter", "Abbreviated Units": "kg/cm^2" }, + { "Units Code": "11", "Description": "pascals", "Abbreviated Units": "Pa" }, + { "Units Code": "12", "Description": "kilopascals", "Abbreviated Units": "kPa" }, + { "Units Code": "13", "Description": "torr", "Abbreviated Units": "torr" }, + { "Units Code": "14", "Description": "atmospheres", "Abbreviated Units": "atm" }, + { "Units Code": "15", "Description": "cubic feet per minute", "Abbreviated Units": "ft^3/min" }, + { "Units Code": "16", "Description": "gallons per minute", "Abbreviated Units": "usg/min" }, + { "Units Code": "17", "Description": "liters per minute", "Abbreviated Units": "L/min" }, + { "Units Code": "18", "Description": "imperial gallons per minute", "Abbreviated Units": "impgal/min" }, + { "Units Code": "19", "Description": "cubic meter per hour", "Abbreviated Units": "m^3/h" }, + { "Units Code": "20", "Description": "feet per second", "Abbreviated Units": "ft/s" }, + { "Units Code": "21", "Description": "meters per seond", "Abbreviated Units": "m/s" }, + { "Units Code": "22", "Description": "gallons per second", "Abbreviated Units": "usg/s" }, + { "Units Code": "23", "Description": "million gallons per day", "Abbreviated Units": "Musg/d" }, + { "Units Code": "24", "Description": "liters per second", "Abbreviated Units": "L/s" }, + { "Units Code": "25", "Description": "million liters per day", "Abbreviated Units": "ML/d" }, + { "Units Code": "26", "Description": "cubic feet per second", "Abbreviated Units": "ft^3/s" }, + { "Units Code": "27", "Description": "cubic feet per day", "Abbreviated Units": "ft^3/d" }, + { "Units Code": "28", "Description": "cubic meters per second", "Abbreviated Units": "m^3/s" }, + { "Units Code": "29", "Description": "cubic meters per day", "Abbreviated Units": "m^3/d" }, + { "Units Code": "30", "Description": "imperial gallons per hour", "Abbreviated Units": "impgal/h" }, + { "Units Code": "31", "Description": "imperial gallons per day", "Abbreviated Units": "impgal/d" }, + { "Units Code": "32", "Description": "Degrees Celsius", "Abbreviated Units": "C" }, + { "Units Code": "33", "Description": "Degrees Fahrenheit", "Abbreviated Units": "F" }, + { "Units Code": "34", "Description": "Degrees Rankine", "Abbreviated Units": "R" }, + { "Units Code": "35", "Description": "Kelvin", "Abbreviated Units": "K" }, + { "Units Code": "36", "Description": "millivolts", "Abbreviated Units": "mV" }, + { "Units Code": "37", "Description": "ohms", "Abbreviated Units": "ohm" }, + { "Units Code": "38", "Description": "hertz", "Abbreviated Units": "hz" }, + { "Units Code": "39", "Description": "milliamperes", "Abbreviated Units": "mA" }, + { "Units Code": "40", "Description": "gallons", "Abbreviated Units": "usg" }, + { "Units Code": "41", "Description": "liters", "Abbreviated Units": "L" }, + { "Units Code": "42", "Description": "imperial gallons", "Abbreviated Units": "impgal" }, + { "Units Code": "43", "Description": "cubic meters", "Abbreviated Units": "m^3" }, + { "Units Code": "44", "Description": "feet", "Abbreviated Units": "ft" }, + { "Units Code": "45", "Description": "meters", "Abbreviated Units": "m" }, + { "Units Code": "46", "Description": "barrels", "Abbreviated Units": "bbl" }, + { "Units Code": "47", "Description": "inches", "Abbreviated Units": "in" }, + { "Units Code": "48", "Description": "centimeters", "Abbreviated Units": "cm" }, + { "Units Code": "49", "Description": "millimeters", "Abbreviated Units": "mm" }, + { "Units Code": "50", "Description": "minutes", "Abbreviated Units": "min" }, + { "Units Code": "51", "Description": "seconds", "Abbreviated Units": "s" }, + { "Units Code": "52", "Description": "hours", "Abbreviated Units": "h" }, + { "Units Code": "53", "Description": "days", "Abbreviated Units": "d" }, + { "Units Code": "54", "Description": "centistokes", "Abbreviated Units": "centistokes" }, + { "Units Code": "55", "Description": "centipoise", "Abbreviated Units": "cP" }, + { "Units Code": "56", "Description": "microsiemens", "Abbreviated Units": "microsiemens" }, + { "Units Code": "57", "Description": "percent", "Abbreviated Units": "%" }, + { "Units Code": "58", "Description": "volts", "Abbreviated Units": "V" }, + { "Units Code": "59", "Description": "pH", "Abbreviated Units": "pH" }, + { "Units Code": "60", "Description": "grams", "Abbreviated Units": "g" }, + { "Units Code": "61", "Description": "kilograms", "Abbreviated Units": "kg" }, + { "Units Code": "62", "Description": "metric tons", "Abbreviated Units": "t" }, + { "Units Code": "63", "Description": "pounds", "Abbreviated Units": "lb" }, + { "Units Code": "64", "Description": "short tons", "Abbreviated Units": "short ton" }, + { "Units Code": "65", "Description": "long tons", "Abbreviated Units": "long ton" }, + { "Units Code": "66", "Description": "millisiemens per centimeter", "Abbreviated Units": "millisiemens/cm" }, + { "Units Code": "67", "Description": "microsiemens per centimeter", "Abbreviated Units": "microsiemens/cm" }, + { "Units Code": "68", "Description": "newton", "Abbreviated Units": "N" }, + { "Units Code": "69", "Description": "newton meter", "Abbreviated Units": "N m" }, + { "Units Code": "70", "Description": "grams per second", "Abbreviated Units": "g/s" }, + { "Units Code": "71", "Description": "grams per minute", "Abbreviated Units": "g/min" }, + { "Units Code": "72", "Description": "grams per hour", "Abbreviated Units": "g/h" }, + { "Units Code": "73", "Description": "kilograms per second", "Abbreviated Units": "kg/s" }, + { "Units Code": "74", "Description": "kilograms per minute", "Abbreviated Units": "kg/min" }, + { "Units Code": "75", "Description": "kilograms per hour", "Abbreviated Units": "kg/h" }, + { "Units Code": "76", "Description": "kilograms per day", "Abbreviated Units": "kg/d" }, + { "Units Code": "77", "Description": "metric tons per minute", "Abbreviated Units": "t/min" }, + { "Units Code": "78", "Description": "metric tons per hour", "Abbreviated Units": "t/h" }, + { "Units Code": "79", "Description": "metric tons per day", "Abbreviated Units": "t/d" }, + { "Units Code": "80", "Description": "pounds per second", "Abbreviated Units": "lb/s" }, + { "Units Code": "81", "Description": "pounds per minute", "Abbreviated Units": "lb/min" }, + { "Units Code": "82", "Description": "pounds per hour", "Abbreviated Units": "lb/h" }, + { "Units Code": "83", "Description": "pounds per day", "Abbreviated Units": "lb/d" }, + { "Units Code": "84", "Description": "short tons per minute", "Abbreviated Units": "short ton/min" }, + { "Units Code": "85", "Description": "short tons per hour", "Abbreviated Units": "short ton/h" }, + { "Units Code": "86", "Description": "short tons per day", "Abbreviated Units": "short ton/d" }, + { "Units Code": "87", "Description": "long tons per hour", "Abbreviated Units": "long ton/h" }, + { "Units Code": "88", "Description": "long tons per day", "Abbreviated Units": "long ton/d" }, + { "Units Code": "89", "Description": "deka therm", "Abbreviated Units": "Dth" }, + { "Units Code": "90", "Description": "specific gravity units", "Abbreviated Units": "specific gravity units" }, + { "Units Code": "91", "Description": "grams per cubic centimeter", "Abbreviated Units": "g/cm^3" }, + { "Units Code": "92", "Description": "kilograms per cubic meter", "Abbreviated Units": "kg/m^3" }, + { "Units Code": "93", "Description": "pounds per gallon", "Abbreviated Units": "lb/usg" }, + { "Units Code": "94", "Description": "pounds per cubic feet", "Abbreviated Units": "lb/ft^3" }, + { "Units Code": "95", "Description": "grams per milliliter", "Abbreviated Units": "g/mL" }, + { "Units Code": "96", "Description": "kilograms per liter", "Abbreviated Units": "kg/L" }, + { "Units Code": "97", "Description": "grams per liter", "Abbreviated Units": "g/L" }, + { "Units Code": "98", "Description": "pounds per cubic inch", "Abbreviated Units": "lb/in^3" }, + { "Units Code": "99", "Description": "short tons per cubic yard", "Abbreviated Units": "short ton/yd^3" }, + { "Units Code": "100", "Description": "degrees twaddell", "Abbreviated Units": "degTw" }, + { "Units Code": "101", "Description": "degrees brix", "Abbreviated Units": "degBx" }, + { "Units Code": "102", "Description": "degrees baume heavy", "Abbreviated Units": "BH" }, + { "Units Code": "103", "Description": "degrees baume light", "Abbreviated Units": "BL" }, + { "Units Code": "104", "Description": "degrees API", "Abbreviated Units": "degAPI" }, + { "Units Code": "105", "Description": "percent solids per weight", "Abbreviated Units": "% solid/weight" }, + { "Units Code": "106", "Description": "percent solids per volume", "Abbreviated Units": "% solid/volume" }, + { "Units Code": "107", "Description": "degrees balling", "Abbreviated Units": "degrees balling" }, + { "Units Code": "108", "Description": "proof per volume", "Abbreviated Units": "proof/volume" }, + { "Units Code": "109", "Description": "proof per mass", "Abbreviated Units": "proof/mass" }, + { "Units Code": "110", "Description": "bushels", "Abbreviated Units": "bushel" }, + { "Units Code": "111", "Description": "cubic yards", "Abbreviated Units": "yd^3" }, + { "Units Code": "112", "Description": "cubic feet", "Abbreviated Units": "ft^3" }, + { "Units Code": "113", "Description": "cubic inches", "Abbreviated Units": "in^3" }, + { "Units Code": "114", "Description": "inches per second", "Abbreviated Units": "in/s" }, + { "Units Code": "115", "Description": "inches per minute", "Abbreviated Units": "in/min" }, + { "Units Code": "116", "Description": "feet per minute", "Abbreviated Units": "ft/min" }, + { "Units Code": "117", "Description": "degrees per second", "Abbreviated Units": "deg/s" }, + { "Units Code": "118", "Description": "revolutions per second", "Abbreviated Units": "rev/s" }, + { "Units Code": "119", "Description": "revolutions per minute", "Abbreviated Units": "rpm" }, + { "Units Code": "120", "Description": "meters per hour", "Abbreviated Units": "m/hr" }, + { "Units Code": "121", "Description": "normal cubic meters per hour", "Abbreviated Units": "normal m^3/h" }, + { "Units Code": "122", "Description": "normal liter per hour", "Abbreviated Units": "normal L/h" }, + { "Units Code": "123", "Description": "standard cubic feet per minute", "Abbreviated Units": "standard ft^3/min" }, + { "Units Code": "124", "Description": "bbl liq", "Abbreviated Units": "bbl liq" }, + { "Units Code": "125", "Description": "ounce", "Abbreviated Units": "oz" }, + { "Units Code": "126", "Description": "foot pound force", "Abbreviated Units": "ft lb force" }, + { "Units Code": "127", "Description": "kilowatt", "Abbreviated Units": "kW" }, + { "Units Code": "128", "Description": "kilowatt hour", "Abbreviated Units": "KWh" }, + { "Units Code": "129", "Description": "horsepower", "Abbreviated Units": "hp" }, + { "Units Code": "130", "Description": "cubic feet per hour", "Abbreviated Units": "ft^3/h" }, + { "Units Code": "131", "Description": "cubic meters per minute", "Abbreviated Units": "m^3/min" }, + { "Units Code": "132", "Description": "barrels per second", "Abbreviated Units": "bbl/s" }, + { "Units Code": "133", "Description": "barrels per minute", "Abbreviated Units": "bbl/min" }, + { "Units Code": "134", "Description": "barrels per hour", "Abbreviated Units": "bbl/h" }, + { "Units Code": "135", "Description": "barrels per day", "Abbreviated Units": "bbl/d" }, + { "Units Code": "136", "Description": "gallons per hour", "Abbreviated Units": "usg/h" }, + { "Units Code": "137", "Description": "imperial gallons per second", "Abbreviated Units": "impgal/s" }, + { "Units Code": "138", "Description": "liters per hour", "Abbreviated Units": "L/h" }, + { "Units Code": "139", "Description": "parts per million", "Abbreviated Units": "ppm" }, + { "Units Code": "140", "Description": "megacalorie per hour", "Abbreviated Units": "Mcal/h" }, + { "Units Code": "141", "Description": "megajoule per hour", "Abbreviated Units": "MJ/h" }, + { "Units Code": "142", "Description": "British thermal unit per hour", "Abbreviated Units": "BTU/h" }, + { "Units Code": "143", "Description": "degrees", "Abbreviated Units": "degrees" }, + { "Units Code": "144", "Description": "radian", "Abbreviated Units": "rad" }, + { "Units Code": "145", "Description": "inches of water at 15.6 degC (60 degF)", "Abbreviated Units": "inH20 (15.6 degC or 60 degF)" }, + { "Units Code": "146", "Description": "micrograms per liter", "Abbreviated Units": "micrograms/L" }, + { "Units Code": "147", "Description": "micrograms per cubic meter", "Abbreviated Units": "micrograms/m^3" }, + { "Units Code": "148", "Description": "percent consitency", "Abbreviated Units": "% consistency" }, + { "Units Code": "149", "Description": "volume percent", "Abbreviated Units": "volume %" }, + { "Units Code": "150", "Description": "percent steam quality", "Abbreviated Units": "% steam quality" }, + { "Units Code": "151", "Description": "feet in sixteenths", "Abbreviated Units": "ft in sixteenths" }, + { "Units Code": "152", "Description": "cubic feet per pound", "Abbreviated Units": "ft^3/lb" }, + { "Units Code": "153", "Description": "picofarads", "Abbreviated Units": "pF" }, + { "Units Code": "154", "Description": "milliliters per liter", "Abbreviated Units": "mL/L" }, + { "Units Code": "155", "Description": "microliters per liter", "Abbreviated Units": "microliters/L" }, + { "Units Code": "156", "Description": "percent plato", "Abbreviated Units": "% plato" }, + { "Units Code": "157", "Description": "percent lower explosion level", "Abbreviated Units": "% lower explosion level" }, + { "Units Code": "158", "Description": "mega calorie", "Abbreviated Units": "Mcal" }, + { "Units Code": "159", "Description": "kiloohms", "Abbreviated Units": "kohm" }, + { "Units Code": "160", "Description": "megajoule", "Abbreviated Units": "MJ" }, + { "Units Code": "161", "Description": "British thermal unit", "Abbreviated Units": "BTU" }, + { "Units Code": "162", "Description": "normal cubic meter", "Abbreviated Units": "normal m^3" }, + { "Units Code": "163", "Description": "normal liter", "Abbreviated Units": "normal L" }, + { "Units Code": "164", "Description": "standard cubic feet", "Abbreviated Units": "normal ft^3" }, + { "Units Code": "165", "Description": "parts per billion", "Abbreviated Units": "parts/billion" }, + { "Units Code": "166", "Description": "ampere", "Abbreviated Units": "A" }, + { "Units Code": "167", "Description": "millimeters per meter", "Abbreviated Units": "mm/m" }, + { "Units Code": "168", "Description": "seconds since epoch", "Abbreviated Units": "epoch sec" }, + { "Units Code": "169", "Description": "no units", "Abbreviated Units": "no units" }, + { "Units Code": "170", "Description": "percentage", "Abbreviated Units": "%" }, + { "Units Code": "171", "Description": "kilohertz", "Abbreviated Units": "khz" }, + { "Units Code": "235", "Description": "gallons per day", "Abbreviated Units": "usg/d" }, + { "Units Code": "236", "Description": "hectoliters", "Abbreviated Units": "hL" }, + { "Units Code": "237", "Description": "megapascals", "Abbreviated Units": "MPa" }, + { "Units Code": "238", "Description": "inches of water at 4 degC (39.2 degF)", "Abbreviated Units": "inH2O (4 degC or 39.2 degF)" }, + { "Units Code": "239", "Description": "millimeters of water at 4 degC (39.2 degF)", "Abbreviated Units": "mmH2O (4 degC or 39.2 degF)" }, + { "Units Code": "253", "Description": "edge count or pulse count", "Abbreviated Units": "counts" }, + { "Units Code": "254", "Description": "raw ADC code", "Abbreviated Units": "ADC code" }, + { "Units Code": "255", "Description": "data not valid", "Abbreviated Units": "data not valid" } + ]; + + var outputString = ""; + var engUnitsRow = engUnitsTable.find(element => element["Units Code"] == unitCode.toString()); + + if (typeof engUnitsRow == 'undefined') { + return "Unknown Units"; + } + + if (outputType == 0) { + outputString = engUnitsRow["Description"]; + } + else if (outputType == 1) { + outputString = engUnitsRow["Abbreviated Units"]; + } + else { + errorMsg = outputType + " -- " + "Invalid Output Type. Use 0 for Description or 1 for Abbreviated Units."; + throw new Error(errorMsg); + } + + return outputString; +} + +//=========================================================== +// Utility functions +// Decoder doesn't call these functions +// Functions provided for customer usage +// +// List of functions: +// hexToFloat - Conversion from Hex to Float Number +// modbusToFloat_AB_CD_to_ABCD - Takes two Modbus registers, does no swapping, and converts them to 32 bit floating point number +// modbusToFloat_AB_CD_to_DCBA - Takes two Modbus registers, does byte and word swapping, and converts them to 32 bit floating point number +// modbusToFloat_AB_CD_to_BADC - Takes two Modbus registers, does byte swapping, and converts them to 32bit floating point number +// modbusToFloat_AB_CD_to_CDAB - Takes two Modbus registers, does word swapping, and converts them to 32bit floating point number +// printPayload - Prints Decoded payloads in formatted fashion +//=========================================================== + +/* istanbul ignore next */ +function hexToFloat(hex) { + var s = hex >> 31 ? -1 : 1; + var e = (hex >> 23) & 0xFF; + return s * (hex & 0x7fffff | 0x800000) * 1.0 / Math.pow(2, 23) * Math.pow(2, (e - 127)) +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_ABCD(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = (modbusReg1 & 0xFF00) >> 8; + bytes[1] = modbusReg1 & 0xFF; + bytes[2] = (modbusReg0 & 0xFF00) >> 8; + bytes[3] = modbusReg0 & 0xFF; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_DCBA(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = modbusReg0 & 0xFF; + bytes[1] = (modbusReg0 & 0xFF00) >> 8; + bytes[2] = modbusReg1 & 0xFF; + bytes[3] = (modbusReg1 & 0xFF00) >> 8; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_BADC(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = modbusReg1 & 0xFF; + bytes[1] = (modbusReg1 & 0xFF00) >> 8; + bytes[2] = modbusReg0 & 0xFF; + bytes[3] = (modbusReg0 & 0xFF00) >> 8; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_CDAB(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = (modbusReg0 & 0xFF00) >> 8; + bytes[1] = modbusReg0 & 0xFF; + bytes[2] = (modbusReg1 & 0xFF00) >> 8; + bytes[3] = modbusReg1 & 0xFF; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function printPayload(payload) { + if (payload.data.payloadType == "Standard") { + let modbusSlot0HexString = payload.data.Modbus0.toString(16).toUpperCase().padStart(4, 0); + console.log("%s | %s | %s | %s | " + + "DIN1 = %d | DIN2 = %d | DIN3 = %d | WKUP = %d | ADC1 = %d | ADC2 = %d | ADC3 = %d | " + + "Battery = %d | Temperature = %f | Modbus0 = 0x%s |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.DIN1, payload.data.DIN2, payload.data.DIN3, payload.data.WKUP, payload.data.ADC1, payload.data.ADC2, payload.data.ADC3, + payload.data.Battery, payload.data.Temperature, modbusSlot0HexString); + return; + } + + if (payload.data.payloadType == "Modbus Standard") { + dataKeys = Object.keys(payload.data); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + if ((dataKeys[idx] != "fport") && (dataKeys[idx] != "voboType") && (dataKeys[idx] != "payloadType")) { + let fieldName = dataKeys[idx]; + let valueHex = "0x" + payload.data[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + valueHex + " | "; + } + } + console.log("%s\n", stringToPrint); + return; + } + + if (payload.data.payloadType == "Heartbeat 1.0") { + let firmwareRevisionHexString = payload.data.fwVersionMajor.toString(10) + "." + payload.data.fwVersionMinor.toString(10) + "." + payload.data.fwVersionPatch.toString(10) + "." + payload.data.fwVersionCustom.toString(10); + console.log("%s | %s | %s | %s | " + + "Battery Level MV = %d | FW Version = %s | Last RSSI = %d | Analog Voltage Config = %d | Analog Power Time Config = %d | Failed Transmissions Before Rejoin Config = %d | " + + "Cycle Through FSB Config = %d | ACK Enable Config = %d | ACK Frequency Config = %d | ACK Request Config = %d | Battery Level Threshold Config = %d |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.batteryLevelMV, firmwareRevisionHexString, payload.data.recSignalLevels, payload.data.analogVoltageConfig, payload.data.analogPowerTimeConfig, payload.data.failedTransmissionBeforeRejoinConfig, + payload.data.cycleThroughFSB, payload.data.ackEnable, payload.data.ackFreq, payload.data.ackReq, payload.data.batteryLevelThreshold); + return; + } + + if (payload.data.payloadType == "Heartbeat 2.0") { + let firmwareRevisionHexString = payload.data.firmwareRevision.toString(16).toUpperCase().padStart(3, 0); + console.log("%s | %s | %s | %s | " + + "Battery Level = %d | Fatal Errors Total = %d | RSSI Avg = %d | Failed Join Attempts Total = %d | Config Update Occurred = %d | Firmware Revision = 0x%s | " + + "Reboots Total = %d | Failed Transmits Total = %d | Error Event Logs Total = %d | Warning Event Logs Total = %d | Info Event Logs Total = %d | Measurement Packets Total = %d |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.batteryLevel, payload.data.fatalErrorsTotal, payload.data.rssiAvg, payload.data.failedJoinAttemptsTotal, payload.data.configUpdateOccurred, firmwareRevisionHexString, + payload.data.rebootsTotal, payload.data.failedTransmitsTotal, payload.data.errorEventLogsTotal, payload.data.warningEventLogsTotal, payload.data.infoEventLogsTotal, payload.data.measurementPacketsTotal); + return; + } + + if (payload.data.payloadType == "One Analog Input") { + let fractionDigitsData0 = 1; + if (payload.data.engUnitsString0 == "mV" || payload.data.engUnitsString0 == "V") fractionDigitsData0 = 3; + if (payload.data.engUnitsString0 == "ADC code") fractionDigitsData0 = 0; + console.log("%s | %s | %s | %s | %s = %f %s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, payload.data.analogSensorString0, parseFloat(payload.data.sensorData0).toFixed(fractionDigitsData0), payload.data.engUnitsString0); + return; + } + + if (payload.data.payloadType == "Two Analog Inputs") { + let fractionDigitsData0 = 1; + if (payload.data.engUnitsString0 == "mV" || payload.data.engUnitsString0 == "V" || payload.data.engUnitsString0 == "mA" || payload.data.engUnitsString0 == "A") fractionDigitsData0 = 3; + if (payload.data.engUnitsString0 == "ADC code") fractionDigitsData0 = 0; + let fractionDigitsData1 = 1; + if (payload.data.engUnitsString1 == "mV" || payload.data.engUnitsString1 == "V" || payload.data.engUnitsString1 == "mA" || payload.data.engUnitsString1 == "A") fractionDigitsData1 = 3; + if (payload.data.engUnitsString1 == "ADC code") fractionDigitsData1 = 0; + console.log("%s | %s | %s | %s | %s = %f %s | %s = %f %s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, payload.data.analogSensorString0, parseFloat(payload.data.sensorData0).toFixed(fractionDigitsData0), payload.data.engUnitsString0, payload.data.analogSensorString1, parseFloat(payload.data.sensorData1).toFixed(fractionDigitsData1), payload.data.engUnitsString1); + return; + } + + if (payload.data.payloadType == "Analog Input Variable Length") { + var stringToPrint = ""; + for (let i = 0; i < payload.data.numOfAinPayloads; i++) { + analogSensorString = payload.data["analogSensorString" + i]; + engUnitsString = payload.data["engUnitsString" + i]; + sensorData = payload.data.ainPayloads[i].sensorData0; + + let fractionDigitsData = 1; + if (engUnitsString == "mV" || engUnitsString == "V" || engUnitsString == "mA" || engUnitsString == "A") fractionDigitsData = 3; + if (engUnitsString == "ADC code") fractionDigitsData = 0; + stringToPrint = stringToPrint + analogSensorString + " = " + parseFloat(sensorData).toFixed(fractionDigitsData) + " " + engUnitsString + " | "; + } + console.log("%s | %s | %s | %s | %s\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, stringToPrint); + return; + } + + if (payload.data.payloadType == "Modbus Standard Variable Length") { + dataKeys = Object.keys(payload.data.modbusSlots); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + let fieldName = dataKeys[idx]; + let valueHex = "0x" + payload.data.modbusSlots[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + valueHex + " | "; + } + console.log("%s\n", stringToPrint); + return; + } + + if (payload.data.payloadType == "Digital Inputs") { + var stringToPrint = "" + for (let i = 0; i < payload.data.digitalSensorData.length; i++) { + stringToPrint = stringToPrint + payload.data.digitalSensorStrings[i] + " = " + payload.data.digitalSensorData[i].toString() + " | "; + } + console.log("%s | %s | %s | %s | %s\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, stringToPrint); + return; + } + + if (payload.data.payloadType == "Event Log") { + let eventTimestampDateTime = new Date(payload.data.eventTimestamp * 1000).toISOString(); + let eventCodeHexString = payload.data.eventCode.toString(16).toUpperCase().padStart(4, 0); + let metadataHexString = Buffer.from(payload.data.metadata).toString("hex").toUpperCase(); + console.log("%s | %s | %s | %s | Timestamp = %s | Code = 0x%s | Metadata = 0x%s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, eventTimestampDateTime, eventCodeHexString, metadataHexString); + return; + } + + if (payload.data.payloadType == "Modbus Generic") { + let dataKeys = Object.keys(payload.data); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + if ((dataKeys[idx] != "fport") && (dataKeys[idx] != "voboType") && (dataKeys[idx] != "payloadType")) { + let fieldName = dataKeys[idx]; + let regValueHexStr = "0x" + payload.data[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + regValueHexStr + " | "; + } + } + console.log("%s\n", stringToPrint); + return; + } + + console.log(JSON.stringify(payload, null, 4)); + console.log(""); +} + +//=========================================================== +// Changes made to decoder for TagoIO below this point. +// TagoIO executes the decoder file (payload.js) rather than +// calling the Decoder function, so code must be added here +// to map the arguments to the Decoder function, and the +// exports had to be commented out. +//=========================================================== + +const bytes_vb = payload.find((x) => x.variable === "bytes"); +const fPort_vb = payload.find((x) => x.variable === "port" || x.variable === "fPort")?.value; + +if (bytes_vb) { + try { + const buffer = Buffer.from(bytes_vb.value, "hex") + const decoded = Decoder(buffer, fPort_vb); + payload = decoded; + } catch (error: any) { + console.error(error); + payload = [{ variable: "parse_error", value: error.message }]; + } +} + + +// module.exports = { +// customDecoder, decodeUplink, Decoder, addVoboMetadata, lookupAnalogSensorName, lookupDigitalSensorName, lookupUnits, +// hexToFloat, modbusToFloat_AB_CD_to_ABCD, modbusToFloat_AB_CD_to_DCBA, modbusToFloat_AB_CD_to_BADC, modbusToFloat_AB_CD_to_CDAB, printPayload +// } \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload.test.ts b/decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload.test.ts new file mode 100644 index 0000000..98a7efe --- /dev/null +++ b/decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload.test.ts @@ -0,0 +1,574 @@ +import { describe, test, expect } from "vitest"; +import { decoderRun } from "../../../../../src/functions/decoder-run"; + +const file_path = "decoders/connector/volley-boast/vobo-hl-1/v1.0.0/payload.js" + +const testPayloads = { + "data": [ + { "fport": 1, "bytes": "273ef1336472e30c000000", "name": "Standard Payload" }, + { "fport": 2, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 3, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 4, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 5, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 6, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 7, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 8, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 9, "bytes": "0122022203220422052200", "name": "Modbus Standard Payload" }, + { "fport": 20, "bytes": "cc0d3900000c0000000400", "name": "Heartbeat 2.0 Payload" }, + { "fport": 30, "bytes": "0f2041d30000", "name": "One Analog Input Payload" }, + { "fport": 40, "bytes": "1227411ffefa3a4022147b", "name": "Two Analog Input Payload" }, + { "fport": 50, "bytes": "0f000e00", "name": "Digital Input Payload" }, + { "fport": 60, "bytes": "681ebd64ffff0000000000", "name": "Event Log Payload" }, + { "fport": 70, "bytes": "004ae6e23c002405200000", "name": "Configuration Payload" }, + { "fport": 100, "bytes": "0313020000000000000000", "name": "Modbus Generic Payload - One Register Group" }, + { "fport": 110, "bytes": "01fe3f80000002fe4000000003fe3f800000003a405810620f2041b60000", "name": "VoBo-XX Analog Input Variable Length Payload" }, + { "fport": 120, "bytes": "0f270f270f270f270f270f270f2701", "name": "VoBo-XX Modbus Standard Variable Length Payload" }, + ] +} + +describe(`fPort: ${testPayloads.data[0].fport} - ${testPayloads.data[0].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[0].bytes }, + { variable: "fPort", value: testPayloads.data[0].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "DIN1": 1, + "DIN2": 1, + "DIN3": 1, + "WKUP": 0, + "ADC1": 994, + "ADC2": 1009, + "ADC3": 1603, + "Battery": 3528, + "Temperature": 25.75, + "Modbus0": 0, + "fport": 1, + "voboType": "VoBoXX", + "payloadType": "Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[1].fport} - ${testPayloads.data[1].name}`, () => { // fPort 2 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[1].bytes }, + { variable: "fPort", value: testPayloads.data[1].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus1": 8705, + "Modbus2": 8706, + "Modbus3": 8707, + "Modbus4": 8708, + "Modbus5": 8709, + "fport": 2, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[2].fport} - ${testPayloads.data[2].name}`, () => { // fPort 3 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[2].bytes }, + { variable: "fPort", value: testPayloads.data[2].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus6": 8705, + "Modbus7": 8706, + "Modbus8": 8707, + "Modbus9": 8708, + "Modbus10": 8709, + "fport": 3, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[3].fport} - ${testPayloads.data[3].name}`, () => { // fPort 4 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[3].bytes }, + { variable: "fPort", value: testPayloads.data[3].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus11": 8705, + "Modbus12": 8706, + "Modbus13": 8707, + "Modbus14": 8708, + "Modbus15": 8709, + "fport": 4, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[4].fport} - ${testPayloads.data[4].name}`, () => { // fPort 5 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[4].bytes }, + { variable: "fPort", value: testPayloads.data[4].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus16": 8705, + "Modbus17": 8706, + "Modbus18": 8707, + "Modbus19": 8708, + "Modbus20": 8709, + "fport": 5, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[5].fport} - ${testPayloads.data[5].name}`, () => { // fPort 6 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[5].bytes }, + { variable: "fPort", value: testPayloads.data[5].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus21": 8705, + "Modbus22": 8706, + "Modbus23": 8707, + "Modbus24": 8708, + "Modbus25": 8709, + "fport": 6, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[6].fport} - ${testPayloads.data[6].name}`, () => { // fPort 7 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[6].bytes }, + { variable: "fPort", value: testPayloads.data[6].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus26": 8705, + "Modbus27": 8706, + "Modbus28": 8707, + "Modbus29": 8708, + "Modbus30": 8709, + "fport": 7, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[7].fport} - ${testPayloads.data[7].name}`, () => { // fPort 8 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[7].bytes }, + { variable: "fPort", value: testPayloads.data[7].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus31": 8705, + "Modbus32": 8706, + "Modbus33": 8707, + "Modbus34": 8708, + "Modbus35": 8709, + "fport": 8, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[8].fport} - ${testPayloads.data[8].name}`, () => { // fPort 9 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[8].bytes }, + { variable: "fPort", value: testPayloads.data[8].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "Modbus36": 8705, + "Modbus37": 8706, + "Modbus38": 8707, + "Modbus39": 8708, + "Modbus40": 8709, + "fport": 9, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[9].fport} - ${testPayloads.data[9].name}`, () => { // fPort 20 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[9].bytes }, + { variable: "fPort", value: testPayloads.data[9].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "batteryLevel": 3532, + "fatalErrorsTotal": 0, + "rssiAvg": 57, + "failedJoinAttemptsTotal": 0, + "configUpdateOccurred": 0, + "firmwareRevision": 0, + "rebootsTotal": 3, + "failedTransmitsTotal": 0, + "errorEventLogsTotal": 0, + "warningEventLogsTotal": 0, + "infoEventLogsTotal": 0, + "measurementPacketsTotal": 4, + "fport": 20, + "voboType": "VoBoXX", + "payloadType": "Heartbeat 2.0" + }, + "warnings": [], + "errors": [] + }); + }); +}); +describe(`fPort: ${testPayloads.data[10].fport} - ${testPayloads.data[10].name}`, () => { // fPort 30 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[10].bytes }, + { variable: "fPort", value: testPayloads.data[10].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorNum0": 15, + "sensorUnits0": 32, + "sensorData0": 26.375, + "analogSensorString0": "ADC Temperature", + "engUnitsString0": "C", + "fport": 30, + "voboType": "VoBoXX", + "payloadType": "One Analog Input" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[11].fport} - ${testPayloads.data[11].name}`, () => { // fPort 40 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[11].bytes }, + { variable: "fPort", value: testPayloads.data[11].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorNum0": 1, + "sensorUnits0": 39, + "sensorData0": 9.999750137329102, + "sensorNum1": 2, + "sensorUnits1": 58, + "sensorData1": 2.5325000286102295, + "analogSensorString0": "AIN1", + "engUnitsString0": "mA", + "analogSensorString1": "AIN2", + "engUnitsString1": "V", + "fport": 40, + "voboType": "VoBoXX", + "payloadType": "Two Analog Inputs" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[12].fport} - ${testPayloads.data[12].name}`, () => { // fPort 50 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[12].bytes }, + { variable: "fPort", value: testPayloads.data[12].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorValid0": 15, + "sensorData0": 14, + "digitalSensorStrings": [ + "WKUP", + "DIN1", + "DIN2", + "DIN3" + ], + "digitalSensorData": [ + 0, + 1, + 1, + 1 + ], + "fport": 50, + "voboType": "VoBoXX", + "payloadType": "Digital Inputs" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[13].fport} - ${testPayloads.data[13].name}`, () => { // fPort 60 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[13].bytes }, + { variable: "fPort", value: testPayloads.data[13].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "eventTimestamp": 1690115688, + "eventCode": 65535, + "metadata": [ + 0, + 0, + 0, + 0, + 0 + ], + "fport": 60, + "voboType": "VoBoXX", + "payloadType": "Event Log" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[14].fport} - ${testPayloads.data[14].name}`, () => { // fPort 70 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[14].bytes }, + { variable: "fPort", value: testPayloads.data[14].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "subgroupID": 0, + "sequenceNumber": 0, + "transRejoin": 10, + "ackFrequency": 4, + "lowBattery": 3.1, + "reserved1": 0, + "heartbeatAckEnable": true, + "operationMode": 1, + "cycleSubBands": true, + "ackRetries": 2, + "reservedLL": 4, + "ackEnable": true, + "heartbeatEnable": true, + "cycleTime": 60, + "backOffReset": 9, + "reservedRD": 5, + "reserved2": 0, + "resendAttempts": 0, + "freqSubBand": 2, + "fport": 70, + "voboType": "VoBoXX", + "payloadType": "Configuration" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[15].fport} - ${testPayloads.data[15].name}`, () => { // fPort 100 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[15].bytes }, + { variable: "fPort", value: testPayloads.data[15].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "group3register1": 531, + "fport": 100, + "voboType": "VoBoXX", + "payloadType": "Modbus Generic" + }, + "warnings": [], + "errors": [] + }); + }); +}); + + +describe(`fPort: ${testPayloads.data[16].fport} - ${testPayloads.data[16].name}`, () => { // fPort 110 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[16].bytes }, + { variable: "fPort", value: testPayloads.data[16].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "ainPayloads": [ + { + "sensorNum0": 1, + "sensorUnits0": 254, + "sensorData0": 1 + }, + { + "sensorNum0": 2, + "sensorUnits0": 254, + "sensorData0": 2 + }, + { + "sensorNum0": 3, + "sensorUnits0": 254, + "sensorData0": 1 + }, + { + "sensorNum0": 0, + "sensorUnits0": 58, + "sensorData0": 3.375999927520752 + }, + { + "sensorNum0": 15, + "sensorUnits0": 32, + "sensorData0": 22.75 + } + ], + "numOfAinPayloads": 5, + "analogSensorString0": "AIN1", + "engUnitsString0": "ADC code", + "analogSensorString1": "AIN2", + "engUnitsString1": "ADC code", + "analogSensorString2": "AIN3", + "engUnitsString2": "ADC code", + "analogSensorString3": "Battery Voltage", + "engUnitsString3": "V", + "analogSensorString4": "ADC Temperature", + "engUnitsString4": "C", + "fport": 110, + "voboType": "VoBoXX", + "payloadType": "Analog Input Variable Length" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[17].fport} - ${testPayloads.data[17].name}`, () => { // fPort 120 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[17].bytes }, + { variable: "fPort", value: testPayloads.data[17].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "modbusSlots": { + "Modbus1": 9999, + "Modbus2": 9999, + "Modbus3": 9999, + "Modbus4": 9999, + "Modbus5": 9999, + "Modbus6": 9999, + "Modbus7": 9999 + }, + "firstSlotNum": 1, + "numModbusSlots": 7, + "fport": 120, + "voboType": "VoBoXX", + "payloadType": "Modbus Standard Variable Length" + }, + "warnings": [], + "errors": [] + }); + }); +}); \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-tc/assets/logo.png b/decoders/connector/volley-boast/vobo-tc/assets/logo.png new file mode 100644 index 0000000..d51c676 Binary files /dev/null and b/decoders/connector/volley-boast/vobo-tc/assets/logo.png differ diff --git a/decoders/connector/volley-boast/vobo-tc/connector.jsonc b/decoders/connector/volley-boast/vobo-tc/connector.jsonc new file mode 100644 index 0000000..d54a8f1 --- /dev/null +++ b/decoders/connector/volley-boast/vobo-tc/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "VoBo TC", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-tc/description.md b/decoders/connector/volley-boast/vobo-tc/description.md new file mode 100644 index 0000000..897c7c4 --- /dev/null +++ b/decoders/connector/volley-boast/vobo-tc/description.md @@ -0,0 +1 @@ +Hazardous area thermocouple (12 channels) LoRaWAN bridge endpoint. \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-tc/v1.0.0/payload-config.jsonc b/decoders/connector/volley-boast/vobo-tc/v1.0.0/payload-config.jsonc new file mode 100644 index 0000000..d6ac8b3 --- /dev/null +++ b/decoders/connector/volley-boast/vobo-tc/v1.0.0/payload-config.jsonc @@ -0,0 +1,24 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The VoBo TC is a Hazardous Area LoRaWAN Class A endpoint which bridges 12 Thermocouple sensors to LoRaWAN.", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-loriot/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-tc/v1.0.0/payload.js b/decoders/connector/volley-boast/vobo-tc/v1.0.0/payload.js new file mode 100644 index 0000000..2244caf --- /dev/null +++ b/decoders/connector/volley-boast/vobo-tc/v1.0.0/payload.js @@ -0,0 +1,1512 @@ +/* istanbul ignore next */ +function customDecoder(bytes, fport) { + var decoded = Decoder(bytes, fport); + //======================================================= + // Insert your customization here + + + //======================================================= + return decoded; +} + +//=========================================================== +// Volley Boast Decoder +// +// Volley Boast maintains the below code, therefore +// DO NOT EDIT BELOW THIS POINT!! +//=========================================================== +const DECODER_MAJOR_VERSION = 2; +const DECODER_MINOR_VERSION = 1; +const DECODER_PATCH_VERSION = 1; + +function decodeUplink(input) { + const decoded = Decoder(input.bytes, input.fPort); + return decoded; +} + +function Decoder(bytes, fport) { + var decoded = {}; + var decodedData = {}; + var warnings = []; + var errors = []; + + if (fport == 1) { + decodedData = parseStandardPayload(bytes); + } + else if ((fport >= 2) && (fport <= 9)) { + decodedData = parseModbusStandardPayload(bytes, fport); + } + else if (fport == 10) { + decodedData = parseHeartbeat1p0Payload(bytes); + } + else if ((fport >= 20) && (fport <= 29)) { + decodedData = parseHeartbeat2p0Payload(bytes); + } + else if ((fport >= 30) && (fport <= 39)) { + decodedData = parseOneAnalogSensorPayload(bytes); + } + else if ((fport >= 40) && (fport <= 49)) { + decodedData = parseTwoAnalogSensorsPayload(bytes); + } + else if ((fport >= 50) && (fport <= 59)) { + decodedData = parseDigitalSensorsPayload(bytes); + } + else if ((fport >= 60) && (fport <= 69)) { + decodedData = parseEventLogPayload(bytes); + } + else if ((fport >= 70) && (fport <= 79)) { + decodedData = parseConfigurationPayload(bytes, fport); + } + else if ((fport >= 100) && (fport <= 109)) { + decodedData = parseModbusGenericPayload(bytes, fport); + } + else if ((fport >= 110) && (fport <= 119)) { + decodedData = parseAnalogInputVariableLengthPayload(bytes, fport); + } + else if ((fport >= 120) && (fport <= 129)) { + decodedData = parseModbusStandardVariableLengthPayload(bytes, fport); + } + else { + errors.push('unknown FPort') + } + + decoded = addVoboMetadata(decodedData, fport); + decoded.warnings = warnings; + decoded.errors = errors; + + return decoded; +} + +function addVoboMetadata(decodedData, fport) { + var payload = {}; + payload.data = decodedData; + + var voboType = ""; + var payloadType = ""; + var portLastDigit = fport % 10; + + if ((portLastDigit == 0) || ((fport >= 1) && (fport < 10))) voboType = "VoBoXX"; + else if (portLastDigit == 1) voboType = "VoBoTC"; + else if (portLastDigit == 2) voboType = "VoBoXP"; + + if (fport == 1) payloadType = "Standard"; + else if ((fport >= 2) && (fport <= 9)) { + payloadType = "Modbus Standard"; + } + else if (fport == 10) payloadType = "Heartbeat 1.0"; + else if ((fport >= 20) && (fport <= 29)) { + payloadType = "Heartbeat 2.0"; + } + else if ((fport >= 30) && (fport <= 39)) { + payloadType = "One Analog Input"; + payload.data.analogSensorString0 = lookupAnalogSensorName(voboType, payload.data.sensorNum0); + payload.data.engUnitsString0 = lookupUnits(1, payload.data.sensorUnits0); + } + else if ((fport >= 40) && (fport <= 49)) { + payloadType = "Two Analog Inputs"; + payload.data.analogSensorString0 = lookupAnalogSensorName(voboType, payload.data.sensorNum0); + payload.data.engUnitsString0 = lookupUnits(1, payload.data.sensorUnits0); + payload.data.analogSensorString1 = lookupAnalogSensorName(voboType, payload.data.sensorNum1); + payload.data.engUnitsString1 = lookupUnits(1, payload.data.sensorUnits1); + } + else if ((fport >= 50) && (fport <= 59)) { + payloadType = "Digital Inputs"; + payload.data.digitalSensorStrings = []; + payload.data.digitalSensorData = []; + for (let i = 0; i < 16; i++) { + let valid = (payload.data.sensorValid0 >> i) & 1; + if (valid == 1) { + payload.data.digitalSensorStrings.push(lookupDigitalSensorName(voboType, i)); + let data = (payload.data.sensorData0 >> i) & 1; + payload.data.digitalSensorData.push(data); + } + } + } + else if ((fport >= 60) && (fport <= 69)) payloadType = "Event Log"; + else if ((fport >= 70) && (fport <= 79)) payloadType = "Configuration"; + else if ((fport >= 100) && (fport <= 109)) payloadType = "Modbus Generic"; + else if ((fport >= 110) && (fport <= 119)) { + payloadType = "Analog Input Variable Length"; + for (let i = 0; i < payload.data.numOfAinPayloads; i++) { + payload.data["analogSensorString" + i] = lookupAnalogSensorName(voboType, payload.data.ainPayloads[i].sensorNum0); + payload.data["engUnitsString" + i] = lookupUnits(1, payload.data.ainPayloads[i].sensorUnits0); + } + } + else if ((fport >= 120) && (fport <= 129)) payloadType = "Modbus Standard Variable Length"; + + payload.data.fport = fport; + payload.data.voboType = voboType; + payload.data.payloadType = payloadType; + + return payload; +} + +function parseStandardPayload(bytes) { + var decoded = {}; + decoded.DIN1 = bytes[0] & 0x01; // Discrete digital 1 (1-bit) + decoded.DIN2 = bytes[0] >> 1 & 0x01; // Discrete digital 2 (1-bit) + decoded.DIN3 = bytes[0] >> 2 & 0x01; // Discrete digital 3 (1-bit) + decoded.WKUP = bytes[0] >> 3 & 0x01; // Discrete digital wakeup (1-bit) + decoded.ADC1 = ((bytes[0] & 0xf0) >> 4) | (bytes[1] << 4); // ADC 1 (12-bit) + decoded.ADC2 = (bytes[3] & 0x0f) << 8 | bytes[2]; // ADC 2 (12-bit) + decoded.ADC3 = ((bytes[3] & 0xf0) >> 4) | (bytes[4] << 4); // ADC 3 (12-bit) + decoded.Battery = ((bytes[6] & 0x0f) << 8 | bytes[5]) * 4; // ADC battery (12-bit) + if ((bytes[7] >> 7 & 0x01) == 0) { + decoded.Temperature = (((bytes[6] & 0xf0) >> 4) | (bytes[7] << 4)) * 0.125; // ADC temperature (12-bit) above 0 degrees C + } else { + decoded.Temperature = (4096 - (((bytes[6] & 0xf0) >> 4) | (bytes[7] << 4))) * 0.125 * (-1); // ADC temperature (12-bit) below 0 degrees C + } + decoded.Modbus0 = bytes[9] << 8 | bytes[8]; // Modbus-RS485 (16-bit) + return decoded; +} + +function parseModbusStandardPayload(bytes, fport) { + var startIdx = (fport - 1) * 5 - 4; + var decoded = {}; + decoded["Modbus" + startIdx++] = (bytes[1] << 8) | bytes[0]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[3] << 8) | bytes[2]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[5] << 8) | bytes[4]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[7] << 8) | bytes[6]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx] = (bytes[9] << 8) | bytes[8]; // Modbus-RS485 (16-bit) + return decoded; +} + +function parseHeartbeat1p0Payload(bytes) { + var decoded = {}; + decoded.batteryLevelMV = ((bytes[1] & 0x0f) << 8 | bytes[0]) * 4; // ADC battery (12-bit) + decoded.fwVersionMajor = (bytes[2] & 0x0f) | (bytes[1] & 0xf0) >> 4; // FW Version Major (8-bit) + decoded.fwVersionMinor = (bytes[3] & 0x0f) | (bytes[2] & 0xf0) >> 4; // FW Version Minor (8-bit) + decoded.fwVersionPatch = (bytes[4] & 0x0f) | (bytes[3] & 0xf0) >> 4; // FW Version Patch (8-bit) + decoded.fwVersionCustom = (bytes[4] & 0xf0) >> 4; // FW Version Custom (4-bit) + decoded.recSignalLevels = bytes[5] * -1; // Last RSSI (8 bits) + decoded.analogVoltageConfig = (bytes[6] & 0x1f); // Analog Voltage Config (5 bits) + decoded.analogPowerTimeConfig = parseFloat((((bytes[7] & 0x1F) << 3 | (bytes[6] & 0xE0) >> 5) / 10).toFixed(1)); // Analog Power Time Config (8 bits) + decoded.failedTransmissionBeforeRejoinConfig = (bytes[8] & 0x01) << 3 | (bytes[7] & 0xE0) >> 5; // Failed Transmissions Before Rejoin Config (4 bits) + decoded.cycleThroughFSB = (bytes[8] & 0x02) >> 1; // Cycle Through FSB (1 bit) + decoded.ackEnable = (bytes[8] & 0x04) >> 2; // ACK Enable (1 bit) + decoded.ackFreq = (bytes[8] & 0x78) >> 3; // ACK Frequency (4 bits) + decoded.ackReq = (bytes[9] & 0x07) << 1 | (bytes[8] & 0x80) >> 7; // ACK Request (4 bits) + decoded.batteryLevelThreshold = (((bytes[10] & 0x7F) << 5) | (bytes[9] & 0xF8) >> 3) * 4; // Battery Level Threshold (12-bit) + + return decoded; +} + +function parseHeartbeat2p0Payload(bytes) { + var decoded = {}; + decoded.batteryLevel = (bytes[1] & 0x0F) << 8 | bytes[0]; // Current Battery level in mV + decoded.fatalErrorsTotal = (bytes[1] & 0xF0) >> 4; // Total number of Fatal Error detected by the handler (triggering reboot) for the last HeartBeat interval + decoded.rssiAvg = bytes[2]; // RSSI of received signal from LoRaWAN GW - average for the last HeartBeat interval + decoded.failedJoinAttemptsTotal = bytes[3] & 0x7F; // Total Number of Failed Join Attempts to LoRaWAN network for the last HeartBeat interval + decoded.configUpdateOccurred = (bytes[3] & 0x80) >> 7; // Indicates if one or more config updates occurred for the last HeartBeat interval + decoded.firmwareRevision = (bytes[5] & 0x03) << 8 | bytes[4]; // Firmware revision encoding. + decoded.rebootsTotal = (bytes[5] & 0x1C) >> 2; // Total number of VoBo reboots not including Fatal Error for the last HeartBeat interval + decoded.failedTransmitsTotal = (bytes[5] & 0xE0) >> 5; // Total Number of Failed LoRaWAN Transmits for the last HeartBeat interval + decoded.errorEventLogsTotal = bytes[6]; // Total number of Error Event Logs written for the last HeartBeat interval + decoded.warningEventLogsTotal = bytes[7]; // Total number of Warning Event Logs written for the last HeartBeat interval + decoded.infoEventLogsTotal = bytes[8]; // Total number of Info Event Logs written for the last HeartBeat interval + decoded.measurementPacketsTotal = (bytes[10]) << 8 | bytes[9]; // Number of Measurements Packets successfully sent for the last HeartBeat interval + + return decoded; +} + +function bytesToFloat32(bytesArray) { + var buffer = new ArrayBuffer(4); + var bytesRaw = new Uint8Array(buffer); + bytesRaw[0] = bytesArray[0]; + bytesRaw[1] = bytesArray[1]; + bytesRaw[2] = bytesArray[2]; + bytesRaw[3] = bytesArray[3]; + var float32Res = new DataView(buffer).getFloat32(0, false); + + return float32Res; +} + +function parseOneAnalogSensorPayload(bytes) { + var decoded = {}; + decoded.sensorNum0 = bytes[0]; // Sensor Num 0 (8-bit) + decoded.sensorUnits0 = bytes[1]; // Sensor Units 0 (8-bit) + decoded.sensorData0 = bytesToFloat32(bytes.slice(2, 6)); // Sensor Data 0 (32-bit) + + return decoded; +} + +function parseTwoAnalogSensorsPayload(bytes) { + var decoded = {}; + decoded.sensorNum0 = (bytes[0] & 0xF0) >> 4; // Sensor Num 0 (4-bit) + decoded.sensorUnits0 = bytes[1]; // Sensor Units 0 (8-bit) + decoded.sensorData0 = bytesToFloat32(bytes.slice(2, 6)); // Sensor Data 0 (32-bit) + decoded.sensorNum1 = bytes[0] & 0x0F; // Sensor Num 1 (4-bit) + decoded.sensorUnits1 = bytes[6]; // Sensor Units 1 (8-bit) + decoded.sensorData1 = bytesToFloat32(bytes.slice(7, 11)); // Sensor Data 1 (32-bit) + + return decoded; +} + +function parseDigitalSensorsPayload(bytes) { + var decoded = {}; + decoded.sensorValid0 = (bytes[1] << 8) | bytes[0]; // Sensor Valid 0 (16-bit) + decoded.sensorData0 = (bytes[3] << 8) | bytes[2]; // Sensor Data 0 (16-bit) + + return decoded; +} + +function parseEventLogPayload(bytes) { + var decoded = {}; + decoded.eventTimestamp = bytes[0] + bytes[1] * Math.pow(2, 8) + bytes[2] * Math.pow(2, 16) + bytes[3] * Math.pow(2, 24); + decoded.eventCode = bytes[4] + bytes[5] * Math.pow(2, 8); + decoded.metadata = Array.prototype.slice.call(bytes.slice(6, 11), 0); + + return decoded; +} + +function parseVoboLibGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.transRejoin = bytes[1] & 0x0F; // Transmission Rejoin (4-bit) + decoded.ackFrequency = (bytes[1] & 0xF0) >> 4; // Acknowledgement Frequency for Data (4-bit) + decoded.lowBattery = parseFloat(((bytes[2] & 0x0F) / 10.0 + 2.5).toFixed(1)); // Low Battery Threshold (4-bit) + decoded.reserved1 = (bytes[2] & 0x10) >> 4; // Reserved 1 Field (1-bit) + decoded.heartbeatAckEnable = Boolean((bytes[2] & 0x20) >> 5); // Acknowledgement Enable for Heartbeat (1-bit) + decoded.operationMode = (bytes[2] & 0x40) >> 6; // Operation Mode (1-bit) + decoded.cycleSubBands = Boolean((bytes[2] & 0x80) >> 7); // Cycle Sub Bands Enable (1-bit) + decoded.ackRetries = (bytes[3] & 0x07); // Acknowledgement Retries (3-bit) + decoded.reservedLL = (bytes[3] & 0x38) >> 3; // Reserved LL (3-bit) + decoded.ackEnable = Boolean((bytes[3] & 0x40) >> 6); // Acknowledgement Enable for Data (1-bit) + decoded.heartbeatEnable = Boolean((bytes[3] & 0x80) >> 7); // Heartbeat Enable (1-bit) + decoded.cycleTime = ((bytes[6] & 0x03) << 16) | (bytes[5] << 8) | bytes[4]; // Cycle Time (18-bit) + decoded.backOffReset = (bytes[6] & 0xFC) >> 2; // BackOff Reset (6-bit) + decoded.reservedRD = bytes[7] & 0x3F; // Reserved RD (6-bit) + decoded.reserved2 = (bytes[7] & 0xC0) >> 6; // Reserved 2 Field (2-bit) + decoded.resendAttempts = bytes[8] & 0x0F; // Resend Attempts (4-bit) + decoded.freqSubBand = (bytes[8] & 0xF0) >> 4; // Frequency Sub Band (4-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.timeSyncInterval = bytes[1]; // Time Sync Interval in 30 minutes quantities (8-bit) + decoded.joinEUI = ""; // LoRaWAN Join EUI (64-bit) + for (let i = 9; i >= 2; i--) { + decoded.joinEUI += bytes[i].toString(16).toUpperCase().padStart(2, 0); + if (i != 2) decoded.joinEUI += "-"; + } + decoded.joinNonceResetEnable = Boolean(bytes[10] & 0x01); // Join Nonce Reset Enable (1-bit) + decoded.timeSyncWakeupEnable = Boolean((bytes[10] & 0x02) >> 1); // Time Sync Wakeup Enable (1-bit) + decoded.reserved1 = (bytes[10] & 0xFC) >> 2; // Reserved 1 Field (1-bit) + } + + return decoded; +} + +function parseVoboLibVoboSyncConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.vbsNodeNumber = (bytes[2] << 8) | bytes[1]; // VoboSync Node Number (16-bit) + decoded.vbsTimeReference = bytes.slice(3, 7).readUInt32LE(); // VoboSync Time Reference (32-bit) + decoded.reservedVCPDS = bytes[7] & 0x0F; // Reserved VCPDS (4-bit) + decoded.reservedVMPDS = (bytes[7] & 0xF0) >> 4; // Reserved VMPDS (4-bit) + decoded.reservedVUDS = bytes[8] & 0x0F; // Reserved VUDS (4-bit) + decoded.reserved1 = (bytes[8] & 0x30) >> 4; // Reserved 1 Field (2-bit) + decoded.reservedVSAE = Boolean((bytes[8] & 0x40) >> 6); // Reserved VSAE (1-bit) + decoded.vbsEnable = Boolean((bytes[8] & 0x80) >> 7); // VoboSync Enable (1-bit) + decoded.reserved3 = bytes[9] & 0x3F; // Reserved 3 (6-bit) + decoded.reserved2 = (bytes[9] & 0xC0) >> 6; // Reserved 2 Field (2-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.vbsMeasurementDelaySec = bytes[1] & 0x7F; // VoboSync Measurement Delay (7-bit) + decoded.reserved1 = (bytes[1] & 0x80) >> 7; // Reserved 1 Field (1-bit) + decoded.vbsUplinkDelaySec = bytes[2] & 0x7F; // VoboSync Uplink Delay (7-bit) + decoded.reserved2 = (bytes[2] & 0x80) >> 7; // Reserved 2 Field (1-bit) + } + + return decoded; +} + +function parseVoboXXGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + decoded.analogVoltage = parseFloat((bytes[1] / 10).toFixed(1)); // Analog Voltage (8-bit) + decoded.powerTime = parseFloat((bytes[2] / 10).toFixed(1)); // Analog Power Time (8-bit) + decoded.mbEnable = Boolean(bytes[3] & 0x01); // Modbus Enable (1-bit) + decoded.engUnitsEnable = Boolean((bytes[3] >> 1) & 0x01); // Engineering Units Enable (1-bit) + decoded.din1TransmitEnable = Boolean((bytes[3] >> 2) & 0x01); // DIN1 Transmit Enable (1-bit) + decoded.din2TransmitEnable = Boolean((bytes[3] >> 3) & 0x01); // DIN2 Transmit Enable (1-bit) + decoded.din3TransmitEnable = Boolean((bytes[3] >> 4) & 0x01); // DIN3 Transmit Enable (1-bit) + decoded.wkupTransmitEnable = Boolean((bytes[3] >> 5) & 0x01); // WKUP Transmit Enable (1-bit) + decoded.ain1TransmitEnable = Boolean((bytes[3] >> 6) & 0x01); // AIN1 Transmit Enable (1-bit) + decoded.ain2TransmitEnable = Boolean((bytes[3] >> 7) & 0x01); // AIN2 Transmit Enable (1-bit) + decoded.ain3TransmitEnable = Boolean(bytes[4] & 0x01); // AIN3 Transmit Enable (1-bit) + decoded.batteryLevelTransmitEnable = Boolean((bytes[4] >> 1) & 0x01); // Battery Level Transmit Enable (1-bit) + decoded.adcTemperatureTransmitEnable = Boolean((bytes[4] >> 2) & 0x01); // ADC Temperature Transmit Enable (1-bit) + decoded.mbTransmitEnable = Boolean((bytes[4] >> 3) & 0x01); // Modbus Transmit Enable (1-bit) + decoded.ainPayloadType = (bytes[4] >> 4) & 0x01; // Ain Payload Type (1-bit) + decoded.reservedMAWE = Boolean((bytes[4] >> 5) & 0x01); // Reserved MAWE Field (1-bit) + decoded.reservedMADE = Boolean((bytes[4] >> 6) & 0x01); // Reserved MADE Field (1-bit) + decoded.reservedMAME = Boolean((bytes[4] >> 7) & 0x01); // Reserved MAME Field (1-bit) + + return decoded; +} + +function parseVoboXXModbusGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + decoded.mbTimeout = (bytes[2] << 8) | bytes[1]; // Modbus Timeout in milliseconds (16-bit) + decoded.mbBaud = bytes[3] & 0x0F; // Modbus Baud Rate - encoded (4-bit) + decoded.mbStopBits = (bytes[3] >> 4) & 0x03; // Modbus Stop Bits (2-bit) + decoded.mbParity = (bytes[3] >> 6) & 0x03; // Modbus Parity (2-bit) + decoded.mbPayloadType = bytes[4] & 0x03; // Modbus Payload Type (2-bit) + decoded.reserved1 = (bytes[4] >> 2) & 0x3F; // Reserved 1 Field (6-bit) + + return decoded; +} + +function parseVoboXXModbusGroupsEnableConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.subgroupID == 6) { + decoded.mbFirstCycleG1 = Boolean(bytes[1] & 0x01); // Group 1 First Cycle Enable + decoded.mbFirstCycleG2 = Boolean((bytes[1] >> 1) & 0x01); // Group 2 First Cycle Enable + decoded.mbFirstCycleG3 = Boolean((bytes[1] >> 2) & 0x01); // Group 3 First Cycle Enable + decoded.mbFirstCycleG4 = Boolean((bytes[1] >> 3) & 0x01); // Group 4 First Cycle Enable + decoded.mbFirstCycleG5 = Boolean((bytes[1] >> 4) & 0x01); // Group 5 First Cycle Enable + decoded.mbFirstCycleG6 = Boolean((bytes[1] >> 5) & 0x01); // Group 6 First Cycle Enable + decoded.mbFirstCycleG7 = Boolean((bytes[1] >> 6) & 0x01); // Group 7 First Cycle Enable + decoded.mbFirstCycleG8 = Boolean((bytes[1] >> 7) & 0x01); // Group 8 First Cycle Enable + decoded.mbFirstCycleG9 = Boolean(bytes[2] & 0x01); // Group 9 First Cycle Enable + decoded.mbFirstCycleG10 = Boolean((bytes[2] >> 1) & 0x01); // Group 10 First Cycle Enable + decoded.mbFirstCycleG11 = Boolean((bytes[2] >> 2) & 0x01); // Group 11 First Cycle Enable + decoded.mbFirstCycleG12 = Boolean((bytes[2] >> 3) & 0x01); // Group 12 First Cycle Enable + decoded.mbFirstCycleG13 = Boolean((bytes[2] >> 4) & 0x01); // Group 13 First Cycle Enable + decoded.mbFirstCycleG14 = Boolean((bytes[2] >> 5) & 0x01); // Group 14 First Cycle Enable + decoded.mbFirstCycleG15 = Boolean((bytes[2] >> 6) & 0x01); // Group 15 First Cycle Enable + decoded.mbFirstCycleG16 = Boolean((bytes[2] >> 7) & 0x01); // Group 16 First Cycle Enable + decoded.mbFirstCycleG17 = Boolean(bytes[3] & 0x01); // Group 17 First Cycle Enable + decoded.mbFirstCycleG18 = Boolean((bytes[3] >> 1) & 0x01); // Group 18 First Cycle Enable + decoded.mbFirstCycleG19 = Boolean((bytes[3] >> 2) & 0x01); // Group 19 First Cycle Enable + decoded.mbFirstCycleG20 = Boolean((bytes[3] >> 3) & 0x01); // Group 20 First Cycle Enable + decoded.mbFirstCycleG21 = Boolean((bytes[3] >> 4) & 0x01); // Group 21 First Cycle Enable + decoded.mbFirstCycleG22 = Boolean((bytes[3] >> 5) & 0x01); // Group 22 First Cycle Enable + decoded.mbFirstCycleG23 = Boolean((bytes[3] >> 6) & 0x01); // Group 23 First Cycle Enable + decoded.mbFirstCycleG24 = Boolean((bytes[3] >> 7) & 0x01); // Group 24 First Cycle Enable + decoded.mbFirstCycleG25 = Boolean(bytes[4] & 0x01); // Group 25 First Cycle Enable + decoded.mbFirstCycleG26 = Boolean((bytes[4] >> 1) & 0x01); // Group 26 First Cycle Enable + decoded.mbFirstCycleG27 = Boolean((bytes[4] >> 2) & 0x01); // Group 27 First Cycle Enable + decoded.mbFirstCycleG28 = Boolean((bytes[4] >> 3) & 0x01); // Group 28 First Cycle Enable + decoded.mbFirstCycleG29 = Boolean((bytes[4] >> 4) & 0x01); // Group 29 First Cycle Enable + decoded.mbFirstCycleG30 = Boolean((bytes[4] >> 5) & 0x01); // Group 30 First Cycle Enable + decoded.mbFirstCycleG31 = Boolean((bytes[4] >> 6) & 0x01); // Group 31 First Cycle Enable + decoded.mbFirstCycleG32 = Boolean((bytes[4] >> 7) & 0x01); // Group 32 First Cycle Enable + decoded.mbFirstCycleG33 = Boolean(bytes[5] & 0x01); // Group 33 First Cycle Enable + decoded.mbFirstCycleG34 = Boolean((bytes[5] >> 1) & 0x01); // Group 34 First Cycle Enable + decoded.mbFirstCycleG35 = Boolean((bytes[5] >> 2) & 0x01); // Group 35 First Cycle Enable + decoded.mbFirstCycleG36 = Boolean((bytes[5] >> 3) & 0x01); // Group 36 First Cycle Enable + decoded.mbFirstCycleG37 = Boolean((bytes[5] >> 4) & 0x01); // Group 37 First Cycle Enable + decoded.mbFirstCycleG38 = Boolean((bytes[5] >> 5) & 0x01); // Group 38 First Cycle Enable + decoded.mbFirstCycleG39 = Boolean((bytes[5] >> 6) & 0x01); // Group 39 First Cycle Enable + decoded.mbFirstCycleG40 = Boolean((bytes[5] >> 7) & 0x01); // Group 40 First Cycle Enable + decoded.mbFirstCycleG41 = Boolean(bytes[6] & 0x01); // Group 41 First Cycle Enable + decoded.reserved1 = (bytes[6] >> 1) & 0x7F; // Reserved 1 Field (7-bit) + } + if (decoded.subgroupID == 7) { + decoded.mbSubseqCyclesG1 = Boolean(bytes[1] & 0x01); // Group 1 Subsequent Cycle Enable + decoded.mbSubseqCyclesG2 = Boolean((bytes[1] >> 1) & 0x01); // Group 2 Subsequent Cycle Enable + decoded.mbSubseqCyclesG3 = Boolean((bytes[1] >> 2) & 0x01); // Group 3 Subsequent Cycle Enable + decoded.mbSubseqCyclesG4 = Boolean((bytes[1] >> 3) & 0x01); // Group 4 Subsequent Cycle Enable + decoded.mbSubseqCyclesG5 = Boolean((bytes[1] >> 4) & 0x01); // Group 5 Subsequent Cycle Enable + decoded.mbSubseqCyclesG6 = Boolean((bytes[1] >> 5) & 0x01); // Group 6 Subsequent Cycle Enable + decoded.mbSubseqCyclesG7 = Boolean((bytes[1] >> 6) & 0x01); // Group 7 Subsequent Cycle Enable + decoded.mbSubseqCyclesG8 = Boolean((bytes[1] >> 7) & 0x01); // Group 8 Subsequent Cycle Enable + decoded.mbSubseqCyclesG9 = Boolean(bytes[2] & 0x01); // Group 9 Subsequent Cycle Enable + decoded.mbSubseqCyclesG10 = Boolean((bytes[2] >> 1) & 0x01); // Group 10 Subsequent Cycle Enable + decoded.mbSubseqCyclesG11 = Boolean((bytes[2] >> 2) & 0x01); // Group 11 Subsequent Cycle Enable + decoded.mbSubseqCyclesG12 = Boolean((bytes[2] >> 3) & 0x01); // Group 12 Subsequent Cycle Enable + decoded.mbSubseqCyclesG13 = Boolean((bytes[2] >> 4) & 0x01); // Group 13 Subsequent Cycle Enable + decoded.mbSubseqCyclesG14 = Boolean((bytes[2] >> 5) & 0x01); // Group 14 Subsequent Cycle Enable + decoded.mbSubseqCyclesG15 = Boolean((bytes[2] >> 6) & 0x01); // Group 15 Subsequent Cycle Enable + decoded.mbSubseqCyclesG16 = Boolean((bytes[2] >> 7) & 0x01); // Group 16 Subsequent Cycle Enable + decoded.mbSubseqCyclesG17 = Boolean(bytes[3] & 0x01); // Group 17 Subsequent Cycle Enable + decoded.mbSubseqCyclesG18 = Boolean((bytes[3] >> 1) & 0x01); // Group 18 Subsequent Cycle Enable + decoded.mbSubseqCyclesG19 = Boolean((bytes[3] >> 2) & 0x01); // Group 19 Subsequent Cycle Enable + decoded.mbSubseqCyclesG20 = Boolean((bytes[3] >> 3) & 0x01); // Group 20 Subsequent Cycle Enable + decoded.mbSubseqCyclesG21 = Boolean((bytes[3] >> 4) & 0x01); // Group 21 Subsequent Cycle Enable + decoded.mbSubseqCyclesG22 = Boolean((bytes[3] >> 5) & 0x01); // Group 22 Subsequent Cycle Enable + decoded.mbSubseqCyclesG23 = Boolean((bytes[3] >> 6) & 0x01); // Group 23 Subsequent Cycle Enable + decoded.mbSubseqCyclesG24 = Boolean((bytes[3] >> 7) & 0x01); // Group 24 Subsequent Cycle Enable + decoded.mbSubseqCyclesG25 = Boolean(bytes[4] & 0x01); // Group 25 Subsequent Cycle Enable + decoded.mbSubseqCyclesG26 = Boolean((bytes[4] >> 1) & 0x01); // Group 26 Subsequent Cycle Enable + decoded.mbSubseqCyclesG27 = Boolean((bytes[4] >> 2) & 0x01); // Group 27 Subsequent Cycle Enable + decoded.mbSubseqCyclesG28 = Boolean((bytes[4] >> 3) & 0x01); // Group 28 Subsequent Cycle Enable + decoded.mbSubseqCyclesG29 = Boolean((bytes[4] >> 4) & 0x01); // Group 29 Subsequent Cycle Enable + decoded.mbSubseqCyclesG30 = Boolean((bytes[4] >> 5) & 0x01); // Group 30 Subsequent Cycle Enable + decoded.mbSubseqCyclesG31 = Boolean((bytes[4] >> 6) & 0x01); // Group 31 Subsequent Cycle Enable + decoded.mbSubseqCyclesG32 = Boolean((bytes[4] >> 7) & 0x01); // Group 32 Subsequent Cycle Enable + decoded.mbSubseqCyclesG33 = Boolean(bytes[5] & 0x01); // Group 33 Subsequent Cycle Enable + decoded.mbSubseqCyclesG34 = Boolean((bytes[5] >> 1) & 0x01); // Group 34 Subsequent Cycle Enable + decoded.mbSubseqCyclesG35 = Boolean((bytes[5] >> 2) & 0x01); // Group 35 Subsequent Cycle Enable + decoded.mbSubseqCyclesG36 = Boolean((bytes[5] >> 3) & 0x01); // Group 36 Subsequent Cycle Enable + decoded.mbSubseqCyclesG37 = Boolean((bytes[5] >> 4) & 0x01); // Group 37 Subsequent Cycle Enable + decoded.mbSubseqCyclesG38 = Boolean((bytes[5] >> 5) & 0x01); // Group 38 Subsequent Cycle Enable + decoded.mbSubseqCyclesG39 = Boolean((bytes[5] >> 6) & 0x01); // Group 39 Subsequent Cycle Enable + decoded.mbSubseqCyclesG40 = Boolean((bytes[5] >> 7) & 0x01); // Group 40 Subsequent Cycle Enable + decoded.mbSubseqCyclesG41 = Boolean(bytes[6] & 0x01); // Group 41 Subsequent Cycle Enable + decoded.reserved1 = (bytes[6] >> 1) & 0x7F; // Reserved 1 Field (7-bit) + } + + return decoded; +} + +function parseVoboXXModbusGroupsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + var groupIdx = ((decoded.subgroupID - 8) * 16) + decoded.sequenceNumber + 1; // Sub-Groups 8, 9 and 10 carry the groups configurations. Group index is determined by Sub-Group ID and Sequence Number. + var groupIdxStr = (groupIdx).toString(); + decoded["mbFirstCycleG" + groupIdxStr] = Boolean(bytes[1] & 0x01); // Modbus Group First Cycle Enable (1-bit) + decoded["mbSubseqCyclesG" + groupIdxStr] = Boolean((bytes[1] >> 1) & 0x01); // Modbus Group Subsequent Cycle Enable (1-bit) + decoded["mbByteSwapG" + groupIdxStr] = Boolean((bytes[1] >> 2) & 0x01); // Modbus Group Byte Swap Enable (1-bit) + decoded["mbWordSwapG" + groupIdxStr] = Boolean((bytes[1] >> 3) & 0x01); // Modbus Group Word Swap Enable (1-bit) + decoded["mbNumTypeG" + groupIdxStr] = (bytes[1] >> 4) & 0x03; // Modbus Group Numerical Type (2-bit) + decoded["reserved1"] = (bytes[1] >> 6) & 0x03; // Reserved 1 Field (2-bit) + decoded["mbSlaveAddrG" + groupIdxStr] = bytes[2]; // Modbus Group Slave Address (8-bit) + decoded["mbStartAddrG" + groupIdxStr] = (bytes[4] << 8) | bytes[3]; // Modbus Group Start Address (16-bit) + decoded["mbNumRegsG" + groupIdxStr] = bytes[5]; // Modbus Group Number of Registers (8-bit) + decoded["mbWdataG" + groupIdxStr] = (bytes[7] << 8) | bytes[6]; // Modbus Group Write Data (16-bit) + decoded["mbGrpDelayG" + groupIdxStr] = (bytes[9] << 8) | bytes[8]; // Modbus Group Delay in milliseconds (16-bit) + decoded["mbFuncCodeG" + groupIdxStr] = bytes[10] & 0x3F; // Modbus Group Function Code (6-bit) + decoded["reserved2"] = (bytes[10] >> 6) & 0x03; // Reserved 2 Field (2-bit) + + return decoded; +} + +function parseVoboXXModbusPayloadsSlotsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.mbGroupPaySlot0 = bytes[1]; // Modbus Payload Slot 0 Group Idx (8-bit) + decoded.mbRegPaySlot0 = bytes[2]; // Modbus Payload Slot 0 Register Idx (8-bit) + decoded.mbGroupPaySlot1 = bytes[3]; // Modbus Payload Slot 1 Group Idx (8-bit) + decoded.mbRegPaySlot1 = bytes[4]; // Modbus Payload Slot 1 Register Idx (8-bit) + decoded.mbGroupPaySlot2 = bytes[5]; // Modbus Payload Slot 2 Group Idx (8-bit) + decoded.mbRegPaySlot2 = bytes[6]; // Modbus Payload Slot 2 Register Idx (8-bit) + decoded.mbGroupPaySlot3 = bytes[7]; // Modbus Payload Slot 3 Group Idx (8-bit) + decoded.mbRegPaySlot3 = bytes[8]; // Modbus Payload Slot 3 Register Idx (8-bit) + decoded.mbGroupPaySlot4 = bytes[9]; // Modbus Payload Slot 4 Group Idx (8-bit) + decoded.mbRegPaySlot4 = bytes[10]; // Modbus Payload Slot 4 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.mbGroupPaySlot5 = bytes[1]; // Modbus Payload Slot 5 Group Idx (8-bit) + decoded.mbRegPaySlot5 = bytes[2]; // Modbus Payload Slot 5 Register Idx (8-bit) + decoded.mbGroupPaySlot6 = bytes[3]; // Modbus Payload Slot 6 Group Idx (8-bit) + decoded.mbRegPaySlot6 = bytes[4]; // Modbus Payload Slot 6 Register Idx (8-bit) + decoded.mbGroupPaySlot7 = bytes[5]; // Modbus Payload Slot 7 Group Idx (8-bit) + decoded.mbRegPaySlot7 = bytes[6]; // Modbus Payload Slot 7 Register Idx (8-bit) + decoded.mbGroupPaySlot8 = bytes[7]; // Modbus Payload Slot 8 Group Idx (8-bit) + decoded.mbRegPaySlot8 = bytes[8]; // Modbus Payload Slot 8 Register Idx (8-bit) + decoded.mbGroupPaySlot9 = bytes[9]; // Modbus Payload Slot 9 Group Idx (8-bit) + decoded.mbRegPaySlot9 = bytes[10]; // Modbus Payload Slot 9 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 2) { + decoded.mbGroupPaySlot10 = bytes[1]; // Modbus Payload Slot 10 Group Idx (8-bit) + decoded.mbRegPaySlot10 = bytes[2]; // Modbus Payload Slot 10 Register Idx (8-bit) + decoded.mbGroupPaySlot11 = bytes[3]; // Modbus Payload Slot 11 Group Idx (8-bit) + decoded.mbRegPaySlot11 = bytes[4]; // Modbus Payload Slot 11 Register Idx (8-bit) + decoded.mbGroupPaySlot12 = bytes[5]; // Modbus Payload Slot 12 Group Idx (8-bit) + decoded.mbRegPaySlot12 = bytes[6]; // Modbus Payload Slot 12 Register Idx (8-bit) + decoded.mbGroupPaySlot13 = bytes[7]; // Modbus Payload Slot 13 Group Idx (8-bit) + decoded.mbRegPaySlot13 = bytes[8]; // Modbus Payload Slot 13 Register Idx (8-bit) + decoded.mbGroupPaySlot14 = bytes[9]; // Modbus Payload Slot 14 Group Idx (8-bit) + decoded.mbRegPaySlot14 = bytes[10]; // Modbus Payload Slot 14 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 3) { + decoded.mbGroupPaySlot15 = bytes[1]; // Modbus Payload Slot 15 Group Idx (8-bit) + decoded.mbRegPaySlot15 = bytes[2]; // Modbus Payload Slot 15 Register Idx (8-bit) + decoded.mbGroupPaySlot16 = bytes[3]; // Modbus Payload Slot 16 Group Idx (8-bit) + decoded.mbRegPaySlot16 = bytes[4]; // Modbus Payload Slot 16 Register Idx (8-bit) + decoded.mbGroupPaySlot17 = bytes[5]; // Modbus Payload Slot 17 Group Idx (8-bit) + decoded.mbRegPaySlot17 = bytes[6]; // Modbus Payload Slot 17 Register Idx (8-bit) + decoded.mbGroupPaySlot18 = bytes[7]; // Modbus Payload Slot 18 Group Idx (8-bit) + decoded.mbRegPaySlot18 = bytes[8]; // Modbus Payload Slot 18 Register Idx (8-bit) + decoded.mbGroupPaySlot19 = bytes[9]; // Modbus Payload Slot 19 Group Idx (8-bit) + decoded.mbRegPaySlot19 = bytes[10]; // Modbus Payload Slot 19 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 4) { + decoded.mbGroupPaySlot20 = bytes[1]; // Modbus Payload Slot 20 Group Idx (8-bit) + decoded.mbRegPaySlot20 = bytes[2]; // Modbus Payload Slot 20 Register Idx (8-bit) + decoded.mbGroupPaySlot21 = bytes[3]; // Modbus Payload Slot 21 Group Idx (8-bit) + decoded.mbRegPaySlot21 = bytes[4]; // Modbus Payload Slot 21 Register Idx (8-bit) + decoded.mbGroupPaySlot22 = bytes[5]; // Modbus Payload Slot 22 Group Idx (8-bit) + decoded.mbRegPaySlot22 = bytes[6]; // Modbus Payload Slot 22 Register Idx (8-bit) + decoded.mbGroupPaySlot23 = bytes[7]; // Modbus Payload Slot 23 Group Idx (8-bit) + decoded.mbRegPaySlot23 = bytes[8]; // Modbus Payload Slot 23 Register Idx (8-bit) + decoded.mbGroupPaySlot24 = bytes[9]; // Modbus Payload Slot 24 Group Idx (8-bit) + decoded.mbRegPaySlot24 = bytes[10]; // Modbus Payload Slot 24 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 5) { + decoded.mbGroupPaySlot25 = bytes[1]; // Modbus Payload Slot 25 Group Idx (8-bit) + decoded.mbRegPaySlot25 = bytes[2]; // Modbus Payload Slot 25 Register Idx (8-bit) + decoded.mbGroupPaySlot26 = bytes[3]; // Modbus Payload Slot 26 Group Idx (8-bit) + decoded.mbRegPaySlot26 = bytes[4]; // Modbus Payload Slot 26 Register Idx (8-bit) + decoded.mbGroupPaySlot27 = bytes[5]; // Modbus Payload Slot 27 Group Idx (8-bit) + decoded.mbRegPaySlot27 = bytes[6]; // Modbus Payload Slot 27 Register Idx (8-bit) + decoded.mbGroupPaySlot28 = bytes[7]; // Modbus Payload Slot 28 Group Idx (8-bit) + decoded.mbRegPaySlot28 = bytes[8]; // Modbus Payload Slot 28 Register Idx (8-bit) + decoded.mbGroupPaySlot29 = bytes[9]; // Modbus Payload Slot 29 Group Idx (8-bit) + decoded.mbRegPaySlot29 = bytes[10]; // Modbus Payload Slot 29 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 6) { + decoded.mbGroupPaySlot30 = bytes[1]; // Modbus Payload Slot 30 Group Idx (8-bit) + decoded.mbRegPaySlot30 = bytes[2]; // Modbus Payload Slot 30 Register Idx (8-bit) + decoded.mbGroupPaySlot31 = bytes[3]; // Modbus Payload Slot 31 Group Idx (8-bit) + decoded.mbRegPaySlot31 = bytes[4]; // Modbus Payload Slot 31 Register Idx (8-bit) + decoded.mbGroupPaySlot32 = bytes[5]; // Modbus Payload Slot 32 Group Idx (8-bit) + decoded.mbRegPaySlot32 = bytes[6]; // Modbus Payload Slot 32 Register Idx (8-bit) + decoded.mbGroupPaySlot33 = bytes[7]; // Modbus Payload Slot 33 Group Idx (8-bit) + decoded.mbRegPaySlot33 = bytes[8]; // Modbus Payload Slot 33 Register Idx (8-bit) + decoded.mbGroupPaySlot34 = bytes[9]; // Modbus Payload Slot 34 Group Idx (8-bit) + decoded.mbRegPaySlot34 = bytes[10]; // Modbus Payload Slot 34 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 7) { + decoded.mbGroupPaySlot35 = bytes[1]; // Modbus Payload Slot 35 Group Idx (8-bit) + decoded.mbRegPaySlot35 = bytes[2]; // Modbus Payload Slot 35 Register Idx (8-bit) + decoded.mbGroupPaySlot36 = bytes[3]; // Modbus Payload Slot 36 Group Idx (8-bit) + decoded.mbRegPaySlot36 = bytes[4]; // Modbus Payload Slot 36 Register Idx (8-bit) + decoded.mbGroupPaySlot37 = bytes[5]; // Modbus Payload Slot 37 Group Idx (8-bit) + decoded.mbRegPaySlot37 = bytes[6]; // Modbus Payload Slot 37 Register Idx (8-bit) + decoded.mbGroupPaySlot38 = bytes[7]; // Modbus Payload Slot 38 Group Idx (8-bit) + decoded.mbRegPaySlot38 = bytes[8]; // Modbus Payload Slot 38 Register Idx (8-bit) + decoded.mbGroupPaySlot39 = bytes[9]; // Modbus Payload Slot 39 Group Idx (8-bit) + decoded.mbRegPaySlot39 = bytes[10]; // Modbus Payload Slot 39 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 8) { + decoded.mbGroupPaySlot40 = bytes[1]; // Modbus Payload Slot 40 Group Idx (8-bit) + decoded.mbRegPaySlot40 = bytes[2]; // Modbus Payload Slot 40 Register Idx (8-bit) + } + + return decoded; +} + +function parseVoboXXEngineeringUnitsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + // AIN1 General + if (decoded.sequenceNumber == 0) { + decoded.ain1UnitsCode = bytes[1]; // AIN1 Units Code (8-bit) + decoded.ain1MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN1 Minimum Value (32-bit) + decoded.ain1MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN1 Maximum Value (32-bit) + decoded.ain1Type = bytes[10] & 0x03; // AIN1 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN1 Series Resistance + if (decoded.sequenceNumber == 1) { + decoded.ain1SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN1 Series Resistance (32-bit) + } + // AIN1 Calibration + if (decoded.sequenceNumber == 2) { + decoded.ain1Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN1 Calibration Gain (32-bit) + decoded.ain1Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN1 Calibration Offset (32-bit) + } + + // AIN2 General + if (decoded.sequenceNumber == 3) { + decoded.ain2UnitsCode = bytes[1]; // AIN2 Units Code (8-bit) + decoded.ain2MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN2 Minimum Value (32-bit) + decoded.ain2MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN2 Maximum Value (32-bit) + decoded.ain2Type = bytes[10] & 0x03; // AIN2 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN2 Series Resistance + if (decoded.sequenceNumber == 4) { + decoded.ain2SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN2 Series Resistance (32-bit) + } + // AIN2 Calibration + if (decoded.sequenceNumber == 5) { + decoded.ain2Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN2 Calibration Gain (32-bit) + decoded.ain2Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN2 Calibration Offset (32-bit) + } + + // AIN3 General + if (decoded.sequenceNumber == 6) { + decoded.ain3UnitsCode = bytes[1]; // AIN3 Units Code (8-bit) + decoded.ain3MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN3 Minimum Value (32-bit) + decoded.ain3MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN3 Maximum Value (32-bit) + decoded.ain3Type = bytes[10] & 0x03; // AIN3 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN3 Series Resistance + if (decoded.sequenceNumber == 7) { + decoded.ain3SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN3 Series Resistance (32-bit) + } + // AIN3 Calibration + if (decoded.sequenceNumber == 8) { + decoded.ain3Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN3 Calibration Gain (32-bit) + decoded.ain3Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN3 Calibration Offset (32-bit) + } + + return decoded; +} + +function parseVoboTCGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.deviceType1 = bytes[1] & 0x07; // Device Type 1 (3-bit) + decoded.enable1 = Boolean((bytes[1] & 0x08) >> 3); // Enable 1 (1-bit) + decoded.deviceType2 = (bytes[1] & 0x70) >> 4; // Device Type 2 (3-bit) + decoded.enable2 = Boolean((bytes[1] & 0x80) >> 7); // Enable 2 (1-bit) + decoded.deviceType3 = bytes[2] & 0x07; // Device Type 3 (3-bit) + decoded.enable3 = Boolean((bytes[2] & 0x08) >> 3); // Enable 3 (1-bit) + decoded.deviceType4 = (bytes[2] & 0x70) >> 4; // Device Type 4 (3-bit) + decoded.enable4 = Boolean((bytes[2] & 0x80) >> 7); // Enable 4 (1-bit) + decoded.deviceType5 = bytes[3] & 0x07; // Device Type 5 (3-bit) + decoded.enable5 = Boolean((bytes[3] & 0x08) >> 3); // Enable 5 (1-bit) + decoded.deviceType6 = (bytes[3] & 0x70) >> 4; // Device Type 6 (3-bit) + decoded.enable6 = Boolean((bytes[3] & 0x80) >> 7); // Enable 6 (1-bit) + decoded.deviceType7 = bytes[4] & 0x07; // Device Type 7 (3-bit) + decoded.enable7 = Boolean((bytes[4] & 0x08) >> 3); // Enable 7 (1-bit) + decoded.deviceType8 = (bytes[4] & 0x70) >> 4; // Device Type 8 (3-bit) + decoded.enable8 = Boolean((bytes[4] & 0x80) >> 7); // Enable 8 (1-bit) + decoded.deviceType9 = bytes[5] & 0x07; // Device Type 9 (3-bit) + decoded.enable9 = Boolean((bytes[5] & 0x08) >> 3); // Enable 9 (1-bit) + decoded.deviceType10 = (bytes[5] & 0x70) >> 4; // Device Type 10 (3-bit) + decoded.enable10 = Boolean((bytes[5] & 0x80) >> 7); // Enable 10 (1-bit) + decoded.deviceType11 = bytes[6] & 0x07; // Device Type 11 (3-bit) + decoded.enable11 = Boolean((bytes[6] & 0x08) >> 3); // Enable 11 (1-bit) + decoded.deviceType12 = (bytes[6] & 0x70) >> 4; // Device Type 12 (3-bit) + decoded.enable12 = Boolean((bytes[6] & 0x80) >> 7); // Enable 12 (1-bit) + decoded.deviceUnits1 = bytes[7] & 0x01; // Device Units 1 (1-bit) + decoded.deviceUnits2 = (bytes[7] & 0x02) >> 1; // Device Units 2 (1-bit) + decoded.deviceUnits3 = (bytes[7] & 0x04) >> 2; // Device Units 3 (1-bit) + decoded.deviceUnits4 = (bytes[7] & 0x08) >> 3; // Device Units 4 (1-bit) + decoded.deviceUnits5 = (bytes[7] & 0x10) >> 4; // Device Units 5 (1-bit) + decoded.deviceUnits6 = (bytes[7] & 0x20) >> 5; // Device Units 6 (1-bit) + decoded.deviceUnits7 = (bytes[7] & 0x40) >> 6; // Device Units 7 (1-bit) + decoded.deviceUnits8 = (bytes[7] & 0x80) >> 7; // Device Units 8 (1-bit) + decoded.deviceUnits9 = bytes[8] & 0x01; // Device Units 9 (1-bit) + decoded.deviceUnits10 = (bytes[8] & 0x02) >> 1; // Device Units 10 (1-bit) + decoded.deviceUnits11 = (bytes[8] & 0x04) >> 2; // Device Units 11 (1-bit) + decoded.deviceUnits12 = (bytes[8] & 0x08) >> 3; // Device Units 12 (1-bit) + decoded.wkupSensorTransmitEnable = Boolean((bytes[8] & 0x10) >> 4); // WKUP Sensor Transmit Enable (1-bit) + decoded.batterySensorTransmitEnable = Boolean((bytes[8] & 0x20) >> 5); // Battery Sensor Transmit Enable (1-bit) + decoded.coldJointSensorTransmitEnable = Boolean((bytes[8] & 0x40) >> 6); // Cold Joint Sensor Transmit Enable (1-bit) + decoded.reserved1 = (bytes[8] & 0x80) >> 7; // Reserved 1 Field (1-bit) + } + else if (decoded.sequenceNumber == 1) { + decoded.deviceType1 = bytes[1] & 0x07; // Device Type 1 (3-bit) + decoded.enable1 = Boolean((bytes[1] & 0x08) >> 3); // Enable 1 (1-bit) + decoded.deviceType2 = (bytes[1] & 0x70) >> 4; // Device Type 2 (3-bit) + decoded.enable2 = Boolean((bytes[1] & 0x80) >> 7); // Enable 2 (1-bit) + decoded.deviceType3 = bytes[2] & 0x07; // Device Type 3 (3-bit) + decoded.enable3 = Boolean((bytes[2] & 0x08) >> 3); // Enable 3 (1-bit) + decoded.deviceType4 = (bytes[2] & 0x70) >> 4; // Device Type 4 (3-bit) + decoded.enable4 = Boolean((bytes[2] & 0x80) >> 7); // Enable 4 (1-bit) + decoded.deviceType5 = bytes[3] & 0x07; // Device Type 5 (3-bit) + decoded.enable5 = Boolean((bytes[3] & 0x08) >> 3); // Enable 5 (1-bit) + decoded.deviceType6 = (bytes[3] & 0x70) >> 4; // Device Type 6 (3-bit) + decoded.enable6 = Boolean((bytes[3] & 0x80) >> 7); // Enable 6 (1-bit) + decoded.deviceType7 = bytes[4] & 0x07; // Device Type 7 (3-bit) + decoded.enable7 = Boolean((bytes[4] & 0x08) >> 3); // Enable 7 (1-bit) + decoded.deviceType8 = (bytes[4] & 0x70) >> 4; // Device Type 8 (3-bit) + decoded.enable8 = Boolean((bytes[4] & 0x80) >> 7); // Enable 8 (1-bit) + decoded.deviceType9 = bytes[5] & 0x07; // Device Type 9 (3-bit) + decoded.enable9 = Boolean((bytes[5] & 0x08) >> 3); // Enable 9 (1-bit) + decoded.deviceType10 = (bytes[5] & 0x70) >> 4; // Device Type 10 (3-bit) + decoded.enable10 = Boolean((bytes[5] & 0x80) >> 7); // Enable 10 (1-bit) + decoded.deviceType11 = bytes[6] & 0x07; // Device Type 11 (3-bit) + decoded.enable11 = Boolean((bytes[6] & 0x08) >> 3); // Enable 11 (1-bit) + decoded.deviceType12 = (bytes[6] & 0x70) >> 4; // Device Type 12 (3-bit) + decoded.enable12 = Boolean((bytes[6] & 0x80) >> 7); // Enable 12 (1-bit) + decoded.deviceUnits1 = bytes[7] & 0x03; // Device Units 1 (2-bit) + decoded.deviceUnits2 = (bytes[7] & 0x0C) >> 2; // Device Units 2 (2-bit) + decoded.deviceUnits3 = (bytes[7] & 0x30) >> 4; // Device Units 3 (2-bit) + decoded.deviceUnits4 = (bytes[7] & 0xC0) >> 6; // Device Units 4 (2-bit) + decoded.deviceUnits5 = bytes[8] & 0x03; // Device Units 5 (2-bit) + decoded.deviceUnits6 = (bytes[8] & 0x0C) >> 2; // Device Units 6 (2-bit) + decoded.deviceUnits7 = (bytes[8] & 0x30) >> 4; // Device Units 7 (2-bit) + decoded.deviceUnits8 = (bytes[8] & 0xC0) >> 6; // Device Units 8 (2-bit) + decoded.deviceUnits9 = bytes[9] & 0x03; // Device Units 9 (2-bit) + decoded.deviceUnits10 = (bytes[9] & 0x0C) >> 2; // Device Units 10 (2-bit) + decoded.deviceUnits11 = (bytes[9] & 0x30) >> 4; // Device Units 11 (2-bit) + decoded.deviceUnits12 = (bytes[9] & 0xC0) >> 6; // Device Units 12 (2-bit) + decoded.coldJointSensorUnits = bytes[10] & 0x03; // Cold Joint Sensor Units (2-bit) + decoded.wkupSensorTransmitEnable = Boolean((bytes[10] & 0x04) >> 2); // WKUP Sensor Transmit Enable (1-bit) + decoded.batterySensorTransmitEnable = Boolean((bytes[10] & 0x08) >> 3); // Battery Sensor Transmit Enable (1-bit) + decoded.coldJointSensorTransmitEnable = Boolean((bytes[10] & 0x10) >> 4); // Cold Joint Sensor Transmit Enable (1-bit) + decoded.tcSensorsTransmitEnable = Boolean((bytes[10] & 0x20) >> 5);; // TC Sensors Transmit Enable (1-bit) + decoded.ainPayloadType = (bytes[10] & 0x40) >> 6; // Ain Payload Type (1-bit) + decoded.reserved1 = (bytes[10] & 0x80) >> 7; // Reserved 1 Field (1-bit) + } + + return decoded; +} + +function parseVoboTCCalibrationConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.gain1 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 1 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset1 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 1 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain2 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 2 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset2 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 2 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.gain3 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 3 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset3 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 3 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain4 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 4 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset4 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 4 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 2) { + decoded.gain5 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 5 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset5 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 5 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain6 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 6 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset6 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 6 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 3) { + decoded.gain7 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 7 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset7 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 7 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain8 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 8 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset8 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 8 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 4) { + decoded.gain9 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 9 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset9 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 9 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain10 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 10 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset10 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3));// Offset 10 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 5) { + decoded.gain11 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 11 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset11 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3));// Offset 11 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain12 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 12 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset12 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3));// Offset 12 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + + return decoded; +} + +function parseConfigurationPayload(bytes, fport) { + var decoded = {}; + var subgroupID = bytes[0] & 0x0F; + + // VoBo Lib Configuration Payloads + if (subgroupID == 0) decoded = parseVoboLibGeneralConfigurationPayload(bytes); + else if (subgroupID == 1) decoded = parseVoboLibVoboSyncConfigurationPayload(bytes); + // VoBo XX Configuration Payloads + else if (fport == 70 && subgroupID == 4) decoded = parseVoboXXGeneralConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 5) decoded = parseVoboXXModbusGeneralConfigurationPayload(bytes); + else if (fport == 70 && (subgroupID == 6 || subgroupID == 7)) decoded = parseVoboXXModbusGroupsEnableConfigurationPayload(bytes); + else if (fport == 70 && (subgroupID == 8 || subgroupID == 9 || subgroupID == 10)) decoded = parseVoboXXModbusGroupsConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 11) decoded = parseVoboXXModbusPayloadsSlotsConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 12) decoded = parseVoboXXEngineeringUnitsConfigurationPayload(bytes); + // VoBo TC Configuration Payloads + else if (fport == 71 && subgroupID == 4) decoded = parseVoboTCGeneralConfigurationPayload(bytes); + else if (fport == 71 && subgroupID == 5) decoded = parseVoboTCCalibrationConfigurationPayload(bytes); + + return decoded; +} + +function parseModbusGenericPayload(bytes, fport) { + var decoded = {}; + var payloadSize = bytes.length; + var byteIdx = 0; + while (byteIdx < (payloadSize - 1)) { + var groupIdx = bytes[byteIdx] & 0x3F; + if (groupIdx > 0) { + var blockType = (bytes[byteIdx] >> 6) & 0x03; + byteIdx++; + + if (blockType == 0x00) // One register block + { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register1"] = registerValue; + byteIdx += 2; + } + else if (blockType == 0x01) // Two registers block + { + var register1Value = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register1"] = register1Value; + var register2Value = (bytes[byteIdx + 3] << 8) | (bytes[byteIdx + 2]); + decoded["group" + groupIdx + "register2"] = register2Value; + byteIdx += (2 * 2); + } + else if (blockType == 0x02) // Non fragmented block + { + var registersValues = []; + var numOfRegisters = bytes[byteIdx] & 0x7F; + byteIdx++; + for (let regIdx = 0; regIdx < numOfRegisters; regIdx++) { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register" + (regIdx + 1)] = registerValue; + registersValues.push(registerValue); + byteIdx += 2; + } + } + else if (blockType == 0x03) // Fragmented block + { + var registersValues = []; + var numOfRegisters = bytes[byteIdx] & 0x7F; + byteIdx++; + var registerOffset = bytes[byteIdx] & 0x7F; + byteIdx++; + + var numOfRegToRead = numOfRegisters - registerOffset; + var freeRegToRead = Math.floor((payloadSize - byteIdx) / 2); + if (numOfRegToRead > freeRegToRead) numOfRegToRead = freeRegToRead; + for (let regIdx = 0; regIdx < numOfRegToRead; regIdx++) { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register" + (registerOffset + regIdx + 1)] = registerValue; + registersValues.push(registerValue); + byteIdx += 2; + } + } + } + else break; + } + return decoded; +} + +function parseAnalogInputVariableLengthPayload(bytes, fport) { + var decoded = {}; + var decodedAinPayloads = []; + const AIN_PAYLOAD_SIZE = 6; + + var payloadLength = bytes.length + var byteIdx = 0; + while (byteIdx < payloadLength) { + var ainPayload = bytes.slice(byteIdx, byteIdx + AIN_PAYLOAD_SIZE) + var decodedAinPayload = parseOneAnalogSensorPayload(ainPayload) + decodedAinPayloads.push(decodedAinPayload) + byteIdx += AIN_PAYLOAD_SIZE; + } + + decoded.ainPayloads = decodedAinPayloads; + decoded.numOfAinPayloads = decodedAinPayloads.length + + return decoded; +} + +function parseModbusStandardVariableLengthPayload(bytes, fport) { + var decoded = {}; + var decodedModbusSlots = {}; + + var payloadLength = bytes.length + var firstSlotNum = bytes[payloadLength - 1] + const REGISTER_SIZE = 2 + var byteIdx = 0; + while (byteIdx < payloadLength - 1) { + var registerIdx = byteIdx / 2; + var slotIdx = firstSlotNum + registerIdx; + decodedModbusSlots["Modbus" + slotIdx] = (bytes[byteIdx + 1] << 8) | bytes[byteIdx]; + byteIdx += REGISTER_SIZE; + } + + decoded.modbusSlots = decodedModbusSlots; + decoded.firstSlotNum = firstSlotNum; + decoded.numModbusSlots = (payloadLength - 1) / 2; + + return decoded; +} + +function lookupAnalogSensorName(voboType, sensorNum) { + const analogSensorsTableXX = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "Battery Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "AIN1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "AIN2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "AIN3" }, + { "Analog Sensor Number": "15", "Analog Sensor Name": "ADC Temperature" } + ]; + + const analogSensorsTableTC = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "Battery Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "TC1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "TC2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "TC3" }, + { "Analog Sensor Number": "4", "Analog Sensor Name": "TC4" }, + { "Analog Sensor Number": "5", "Analog Sensor Name": "TC5" }, + { "Analog Sensor Number": "6", "Analog Sensor Name": "TC6" }, + { "Analog Sensor Number": "7", "Analog Sensor Name": "TC7" }, + { "Analog Sensor Number": "8", "Analog Sensor Name": "TC8" }, + { "Analog Sensor Number": "9", "Analog Sensor Name": "TC9" }, + { "Analog Sensor Number": "10", "Analog Sensor Name": "TC10" }, + { "Analog Sensor Number": "11", "Analog Sensor Name": "TC11" }, + { "Analog Sensor Number": "12", "Analog Sensor Name": "TC12" }, + { "Analog Sensor Number": "13", "Analog Sensor Name": "Cold Joint Temperature" } + ]; + + const analogSensorsTableXP = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "3.3V Supply Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "AIN1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "AIN2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "AIN3" }, + { "Analog Sensor Number": "4", "Analog Sensor Name": "DIN1" }, + { "Analog Sensor Number": "5", "Analog Sensor Name": "DIN1 Pulse Count" }, + { "Analog Sensor Number": "6", "Analog Sensor Name": "DIN2" }, + { "Analog Sensor Number": "7", "Analog Sensor Name": "DIN2 Pulse Count" }, + { "Analog Sensor Number": "8", "Analog Sensor Name": "RLY1 Voltage" }, + { "Analog Sensor Number": "9", "Analog Sensor Name": "RLY2 Voltage" }, + { "Analog Sensor Number": "10", "Analog Sensor Name": "RLY3 Voltage" }, + { "Analog Sensor Number": "11", "Analog Sensor Name": "RLY4 Voltage" }, + { "Analog Sensor Number": "12", "Analog Sensor Name": "WKUP" }, + { "Analog Sensor Number": "13", "Analog Sensor Name": "VPP Voltage" }, + { "Analog Sensor Number": "14", "Analog Sensor Name": "VIN Voltage" }, + { "Analog Sensor Number": "15", "Analog Sensor Name": "ADC Temperature" }, + { "Analog Sensor Number": "16", "Analog Sensor Name": "Cont Meas Period" }, + { "Analog Sensor Number": "17", "Analog Sensor Name": "Cont Meas Count" } + ]; + + var analogSensorsTable = []; + var analogSensorNameSuffix = ""; + if (voboType == "VoBoXX") { + analogSensorsTable = analogSensorsTableXX; + } + else if (voboType == "VoBoTC") { + analogSensorsTable = analogSensorsTableTC; + } + else if (voboType == "VoBoXP") { + analogSensorsTable = analogSensorsTableXP; + var sensorNumEncoding = (sensorNum & 0xC0) >> 6; + if (sensorNumEncoding == 0x01) analogSensorNameSuffix = "(Max)"; + else if (sensorNumEncoding == 0x02) analogSensorNameSuffix = "(Min)"; + else if (sensorNumEncoding == 0x03) analogSensorNameSuffix = "(Avg)"; + sensorNum = sensorNum & 0x3F; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\", \"VoBoTC\" or \"VoBoXP\""; + throw new Error(errorMsg); + } + + var analogSensorRow = analogSensorsTable.find(element => element["Analog Sensor Number"] == sensorNum.toString()); + if (typeof analogSensorRow == 'undefined') { + var genericAnalogSensorName = "AnalogSensor" + sensorNum.toString(); + return genericAnalogSensorName; + } + + var analogSensorName = analogSensorRow["Analog Sensor Name"]; + if (voboType == "VoBoXP") { + analogSensorName = analogSensorName + " " + analogSensorNameSuffix; + } + return analogSensorName; +} + +function lookupDigitalSensorName(voboType, sensorNum) { + const digitalSensorsTableXX = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" }, + { "Digital Sensor Number": "1", "Digital Sensor Name": "DIN1" }, + { "Digital Sensor Number": "2", "Digital Sensor Name": "DIN2" }, + { "Digital Sensor Number": "3", "Digital Sensor Name": "DIN3" } + ]; + + const digitalSensorsTableTC = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" } + ]; + + const digitalSensorsTableXP = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" }, + { "Digital Sensor Number": "1", "Digital Sensor Name": "DIN1" }, + { "Digital Sensor Number": "2", "Digital Sensor Name": "DIN2" } + ]; + + var digitalSensorsTable = []; + if (voboType == "VoBoXX") { + digitalSensorsTable = digitalSensorsTableXX; + } + else if (voboType == "VoBoTC") { + digitalSensorsTable = digitalSensorsTableTC; + } + else if (voboType == "VoBoXP") { + digitalSensorsTable = digitalSensorsTableXP; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\", \"VoBoTC\" or \"VoBoXP\""; + throw new Error(errorMsg); + } + + var digitalSensorRow = digitalSensorsTable.find(element => element["Digital Sensor Number"] == sensorNum.toString()); + if (typeof digitalSensorRow == 'undefined') { + var genericDigitalSensorName = "DigitalSensor" + sensorNum.toString(); + return genericDigitalSensorName; + } + + var digitalSensorName = digitalSensorRow["Digital Sensor Name"]; + return digitalSensorName; +} + +function lookupUnits(outputType, unitCode) { + const engUnitsTable = + [ + { "Units Code": "1", "Description": "inches of water at 20 degC (68 degF)", "Abbreviated Units": "inH2O (20 degC or 68 degF)" }, + { "Units Code": "2", "Description": "inches of mercury at 0 degC (32 degF)", "Abbreviated Units": "inHg (20 degC or 68 degF)" }, + { "Units Code": "3", "Description": "feet of water at 20 degC (68 degF)", "Abbreviated Units": "ftH2O (20 degC or 68 degF)" }, + { "Units Code": "4", "Description": "millimeters of water at 20 degC (68 degF)", "Abbreviated Units": "mmH2O (20 degC or 68 degF)" }, + { "Units Code": "5", "Description": "millimeters of mercury at 0 degC (32 degF)", "Abbreviated Units": "mmHg (0 degC or 32 degF)" }, + { "Units Code": "6", "Description": "pounds per square inch", "Abbreviated Units": "psi" }, + { "Units Code": "7", "Description": "bars", "Abbreviated Units": "bar" }, + { "Units Code": "8", "Description": "millibars", "Abbreviated Units": "mbar" }, + { "Units Code": "9", "Description": "grams per square centimeter", "Abbreviated Units": "g/cm^2" }, + { "Units Code": "10", "Description": "kilograms per square centimeter", "Abbreviated Units": "kg/cm^2" }, + { "Units Code": "11", "Description": "pascals", "Abbreviated Units": "Pa" }, + { "Units Code": "12", "Description": "kilopascals", "Abbreviated Units": "kPa" }, + { "Units Code": "13", "Description": "torr", "Abbreviated Units": "torr" }, + { "Units Code": "14", "Description": "atmospheres", "Abbreviated Units": "atm" }, + { "Units Code": "15", "Description": "cubic feet per minute", "Abbreviated Units": "ft^3/min" }, + { "Units Code": "16", "Description": "gallons per minute", "Abbreviated Units": "usg/min" }, + { "Units Code": "17", "Description": "liters per minute", "Abbreviated Units": "L/min" }, + { "Units Code": "18", "Description": "imperial gallons per minute", "Abbreviated Units": "impgal/min" }, + { "Units Code": "19", "Description": "cubic meter per hour", "Abbreviated Units": "m^3/h" }, + { "Units Code": "20", "Description": "feet per second", "Abbreviated Units": "ft/s" }, + { "Units Code": "21", "Description": "meters per seond", "Abbreviated Units": "m/s" }, + { "Units Code": "22", "Description": "gallons per second", "Abbreviated Units": "usg/s" }, + { "Units Code": "23", "Description": "million gallons per day", "Abbreviated Units": "Musg/d" }, + { "Units Code": "24", "Description": "liters per second", "Abbreviated Units": "L/s" }, + { "Units Code": "25", "Description": "million liters per day", "Abbreviated Units": "ML/d" }, + { "Units Code": "26", "Description": "cubic feet per second", "Abbreviated Units": "ft^3/s" }, + { "Units Code": "27", "Description": "cubic feet per day", "Abbreviated Units": "ft^3/d" }, + { "Units Code": "28", "Description": "cubic meters per second", "Abbreviated Units": "m^3/s" }, + { "Units Code": "29", "Description": "cubic meters per day", "Abbreviated Units": "m^3/d" }, + { "Units Code": "30", "Description": "imperial gallons per hour", "Abbreviated Units": "impgal/h" }, + { "Units Code": "31", "Description": "imperial gallons per day", "Abbreviated Units": "impgal/d" }, + { "Units Code": "32", "Description": "Degrees Celsius", "Abbreviated Units": "C" }, + { "Units Code": "33", "Description": "Degrees Fahrenheit", "Abbreviated Units": "F" }, + { "Units Code": "34", "Description": "Degrees Rankine", "Abbreviated Units": "R" }, + { "Units Code": "35", "Description": "Kelvin", "Abbreviated Units": "K" }, + { "Units Code": "36", "Description": "millivolts", "Abbreviated Units": "mV" }, + { "Units Code": "37", "Description": "ohms", "Abbreviated Units": "ohm" }, + { "Units Code": "38", "Description": "hertz", "Abbreviated Units": "hz" }, + { "Units Code": "39", "Description": "milliamperes", "Abbreviated Units": "mA" }, + { "Units Code": "40", "Description": "gallons", "Abbreviated Units": "usg" }, + { "Units Code": "41", "Description": "liters", "Abbreviated Units": "L" }, + { "Units Code": "42", "Description": "imperial gallons", "Abbreviated Units": "impgal" }, + { "Units Code": "43", "Description": "cubic meters", "Abbreviated Units": "m^3" }, + { "Units Code": "44", "Description": "feet", "Abbreviated Units": "ft" }, + { "Units Code": "45", "Description": "meters", "Abbreviated Units": "m" }, + { "Units Code": "46", "Description": "barrels", "Abbreviated Units": "bbl" }, + { "Units Code": "47", "Description": "inches", "Abbreviated Units": "in" }, + { "Units Code": "48", "Description": "centimeters", "Abbreviated Units": "cm" }, + { "Units Code": "49", "Description": "millimeters", "Abbreviated Units": "mm" }, + { "Units Code": "50", "Description": "minutes", "Abbreviated Units": "min" }, + { "Units Code": "51", "Description": "seconds", "Abbreviated Units": "s" }, + { "Units Code": "52", "Description": "hours", "Abbreviated Units": "h" }, + { "Units Code": "53", "Description": "days", "Abbreviated Units": "d" }, + { "Units Code": "54", "Description": "centistokes", "Abbreviated Units": "centistokes" }, + { "Units Code": "55", "Description": "centipoise", "Abbreviated Units": "cP" }, + { "Units Code": "56", "Description": "microsiemens", "Abbreviated Units": "microsiemens" }, + { "Units Code": "57", "Description": "percent", "Abbreviated Units": "%" }, + { "Units Code": "58", "Description": "volts", "Abbreviated Units": "V" }, + { "Units Code": "59", "Description": "pH", "Abbreviated Units": "pH" }, + { "Units Code": "60", "Description": "grams", "Abbreviated Units": "g" }, + { "Units Code": "61", "Description": "kilograms", "Abbreviated Units": "kg" }, + { "Units Code": "62", "Description": "metric tons", "Abbreviated Units": "t" }, + { "Units Code": "63", "Description": "pounds", "Abbreviated Units": "lb" }, + { "Units Code": "64", "Description": "short tons", "Abbreviated Units": "short ton" }, + { "Units Code": "65", "Description": "long tons", "Abbreviated Units": "long ton" }, + { "Units Code": "66", "Description": "millisiemens per centimeter", "Abbreviated Units": "millisiemens/cm" }, + { "Units Code": "67", "Description": "microsiemens per centimeter", "Abbreviated Units": "microsiemens/cm" }, + { "Units Code": "68", "Description": "newton", "Abbreviated Units": "N" }, + { "Units Code": "69", "Description": "newton meter", "Abbreviated Units": "N m" }, + { "Units Code": "70", "Description": "grams per second", "Abbreviated Units": "g/s" }, + { "Units Code": "71", "Description": "grams per minute", "Abbreviated Units": "g/min" }, + { "Units Code": "72", "Description": "grams per hour", "Abbreviated Units": "g/h" }, + { "Units Code": "73", "Description": "kilograms per second", "Abbreviated Units": "kg/s" }, + { "Units Code": "74", "Description": "kilograms per minute", "Abbreviated Units": "kg/min" }, + { "Units Code": "75", "Description": "kilograms per hour", "Abbreviated Units": "kg/h" }, + { "Units Code": "76", "Description": "kilograms per day", "Abbreviated Units": "kg/d" }, + { "Units Code": "77", "Description": "metric tons per minute", "Abbreviated Units": "t/min" }, + { "Units Code": "78", "Description": "metric tons per hour", "Abbreviated Units": "t/h" }, + { "Units Code": "79", "Description": "metric tons per day", "Abbreviated Units": "t/d" }, + { "Units Code": "80", "Description": "pounds per second", "Abbreviated Units": "lb/s" }, + { "Units Code": "81", "Description": "pounds per minute", "Abbreviated Units": "lb/min" }, + { "Units Code": "82", "Description": "pounds per hour", "Abbreviated Units": "lb/h" }, + { "Units Code": "83", "Description": "pounds per day", "Abbreviated Units": "lb/d" }, + { "Units Code": "84", "Description": "short tons per minute", "Abbreviated Units": "short ton/min" }, + { "Units Code": "85", "Description": "short tons per hour", "Abbreviated Units": "short ton/h" }, + { "Units Code": "86", "Description": "short tons per day", "Abbreviated Units": "short ton/d" }, + { "Units Code": "87", "Description": "long tons per hour", "Abbreviated Units": "long ton/h" }, + { "Units Code": "88", "Description": "long tons per day", "Abbreviated Units": "long ton/d" }, + { "Units Code": "89", "Description": "deka therm", "Abbreviated Units": "Dth" }, + { "Units Code": "90", "Description": "specific gravity units", "Abbreviated Units": "specific gravity units" }, + { "Units Code": "91", "Description": "grams per cubic centimeter", "Abbreviated Units": "g/cm^3" }, + { "Units Code": "92", "Description": "kilograms per cubic meter", "Abbreviated Units": "kg/m^3" }, + { "Units Code": "93", "Description": "pounds per gallon", "Abbreviated Units": "lb/usg" }, + { "Units Code": "94", "Description": "pounds per cubic feet", "Abbreviated Units": "lb/ft^3" }, + { "Units Code": "95", "Description": "grams per milliliter", "Abbreviated Units": "g/mL" }, + { "Units Code": "96", "Description": "kilograms per liter", "Abbreviated Units": "kg/L" }, + { "Units Code": "97", "Description": "grams per liter", "Abbreviated Units": "g/L" }, + { "Units Code": "98", "Description": "pounds per cubic inch", "Abbreviated Units": "lb/in^3" }, + { "Units Code": "99", "Description": "short tons per cubic yard", "Abbreviated Units": "short ton/yd^3" }, + { "Units Code": "100", "Description": "degrees twaddell", "Abbreviated Units": "degTw" }, + { "Units Code": "101", "Description": "degrees brix", "Abbreviated Units": "degBx" }, + { "Units Code": "102", "Description": "degrees baume heavy", "Abbreviated Units": "BH" }, + { "Units Code": "103", "Description": "degrees baume light", "Abbreviated Units": "BL" }, + { "Units Code": "104", "Description": "degrees API", "Abbreviated Units": "degAPI" }, + { "Units Code": "105", "Description": "percent solids per weight", "Abbreviated Units": "% solid/weight" }, + { "Units Code": "106", "Description": "percent solids per volume", "Abbreviated Units": "% solid/volume" }, + { "Units Code": "107", "Description": "degrees balling", "Abbreviated Units": "degrees balling" }, + { "Units Code": "108", "Description": "proof per volume", "Abbreviated Units": "proof/volume" }, + { "Units Code": "109", "Description": "proof per mass", "Abbreviated Units": "proof/mass" }, + { "Units Code": "110", "Description": "bushels", "Abbreviated Units": "bushel" }, + { "Units Code": "111", "Description": "cubic yards", "Abbreviated Units": "yd^3" }, + { "Units Code": "112", "Description": "cubic feet", "Abbreviated Units": "ft^3" }, + { "Units Code": "113", "Description": "cubic inches", "Abbreviated Units": "in^3" }, + { "Units Code": "114", "Description": "inches per second", "Abbreviated Units": "in/s" }, + { "Units Code": "115", "Description": "inches per minute", "Abbreviated Units": "in/min" }, + { "Units Code": "116", "Description": "feet per minute", "Abbreviated Units": "ft/min" }, + { "Units Code": "117", "Description": "degrees per second", "Abbreviated Units": "deg/s" }, + { "Units Code": "118", "Description": "revolutions per second", "Abbreviated Units": "rev/s" }, + { "Units Code": "119", "Description": "revolutions per minute", "Abbreviated Units": "rpm" }, + { "Units Code": "120", "Description": "meters per hour", "Abbreviated Units": "m/hr" }, + { "Units Code": "121", "Description": "normal cubic meters per hour", "Abbreviated Units": "normal m^3/h" }, + { "Units Code": "122", "Description": "normal liter per hour", "Abbreviated Units": "normal L/h" }, + { "Units Code": "123", "Description": "standard cubic feet per minute", "Abbreviated Units": "standard ft^3/min" }, + { "Units Code": "124", "Description": "bbl liq", "Abbreviated Units": "bbl liq" }, + { "Units Code": "125", "Description": "ounce", "Abbreviated Units": "oz" }, + { "Units Code": "126", "Description": "foot pound force", "Abbreviated Units": "ft lb force" }, + { "Units Code": "127", "Description": "kilowatt", "Abbreviated Units": "kW" }, + { "Units Code": "128", "Description": "kilowatt hour", "Abbreviated Units": "KWh" }, + { "Units Code": "129", "Description": "horsepower", "Abbreviated Units": "hp" }, + { "Units Code": "130", "Description": "cubic feet per hour", "Abbreviated Units": "ft^3/h" }, + { "Units Code": "131", "Description": "cubic meters per minute", "Abbreviated Units": "m^3/min" }, + { "Units Code": "132", "Description": "barrels per second", "Abbreviated Units": "bbl/s" }, + { "Units Code": "133", "Description": "barrels per minute", "Abbreviated Units": "bbl/min" }, + { "Units Code": "134", "Description": "barrels per hour", "Abbreviated Units": "bbl/h" }, + { "Units Code": "135", "Description": "barrels per day", "Abbreviated Units": "bbl/d" }, + { "Units Code": "136", "Description": "gallons per hour", "Abbreviated Units": "usg/h" }, + { "Units Code": "137", "Description": "imperial gallons per second", "Abbreviated Units": "impgal/s" }, + { "Units Code": "138", "Description": "liters per hour", "Abbreviated Units": "L/h" }, + { "Units Code": "139", "Description": "parts per million", "Abbreviated Units": "ppm" }, + { "Units Code": "140", "Description": "megacalorie per hour", "Abbreviated Units": "Mcal/h" }, + { "Units Code": "141", "Description": "megajoule per hour", "Abbreviated Units": "MJ/h" }, + { "Units Code": "142", "Description": "British thermal unit per hour", "Abbreviated Units": "BTU/h" }, + { "Units Code": "143", "Description": "degrees", "Abbreviated Units": "degrees" }, + { "Units Code": "144", "Description": "radian", "Abbreviated Units": "rad" }, + { "Units Code": "145", "Description": "inches of water at 15.6 degC (60 degF)", "Abbreviated Units": "inH20 (15.6 degC or 60 degF)" }, + { "Units Code": "146", "Description": "micrograms per liter", "Abbreviated Units": "micrograms/L" }, + { "Units Code": "147", "Description": "micrograms per cubic meter", "Abbreviated Units": "micrograms/m^3" }, + { "Units Code": "148", "Description": "percent consitency", "Abbreviated Units": "% consistency" }, + { "Units Code": "149", "Description": "volume percent", "Abbreviated Units": "volume %" }, + { "Units Code": "150", "Description": "percent steam quality", "Abbreviated Units": "% steam quality" }, + { "Units Code": "151", "Description": "feet in sixteenths", "Abbreviated Units": "ft in sixteenths" }, + { "Units Code": "152", "Description": "cubic feet per pound", "Abbreviated Units": "ft^3/lb" }, + { "Units Code": "153", "Description": "picofarads", "Abbreviated Units": "pF" }, + { "Units Code": "154", "Description": "milliliters per liter", "Abbreviated Units": "mL/L" }, + { "Units Code": "155", "Description": "microliters per liter", "Abbreviated Units": "microliters/L" }, + { "Units Code": "156", "Description": "percent plato", "Abbreviated Units": "% plato" }, + { "Units Code": "157", "Description": "percent lower explosion level", "Abbreviated Units": "% lower explosion level" }, + { "Units Code": "158", "Description": "mega calorie", "Abbreviated Units": "Mcal" }, + { "Units Code": "159", "Description": "kiloohms", "Abbreviated Units": "kohm" }, + { "Units Code": "160", "Description": "megajoule", "Abbreviated Units": "MJ" }, + { "Units Code": "161", "Description": "British thermal unit", "Abbreviated Units": "BTU" }, + { "Units Code": "162", "Description": "normal cubic meter", "Abbreviated Units": "normal m^3" }, + { "Units Code": "163", "Description": "normal liter", "Abbreviated Units": "normal L" }, + { "Units Code": "164", "Description": "standard cubic feet", "Abbreviated Units": "normal ft^3" }, + { "Units Code": "165", "Description": "parts per billion", "Abbreviated Units": "parts/billion" }, + { "Units Code": "166", "Description": "ampere", "Abbreviated Units": "A" }, + { "Units Code": "167", "Description": "millimeters per meter", "Abbreviated Units": "mm/m" }, + { "Units Code": "168", "Description": "seconds since epoch", "Abbreviated Units": "epoch sec" }, + { "Units Code": "169", "Description": "no units", "Abbreviated Units": "no units" }, + { "Units Code": "170", "Description": "percentage", "Abbreviated Units": "%" }, + { "Units Code": "171", "Description": "kilohertz", "Abbreviated Units": "khz" }, + { "Units Code": "235", "Description": "gallons per day", "Abbreviated Units": "usg/d" }, + { "Units Code": "236", "Description": "hectoliters", "Abbreviated Units": "hL" }, + { "Units Code": "237", "Description": "megapascals", "Abbreviated Units": "MPa" }, + { "Units Code": "238", "Description": "inches of water at 4 degC (39.2 degF)", "Abbreviated Units": "inH2O (4 degC or 39.2 degF)" }, + { "Units Code": "239", "Description": "millimeters of water at 4 degC (39.2 degF)", "Abbreviated Units": "mmH2O (4 degC or 39.2 degF)" }, + { "Units Code": "253", "Description": "edge count or pulse count", "Abbreviated Units": "counts" }, + { "Units Code": "254", "Description": "raw ADC code", "Abbreviated Units": "ADC code" }, + { "Units Code": "255", "Description": "data not valid", "Abbreviated Units": "data not valid" } + ]; + + var outputString = ""; + var engUnitsRow = engUnitsTable.find(element => element["Units Code"] == unitCode.toString()); + + if (typeof engUnitsRow == 'undefined') { + return "Unknown Units"; + } + + if (outputType == 0) { + outputString = engUnitsRow["Description"]; + } + else if (outputType == 1) { + outputString = engUnitsRow["Abbreviated Units"]; + } + else { + errorMsg = outputType + " -- " + "Invalid Output Type. Use 0 for Description or 1 for Abbreviated Units."; + throw new Error(errorMsg); + } + + return outputString; +} + +//=========================================================== +// Utility functions +// Decoder doesn't call these functions +// Functions provided for customer usage +// +// List of functions: +// hexToFloat - Conversion from Hex to Float Number +// modbusToFloat_AB_CD_to_ABCD - Takes two Modbus registers, does no swapping, and converts them to 32 bit floating point number +// modbusToFloat_AB_CD_to_DCBA - Takes two Modbus registers, does byte and word swapping, and converts them to 32 bit floating point number +// modbusToFloat_AB_CD_to_BADC - Takes two Modbus registers, does byte swapping, and converts them to 32bit floating point number +// modbusToFloat_AB_CD_to_CDAB - Takes two Modbus registers, does word swapping, and converts them to 32bit floating point number +// printPayload - Prints Decoded payloads in formatted fashion +//=========================================================== + +/* istanbul ignore next */ +function hexToFloat(hex) { + var s = hex >> 31 ? -1 : 1; + var e = (hex >> 23) & 0xFF; + return s * (hex & 0x7fffff | 0x800000) * 1.0 / Math.pow(2, 23) * Math.pow(2, (e - 127)) +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_ABCD(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = (modbusReg1 & 0xFF00) >> 8; + bytes[1] = modbusReg1 & 0xFF; + bytes[2] = (modbusReg0 & 0xFF00) >> 8; + bytes[3] = modbusReg0 & 0xFF; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_DCBA(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = modbusReg0 & 0xFF; + bytes[1] = (modbusReg0 & 0xFF00) >> 8; + bytes[2] = modbusReg1 & 0xFF; + bytes[3] = (modbusReg1 & 0xFF00) >> 8; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_BADC(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = modbusReg1 & 0xFF; + bytes[1] = (modbusReg1 & 0xFF00) >> 8; + bytes[2] = modbusReg0 & 0xFF; + bytes[3] = (modbusReg0 & 0xFF00) >> 8; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_CDAB(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = (modbusReg0 & 0xFF00) >> 8; + bytes[1] = modbusReg0 & 0xFF; + bytes[2] = (modbusReg1 & 0xFF00) >> 8; + bytes[3] = modbusReg1 & 0xFF; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function printPayload(payload) { + if (payload.data.payloadType == "Standard") { + let modbusSlot0HexString = payload.data.Modbus0.toString(16).toUpperCase().padStart(4, 0); + console.log("%s | %s | %s | %s | " + + "DIN1 = %d | DIN2 = %d | DIN3 = %d | WKUP = %d | ADC1 = %d | ADC2 = %d | ADC3 = %d | " + + "Battery = %d | Temperature = %f | Modbus0 = 0x%s |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.DIN1, payload.data.DIN2, payload.data.DIN3, payload.data.WKUP, payload.data.ADC1, payload.data.ADC2, payload.data.ADC3, + payload.data.Battery, payload.data.Temperature, modbusSlot0HexString); + return; + } + + if (payload.data.payloadType == "Modbus Standard") { + dataKeys = Object.keys(payload.data); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + if ((dataKeys[idx] != "fport") && (dataKeys[idx] != "voboType") && (dataKeys[idx] != "payloadType")) { + let fieldName = dataKeys[idx]; + let valueHex = "0x" + payload.data[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + valueHex + " | "; + } + } + console.log("%s\n", stringToPrint); + return; + } + + if (payload.data.payloadType == "Heartbeat 1.0") { + let firmwareRevisionHexString = payload.data.fwVersionMajor.toString(10) + "." + payload.data.fwVersionMinor.toString(10) + "." + payload.data.fwVersionPatch.toString(10) + "." + payload.data.fwVersionCustom.toString(10); + console.log("%s | %s | %s | %s | " + + "Battery Level MV = %d | FW Version = %s | Last RSSI = %d | Analog Voltage Config = %d | Analog Power Time Config = %d | Failed Transmissions Before Rejoin Config = %d | " + + "Cycle Through FSB Config = %d | ACK Enable Config = %d | ACK Frequency Config = %d | ACK Request Config = %d | Battery Level Threshold Config = %d |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.batteryLevelMV, firmwareRevisionHexString, payload.data.recSignalLevels, payload.data.analogVoltageConfig, payload.data.analogPowerTimeConfig, payload.data.failedTransmissionBeforeRejoinConfig, + payload.data.cycleThroughFSB, payload.data.ackEnable, payload.data.ackFreq, payload.data.ackReq, payload.data.batteryLevelThreshold); + return; + } + + if (payload.data.payloadType == "Heartbeat 2.0") { + let firmwareRevisionHexString = payload.data.firmwareRevision.toString(16).toUpperCase().padStart(3, 0); + console.log("%s | %s | %s | %s | " + + "Battery Level = %d | Fatal Errors Total = %d | RSSI Avg = %d | Failed Join Attempts Total = %d | Config Update Occurred = %d | Firmware Revision = 0x%s | " + + "Reboots Total = %d | Failed Transmits Total = %d | Error Event Logs Total = %d | Warning Event Logs Total = %d | Info Event Logs Total = %d | Measurement Packets Total = %d |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.batteryLevel, payload.data.fatalErrorsTotal, payload.data.rssiAvg, payload.data.failedJoinAttemptsTotal, payload.data.configUpdateOccurred, firmwareRevisionHexString, + payload.data.rebootsTotal, payload.data.failedTransmitsTotal, payload.data.errorEventLogsTotal, payload.data.warningEventLogsTotal, payload.data.infoEventLogsTotal, payload.data.measurementPacketsTotal); + return; + } + + if (payload.data.payloadType == "One Analog Input") { + let fractionDigitsData0 = 1; + if (payload.data.engUnitsString0 == "mV" || payload.data.engUnitsString0 == "V") fractionDigitsData0 = 3; + if (payload.data.engUnitsString0 == "ADC code") fractionDigitsData0 = 0; + console.log("%s | %s | %s | %s | %s = %f %s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, payload.data.analogSensorString0, parseFloat(payload.data.sensorData0).toFixed(fractionDigitsData0), payload.data.engUnitsString0); + return; + } + + if (payload.data.payloadType == "Two Analog Inputs") { + let fractionDigitsData0 = 1; + if (payload.data.engUnitsString0 == "mV" || payload.data.engUnitsString0 == "V" || payload.data.engUnitsString0 == "mA" || payload.data.engUnitsString0 == "A") fractionDigitsData0 = 3; + if (payload.data.engUnitsString0 == "ADC code") fractionDigitsData0 = 0; + let fractionDigitsData1 = 1; + if (payload.data.engUnitsString1 == "mV" || payload.data.engUnitsString1 == "V" || payload.data.engUnitsString1 == "mA" || payload.data.engUnitsString1 == "A") fractionDigitsData1 = 3; + if (payload.data.engUnitsString1 == "ADC code") fractionDigitsData1 = 0; + console.log("%s | %s | %s | %s | %s = %f %s | %s = %f %s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, payload.data.analogSensorString0, parseFloat(payload.data.sensorData0).toFixed(fractionDigitsData0), payload.data.engUnitsString0, payload.data.analogSensorString1, parseFloat(payload.data.sensorData1).toFixed(fractionDigitsData1), payload.data.engUnitsString1); + return; + } + + if (payload.data.payloadType == "Analog Input Variable Length") { + var stringToPrint = ""; + for (let i = 0; i < payload.data.numOfAinPayloads; i++) { + analogSensorString = payload.data["analogSensorString" + i]; + engUnitsString = payload.data["engUnitsString" + i]; + sensorData = payload.data.ainPayloads[i].sensorData0; + + let fractionDigitsData = 1; + if (engUnitsString == "mV" || engUnitsString == "V" || engUnitsString == "mA" || engUnitsString == "A") fractionDigitsData = 3; + if (engUnitsString == "ADC code") fractionDigitsData = 0; + stringToPrint = stringToPrint + analogSensorString + " = " + parseFloat(sensorData).toFixed(fractionDigitsData) + " " + engUnitsString + " | "; + } + console.log("%s | %s | %s | %s | %s\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, stringToPrint); + return; + } + + if (payload.data.payloadType == "Modbus Standard Variable Length") { + dataKeys = Object.keys(payload.data.modbusSlots); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + let fieldName = dataKeys[idx]; + let valueHex = "0x" + payload.data.modbusSlots[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + valueHex + " | "; + } + console.log("%s\n", stringToPrint); + return; + } + + if (payload.data.payloadType == "Digital Inputs") { + var stringToPrint = "" + for (let i = 0; i < payload.data.digitalSensorData.length; i++) { + stringToPrint = stringToPrint + payload.data.digitalSensorStrings[i] + " = " + payload.data.digitalSensorData[i].toString() + " | "; + } + console.log("%s | %s | %s | %s | %s\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, stringToPrint); + return; + } + + if (payload.data.payloadType == "Event Log") { + let eventTimestampDateTime = new Date(payload.data.eventTimestamp * 1000).toISOString(); + let eventCodeHexString = payload.data.eventCode.toString(16).toUpperCase().padStart(4, 0); + let metadataHexString = Buffer.from(payload.data.metadata).toString("hex").toUpperCase(); + console.log("%s | %s | %s | %s | Timestamp = %s | Code = 0x%s | Metadata = 0x%s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, eventTimestampDateTime, eventCodeHexString, metadataHexString); + return; + } + + if (payload.data.payloadType == "Modbus Generic") { + let dataKeys = Object.keys(payload.data); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + if ((dataKeys[idx] != "fport") && (dataKeys[idx] != "voboType") && (dataKeys[idx] != "payloadType")) { + let fieldName = dataKeys[idx]; + let regValueHexStr = "0x" + payload.data[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + regValueHexStr + " | "; + } + } + console.log("%s\n", stringToPrint); + return; + } + + console.log(JSON.stringify(payload, null, 4)); + console.log(""); +} + +//=========================================================== +// Changes made to decoder for TagoIO below this point. +// TagoIO executes the decoder file (payload.js) rather than +// calling the Decoder function, so code must be added here +// to map the arguments to the Decoder function, and the +// exports had to be commented out. +//=========================================================== + +const bytes_vb = payload.find((x) => x.variable === "bytes"); +const fPort_vb = payload.find((x) => x.variable === "port" || x.variable === "fPort")?.value; + +if (bytes_vb) { + try { + const buffer = Buffer.from(bytes_vb.value, "hex") + const decoded = Decoder(buffer, fPort_vb); + payload = decoded; + } catch (error: any) { + console.error(error); + payload = [{ variable: "parse_error", value: error.message }]; + } +} + + +// module.exports = { +// customDecoder, decodeUplink, Decoder, addVoboMetadata, lookupAnalogSensorName, lookupDigitalSensorName, lookupUnits, +// hexToFloat, modbusToFloat_AB_CD_to_ABCD, modbusToFloat_AB_CD_to_DCBA, modbusToFloat_AB_CD_to_BADC, modbusToFloat_AB_CD_to_CDAB, printPayload +// } \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-tc/v1.0.0/payload.test.ts b/decoders/connector/volley-boast/vobo-tc/v1.0.0/payload.test.ts new file mode 100644 index 0000000..e63283a --- /dev/null +++ b/decoders/connector/volley-boast/vobo-tc/v1.0.0/payload.test.ts @@ -0,0 +1,348 @@ +import { describe, test, expect } from "vitest"; +import { decoderRun } from "../../../../../src/functions/decoder-run"; + +const file_path = "decoders/connector/volley-boast/vobo-tc/v1.0.0/payload.js" + +const testPayloads = { + "data": [ + { "fport": 21, "bytes": "cc0d3900000c0000000400", "name": "Heartbeat 2.0 Payload" }, + { "fport": 31, "bytes": "0f2041d30000", "name": "One Analog Input Payload" }, + { "fport": 41, "bytes": "d02041cc20003a405d8566", "name": "Two Analog Input Payload" }, + { "fport": 51, "bytes": "01000000", "name": "Digial Input Payload" }, + { "fport": 61, "bytes": "4949b36406800000000000", "name": "Event Log Response Payload" }, + { "fport": 71, "bytes": "55e8031027e80310270000", "name": "Configuration Response Payload" }, + { "fport": 111, "bytes": "0d2041b1a000003a40554fdf012044ab7000022044ab7000032044ab7000042044ab7000052044ab7000062044ab7000", "name": "VoBo-TC Analog Input Variable Length Payload - Cold Joint Temperature, Battery Level and Channels 1 - 6" }, + { "fport": 111, "bytes": "072044ab7000082044ab7000092044ab70000a2044ab70000b2044ab70000c2044ab7000", "name": "VoBo-TC Analog Input Variable Length Payload - Channels 7 - 12" }, + ] +} + + +describe(`fPort: ${testPayloads.data[0].fport} - ${testPayloads.data[0].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[0].bytes }, + { variable: "fPort", value: testPayloads.data[0].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "batteryLevel": 3532, + "fatalErrorsTotal": 0, + "rssiAvg": 57, + "failedJoinAttemptsTotal": 0, + "configUpdateOccurred": 0, + "firmwareRevision": 0, + "rebootsTotal": 3, + "failedTransmitsTotal": 0, + "errorEventLogsTotal": 0, + "warningEventLogsTotal": 0, + "infoEventLogsTotal": 0, + "measurementPacketsTotal": 4, + "fport": 21, + "voboType": "VoBoTC", + "payloadType": "Heartbeat 2.0" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[1].fport} - ${testPayloads.data[1].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[1].bytes }, + { variable: "fPort", value: testPayloads.data[1].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorNum0": 15, + "sensorUnits0": 32, + "sensorData0": 26.375, + "analogSensorString0": "AnalogSensor15", + "engUnitsString0": "C", + "fport": 31, + "voboType": "VoBoTC", + "payloadType": "One Analog Input" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[2].fport} - ${testPayloads.data[2].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[2].bytes }, + { variable: "fPort", value: testPayloads.data[2].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorNum0": 13, + "sensorUnits0": 32, + "sensorData0": 25.515625, + "sensorNum1": 0, + "sensorUnits1": 58, + "sensorData1": 3.4612669944763184, + "analogSensorString0": "Cold Joint Temperature", + "engUnitsString0": "C", + "analogSensorString1": "Battery Voltage", + "engUnitsString1": "V", + "fport": 41, + "voboType": "VoBoTC", + "payloadType": "Two Analog Inputs" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[3].fport} - ${testPayloads.data[3].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[3].bytes }, + { variable: "fPort", value: testPayloads.data[3].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorValid0": 1, + "sensorData0": 0, + "digitalSensorStrings": [ + "WKUP" + ], + "digitalSensorData": [ + 0 + ], + "fport": 51, + "voboType": "VoBoTC", + "payloadType": "Digital Inputs" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[4].fport} - ${testPayloads.data[4].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[4].bytes }, + { variable: "fPort", value: testPayloads.data[4].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "eventTimestamp": 1689471305, + "eventCode": 32774, + "metadata": [ + 0, + 0, + 0, + 0, + 0 + ], + "fport": 61, + "voboType": "VoBoTC", + "payloadType": "Event Log" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[5].fport} - ${testPayloads.data[5].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[5].bytes }, + { variable: "fPort", value: testPayloads.data[5].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "subgroupID": 5, + "sequenceNumber": 5, + "gain11": 1, + "reserved1": 0, + "offset11": 0, + "reserved2": 0, + "gain12": 1, + "reserved3": 0, + "offset12": 0, + "reserved4": 0, + "fport": 71, + "voboType": "VoBoTC", + "payloadType": "Configuration" + }, + "warnings": [], + "errors": [] + }); + }); +}); + + +describe(`fPort: ${testPayloads.data[6].fport} - ${testPayloads.data[6].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[6].bytes }, + { variable: "fPort", value: testPayloads.data[6].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "ainPayloads": [ + { + "sensorNum0": 13, + "sensorUnits0": 32, + "sensorData0": 22.203125 + }, + { + "sensorNum0": 0, + "sensorUnits0": 58, + "sensorData0": 3.3329999446868896 + }, + { + "sensorNum0": 1, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 2, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 3, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 4, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 5, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 6, + "sensorUnits0": 32, + "sensorData0": 1371.5 + } + ], + "numOfAinPayloads": 8, + "analogSensorString0": "Cold Joint Temperature", + "engUnitsString0": "C", + "analogSensorString1": "Battery Voltage", + "engUnitsString1": "V", + "analogSensorString2": "TC1", + "engUnitsString2": "C", + "analogSensorString3": "TC2", + "engUnitsString3": "C", + "analogSensorString4": "TC3", + "engUnitsString4": "C", + "analogSensorString5": "TC4", + "engUnitsString5": "C", + "analogSensorString6": "TC5", + "engUnitsString6": "C", + "analogSensorString7": "TC6", + "engUnitsString7": "C", + "fport": 111, + "voboType": "VoBoTC", + "payloadType": "Analog Input Variable Length" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[7].fport} - ${testPayloads.data[7].name}`, () => { + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[7].bytes }, + { variable: "fPort", value: testPayloads.data[7].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "ainPayloads": [ + { + "sensorNum0": 7, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 8, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 9, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 10, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 11, + "sensorUnits0": 32, + "sensorData0": 1371.5 + }, + { + "sensorNum0": 12, + "sensorUnits0": 32, + "sensorData0": 1371.5 + } + ], + "numOfAinPayloads": 6, + "analogSensorString0": "TC7", + "engUnitsString0": "C", + "analogSensorString1": "TC8", + "engUnitsString1": "C", + "analogSensorString2": "TC9", + "engUnitsString2": "C", + "analogSensorString3": "TC10", + "engUnitsString3": "C", + "analogSensorString4": "TC11", + "engUnitsString4": "C", + "analogSensorString5": "TC12", + "engUnitsString5": "C", + "fport": 111, + "voboType": "VoBoTC", + "payloadType": "Analog Input Variable Length" + }, + "warnings": [], + "errors": [] + }); + }); +}); + + + diff --git a/decoders/connector/volley-boast/vobo-xp/assets/logo.png b/decoders/connector/volley-boast/vobo-xp/assets/logo.png new file mode 100644 index 0000000..416fb8f Binary files /dev/null and b/decoders/connector/volley-boast/vobo-xp/assets/logo.png differ diff --git a/decoders/connector/volley-boast/vobo-xp/connector.jsonc b/decoders/connector/volley-boast/vobo-xp/connector.jsonc new file mode 100644 index 0000000..6e7091e --- /dev/null +++ b/decoders/connector/volley-boast/vobo-xp/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "VoBo XP", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/volley-boast/vobo-xp/description.md b/decoders/connector/volley-boast/vobo-xp/description.md new file mode 100644 index 0000000..9697b2f --- /dev/null +++ b/decoders/connector/volley-boast/vobo-xp/description.md @@ -0,0 +1 @@ +Sensor agnostic hazardous area LoRaWAN bridge endpoint (Class A or C, externally powered). \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-xp/v1.0.0/payload-config.jsonc b/decoders/connector/volley-boast/vobo-xp/v1.0.0/payload-config.jsonc new file mode 100644 index 0000000..a3692cf --- /dev/null +++ b/decoders/connector/volley-boast/vobo-xp/v1.0.0/payload-config.jsonc @@ -0,0 +1,24 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The VoBo XP is a Hazardous Area LoRaWAN (Class A or C) endpoint which bridges Analog, Digital and RS485 Modbus RTU transmitters to LoRaWAN and is externally powered.", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-loriot/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-xp/v1.0.0/payload.js b/decoders/connector/volley-boast/vobo-xp/v1.0.0/payload.js new file mode 100644 index 0000000..2244caf --- /dev/null +++ b/decoders/connector/volley-boast/vobo-xp/v1.0.0/payload.js @@ -0,0 +1,1512 @@ +/* istanbul ignore next */ +function customDecoder(bytes, fport) { + var decoded = Decoder(bytes, fport); + //======================================================= + // Insert your customization here + + + //======================================================= + return decoded; +} + +//=========================================================== +// Volley Boast Decoder +// +// Volley Boast maintains the below code, therefore +// DO NOT EDIT BELOW THIS POINT!! +//=========================================================== +const DECODER_MAJOR_VERSION = 2; +const DECODER_MINOR_VERSION = 1; +const DECODER_PATCH_VERSION = 1; + +function decodeUplink(input) { + const decoded = Decoder(input.bytes, input.fPort); + return decoded; +} + +function Decoder(bytes, fport) { + var decoded = {}; + var decodedData = {}; + var warnings = []; + var errors = []; + + if (fport == 1) { + decodedData = parseStandardPayload(bytes); + } + else if ((fport >= 2) && (fport <= 9)) { + decodedData = parseModbusStandardPayload(bytes, fport); + } + else if (fport == 10) { + decodedData = parseHeartbeat1p0Payload(bytes); + } + else if ((fport >= 20) && (fport <= 29)) { + decodedData = parseHeartbeat2p0Payload(bytes); + } + else if ((fport >= 30) && (fport <= 39)) { + decodedData = parseOneAnalogSensorPayload(bytes); + } + else if ((fport >= 40) && (fport <= 49)) { + decodedData = parseTwoAnalogSensorsPayload(bytes); + } + else if ((fport >= 50) && (fport <= 59)) { + decodedData = parseDigitalSensorsPayload(bytes); + } + else if ((fport >= 60) && (fport <= 69)) { + decodedData = parseEventLogPayload(bytes); + } + else if ((fport >= 70) && (fport <= 79)) { + decodedData = parseConfigurationPayload(bytes, fport); + } + else if ((fport >= 100) && (fport <= 109)) { + decodedData = parseModbusGenericPayload(bytes, fport); + } + else if ((fport >= 110) && (fport <= 119)) { + decodedData = parseAnalogInputVariableLengthPayload(bytes, fport); + } + else if ((fport >= 120) && (fport <= 129)) { + decodedData = parseModbusStandardVariableLengthPayload(bytes, fport); + } + else { + errors.push('unknown FPort') + } + + decoded = addVoboMetadata(decodedData, fport); + decoded.warnings = warnings; + decoded.errors = errors; + + return decoded; +} + +function addVoboMetadata(decodedData, fport) { + var payload = {}; + payload.data = decodedData; + + var voboType = ""; + var payloadType = ""; + var portLastDigit = fport % 10; + + if ((portLastDigit == 0) || ((fport >= 1) && (fport < 10))) voboType = "VoBoXX"; + else if (portLastDigit == 1) voboType = "VoBoTC"; + else if (portLastDigit == 2) voboType = "VoBoXP"; + + if (fport == 1) payloadType = "Standard"; + else if ((fport >= 2) && (fport <= 9)) { + payloadType = "Modbus Standard"; + } + else if (fport == 10) payloadType = "Heartbeat 1.0"; + else if ((fport >= 20) && (fport <= 29)) { + payloadType = "Heartbeat 2.0"; + } + else if ((fport >= 30) && (fport <= 39)) { + payloadType = "One Analog Input"; + payload.data.analogSensorString0 = lookupAnalogSensorName(voboType, payload.data.sensorNum0); + payload.data.engUnitsString0 = lookupUnits(1, payload.data.sensorUnits0); + } + else if ((fport >= 40) && (fport <= 49)) { + payloadType = "Two Analog Inputs"; + payload.data.analogSensorString0 = lookupAnalogSensorName(voboType, payload.data.sensorNum0); + payload.data.engUnitsString0 = lookupUnits(1, payload.data.sensorUnits0); + payload.data.analogSensorString1 = lookupAnalogSensorName(voboType, payload.data.sensorNum1); + payload.data.engUnitsString1 = lookupUnits(1, payload.data.sensorUnits1); + } + else if ((fport >= 50) && (fport <= 59)) { + payloadType = "Digital Inputs"; + payload.data.digitalSensorStrings = []; + payload.data.digitalSensorData = []; + for (let i = 0; i < 16; i++) { + let valid = (payload.data.sensorValid0 >> i) & 1; + if (valid == 1) { + payload.data.digitalSensorStrings.push(lookupDigitalSensorName(voboType, i)); + let data = (payload.data.sensorData0 >> i) & 1; + payload.data.digitalSensorData.push(data); + } + } + } + else if ((fport >= 60) && (fport <= 69)) payloadType = "Event Log"; + else if ((fport >= 70) && (fport <= 79)) payloadType = "Configuration"; + else if ((fport >= 100) && (fport <= 109)) payloadType = "Modbus Generic"; + else if ((fport >= 110) && (fport <= 119)) { + payloadType = "Analog Input Variable Length"; + for (let i = 0; i < payload.data.numOfAinPayloads; i++) { + payload.data["analogSensorString" + i] = lookupAnalogSensorName(voboType, payload.data.ainPayloads[i].sensorNum0); + payload.data["engUnitsString" + i] = lookupUnits(1, payload.data.ainPayloads[i].sensorUnits0); + } + } + else if ((fport >= 120) && (fport <= 129)) payloadType = "Modbus Standard Variable Length"; + + payload.data.fport = fport; + payload.data.voboType = voboType; + payload.data.payloadType = payloadType; + + return payload; +} + +function parseStandardPayload(bytes) { + var decoded = {}; + decoded.DIN1 = bytes[0] & 0x01; // Discrete digital 1 (1-bit) + decoded.DIN2 = bytes[0] >> 1 & 0x01; // Discrete digital 2 (1-bit) + decoded.DIN3 = bytes[0] >> 2 & 0x01; // Discrete digital 3 (1-bit) + decoded.WKUP = bytes[0] >> 3 & 0x01; // Discrete digital wakeup (1-bit) + decoded.ADC1 = ((bytes[0] & 0xf0) >> 4) | (bytes[1] << 4); // ADC 1 (12-bit) + decoded.ADC2 = (bytes[3] & 0x0f) << 8 | bytes[2]; // ADC 2 (12-bit) + decoded.ADC3 = ((bytes[3] & 0xf0) >> 4) | (bytes[4] << 4); // ADC 3 (12-bit) + decoded.Battery = ((bytes[6] & 0x0f) << 8 | bytes[5]) * 4; // ADC battery (12-bit) + if ((bytes[7] >> 7 & 0x01) == 0) { + decoded.Temperature = (((bytes[6] & 0xf0) >> 4) | (bytes[7] << 4)) * 0.125; // ADC temperature (12-bit) above 0 degrees C + } else { + decoded.Temperature = (4096 - (((bytes[6] & 0xf0) >> 4) | (bytes[7] << 4))) * 0.125 * (-1); // ADC temperature (12-bit) below 0 degrees C + } + decoded.Modbus0 = bytes[9] << 8 | bytes[8]; // Modbus-RS485 (16-bit) + return decoded; +} + +function parseModbusStandardPayload(bytes, fport) { + var startIdx = (fport - 1) * 5 - 4; + var decoded = {}; + decoded["Modbus" + startIdx++] = (bytes[1] << 8) | bytes[0]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[3] << 8) | bytes[2]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[5] << 8) | bytes[4]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx++] = (bytes[7] << 8) | bytes[6]; // Modbus-RS485 (16-bit) + decoded["Modbus" + startIdx] = (bytes[9] << 8) | bytes[8]; // Modbus-RS485 (16-bit) + return decoded; +} + +function parseHeartbeat1p0Payload(bytes) { + var decoded = {}; + decoded.batteryLevelMV = ((bytes[1] & 0x0f) << 8 | bytes[0]) * 4; // ADC battery (12-bit) + decoded.fwVersionMajor = (bytes[2] & 0x0f) | (bytes[1] & 0xf0) >> 4; // FW Version Major (8-bit) + decoded.fwVersionMinor = (bytes[3] & 0x0f) | (bytes[2] & 0xf0) >> 4; // FW Version Minor (8-bit) + decoded.fwVersionPatch = (bytes[4] & 0x0f) | (bytes[3] & 0xf0) >> 4; // FW Version Patch (8-bit) + decoded.fwVersionCustom = (bytes[4] & 0xf0) >> 4; // FW Version Custom (4-bit) + decoded.recSignalLevels = bytes[5] * -1; // Last RSSI (8 bits) + decoded.analogVoltageConfig = (bytes[6] & 0x1f); // Analog Voltage Config (5 bits) + decoded.analogPowerTimeConfig = parseFloat((((bytes[7] & 0x1F) << 3 | (bytes[6] & 0xE0) >> 5) / 10).toFixed(1)); // Analog Power Time Config (8 bits) + decoded.failedTransmissionBeforeRejoinConfig = (bytes[8] & 0x01) << 3 | (bytes[7] & 0xE0) >> 5; // Failed Transmissions Before Rejoin Config (4 bits) + decoded.cycleThroughFSB = (bytes[8] & 0x02) >> 1; // Cycle Through FSB (1 bit) + decoded.ackEnable = (bytes[8] & 0x04) >> 2; // ACK Enable (1 bit) + decoded.ackFreq = (bytes[8] & 0x78) >> 3; // ACK Frequency (4 bits) + decoded.ackReq = (bytes[9] & 0x07) << 1 | (bytes[8] & 0x80) >> 7; // ACK Request (4 bits) + decoded.batteryLevelThreshold = (((bytes[10] & 0x7F) << 5) | (bytes[9] & 0xF8) >> 3) * 4; // Battery Level Threshold (12-bit) + + return decoded; +} + +function parseHeartbeat2p0Payload(bytes) { + var decoded = {}; + decoded.batteryLevel = (bytes[1] & 0x0F) << 8 | bytes[0]; // Current Battery level in mV + decoded.fatalErrorsTotal = (bytes[1] & 0xF0) >> 4; // Total number of Fatal Error detected by the handler (triggering reboot) for the last HeartBeat interval + decoded.rssiAvg = bytes[2]; // RSSI of received signal from LoRaWAN GW - average for the last HeartBeat interval + decoded.failedJoinAttemptsTotal = bytes[3] & 0x7F; // Total Number of Failed Join Attempts to LoRaWAN network for the last HeartBeat interval + decoded.configUpdateOccurred = (bytes[3] & 0x80) >> 7; // Indicates if one or more config updates occurred for the last HeartBeat interval + decoded.firmwareRevision = (bytes[5] & 0x03) << 8 | bytes[4]; // Firmware revision encoding. + decoded.rebootsTotal = (bytes[5] & 0x1C) >> 2; // Total number of VoBo reboots not including Fatal Error for the last HeartBeat interval + decoded.failedTransmitsTotal = (bytes[5] & 0xE0) >> 5; // Total Number of Failed LoRaWAN Transmits for the last HeartBeat interval + decoded.errorEventLogsTotal = bytes[6]; // Total number of Error Event Logs written for the last HeartBeat interval + decoded.warningEventLogsTotal = bytes[7]; // Total number of Warning Event Logs written for the last HeartBeat interval + decoded.infoEventLogsTotal = bytes[8]; // Total number of Info Event Logs written for the last HeartBeat interval + decoded.measurementPacketsTotal = (bytes[10]) << 8 | bytes[9]; // Number of Measurements Packets successfully sent for the last HeartBeat interval + + return decoded; +} + +function bytesToFloat32(bytesArray) { + var buffer = new ArrayBuffer(4); + var bytesRaw = new Uint8Array(buffer); + bytesRaw[0] = bytesArray[0]; + bytesRaw[1] = bytesArray[1]; + bytesRaw[2] = bytesArray[2]; + bytesRaw[3] = bytesArray[3]; + var float32Res = new DataView(buffer).getFloat32(0, false); + + return float32Res; +} + +function parseOneAnalogSensorPayload(bytes) { + var decoded = {}; + decoded.sensorNum0 = bytes[0]; // Sensor Num 0 (8-bit) + decoded.sensorUnits0 = bytes[1]; // Sensor Units 0 (8-bit) + decoded.sensorData0 = bytesToFloat32(bytes.slice(2, 6)); // Sensor Data 0 (32-bit) + + return decoded; +} + +function parseTwoAnalogSensorsPayload(bytes) { + var decoded = {}; + decoded.sensorNum0 = (bytes[0] & 0xF0) >> 4; // Sensor Num 0 (4-bit) + decoded.sensorUnits0 = bytes[1]; // Sensor Units 0 (8-bit) + decoded.sensorData0 = bytesToFloat32(bytes.slice(2, 6)); // Sensor Data 0 (32-bit) + decoded.sensorNum1 = bytes[0] & 0x0F; // Sensor Num 1 (4-bit) + decoded.sensorUnits1 = bytes[6]; // Sensor Units 1 (8-bit) + decoded.sensorData1 = bytesToFloat32(bytes.slice(7, 11)); // Sensor Data 1 (32-bit) + + return decoded; +} + +function parseDigitalSensorsPayload(bytes) { + var decoded = {}; + decoded.sensorValid0 = (bytes[1] << 8) | bytes[0]; // Sensor Valid 0 (16-bit) + decoded.sensorData0 = (bytes[3] << 8) | bytes[2]; // Sensor Data 0 (16-bit) + + return decoded; +} + +function parseEventLogPayload(bytes) { + var decoded = {}; + decoded.eventTimestamp = bytes[0] + bytes[1] * Math.pow(2, 8) + bytes[2] * Math.pow(2, 16) + bytes[3] * Math.pow(2, 24); + decoded.eventCode = bytes[4] + bytes[5] * Math.pow(2, 8); + decoded.metadata = Array.prototype.slice.call(bytes.slice(6, 11), 0); + + return decoded; +} + +function parseVoboLibGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.transRejoin = bytes[1] & 0x0F; // Transmission Rejoin (4-bit) + decoded.ackFrequency = (bytes[1] & 0xF0) >> 4; // Acknowledgement Frequency for Data (4-bit) + decoded.lowBattery = parseFloat(((bytes[2] & 0x0F) / 10.0 + 2.5).toFixed(1)); // Low Battery Threshold (4-bit) + decoded.reserved1 = (bytes[2] & 0x10) >> 4; // Reserved 1 Field (1-bit) + decoded.heartbeatAckEnable = Boolean((bytes[2] & 0x20) >> 5); // Acknowledgement Enable for Heartbeat (1-bit) + decoded.operationMode = (bytes[2] & 0x40) >> 6; // Operation Mode (1-bit) + decoded.cycleSubBands = Boolean((bytes[2] & 0x80) >> 7); // Cycle Sub Bands Enable (1-bit) + decoded.ackRetries = (bytes[3] & 0x07); // Acknowledgement Retries (3-bit) + decoded.reservedLL = (bytes[3] & 0x38) >> 3; // Reserved LL (3-bit) + decoded.ackEnable = Boolean((bytes[3] & 0x40) >> 6); // Acknowledgement Enable for Data (1-bit) + decoded.heartbeatEnable = Boolean((bytes[3] & 0x80) >> 7); // Heartbeat Enable (1-bit) + decoded.cycleTime = ((bytes[6] & 0x03) << 16) | (bytes[5] << 8) | bytes[4]; // Cycle Time (18-bit) + decoded.backOffReset = (bytes[6] & 0xFC) >> 2; // BackOff Reset (6-bit) + decoded.reservedRD = bytes[7] & 0x3F; // Reserved RD (6-bit) + decoded.reserved2 = (bytes[7] & 0xC0) >> 6; // Reserved 2 Field (2-bit) + decoded.resendAttempts = bytes[8] & 0x0F; // Resend Attempts (4-bit) + decoded.freqSubBand = (bytes[8] & 0xF0) >> 4; // Frequency Sub Band (4-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.timeSyncInterval = bytes[1]; // Time Sync Interval in 30 minutes quantities (8-bit) + decoded.joinEUI = ""; // LoRaWAN Join EUI (64-bit) + for (let i = 9; i >= 2; i--) { + decoded.joinEUI += bytes[i].toString(16).toUpperCase().padStart(2, 0); + if (i != 2) decoded.joinEUI += "-"; + } + decoded.joinNonceResetEnable = Boolean(bytes[10] & 0x01); // Join Nonce Reset Enable (1-bit) + decoded.timeSyncWakeupEnable = Boolean((bytes[10] & 0x02) >> 1); // Time Sync Wakeup Enable (1-bit) + decoded.reserved1 = (bytes[10] & 0xFC) >> 2; // Reserved 1 Field (1-bit) + } + + return decoded; +} + +function parseVoboLibVoboSyncConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.vbsNodeNumber = (bytes[2] << 8) | bytes[1]; // VoboSync Node Number (16-bit) + decoded.vbsTimeReference = bytes.slice(3, 7).readUInt32LE(); // VoboSync Time Reference (32-bit) + decoded.reservedVCPDS = bytes[7] & 0x0F; // Reserved VCPDS (4-bit) + decoded.reservedVMPDS = (bytes[7] & 0xF0) >> 4; // Reserved VMPDS (4-bit) + decoded.reservedVUDS = bytes[8] & 0x0F; // Reserved VUDS (4-bit) + decoded.reserved1 = (bytes[8] & 0x30) >> 4; // Reserved 1 Field (2-bit) + decoded.reservedVSAE = Boolean((bytes[8] & 0x40) >> 6); // Reserved VSAE (1-bit) + decoded.vbsEnable = Boolean((bytes[8] & 0x80) >> 7); // VoboSync Enable (1-bit) + decoded.reserved3 = bytes[9] & 0x3F; // Reserved 3 (6-bit) + decoded.reserved2 = (bytes[9] & 0xC0) >> 6; // Reserved 2 Field (2-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.vbsMeasurementDelaySec = bytes[1] & 0x7F; // VoboSync Measurement Delay (7-bit) + decoded.reserved1 = (bytes[1] & 0x80) >> 7; // Reserved 1 Field (1-bit) + decoded.vbsUplinkDelaySec = bytes[2] & 0x7F; // VoboSync Uplink Delay (7-bit) + decoded.reserved2 = (bytes[2] & 0x80) >> 7; // Reserved 2 Field (1-bit) + } + + return decoded; +} + +function parseVoboXXGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + decoded.analogVoltage = parseFloat((bytes[1] / 10).toFixed(1)); // Analog Voltage (8-bit) + decoded.powerTime = parseFloat((bytes[2] / 10).toFixed(1)); // Analog Power Time (8-bit) + decoded.mbEnable = Boolean(bytes[3] & 0x01); // Modbus Enable (1-bit) + decoded.engUnitsEnable = Boolean((bytes[3] >> 1) & 0x01); // Engineering Units Enable (1-bit) + decoded.din1TransmitEnable = Boolean((bytes[3] >> 2) & 0x01); // DIN1 Transmit Enable (1-bit) + decoded.din2TransmitEnable = Boolean((bytes[3] >> 3) & 0x01); // DIN2 Transmit Enable (1-bit) + decoded.din3TransmitEnable = Boolean((bytes[3] >> 4) & 0x01); // DIN3 Transmit Enable (1-bit) + decoded.wkupTransmitEnable = Boolean((bytes[3] >> 5) & 0x01); // WKUP Transmit Enable (1-bit) + decoded.ain1TransmitEnable = Boolean((bytes[3] >> 6) & 0x01); // AIN1 Transmit Enable (1-bit) + decoded.ain2TransmitEnable = Boolean((bytes[3] >> 7) & 0x01); // AIN2 Transmit Enable (1-bit) + decoded.ain3TransmitEnable = Boolean(bytes[4] & 0x01); // AIN3 Transmit Enable (1-bit) + decoded.batteryLevelTransmitEnable = Boolean((bytes[4] >> 1) & 0x01); // Battery Level Transmit Enable (1-bit) + decoded.adcTemperatureTransmitEnable = Boolean((bytes[4] >> 2) & 0x01); // ADC Temperature Transmit Enable (1-bit) + decoded.mbTransmitEnable = Boolean((bytes[4] >> 3) & 0x01); // Modbus Transmit Enable (1-bit) + decoded.ainPayloadType = (bytes[4] >> 4) & 0x01; // Ain Payload Type (1-bit) + decoded.reservedMAWE = Boolean((bytes[4] >> 5) & 0x01); // Reserved MAWE Field (1-bit) + decoded.reservedMADE = Boolean((bytes[4] >> 6) & 0x01); // Reserved MADE Field (1-bit) + decoded.reservedMAME = Boolean((bytes[4] >> 7) & 0x01); // Reserved MAME Field (1-bit) + + return decoded; +} + +function parseVoboXXModbusGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + decoded.mbTimeout = (bytes[2] << 8) | bytes[1]; // Modbus Timeout in milliseconds (16-bit) + decoded.mbBaud = bytes[3] & 0x0F; // Modbus Baud Rate - encoded (4-bit) + decoded.mbStopBits = (bytes[3] >> 4) & 0x03; // Modbus Stop Bits (2-bit) + decoded.mbParity = (bytes[3] >> 6) & 0x03; // Modbus Parity (2-bit) + decoded.mbPayloadType = bytes[4] & 0x03; // Modbus Payload Type (2-bit) + decoded.reserved1 = (bytes[4] >> 2) & 0x3F; // Reserved 1 Field (6-bit) + + return decoded; +} + +function parseVoboXXModbusGroupsEnableConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.subgroupID == 6) { + decoded.mbFirstCycleG1 = Boolean(bytes[1] & 0x01); // Group 1 First Cycle Enable + decoded.mbFirstCycleG2 = Boolean((bytes[1] >> 1) & 0x01); // Group 2 First Cycle Enable + decoded.mbFirstCycleG3 = Boolean((bytes[1] >> 2) & 0x01); // Group 3 First Cycle Enable + decoded.mbFirstCycleG4 = Boolean((bytes[1] >> 3) & 0x01); // Group 4 First Cycle Enable + decoded.mbFirstCycleG5 = Boolean((bytes[1] >> 4) & 0x01); // Group 5 First Cycle Enable + decoded.mbFirstCycleG6 = Boolean((bytes[1] >> 5) & 0x01); // Group 6 First Cycle Enable + decoded.mbFirstCycleG7 = Boolean((bytes[1] >> 6) & 0x01); // Group 7 First Cycle Enable + decoded.mbFirstCycleG8 = Boolean((bytes[1] >> 7) & 0x01); // Group 8 First Cycle Enable + decoded.mbFirstCycleG9 = Boolean(bytes[2] & 0x01); // Group 9 First Cycle Enable + decoded.mbFirstCycleG10 = Boolean((bytes[2] >> 1) & 0x01); // Group 10 First Cycle Enable + decoded.mbFirstCycleG11 = Boolean((bytes[2] >> 2) & 0x01); // Group 11 First Cycle Enable + decoded.mbFirstCycleG12 = Boolean((bytes[2] >> 3) & 0x01); // Group 12 First Cycle Enable + decoded.mbFirstCycleG13 = Boolean((bytes[2] >> 4) & 0x01); // Group 13 First Cycle Enable + decoded.mbFirstCycleG14 = Boolean((bytes[2] >> 5) & 0x01); // Group 14 First Cycle Enable + decoded.mbFirstCycleG15 = Boolean((bytes[2] >> 6) & 0x01); // Group 15 First Cycle Enable + decoded.mbFirstCycleG16 = Boolean((bytes[2] >> 7) & 0x01); // Group 16 First Cycle Enable + decoded.mbFirstCycleG17 = Boolean(bytes[3] & 0x01); // Group 17 First Cycle Enable + decoded.mbFirstCycleG18 = Boolean((bytes[3] >> 1) & 0x01); // Group 18 First Cycle Enable + decoded.mbFirstCycleG19 = Boolean((bytes[3] >> 2) & 0x01); // Group 19 First Cycle Enable + decoded.mbFirstCycleG20 = Boolean((bytes[3] >> 3) & 0x01); // Group 20 First Cycle Enable + decoded.mbFirstCycleG21 = Boolean((bytes[3] >> 4) & 0x01); // Group 21 First Cycle Enable + decoded.mbFirstCycleG22 = Boolean((bytes[3] >> 5) & 0x01); // Group 22 First Cycle Enable + decoded.mbFirstCycleG23 = Boolean((bytes[3] >> 6) & 0x01); // Group 23 First Cycle Enable + decoded.mbFirstCycleG24 = Boolean((bytes[3] >> 7) & 0x01); // Group 24 First Cycle Enable + decoded.mbFirstCycleG25 = Boolean(bytes[4] & 0x01); // Group 25 First Cycle Enable + decoded.mbFirstCycleG26 = Boolean((bytes[4] >> 1) & 0x01); // Group 26 First Cycle Enable + decoded.mbFirstCycleG27 = Boolean((bytes[4] >> 2) & 0x01); // Group 27 First Cycle Enable + decoded.mbFirstCycleG28 = Boolean((bytes[4] >> 3) & 0x01); // Group 28 First Cycle Enable + decoded.mbFirstCycleG29 = Boolean((bytes[4] >> 4) & 0x01); // Group 29 First Cycle Enable + decoded.mbFirstCycleG30 = Boolean((bytes[4] >> 5) & 0x01); // Group 30 First Cycle Enable + decoded.mbFirstCycleG31 = Boolean((bytes[4] >> 6) & 0x01); // Group 31 First Cycle Enable + decoded.mbFirstCycleG32 = Boolean((bytes[4] >> 7) & 0x01); // Group 32 First Cycle Enable + decoded.mbFirstCycleG33 = Boolean(bytes[5] & 0x01); // Group 33 First Cycle Enable + decoded.mbFirstCycleG34 = Boolean((bytes[5] >> 1) & 0x01); // Group 34 First Cycle Enable + decoded.mbFirstCycleG35 = Boolean((bytes[5] >> 2) & 0x01); // Group 35 First Cycle Enable + decoded.mbFirstCycleG36 = Boolean((bytes[5] >> 3) & 0x01); // Group 36 First Cycle Enable + decoded.mbFirstCycleG37 = Boolean((bytes[5] >> 4) & 0x01); // Group 37 First Cycle Enable + decoded.mbFirstCycleG38 = Boolean((bytes[5] >> 5) & 0x01); // Group 38 First Cycle Enable + decoded.mbFirstCycleG39 = Boolean((bytes[5] >> 6) & 0x01); // Group 39 First Cycle Enable + decoded.mbFirstCycleG40 = Boolean((bytes[5] >> 7) & 0x01); // Group 40 First Cycle Enable + decoded.mbFirstCycleG41 = Boolean(bytes[6] & 0x01); // Group 41 First Cycle Enable + decoded.reserved1 = (bytes[6] >> 1) & 0x7F; // Reserved 1 Field (7-bit) + } + if (decoded.subgroupID == 7) { + decoded.mbSubseqCyclesG1 = Boolean(bytes[1] & 0x01); // Group 1 Subsequent Cycle Enable + decoded.mbSubseqCyclesG2 = Boolean((bytes[1] >> 1) & 0x01); // Group 2 Subsequent Cycle Enable + decoded.mbSubseqCyclesG3 = Boolean((bytes[1] >> 2) & 0x01); // Group 3 Subsequent Cycle Enable + decoded.mbSubseqCyclesG4 = Boolean((bytes[1] >> 3) & 0x01); // Group 4 Subsequent Cycle Enable + decoded.mbSubseqCyclesG5 = Boolean((bytes[1] >> 4) & 0x01); // Group 5 Subsequent Cycle Enable + decoded.mbSubseqCyclesG6 = Boolean((bytes[1] >> 5) & 0x01); // Group 6 Subsequent Cycle Enable + decoded.mbSubseqCyclesG7 = Boolean((bytes[1] >> 6) & 0x01); // Group 7 Subsequent Cycle Enable + decoded.mbSubseqCyclesG8 = Boolean((bytes[1] >> 7) & 0x01); // Group 8 Subsequent Cycle Enable + decoded.mbSubseqCyclesG9 = Boolean(bytes[2] & 0x01); // Group 9 Subsequent Cycle Enable + decoded.mbSubseqCyclesG10 = Boolean((bytes[2] >> 1) & 0x01); // Group 10 Subsequent Cycle Enable + decoded.mbSubseqCyclesG11 = Boolean((bytes[2] >> 2) & 0x01); // Group 11 Subsequent Cycle Enable + decoded.mbSubseqCyclesG12 = Boolean((bytes[2] >> 3) & 0x01); // Group 12 Subsequent Cycle Enable + decoded.mbSubseqCyclesG13 = Boolean((bytes[2] >> 4) & 0x01); // Group 13 Subsequent Cycle Enable + decoded.mbSubseqCyclesG14 = Boolean((bytes[2] >> 5) & 0x01); // Group 14 Subsequent Cycle Enable + decoded.mbSubseqCyclesG15 = Boolean((bytes[2] >> 6) & 0x01); // Group 15 Subsequent Cycle Enable + decoded.mbSubseqCyclesG16 = Boolean((bytes[2] >> 7) & 0x01); // Group 16 Subsequent Cycle Enable + decoded.mbSubseqCyclesG17 = Boolean(bytes[3] & 0x01); // Group 17 Subsequent Cycle Enable + decoded.mbSubseqCyclesG18 = Boolean((bytes[3] >> 1) & 0x01); // Group 18 Subsequent Cycle Enable + decoded.mbSubseqCyclesG19 = Boolean((bytes[3] >> 2) & 0x01); // Group 19 Subsequent Cycle Enable + decoded.mbSubseqCyclesG20 = Boolean((bytes[3] >> 3) & 0x01); // Group 20 Subsequent Cycle Enable + decoded.mbSubseqCyclesG21 = Boolean((bytes[3] >> 4) & 0x01); // Group 21 Subsequent Cycle Enable + decoded.mbSubseqCyclesG22 = Boolean((bytes[3] >> 5) & 0x01); // Group 22 Subsequent Cycle Enable + decoded.mbSubseqCyclesG23 = Boolean((bytes[3] >> 6) & 0x01); // Group 23 Subsequent Cycle Enable + decoded.mbSubseqCyclesG24 = Boolean((bytes[3] >> 7) & 0x01); // Group 24 Subsequent Cycle Enable + decoded.mbSubseqCyclesG25 = Boolean(bytes[4] & 0x01); // Group 25 Subsequent Cycle Enable + decoded.mbSubseqCyclesG26 = Boolean((bytes[4] >> 1) & 0x01); // Group 26 Subsequent Cycle Enable + decoded.mbSubseqCyclesG27 = Boolean((bytes[4] >> 2) & 0x01); // Group 27 Subsequent Cycle Enable + decoded.mbSubseqCyclesG28 = Boolean((bytes[4] >> 3) & 0x01); // Group 28 Subsequent Cycle Enable + decoded.mbSubseqCyclesG29 = Boolean((bytes[4] >> 4) & 0x01); // Group 29 Subsequent Cycle Enable + decoded.mbSubseqCyclesG30 = Boolean((bytes[4] >> 5) & 0x01); // Group 30 Subsequent Cycle Enable + decoded.mbSubseqCyclesG31 = Boolean((bytes[4] >> 6) & 0x01); // Group 31 Subsequent Cycle Enable + decoded.mbSubseqCyclesG32 = Boolean((bytes[4] >> 7) & 0x01); // Group 32 Subsequent Cycle Enable + decoded.mbSubseqCyclesG33 = Boolean(bytes[5] & 0x01); // Group 33 Subsequent Cycle Enable + decoded.mbSubseqCyclesG34 = Boolean((bytes[5] >> 1) & 0x01); // Group 34 Subsequent Cycle Enable + decoded.mbSubseqCyclesG35 = Boolean((bytes[5] >> 2) & 0x01); // Group 35 Subsequent Cycle Enable + decoded.mbSubseqCyclesG36 = Boolean((bytes[5] >> 3) & 0x01); // Group 36 Subsequent Cycle Enable + decoded.mbSubseqCyclesG37 = Boolean((bytes[5] >> 4) & 0x01); // Group 37 Subsequent Cycle Enable + decoded.mbSubseqCyclesG38 = Boolean((bytes[5] >> 5) & 0x01); // Group 38 Subsequent Cycle Enable + decoded.mbSubseqCyclesG39 = Boolean((bytes[5] >> 6) & 0x01); // Group 39 Subsequent Cycle Enable + decoded.mbSubseqCyclesG40 = Boolean((bytes[5] >> 7) & 0x01); // Group 40 Subsequent Cycle Enable + decoded.mbSubseqCyclesG41 = Boolean(bytes[6] & 0x01); // Group 41 Subsequent Cycle Enable + decoded.reserved1 = (bytes[6] >> 1) & 0x7F; // Reserved 1 Field (7-bit) + } + + return decoded; +} + +function parseVoboXXModbusGroupsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + var groupIdx = ((decoded.subgroupID - 8) * 16) + decoded.sequenceNumber + 1; // Sub-Groups 8, 9 and 10 carry the groups configurations. Group index is determined by Sub-Group ID and Sequence Number. + var groupIdxStr = (groupIdx).toString(); + decoded["mbFirstCycleG" + groupIdxStr] = Boolean(bytes[1] & 0x01); // Modbus Group First Cycle Enable (1-bit) + decoded["mbSubseqCyclesG" + groupIdxStr] = Boolean((bytes[1] >> 1) & 0x01); // Modbus Group Subsequent Cycle Enable (1-bit) + decoded["mbByteSwapG" + groupIdxStr] = Boolean((bytes[1] >> 2) & 0x01); // Modbus Group Byte Swap Enable (1-bit) + decoded["mbWordSwapG" + groupIdxStr] = Boolean((bytes[1] >> 3) & 0x01); // Modbus Group Word Swap Enable (1-bit) + decoded["mbNumTypeG" + groupIdxStr] = (bytes[1] >> 4) & 0x03; // Modbus Group Numerical Type (2-bit) + decoded["reserved1"] = (bytes[1] >> 6) & 0x03; // Reserved 1 Field (2-bit) + decoded["mbSlaveAddrG" + groupIdxStr] = bytes[2]; // Modbus Group Slave Address (8-bit) + decoded["mbStartAddrG" + groupIdxStr] = (bytes[4] << 8) | bytes[3]; // Modbus Group Start Address (16-bit) + decoded["mbNumRegsG" + groupIdxStr] = bytes[5]; // Modbus Group Number of Registers (8-bit) + decoded["mbWdataG" + groupIdxStr] = (bytes[7] << 8) | bytes[6]; // Modbus Group Write Data (16-bit) + decoded["mbGrpDelayG" + groupIdxStr] = (bytes[9] << 8) | bytes[8]; // Modbus Group Delay in milliseconds (16-bit) + decoded["mbFuncCodeG" + groupIdxStr] = bytes[10] & 0x3F; // Modbus Group Function Code (6-bit) + decoded["reserved2"] = (bytes[10] >> 6) & 0x03; // Reserved 2 Field (2-bit) + + return decoded; +} + +function parseVoboXXModbusPayloadsSlotsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.mbGroupPaySlot0 = bytes[1]; // Modbus Payload Slot 0 Group Idx (8-bit) + decoded.mbRegPaySlot0 = bytes[2]; // Modbus Payload Slot 0 Register Idx (8-bit) + decoded.mbGroupPaySlot1 = bytes[3]; // Modbus Payload Slot 1 Group Idx (8-bit) + decoded.mbRegPaySlot1 = bytes[4]; // Modbus Payload Slot 1 Register Idx (8-bit) + decoded.mbGroupPaySlot2 = bytes[5]; // Modbus Payload Slot 2 Group Idx (8-bit) + decoded.mbRegPaySlot2 = bytes[6]; // Modbus Payload Slot 2 Register Idx (8-bit) + decoded.mbGroupPaySlot3 = bytes[7]; // Modbus Payload Slot 3 Group Idx (8-bit) + decoded.mbRegPaySlot3 = bytes[8]; // Modbus Payload Slot 3 Register Idx (8-bit) + decoded.mbGroupPaySlot4 = bytes[9]; // Modbus Payload Slot 4 Group Idx (8-bit) + decoded.mbRegPaySlot4 = bytes[10]; // Modbus Payload Slot 4 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.mbGroupPaySlot5 = bytes[1]; // Modbus Payload Slot 5 Group Idx (8-bit) + decoded.mbRegPaySlot5 = bytes[2]; // Modbus Payload Slot 5 Register Idx (8-bit) + decoded.mbGroupPaySlot6 = bytes[3]; // Modbus Payload Slot 6 Group Idx (8-bit) + decoded.mbRegPaySlot6 = bytes[4]; // Modbus Payload Slot 6 Register Idx (8-bit) + decoded.mbGroupPaySlot7 = bytes[5]; // Modbus Payload Slot 7 Group Idx (8-bit) + decoded.mbRegPaySlot7 = bytes[6]; // Modbus Payload Slot 7 Register Idx (8-bit) + decoded.mbGroupPaySlot8 = bytes[7]; // Modbus Payload Slot 8 Group Idx (8-bit) + decoded.mbRegPaySlot8 = bytes[8]; // Modbus Payload Slot 8 Register Idx (8-bit) + decoded.mbGroupPaySlot9 = bytes[9]; // Modbus Payload Slot 9 Group Idx (8-bit) + decoded.mbRegPaySlot9 = bytes[10]; // Modbus Payload Slot 9 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 2) { + decoded.mbGroupPaySlot10 = bytes[1]; // Modbus Payload Slot 10 Group Idx (8-bit) + decoded.mbRegPaySlot10 = bytes[2]; // Modbus Payload Slot 10 Register Idx (8-bit) + decoded.mbGroupPaySlot11 = bytes[3]; // Modbus Payload Slot 11 Group Idx (8-bit) + decoded.mbRegPaySlot11 = bytes[4]; // Modbus Payload Slot 11 Register Idx (8-bit) + decoded.mbGroupPaySlot12 = bytes[5]; // Modbus Payload Slot 12 Group Idx (8-bit) + decoded.mbRegPaySlot12 = bytes[6]; // Modbus Payload Slot 12 Register Idx (8-bit) + decoded.mbGroupPaySlot13 = bytes[7]; // Modbus Payload Slot 13 Group Idx (8-bit) + decoded.mbRegPaySlot13 = bytes[8]; // Modbus Payload Slot 13 Register Idx (8-bit) + decoded.mbGroupPaySlot14 = bytes[9]; // Modbus Payload Slot 14 Group Idx (8-bit) + decoded.mbRegPaySlot14 = bytes[10]; // Modbus Payload Slot 14 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 3) { + decoded.mbGroupPaySlot15 = bytes[1]; // Modbus Payload Slot 15 Group Idx (8-bit) + decoded.mbRegPaySlot15 = bytes[2]; // Modbus Payload Slot 15 Register Idx (8-bit) + decoded.mbGroupPaySlot16 = bytes[3]; // Modbus Payload Slot 16 Group Idx (8-bit) + decoded.mbRegPaySlot16 = bytes[4]; // Modbus Payload Slot 16 Register Idx (8-bit) + decoded.mbGroupPaySlot17 = bytes[5]; // Modbus Payload Slot 17 Group Idx (8-bit) + decoded.mbRegPaySlot17 = bytes[6]; // Modbus Payload Slot 17 Register Idx (8-bit) + decoded.mbGroupPaySlot18 = bytes[7]; // Modbus Payload Slot 18 Group Idx (8-bit) + decoded.mbRegPaySlot18 = bytes[8]; // Modbus Payload Slot 18 Register Idx (8-bit) + decoded.mbGroupPaySlot19 = bytes[9]; // Modbus Payload Slot 19 Group Idx (8-bit) + decoded.mbRegPaySlot19 = bytes[10]; // Modbus Payload Slot 19 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 4) { + decoded.mbGroupPaySlot20 = bytes[1]; // Modbus Payload Slot 20 Group Idx (8-bit) + decoded.mbRegPaySlot20 = bytes[2]; // Modbus Payload Slot 20 Register Idx (8-bit) + decoded.mbGroupPaySlot21 = bytes[3]; // Modbus Payload Slot 21 Group Idx (8-bit) + decoded.mbRegPaySlot21 = bytes[4]; // Modbus Payload Slot 21 Register Idx (8-bit) + decoded.mbGroupPaySlot22 = bytes[5]; // Modbus Payload Slot 22 Group Idx (8-bit) + decoded.mbRegPaySlot22 = bytes[6]; // Modbus Payload Slot 22 Register Idx (8-bit) + decoded.mbGroupPaySlot23 = bytes[7]; // Modbus Payload Slot 23 Group Idx (8-bit) + decoded.mbRegPaySlot23 = bytes[8]; // Modbus Payload Slot 23 Register Idx (8-bit) + decoded.mbGroupPaySlot24 = bytes[9]; // Modbus Payload Slot 24 Group Idx (8-bit) + decoded.mbRegPaySlot24 = bytes[10]; // Modbus Payload Slot 24 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 5) { + decoded.mbGroupPaySlot25 = bytes[1]; // Modbus Payload Slot 25 Group Idx (8-bit) + decoded.mbRegPaySlot25 = bytes[2]; // Modbus Payload Slot 25 Register Idx (8-bit) + decoded.mbGroupPaySlot26 = bytes[3]; // Modbus Payload Slot 26 Group Idx (8-bit) + decoded.mbRegPaySlot26 = bytes[4]; // Modbus Payload Slot 26 Register Idx (8-bit) + decoded.mbGroupPaySlot27 = bytes[5]; // Modbus Payload Slot 27 Group Idx (8-bit) + decoded.mbRegPaySlot27 = bytes[6]; // Modbus Payload Slot 27 Register Idx (8-bit) + decoded.mbGroupPaySlot28 = bytes[7]; // Modbus Payload Slot 28 Group Idx (8-bit) + decoded.mbRegPaySlot28 = bytes[8]; // Modbus Payload Slot 28 Register Idx (8-bit) + decoded.mbGroupPaySlot29 = bytes[9]; // Modbus Payload Slot 29 Group Idx (8-bit) + decoded.mbRegPaySlot29 = bytes[10]; // Modbus Payload Slot 29 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 6) { + decoded.mbGroupPaySlot30 = bytes[1]; // Modbus Payload Slot 30 Group Idx (8-bit) + decoded.mbRegPaySlot30 = bytes[2]; // Modbus Payload Slot 30 Register Idx (8-bit) + decoded.mbGroupPaySlot31 = bytes[3]; // Modbus Payload Slot 31 Group Idx (8-bit) + decoded.mbRegPaySlot31 = bytes[4]; // Modbus Payload Slot 31 Register Idx (8-bit) + decoded.mbGroupPaySlot32 = bytes[5]; // Modbus Payload Slot 32 Group Idx (8-bit) + decoded.mbRegPaySlot32 = bytes[6]; // Modbus Payload Slot 32 Register Idx (8-bit) + decoded.mbGroupPaySlot33 = bytes[7]; // Modbus Payload Slot 33 Group Idx (8-bit) + decoded.mbRegPaySlot33 = bytes[8]; // Modbus Payload Slot 33 Register Idx (8-bit) + decoded.mbGroupPaySlot34 = bytes[9]; // Modbus Payload Slot 34 Group Idx (8-bit) + decoded.mbRegPaySlot34 = bytes[10]; // Modbus Payload Slot 34 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 7) { + decoded.mbGroupPaySlot35 = bytes[1]; // Modbus Payload Slot 35 Group Idx (8-bit) + decoded.mbRegPaySlot35 = bytes[2]; // Modbus Payload Slot 35 Register Idx (8-bit) + decoded.mbGroupPaySlot36 = bytes[3]; // Modbus Payload Slot 36 Group Idx (8-bit) + decoded.mbRegPaySlot36 = bytes[4]; // Modbus Payload Slot 36 Register Idx (8-bit) + decoded.mbGroupPaySlot37 = bytes[5]; // Modbus Payload Slot 37 Group Idx (8-bit) + decoded.mbRegPaySlot37 = bytes[6]; // Modbus Payload Slot 37 Register Idx (8-bit) + decoded.mbGroupPaySlot38 = bytes[7]; // Modbus Payload Slot 38 Group Idx (8-bit) + decoded.mbRegPaySlot38 = bytes[8]; // Modbus Payload Slot 38 Register Idx (8-bit) + decoded.mbGroupPaySlot39 = bytes[9]; // Modbus Payload Slot 39 Group Idx (8-bit) + decoded.mbRegPaySlot39 = bytes[10]; // Modbus Payload Slot 39 Register Idx (8-bit) + } + if (decoded.sequenceNumber == 8) { + decoded.mbGroupPaySlot40 = bytes[1]; // Modbus Payload Slot 40 Group Idx (8-bit) + decoded.mbRegPaySlot40 = bytes[2]; // Modbus Payload Slot 40 Register Idx (8-bit) + } + + return decoded; +} + +function parseVoboXXEngineeringUnitsConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + // AIN1 General + if (decoded.sequenceNumber == 0) { + decoded.ain1UnitsCode = bytes[1]; // AIN1 Units Code (8-bit) + decoded.ain1MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN1 Minimum Value (32-bit) + decoded.ain1MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN1 Maximum Value (32-bit) + decoded.ain1Type = bytes[10] & 0x03; // AIN1 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN1 Series Resistance + if (decoded.sequenceNumber == 1) { + decoded.ain1SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN1 Series Resistance (32-bit) + } + // AIN1 Calibration + if (decoded.sequenceNumber == 2) { + decoded.ain1Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN1 Calibration Gain (32-bit) + decoded.ain1Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN1 Calibration Offset (32-bit) + } + + // AIN2 General + if (decoded.sequenceNumber == 3) { + decoded.ain2UnitsCode = bytes[1]; // AIN2 Units Code (8-bit) + decoded.ain2MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN2 Minimum Value (32-bit) + decoded.ain2MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN2 Maximum Value (32-bit) + decoded.ain2Type = bytes[10] & 0x03; // AIN2 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN2 Series Resistance + if (decoded.sequenceNumber == 4) { + decoded.ain2SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN2 Series Resistance (32-bit) + } + // AIN2 Calibration + if (decoded.sequenceNumber == 5) { + decoded.ain2Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN2 Calibration Gain (32-bit) + decoded.ain2Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN2 Calibration Offset (32-bit) + } + + // AIN3 General + if (decoded.sequenceNumber == 6) { + decoded.ain3UnitsCode = bytes[1]; // AIN3 Units Code (8-bit) + decoded.ain3MinValue = bytesToFloat32(bytes.slice(2, 6)); // AIN3 Minimum Value (32-bit) + decoded.ain3MaxValue = bytesToFloat32(bytes.slice(6, 10)); // AIN3 Maximum Value (32-bit) + decoded.ain3Type = bytes[10] & 0x03; // AIN3 Type (2-bit) + decoded.reserved1 = (bytes[10] >> 2) & 0x3F // Reserved 1 Field (6-bit) + } + // AIN3 Series Resistance + if (decoded.sequenceNumber == 7) { + decoded.ain3SeriesResistance = bytesToFloat32(bytes.slice(1, 5)); // AIN3 Series Resistance (32-bit) + } + // AIN3 Calibration + if (decoded.sequenceNumber == 8) { + decoded.ain3Gain = bytesToFloat32(bytes.slice(1, 5)); // AIN3 Calibration Gain (32-bit) + decoded.ain3Offset = bytesToFloat32(bytes.slice(5, 9)); // AIN3 Calibration Offset (32-bit) + } + + return decoded; +} + +function parseVoboTCGeneralConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.deviceType1 = bytes[1] & 0x07; // Device Type 1 (3-bit) + decoded.enable1 = Boolean((bytes[1] & 0x08) >> 3); // Enable 1 (1-bit) + decoded.deviceType2 = (bytes[1] & 0x70) >> 4; // Device Type 2 (3-bit) + decoded.enable2 = Boolean((bytes[1] & 0x80) >> 7); // Enable 2 (1-bit) + decoded.deviceType3 = bytes[2] & 0x07; // Device Type 3 (3-bit) + decoded.enable3 = Boolean((bytes[2] & 0x08) >> 3); // Enable 3 (1-bit) + decoded.deviceType4 = (bytes[2] & 0x70) >> 4; // Device Type 4 (3-bit) + decoded.enable4 = Boolean((bytes[2] & 0x80) >> 7); // Enable 4 (1-bit) + decoded.deviceType5 = bytes[3] & 0x07; // Device Type 5 (3-bit) + decoded.enable5 = Boolean((bytes[3] & 0x08) >> 3); // Enable 5 (1-bit) + decoded.deviceType6 = (bytes[3] & 0x70) >> 4; // Device Type 6 (3-bit) + decoded.enable6 = Boolean((bytes[3] & 0x80) >> 7); // Enable 6 (1-bit) + decoded.deviceType7 = bytes[4] & 0x07; // Device Type 7 (3-bit) + decoded.enable7 = Boolean((bytes[4] & 0x08) >> 3); // Enable 7 (1-bit) + decoded.deviceType8 = (bytes[4] & 0x70) >> 4; // Device Type 8 (3-bit) + decoded.enable8 = Boolean((bytes[4] & 0x80) >> 7); // Enable 8 (1-bit) + decoded.deviceType9 = bytes[5] & 0x07; // Device Type 9 (3-bit) + decoded.enable9 = Boolean((bytes[5] & 0x08) >> 3); // Enable 9 (1-bit) + decoded.deviceType10 = (bytes[5] & 0x70) >> 4; // Device Type 10 (3-bit) + decoded.enable10 = Boolean((bytes[5] & 0x80) >> 7); // Enable 10 (1-bit) + decoded.deviceType11 = bytes[6] & 0x07; // Device Type 11 (3-bit) + decoded.enable11 = Boolean((bytes[6] & 0x08) >> 3); // Enable 11 (1-bit) + decoded.deviceType12 = (bytes[6] & 0x70) >> 4; // Device Type 12 (3-bit) + decoded.enable12 = Boolean((bytes[6] & 0x80) >> 7); // Enable 12 (1-bit) + decoded.deviceUnits1 = bytes[7] & 0x01; // Device Units 1 (1-bit) + decoded.deviceUnits2 = (bytes[7] & 0x02) >> 1; // Device Units 2 (1-bit) + decoded.deviceUnits3 = (bytes[7] & 0x04) >> 2; // Device Units 3 (1-bit) + decoded.deviceUnits4 = (bytes[7] & 0x08) >> 3; // Device Units 4 (1-bit) + decoded.deviceUnits5 = (bytes[7] & 0x10) >> 4; // Device Units 5 (1-bit) + decoded.deviceUnits6 = (bytes[7] & 0x20) >> 5; // Device Units 6 (1-bit) + decoded.deviceUnits7 = (bytes[7] & 0x40) >> 6; // Device Units 7 (1-bit) + decoded.deviceUnits8 = (bytes[7] & 0x80) >> 7; // Device Units 8 (1-bit) + decoded.deviceUnits9 = bytes[8] & 0x01; // Device Units 9 (1-bit) + decoded.deviceUnits10 = (bytes[8] & 0x02) >> 1; // Device Units 10 (1-bit) + decoded.deviceUnits11 = (bytes[8] & 0x04) >> 2; // Device Units 11 (1-bit) + decoded.deviceUnits12 = (bytes[8] & 0x08) >> 3; // Device Units 12 (1-bit) + decoded.wkupSensorTransmitEnable = Boolean((bytes[8] & 0x10) >> 4); // WKUP Sensor Transmit Enable (1-bit) + decoded.batterySensorTransmitEnable = Boolean((bytes[8] & 0x20) >> 5); // Battery Sensor Transmit Enable (1-bit) + decoded.coldJointSensorTransmitEnable = Boolean((bytes[8] & 0x40) >> 6); // Cold Joint Sensor Transmit Enable (1-bit) + decoded.reserved1 = (bytes[8] & 0x80) >> 7; // Reserved 1 Field (1-bit) + } + else if (decoded.sequenceNumber == 1) { + decoded.deviceType1 = bytes[1] & 0x07; // Device Type 1 (3-bit) + decoded.enable1 = Boolean((bytes[1] & 0x08) >> 3); // Enable 1 (1-bit) + decoded.deviceType2 = (bytes[1] & 0x70) >> 4; // Device Type 2 (3-bit) + decoded.enable2 = Boolean((bytes[1] & 0x80) >> 7); // Enable 2 (1-bit) + decoded.deviceType3 = bytes[2] & 0x07; // Device Type 3 (3-bit) + decoded.enable3 = Boolean((bytes[2] & 0x08) >> 3); // Enable 3 (1-bit) + decoded.deviceType4 = (bytes[2] & 0x70) >> 4; // Device Type 4 (3-bit) + decoded.enable4 = Boolean((bytes[2] & 0x80) >> 7); // Enable 4 (1-bit) + decoded.deviceType5 = bytes[3] & 0x07; // Device Type 5 (3-bit) + decoded.enable5 = Boolean((bytes[3] & 0x08) >> 3); // Enable 5 (1-bit) + decoded.deviceType6 = (bytes[3] & 0x70) >> 4; // Device Type 6 (3-bit) + decoded.enable6 = Boolean((bytes[3] & 0x80) >> 7); // Enable 6 (1-bit) + decoded.deviceType7 = bytes[4] & 0x07; // Device Type 7 (3-bit) + decoded.enable7 = Boolean((bytes[4] & 0x08) >> 3); // Enable 7 (1-bit) + decoded.deviceType8 = (bytes[4] & 0x70) >> 4; // Device Type 8 (3-bit) + decoded.enable8 = Boolean((bytes[4] & 0x80) >> 7); // Enable 8 (1-bit) + decoded.deviceType9 = bytes[5] & 0x07; // Device Type 9 (3-bit) + decoded.enable9 = Boolean((bytes[5] & 0x08) >> 3); // Enable 9 (1-bit) + decoded.deviceType10 = (bytes[5] & 0x70) >> 4; // Device Type 10 (3-bit) + decoded.enable10 = Boolean((bytes[5] & 0x80) >> 7); // Enable 10 (1-bit) + decoded.deviceType11 = bytes[6] & 0x07; // Device Type 11 (3-bit) + decoded.enable11 = Boolean((bytes[6] & 0x08) >> 3); // Enable 11 (1-bit) + decoded.deviceType12 = (bytes[6] & 0x70) >> 4; // Device Type 12 (3-bit) + decoded.enable12 = Boolean((bytes[6] & 0x80) >> 7); // Enable 12 (1-bit) + decoded.deviceUnits1 = bytes[7] & 0x03; // Device Units 1 (2-bit) + decoded.deviceUnits2 = (bytes[7] & 0x0C) >> 2; // Device Units 2 (2-bit) + decoded.deviceUnits3 = (bytes[7] & 0x30) >> 4; // Device Units 3 (2-bit) + decoded.deviceUnits4 = (bytes[7] & 0xC0) >> 6; // Device Units 4 (2-bit) + decoded.deviceUnits5 = bytes[8] & 0x03; // Device Units 5 (2-bit) + decoded.deviceUnits6 = (bytes[8] & 0x0C) >> 2; // Device Units 6 (2-bit) + decoded.deviceUnits7 = (bytes[8] & 0x30) >> 4; // Device Units 7 (2-bit) + decoded.deviceUnits8 = (bytes[8] & 0xC0) >> 6; // Device Units 8 (2-bit) + decoded.deviceUnits9 = bytes[9] & 0x03; // Device Units 9 (2-bit) + decoded.deviceUnits10 = (bytes[9] & 0x0C) >> 2; // Device Units 10 (2-bit) + decoded.deviceUnits11 = (bytes[9] & 0x30) >> 4; // Device Units 11 (2-bit) + decoded.deviceUnits12 = (bytes[9] & 0xC0) >> 6; // Device Units 12 (2-bit) + decoded.coldJointSensorUnits = bytes[10] & 0x03; // Cold Joint Sensor Units (2-bit) + decoded.wkupSensorTransmitEnable = Boolean((bytes[10] & 0x04) >> 2); // WKUP Sensor Transmit Enable (1-bit) + decoded.batterySensorTransmitEnable = Boolean((bytes[10] & 0x08) >> 3); // Battery Sensor Transmit Enable (1-bit) + decoded.coldJointSensorTransmitEnable = Boolean((bytes[10] & 0x10) >> 4); // Cold Joint Sensor Transmit Enable (1-bit) + decoded.tcSensorsTransmitEnable = Boolean((bytes[10] & 0x20) >> 5);; // TC Sensors Transmit Enable (1-bit) + decoded.ainPayloadType = (bytes[10] & 0x40) >> 6; // Ain Payload Type (1-bit) + decoded.reserved1 = (bytes[10] & 0x80) >> 7; // Reserved 1 Field (1-bit) + } + + return decoded; +} + +function parseVoboTCCalibrationConfigurationPayload(bytes) { + var decoded = {}; + decoded.subgroupID = bytes[0] & 0x0F; // Sub-Group ID (4-bit) + decoded.sequenceNumber = (bytes[0] & 0xF0) >> 4; // Sequence Number (4-bit) + + if (decoded.sequenceNumber == 0) { + decoded.gain1 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 1 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset1 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 1 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain2 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 2 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset2 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 2 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 1) { + decoded.gain3 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 3 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset3 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 3 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain4 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 4 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset4 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 4 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 2) { + decoded.gain5 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 5 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset5 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 5 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain6 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 6 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset6 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 6 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 3) { + decoded.gain7 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 7 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset7 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 7 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain8 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 8 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset8 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3)); // Offset 8 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 4) { + decoded.gain9 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 9 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset9 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3)); // Offset 9 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain10 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 10 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset10 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3));// Offset 10 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + if (decoded.sequenceNumber == 5) { + decoded.gain11 = parseFloat(((((bytes[2] & 0x07) << 8) | bytes[1]) / 1000.0).toFixed(3)); // Gain 11 (11-bit) + decoded.reserved1 = (bytes[2] & 0xF8) >> 3; // Reserved 1 Field (5-bit) + decoded.offset11 = parseFloat(((((bytes[4] & 0x7F) << 8) | bytes[3]) / 1000.0 - 10.000).toFixed(3));// Offset 11 (15-bit) + decoded.reserved2 = (bytes[4] & 0x80) >> 7; // Reserved 2 Field (1-bit) + decoded.gain12 = parseFloat(((((bytes[6] & 0x07) << 8) | bytes[5]) / 1000.0).toFixed(3)); // Gain 12 (11-bit) + decoded.reserved3 = (bytes[6] & 0xF8) >> 3; // Reserved 3 Field (5-bit) + decoded.offset12 = parseFloat(((((bytes[8] & 0x7F) << 8) | bytes[7]) / 1000.0 - 10.000).toFixed(3));// Offset 12 (15-bit) + decoded.reserved4 = (bytes[8] & 0x80) >> 7; // Reserved 4 Field (1-bit) + } + + return decoded; +} + +function parseConfigurationPayload(bytes, fport) { + var decoded = {}; + var subgroupID = bytes[0] & 0x0F; + + // VoBo Lib Configuration Payloads + if (subgroupID == 0) decoded = parseVoboLibGeneralConfigurationPayload(bytes); + else if (subgroupID == 1) decoded = parseVoboLibVoboSyncConfigurationPayload(bytes); + // VoBo XX Configuration Payloads + else if (fport == 70 && subgroupID == 4) decoded = parseVoboXXGeneralConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 5) decoded = parseVoboXXModbusGeneralConfigurationPayload(bytes); + else if (fport == 70 && (subgroupID == 6 || subgroupID == 7)) decoded = parseVoboXXModbusGroupsEnableConfigurationPayload(bytes); + else if (fport == 70 && (subgroupID == 8 || subgroupID == 9 || subgroupID == 10)) decoded = parseVoboXXModbusGroupsConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 11) decoded = parseVoboXXModbusPayloadsSlotsConfigurationPayload(bytes); + else if (fport == 70 && subgroupID == 12) decoded = parseVoboXXEngineeringUnitsConfigurationPayload(bytes); + // VoBo TC Configuration Payloads + else if (fport == 71 && subgroupID == 4) decoded = parseVoboTCGeneralConfigurationPayload(bytes); + else if (fport == 71 && subgroupID == 5) decoded = parseVoboTCCalibrationConfigurationPayload(bytes); + + return decoded; +} + +function parseModbusGenericPayload(bytes, fport) { + var decoded = {}; + var payloadSize = bytes.length; + var byteIdx = 0; + while (byteIdx < (payloadSize - 1)) { + var groupIdx = bytes[byteIdx] & 0x3F; + if (groupIdx > 0) { + var blockType = (bytes[byteIdx] >> 6) & 0x03; + byteIdx++; + + if (blockType == 0x00) // One register block + { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register1"] = registerValue; + byteIdx += 2; + } + else if (blockType == 0x01) // Two registers block + { + var register1Value = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register1"] = register1Value; + var register2Value = (bytes[byteIdx + 3] << 8) | (bytes[byteIdx + 2]); + decoded["group" + groupIdx + "register2"] = register2Value; + byteIdx += (2 * 2); + } + else if (blockType == 0x02) // Non fragmented block + { + var registersValues = []; + var numOfRegisters = bytes[byteIdx] & 0x7F; + byteIdx++; + for (let regIdx = 0; regIdx < numOfRegisters; regIdx++) { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register" + (regIdx + 1)] = registerValue; + registersValues.push(registerValue); + byteIdx += 2; + } + } + else if (blockType == 0x03) // Fragmented block + { + var registersValues = []; + var numOfRegisters = bytes[byteIdx] & 0x7F; + byteIdx++; + var registerOffset = bytes[byteIdx] & 0x7F; + byteIdx++; + + var numOfRegToRead = numOfRegisters - registerOffset; + var freeRegToRead = Math.floor((payloadSize - byteIdx) / 2); + if (numOfRegToRead > freeRegToRead) numOfRegToRead = freeRegToRead; + for (let regIdx = 0; regIdx < numOfRegToRead; regIdx++) { + var registerValue = (bytes[byteIdx + 1] << 8) | (bytes[byteIdx]); + decoded["group" + groupIdx + "register" + (registerOffset + regIdx + 1)] = registerValue; + registersValues.push(registerValue); + byteIdx += 2; + } + } + } + else break; + } + return decoded; +} + +function parseAnalogInputVariableLengthPayload(bytes, fport) { + var decoded = {}; + var decodedAinPayloads = []; + const AIN_PAYLOAD_SIZE = 6; + + var payloadLength = bytes.length + var byteIdx = 0; + while (byteIdx < payloadLength) { + var ainPayload = bytes.slice(byteIdx, byteIdx + AIN_PAYLOAD_SIZE) + var decodedAinPayload = parseOneAnalogSensorPayload(ainPayload) + decodedAinPayloads.push(decodedAinPayload) + byteIdx += AIN_PAYLOAD_SIZE; + } + + decoded.ainPayloads = decodedAinPayloads; + decoded.numOfAinPayloads = decodedAinPayloads.length + + return decoded; +} + +function parseModbusStandardVariableLengthPayload(bytes, fport) { + var decoded = {}; + var decodedModbusSlots = {}; + + var payloadLength = bytes.length + var firstSlotNum = bytes[payloadLength - 1] + const REGISTER_SIZE = 2 + var byteIdx = 0; + while (byteIdx < payloadLength - 1) { + var registerIdx = byteIdx / 2; + var slotIdx = firstSlotNum + registerIdx; + decodedModbusSlots["Modbus" + slotIdx] = (bytes[byteIdx + 1] << 8) | bytes[byteIdx]; + byteIdx += REGISTER_SIZE; + } + + decoded.modbusSlots = decodedModbusSlots; + decoded.firstSlotNum = firstSlotNum; + decoded.numModbusSlots = (payloadLength - 1) / 2; + + return decoded; +} + +function lookupAnalogSensorName(voboType, sensorNum) { + const analogSensorsTableXX = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "Battery Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "AIN1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "AIN2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "AIN3" }, + { "Analog Sensor Number": "15", "Analog Sensor Name": "ADC Temperature" } + ]; + + const analogSensorsTableTC = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "Battery Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "TC1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "TC2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "TC3" }, + { "Analog Sensor Number": "4", "Analog Sensor Name": "TC4" }, + { "Analog Sensor Number": "5", "Analog Sensor Name": "TC5" }, + { "Analog Sensor Number": "6", "Analog Sensor Name": "TC6" }, + { "Analog Sensor Number": "7", "Analog Sensor Name": "TC7" }, + { "Analog Sensor Number": "8", "Analog Sensor Name": "TC8" }, + { "Analog Sensor Number": "9", "Analog Sensor Name": "TC9" }, + { "Analog Sensor Number": "10", "Analog Sensor Name": "TC10" }, + { "Analog Sensor Number": "11", "Analog Sensor Name": "TC11" }, + { "Analog Sensor Number": "12", "Analog Sensor Name": "TC12" }, + { "Analog Sensor Number": "13", "Analog Sensor Name": "Cold Joint Temperature" } + ]; + + const analogSensorsTableXP = + [ + { "Analog Sensor Number": "0", "Analog Sensor Name": "3.3V Supply Voltage" }, + { "Analog Sensor Number": "1", "Analog Sensor Name": "AIN1" }, + { "Analog Sensor Number": "2", "Analog Sensor Name": "AIN2" }, + { "Analog Sensor Number": "3", "Analog Sensor Name": "AIN3" }, + { "Analog Sensor Number": "4", "Analog Sensor Name": "DIN1" }, + { "Analog Sensor Number": "5", "Analog Sensor Name": "DIN1 Pulse Count" }, + { "Analog Sensor Number": "6", "Analog Sensor Name": "DIN2" }, + { "Analog Sensor Number": "7", "Analog Sensor Name": "DIN2 Pulse Count" }, + { "Analog Sensor Number": "8", "Analog Sensor Name": "RLY1 Voltage" }, + { "Analog Sensor Number": "9", "Analog Sensor Name": "RLY2 Voltage" }, + { "Analog Sensor Number": "10", "Analog Sensor Name": "RLY3 Voltage" }, + { "Analog Sensor Number": "11", "Analog Sensor Name": "RLY4 Voltage" }, + { "Analog Sensor Number": "12", "Analog Sensor Name": "WKUP" }, + { "Analog Sensor Number": "13", "Analog Sensor Name": "VPP Voltage" }, + { "Analog Sensor Number": "14", "Analog Sensor Name": "VIN Voltage" }, + { "Analog Sensor Number": "15", "Analog Sensor Name": "ADC Temperature" }, + { "Analog Sensor Number": "16", "Analog Sensor Name": "Cont Meas Period" }, + { "Analog Sensor Number": "17", "Analog Sensor Name": "Cont Meas Count" } + ]; + + var analogSensorsTable = []; + var analogSensorNameSuffix = ""; + if (voboType == "VoBoXX") { + analogSensorsTable = analogSensorsTableXX; + } + else if (voboType == "VoBoTC") { + analogSensorsTable = analogSensorsTableTC; + } + else if (voboType == "VoBoXP") { + analogSensorsTable = analogSensorsTableXP; + var sensorNumEncoding = (sensorNum & 0xC0) >> 6; + if (sensorNumEncoding == 0x01) analogSensorNameSuffix = "(Max)"; + else if (sensorNumEncoding == 0x02) analogSensorNameSuffix = "(Min)"; + else if (sensorNumEncoding == 0x03) analogSensorNameSuffix = "(Avg)"; + sensorNum = sensorNum & 0x3F; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\", \"VoBoTC\" or \"VoBoXP\""; + throw new Error(errorMsg); + } + + var analogSensorRow = analogSensorsTable.find(element => element["Analog Sensor Number"] == sensorNum.toString()); + if (typeof analogSensorRow == 'undefined') { + var genericAnalogSensorName = "AnalogSensor" + sensorNum.toString(); + return genericAnalogSensorName; + } + + var analogSensorName = analogSensorRow["Analog Sensor Name"]; + if (voboType == "VoBoXP") { + analogSensorName = analogSensorName + " " + analogSensorNameSuffix; + } + return analogSensorName; +} + +function lookupDigitalSensorName(voboType, sensorNum) { + const digitalSensorsTableXX = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" }, + { "Digital Sensor Number": "1", "Digital Sensor Name": "DIN1" }, + { "Digital Sensor Number": "2", "Digital Sensor Name": "DIN2" }, + { "Digital Sensor Number": "3", "Digital Sensor Name": "DIN3" } + ]; + + const digitalSensorsTableTC = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" } + ]; + + const digitalSensorsTableXP = + [ + { "Digital Sensor Number": "0", "Digital Sensor Name": "WKUP" }, + { "Digital Sensor Number": "1", "Digital Sensor Name": "DIN1" }, + { "Digital Sensor Number": "2", "Digital Sensor Name": "DIN2" } + ]; + + var digitalSensorsTable = []; + if (voboType == "VoBoXX") { + digitalSensorsTable = digitalSensorsTableXX; + } + else if (voboType == "VoBoTC") { + digitalSensorsTable = digitalSensorsTableTC; + } + else if (voboType == "VoBoXP") { + digitalSensorsTable = digitalSensorsTableXP; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\", \"VoBoTC\" or \"VoBoXP\""; + throw new Error(errorMsg); + } + + var digitalSensorRow = digitalSensorsTable.find(element => element["Digital Sensor Number"] == sensorNum.toString()); + if (typeof digitalSensorRow == 'undefined') { + var genericDigitalSensorName = "DigitalSensor" + sensorNum.toString(); + return genericDigitalSensorName; + } + + var digitalSensorName = digitalSensorRow["Digital Sensor Name"]; + return digitalSensorName; +} + +function lookupUnits(outputType, unitCode) { + const engUnitsTable = + [ + { "Units Code": "1", "Description": "inches of water at 20 degC (68 degF)", "Abbreviated Units": "inH2O (20 degC or 68 degF)" }, + { "Units Code": "2", "Description": "inches of mercury at 0 degC (32 degF)", "Abbreviated Units": "inHg (20 degC or 68 degF)" }, + { "Units Code": "3", "Description": "feet of water at 20 degC (68 degF)", "Abbreviated Units": "ftH2O (20 degC or 68 degF)" }, + { "Units Code": "4", "Description": "millimeters of water at 20 degC (68 degF)", "Abbreviated Units": "mmH2O (20 degC or 68 degF)" }, + { "Units Code": "5", "Description": "millimeters of mercury at 0 degC (32 degF)", "Abbreviated Units": "mmHg (0 degC or 32 degF)" }, + { "Units Code": "6", "Description": "pounds per square inch", "Abbreviated Units": "psi" }, + { "Units Code": "7", "Description": "bars", "Abbreviated Units": "bar" }, + { "Units Code": "8", "Description": "millibars", "Abbreviated Units": "mbar" }, + { "Units Code": "9", "Description": "grams per square centimeter", "Abbreviated Units": "g/cm^2" }, + { "Units Code": "10", "Description": "kilograms per square centimeter", "Abbreviated Units": "kg/cm^2" }, + { "Units Code": "11", "Description": "pascals", "Abbreviated Units": "Pa" }, + { "Units Code": "12", "Description": "kilopascals", "Abbreviated Units": "kPa" }, + { "Units Code": "13", "Description": "torr", "Abbreviated Units": "torr" }, + { "Units Code": "14", "Description": "atmospheres", "Abbreviated Units": "atm" }, + { "Units Code": "15", "Description": "cubic feet per minute", "Abbreviated Units": "ft^3/min" }, + { "Units Code": "16", "Description": "gallons per minute", "Abbreviated Units": "usg/min" }, + { "Units Code": "17", "Description": "liters per minute", "Abbreviated Units": "L/min" }, + { "Units Code": "18", "Description": "imperial gallons per minute", "Abbreviated Units": "impgal/min" }, + { "Units Code": "19", "Description": "cubic meter per hour", "Abbreviated Units": "m^3/h" }, + { "Units Code": "20", "Description": "feet per second", "Abbreviated Units": "ft/s" }, + { "Units Code": "21", "Description": "meters per seond", "Abbreviated Units": "m/s" }, + { "Units Code": "22", "Description": "gallons per second", "Abbreviated Units": "usg/s" }, + { "Units Code": "23", "Description": "million gallons per day", "Abbreviated Units": "Musg/d" }, + { "Units Code": "24", "Description": "liters per second", "Abbreviated Units": "L/s" }, + { "Units Code": "25", "Description": "million liters per day", "Abbreviated Units": "ML/d" }, + { "Units Code": "26", "Description": "cubic feet per second", "Abbreviated Units": "ft^3/s" }, + { "Units Code": "27", "Description": "cubic feet per day", "Abbreviated Units": "ft^3/d" }, + { "Units Code": "28", "Description": "cubic meters per second", "Abbreviated Units": "m^3/s" }, + { "Units Code": "29", "Description": "cubic meters per day", "Abbreviated Units": "m^3/d" }, + { "Units Code": "30", "Description": "imperial gallons per hour", "Abbreviated Units": "impgal/h" }, + { "Units Code": "31", "Description": "imperial gallons per day", "Abbreviated Units": "impgal/d" }, + { "Units Code": "32", "Description": "Degrees Celsius", "Abbreviated Units": "C" }, + { "Units Code": "33", "Description": "Degrees Fahrenheit", "Abbreviated Units": "F" }, + { "Units Code": "34", "Description": "Degrees Rankine", "Abbreviated Units": "R" }, + { "Units Code": "35", "Description": "Kelvin", "Abbreviated Units": "K" }, + { "Units Code": "36", "Description": "millivolts", "Abbreviated Units": "mV" }, + { "Units Code": "37", "Description": "ohms", "Abbreviated Units": "ohm" }, + { "Units Code": "38", "Description": "hertz", "Abbreviated Units": "hz" }, + { "Units Code": "39", "Description": "milliamperes", "Abbreviated Units": "mA" }, + { "Units Code": "40", "Description": "gallons", "Abbreviated Units": "usg" }, + { "Units Code": "41", "Description": "liters", "Abbreviated Units": "L" }, + { "Units Code": "42", "Description": "imperial gallons", "Abbreviated Units": "impgal" }, + { "Units Code": "43", "Description": "cubic meters", "Abbreviated Units": "m^3" }, + { "Units Code": "44", "Description": "feet", "Abbreviated Units": "ft" }, + { "Units Code": "45", "Description": "meters", "Abbreviated Units": "m" }, + { "Units Code": "46", "Description": "barrels", "Abbreviated Units": "bbl" }, + { "Units Code": "47", "Description": "inches", "Abbreviated Units": "in" }, + { "Units Code": "48", "Description": "centimeters", "Abbreviated Units": "cm" }, + { "Units Code": "49", "Description": "millimeters", "Abbreviated Units": "mm" }, + { "Units Code": "50", "Description": "minutes", "Abbreviated Units": "min" }, + { "Units Code": "51", "Description": "seconds", "Abbreviated Units": "s" }, + { "Units Code": "52", "Description": "hours", "Abbreviated Units": "h" }, + { "Units Code": "53", "Description": "days", "Abbreviated Units": "d" }, + { "Units Code": "54", "Description": "centistokes", "Abbreviated Units": "centistokes" }, + { "Units Code": "55", "Description": "centipoise", "Abbreviated Units": "cP" }, + { "Units Code": "56", "Description": "microsiemens", "Abbreviated Units": "microsiemens" }, + { "Units Code": "57", "Description": "percent", "Abbreviated Units": "%" }, + { "Units Code": "58", "Description": "volts", "Abbreviated Units": "V" }, + { "Units Code": "59", "Description": "pH", "Abbreviated Units": "pH" }, + { "Units Code": "60", "Description": "grams", "Abbreviated Units": "g" }, + { "Units Code": "61", "Description": "kilograms", "Abbreviated Units": "kg" }, + { "Units Code": "62", "Description": "metric tons", "Abbreviated Units": "t" }, + { "Units Code": "63", "Description": "pounds", "Abbreviated Units": "lb" }, + { "Units Code": "64", "Description": "short tons", "Abbreviated Units": "short ton" }, + { "Units Code": "65", "Description": "long tons", "Abbreviated Units": "long ton" }, + { "Units Code": "66", "Description": "millisiemens per centimeter", "Abbreviated Units": "millisiemens/cm" }, + { "Units Code": "67", "Description": "microsiemens per centimeter", "Abbreviated Units": "microsiemens/cm" }, + { "Units Code": "68", "Description": "newton", "Abbreviated Units": "N" }, + { "Units Code": "69", "Description": "newton meter", "Abbreviated Units": "N m" }, + { "Units Code": "70", "Description": "grams per second", "Abbreviated Units": "g/s" }, + { "Units Code": "71", "Description": "grams per minute", "Abbreviated Units": "g/min" }, + { "Units Code": "72", "Description": "grams per hour", "Abbreviated Units": "g/h" }, + { "Units Code": "73", "Description": "kilograms per second", "Abbreviated Units": "kg/s" }, + { "Units Code": "74", "Description": "kilograms per minute", "Abbreviated Units": "kg/min" }, + { "Units Code": "75", "Description": "kilograms per hour", "Abbreviated Units": "kg/h" }, + { "Units Code": "76", "Description": "kilograms per day", "Abbreviated Units": "kg/d" }, + { "Units Code": "77", "Description": "metric tons per minute", "Abbreviated Units": "t/min" }, + { "Units Code": "78", "Description": "metric tons per hour", "Abbreviated Units": "t/h" }, + { "Units Code": "79", "Description": "metric tons per day", "Abbreviated Units": "t/d" }, + { "Units Code": "80", "Description": "pounds per second", "Abbreviated Units": "lb/s" }, + { "Units Code": "81", "Description": "pounds per minute", "Abbreviated Units": "lb/min" }, + { "Units Code": "82", "Description": "pounds per hour", "Abbreviated Units": "lb/h" }, + { "Units Code": "83", "Description": "pounds per day", "Abbreviated Units": "lb/d" }, + { "Units Code": "84", "Description": "short tons per minute", "Abbreviated Units": "short ton/min" }, + { "Units Code": "85", "Description": "short tons per hour", "Abbreviated Units": "short ton/h" }, + { "Units Code": "86", "Description": "short tons per day", "Abbreviated Units": "short ton/d" }, + { "Units Code": "87", "Description": "long tons per hour", "Abbreviated Units": "long ton/h" }, + { "Units Code": "88", "Description": "long tons per day", "Abbreviated Units": "long ton/d" }, + { "Units Code": "89", "Description": "deka therm", "Abbreviated Units": "Dth" }, + { "Units Code": "90", "Description": "specific gravity units", "Abbreviated Units": "specific gravity units" }, + { "Units Code": "91", "Description": "grams per cubic centimeter", "Abbreviated Units": "g/cm^3" }, + { "Units Code": "92", "Description": "kilograms per cubic meter", "Abbreviated Units": "kg/m^3" }, + { "Units Code": "93", "Description": "pounds per gallon", "Abbreviated Units": "lb/usg" }, + { "Units Code": "94", "Description": "pounds per cubic feet", "Abbreviated Units": "lb/ft^3" }, + { "Units Code": "95", "Description": "grams per milliliter", "Abbreviated Units": "g/mL" }, + { "Units Code": "96", "Description": "kilograms per liter", "Abbreviated Units": "kg/L" }, + { "Units Code": "97", "Description": "grams per liter", "Abbreviated Units": "g/L" }, + { "Units Code": "98", "Description": "pounds per cubic inch", "Abbreviated Units": "lb/in^3" }, + { "Units Code": "99", "Description": "short tons per cubic yard", "Abbreviated Units": "short ton/yd^3" }, + { "Units Code": "100", "Description": "degrees twaddell", "Abbreviated Units": "degTw" }, + { "Units Code": "101", "Description": "degrees brix", "Abbreviated Units": "degBx" }, + { "Units Code": "102", "Description": "degrees baume heavy", "Abbreviated Units": "BH" }, + { "Units Code": "103", "Description": "degrees baume light", "Abbreviated Units": "BL" }, + { "Units Code": "104", "Description": "degrees API", "Abbreviated Units": "degAPI" }, + { "Units Code": "105", "Description": "percent solids per weight", "Abbreviated Units": "% solid/weight" }, + { "Units Code": "106", "Description": "percent solids per volume", "Abbreviated Units": "% solid/volume" }, + { "Units Code": "107", "Description": "degrees balling", "Abbreviated Units": "degrees balling" }, + { "Units Code": "108", "Description": "proof per volume", "Abbreviated Units": "proof/volume" }, + { "Units Code": "109", "Description": "proof per mass", "Abbreviated Units": "proof/mass" }, + { "Units Code": "110", "Description": "bushels", "Abbreviated Units": "bushel" }, + { "Units Code": "111", "Description": "cubic yards", "Abbreviated Units": "yd^3" }, + { "Units Code": "112", "Description": "cubic feet", "Abbreviated Units": "ft^3" }, + { "Units Code": "113", "Description": "cubic inches", "Abbreviated Units": "in^3" }, + { "Units Code": "114", "Description": "inches per second", "Abbreviated Units": "in/s" }, + { "Units Code": "115", "Description": "inches per minute", "Abbreviated Units": "in/min" }, + { "Units Code": "116", "Description": "feet per minute", "Abbreviated Units": "ft/min" }, + { "Units Code": "117", "Description": "degrees per second", "Abbreviated Units": "deg/s" }, + { "Units Code": "118", "Description": "revolutions per second", "Abbreviated Units": "rev/s" }, + { "Units Code": "119", "Description": "revolutions per minute", "Abbreviated Units": "rpm" }, + { "Units Code": "120", "Description": "meters per hour", "Abbreviated Units": "m/hr" }, + { "Units Code": "121", "Description": "normal cubic meters per hour", "Abbreviated Units": "normal m^3/h" }, + { "Units Code": "122", "Description": "normal liter per hour", "Abbreviated Units": "normal L/h" }, + { "Units Code": "123", "Description": "standard cubic feet per minute", "Abbreviated Units": "standard ft^3/min" }, + { "Units Code": "124", "Description": "bbl liq", "Abbreviated Units": "bbl liq" }, + { "Units Code": "125", "Description": "ounce", "Abbreviated Units": "oz" }, + { "Units Code": "126", "Description": "foot pound force", "Abbreviated Units": "ft lb force" }, + { "Units Code": "127", "Description": "kilowatt", "Abbreviated Units": "kW" }, + { "Units Code": "128", "Description": "kilowatt hour", "Abbreviated Units": "KWh" }, + { "Units Code": "129", "Description": "horsepower", "Abbreviated Units": "hp" }, + { "Units Code": "130", "Description": "cubic feet per hour", "Abbreviated Units": "ft^3/h" }, + { "Units Code": "131", "Description": "cubic meters per minute", "Abbreviated Units": "m^3/min" }, + { "Units Code": "132", "Description": "barrels per second", "Abbreviated Units": "bbl/s" }, + { "Units Code": "133", "Description": "barrels per minute", "Abbreviated Units": "bbl/min" }, + { "Units Code": "134", "Description": "barrels per hour", "Abbreviated Units": "bbl/h" }, + { "Units Code": "135", "Description": "barrels per day", "Abbreviated Units": "bbl/d" }, + { "Units Code": "136", "Description": "gallons per hour", "Abbreviated Units": "usg/h" }, + { "Units Code": "137", "Description": "imperial gallons per second", "Abbreviated Units": "impgal/s" }, + { "Units Code": "138", "Description": "liters per hour", "Abbreviated Units": "L/h" }, + { "Units Code": "139", "Description": "parts per million", "Abbreviated Units": "ppm" }, + { "Units Code": "140", "Description": "megacalorie per hour", "Abbreviated Units": "Mcal/h" }, + { "Units Code": "141", "Description": "megajoule per hour", "Abbreviated Units": "MJ/h" }, + { "Units Code": "142", "Description": "British thermal unit per hour", "Abbreviated Units": "BTU/h" }, + { "Units Code": "143", "Description": "degrees", "Abbreviated Units": "degrees" }, + { "Units Code": "144", "Description": "radian", "Abbreviated Units": "rad" }, + { "Units Code": "145", "Description": "inches of water at 15.6 degC (60 degF)", "Abbreviated Units": "inH20 (15.6 degC or 60 degF)" }, + { "Units Code": "146", "Description": "micrograms per liter", "Abbreviated Units": "micrograms/L" }, + { "Units Code": "147", "Description": "micrograms per cubic meter", "Abbreviated Units": "micrograms/m^3" }, + { "Units Code": "148", "Description": "percent consitency", "Abbreviated Units": "% consistency" }, + { "Units Code": "149", "Description": "volume percent", "Abbreviated Units": "volume %" }, + { "Units Code": "150", "Description": "percent steam quality", "Abbreviated Units": "% steam quality" }, + { "Units Code": "151", "Description": "feet in sixteenths", "Abbreviated Units": "ft in sixteenths" }, + { "Units Code": "152", "Description": "cubic feet per pound", "Abbreviated Units": "ft^3/lb" }, + { "Units Code": "153", "Description": "picofarads", "Abbreviated Units": "pF" }, + { "Units Code": "154", "Description": "milliliters per liter", "Abbreviated Units": "mL/L" }, + { "Units Code": "155", "Description": "microliters per liter", "Abbreviated Units": "microliters/L" }, + { "Units Code": "156", "Description": "percent plato", "Abbreviated Units": "% plato" }, + { "Units Code": "157", "Description": "percent lower explosion level", "Abbreviated Units": "% lower explosion level" }, + { "Units Code": "158", "Description": "mega calorie", "Abbreviated Units": "Mcal" }, + { "Units Code": "159", "Description": "kiloohms", "Abbreviated Units": "kohm" }, + { "Units Code": "160", "Description": "megajoule", "Abbreviated Units": "MJ" }, + { "Units Code": "161", "Description": "British thermal unit", "Abbreviated Units": "BTU" }, + { "Units Code": "162", "Description": "normal cubic meter", "Abbreviated Units": "normal m^3" }, + { "Units Code": "163", "Description": "normal liter", "Abbreviated Units": "normal L" }, + { "Units Code": "164", "Description": "standard cubic feet", "Abbreviated Units": "normal ft^3" }, + { "Units Code": "165", "Description": "parts per billion", "Abbreviated Units": "parts/billion" }, + { "Units Code": "166", "Description": "ampere", "Abbreviated Units": "A" }, + { "Units Code": "167", "Description": "millimeters per meter", "Abbreviated Units": "mm/m" }, + { "Units Code": "168", "Description": "seconds since epoch", "Abbreviated Units": "epoch sec" }, + { "Units Code": "169", "Description": "no units", "Abbreviated Units": "no units" }, + { "Units Code": "170", "Description": "percentage", "Abbreviated Units": "%" }, + { "Units Code": "171", "Description": "kilohertz", "Abbreviated Units": "khz" }, + { "Units Code": "235", "Description": "gallons per day", "Abbreviated Units": "usg/d" }, + { "Units Code": "236", "Description": "hectoliters", "Abbreviated Units": "hL" }, + { "Units Code": "237", "Description": "megapascals", "Abbreviated Units": "MPa" }, + { "Units Code": "238", "Description": "inches of water at 4 degC (39.2 degF)", "Abbreviated Units": "inH2O (4 degC or 39.2 degF)" }, + { "Units Code": "239", "Description": "millimeters of water at 4 degC (39.2 degF)", "Abbreviated Units": "mmH2O (4 degC or 39.2 degF)" }, + { "Units Code": "253", "Description": "edge count or pulse count", "Abbreviated Units": "counts" }, + { "Units Code": "254", "Description": "raw ADC code", "Abbreviated Units": "ADC code" }, + { "Units Code": "255", "Description": "data not valid", "Abbreviated Units": "data not valid" } + ]; + + var outputString = ""; + var engUnitsRow = engUnitsTable.find(element => element["Units Code"] == unitCode.toString()); + + if (typeof engUnitsRow == 'undefined') { + return "Unknown Units"; + } + + if (outputType == 0) { + outputString = engUnitsRow["Description"]; + } + else if (outputType == 1) { + outputString = engUnitsRow["Abbreviated Units"]; + } + else { + errorMsg = outputType + " -- " + "Invalid Output Type. Use 0 for Description or 1 for Abbreviated Units."; + throw new Error(errorMsg); + } + + return outputString; +} + +//=========================================================== +// Utility functions +// Decoder doesn't call these functions +// Functions provided for customer usage +// +// List of functions: +// hexToFloat - Conversion from Hex to Float Number +// modbusToFloat_AB_CD_to_ABCD - Takes two Modbus registers, does no swapping, and converts them to 32 bit floating point number +// modbusToFloat_AB_CD_to_DCBA - Takes two Modbus registers, does byte and word swapping, and converts them to 32 bit floating point number +// modbusToFloat_AB_CD_to_BADC - Takes two Modbus registers, does byte swapping, and converts them to 32bit floating point number +// modbusToFloat_AB_CD_to_CDAB - Takes two Modbus registers, does word swapping, and converts them to 32bit floating point number +// printPayload - Prints Decoded payloads in formatted fashion +//=========================================================== + +/* istanbul ignore next */ +function hexToFloat(hex) { + var s = hex >> 31 ? -1 : 1; + var e = (hex >> 23) & 0xFF; + return s * (hex & 0x7fffff | 0x800000) * 1.0 / Math.pow(2, 23) * Math.pow(2, (e - 127)) +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_ABCD(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = (modbusReg1 & 0xFF00) >> 8; + bytes[1] = modbusReg1 & 0xFF; + bytes[2] = (modbusReg0 & 0xFF00) >> 8; + bytes[3] = modbusReg0 & 0xFF; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_DCBA(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = modbusReg0 & 0xFF; + bytes[1] = (modbusReg0 & 0xFF00) >> 8; + bytes[2] = modbusReg1 & 0xFF; + bytes[3] = (modbusReg1 & 0xFF00) >> 8; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_BADC(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = modbusReg1 & 0xFF; + bytes[1] = (modbusReg1 & 0xFF00) >> 8; + bytes[2] = modbusReg0 & 0xFF; + bytes[3] = (modbusReg0 & 0xFF00) >> 8; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function modbusToFloat_AB_CD_to_CDAB(modbusReg0, modbusReg1) { + var floatVal = 0; + var bytes = Buffer.alloc(4); + + bytes[0] = (modbusReg0 & 0xFF00) >> 8; + bytes[1] = modbusReg0 & 0xFF; + bytes[2] = (modbusReg1 & 0xFF00) >> 8; + bytes[3] = modbusReg1 & 0xFF; + + floatVal = bytes.readFloatBE(0); + return floatVal; +} +/* istanbul ignore next */ +function printPayload(payload) { + if (payload.data.payloadType == "Standard") { + let modbusSlot0HexString = payload.data.Modbus0.toString(16).toUpperCase().padStart(4, 0); + console.log("%s | %s | %s | %s | " + + "DIN1 = %d | DIN2 = %d | DIN3 = %d | WKUP = %d | ADC1 = %d | ADC2 = %d | ADC3 = %d | " + + "Battery = %d | Temperature = %f | Modbus0 = 0x%s |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.DIN1, payload.data.DIN2, payload.data.DIN3, payload.data.WKUP, payload.data.ADC1, payload.data.ADC2, payload.data.ADC3, + payload.data.Battery, payload.data.Temperature, modbusSlot0HexString); + return; + } + + if (payload.data.payloadType == "Modbus Standard") { + dataKeys = Object.keys(payload.data); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + if ((dataKeys[idx] != "fport") && (dataKeys[idx] != "voboType") && (dataKeys[idx] != "payloadType")) { + let fieldName = dataKeys[idx]; + let valueHex = "0x" + payload.data[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + valueHex + " | "; + } + } + console.log("%s\n", stringToPrint); + return; + } + + if (payload.data.payloadType == "Heartbeat 1.0") { + let firmwareRevisionHexString = payload.data.fwVersionMajor.toString(10) + "." + payload.data.fwVersionMinor.toString(10) + "." + payload.data.fwVersionPatch.toString(10) + "." + payload.data.fwVersionCustom.toString(10); + console.log("%s | %s | %s | %s | " + + "Battery Level MV = %d | FW Version = %s | Last RSSI = %d | Analog Voltage Config = %d | Analog Power Time Config = %d | Failed Transmissions Before Rejoin Config = %d | " + + "Cycle Through FSB Config = %d | ACK Enable Config = %d | ACK Frequency Config = %d | ACK Request Config = %d | Battery Level Threshold Config = %d |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.batteryLevelMV, firmwareRevisionHexString, payload.data.recSignalLevels, payload.data.analogVoltageConfig, payload.data.analogPowerTimeConfig, payload.data.failedTransmissionBeforeRejoinConfig, + payload.data.cycleThroughFSB, payload.data.ackEnable, payload.data.ackFreq, payload.data.ackReq, payload.data.batteryLevelThreshold); + return; + } + + if (payload.data.payloadType == "Heartbeat 2.0") { + let firmwareRevisionHexString = payload.data.firmwareRevision.toString(16).toUpperCase().padStart(3, 0); + console.log("%s | %s | %s | %s | " + + "Battery Level = %d | Fatal Errors Total = %d | RSSI Avg = %d | Failed Join Attempts Total = %d | Config Update Occurred = %d | Firmware Revision = 0x%s | " + + "Reboots Total = %d | Failed Transmits Total = %d | Error Event Logs Total = %d | Warning Event Logs Total = %d | Info Event Logs Total = %d | Measurement Packets Total = %d |\n", + payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, + payload.data.batteryLevel, payload.data.fatalErrorsTotal, payload.data.rssiAvg, payload.data.failedJoinAttemptsTotal, payload.data.configUpdateOccurred, firmwareRevisionHexString, + payload.data.rebootsTotal, payload.data.failedTransmitsTotal, payload.data.errorEventLogsTotal, payload.data.warningEventLogsTotal, payload.data.infoEventLogsTotal, payload.data.measurementPacketsTotal); + return; + } + + if (payload.data.payloadType == "One Analog Input") { + let fractionDigitsData0 = 1; + if (payload.data.engUnitsString0 == "mV" || payload.data.engUnitsString0 == "V") fractionDigitsData0 = 3; + if (payload.data.engUnitsString0 == "ADC code") fractionDigitsData0 = 0; + console.log("%s | %s | %s | %s | %s = %f %s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, payload.data.analogSensorString0, parseFloat(payload.data.sensorData0).toFixed(fractionDigitsData0), payload.data.engUnitsString0); + return; + } + + if (payload.data.payloadType == "Two Analog Inputs") { + let fractionDigitsData0 = 1; + if (payload.data.engUnitsString0 == "mV" || payload.data.engUnitsString0 == "V" || payload.data.engUnitsString0 == "mA" || payload.data.engUnitsString0 == "A") fractionDigitsData0 = 3; + if (payload.data.engUnitsString0 == "ADC code") fractionDigitsData0 = 0; + let fractionDigitsData1 = 1; + if (payload.data.engUnitsString1 == "mV" || payload.data.engUnitsString1 == "V" || payload.data.engUnitsString1 == "mA" || payload.data.engUnitsString1 == "A") fractionDigitsData1 = 3; + if (payload.data.engUnitsString1 == "ADC code") fractionDigitsData1 = 0; + console.log("%s | %s | %s | %s | %s = %f %s | %s = %f %s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, payload.data.analogSensorString0, parseFloat(payload.data.sensorData0).toFixed(fractionDigitsData0), payload.data.engUnitsString0, payload.data.analogSensorString1, parseFloat(payload.data.sensorData1).toFixed(fractionDigitsData1), payload.data.engUnitsString1); + return; + } + + if (payload.data.payloadType == "Analog Input Variable Length") { + var stringToPrint = ""; + for (let i = 0; i < payload.data.numOfAinPayloads; i++) { + analogSensorString = payload.data["analogSensorString" + i]; + engUnitsString = payload.data["engUnitsString" + i]; + sensorData = payload.data.ainPayloads[i].sensorData0; + + let fractionDigitsData = 1; + if (engUnitsString == "mV" || engUnitsString == "V" || engUnitsString == "mA" || engUnitsString == "A") fractionDigitsData = 3; + if (engUnitsString == "ADC code") fractionDigitsData = 0; + stringToPrint = stringToPrint + analogSensorString + " = " + parseFloat(sensorData).toFixed(fractionDigitsData) + " " + engUnitsString + " | "; + } + console.log("%s | %s | %s | %s | %s\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, stringToPrint); + return; + } + + if (payload.data.payloadType == "Modbus Standard Variable Length") { + dataKeys = Object.keys(payload.data.modbusSlots); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + let fieldName = dataKeys[idx]; + let valueHex = "0x" + payload.data.modbusSlots[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + valueHex + " | "; + } + console.log("%s\n", stringToPrint); + return; + } + + if (payload.data.payloadType == "Digital Inputs") { + var stringToPrint = "" + for (let i = 0; i < payload.data.digitalSensorData.length; i++) { + stringToPrint = stringToPrint + payload.data.digitalSensorStrings[i] + " = " + payload.data.digitalSensorData[i].toString() + " | "; + } + console.log("%s | %s | %s | %s | %s\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, stringToPrint); + return; + } + + if (payload.data.payloadType == "Event Log") { + let eventTimestampDateTime = new Date(payload.data.eventTimestamp * 1000).toISOString(); + let eventCodeHexString = payload.data.eventCode.toString(16).toUpperCase().padStart(4, 0); + let metadataHexString = Buffer.from(payload.data.metadata).toString("hex").toUpperCase(); + console.log("%s | %s | %s | %s | Timestamp = %s | Code = 0x%s | Metadata = 0x%s |\n", payload.timestamp, payload.deveui, payload.data.voboType, payload.data.payloadType, eventTimestampDateTime, eventCodeHexString, metadataHexString); + return; + } + + if (payload.data.payloadType == "Modbus Generic") { + let dataKeys = Object.keys(payload.data); + let stringToPrint = payload.timestamp + " | " + payload.deveui + " | " + payload.data.voboType + " | " + payload.data.payloadType + " | "; + for (let idx = 0; idx < dataKeys.length; idx++) { + if ((dataKeys[idx] != "fport") && (dataKeys[idx] != "voboType") && (dataKeys[idx] != "payloadType")) { + let fieldName = dataKeys[idx]; + let regValueHexStr = "0x" + payload.data[fieldName].toString(16).toUpperCase().padStart(4, 0); + stringToPrint += fieldName + " = " + regValueHexStr + " | "; + } + } + console.log("%s\n", stringToPrint); + return; + } + + console.log(JSON.stringify(payload, null, 4)); + console.log(""); +} + +//=========================================================== +// Changes made to decoder for TagoIO below this point. +// TagoIO executes the decoder file (payload.js) rather than +// calling the Decoder function, so code must be added here +// to map the arguments to the Decoder function, and the +// exports had to be commented out. +//=========================================================== + +const bytes_vb = payload.find((x) => x.variable === "bytes"); +const fPort_vb = payload.find((x) => x.variable === "port" || x.variable === "fPort")?.value; + +if (bytes_vb) { + try { + const buffer = Buffer.from(bytes_vb.value, "hex") + const decoded = Decoder(buffer, fPort_vb); + payload = decoded; + } catch (error: any) { + console.error(error); + payload = [{ variable: "parse_error", value: error.message }]; + } +} + + +// module.exports = { +// customDecoder, decodeUplink, Decoder, addVoboMetadata, lookupAnalogSensorName, lookupDigitalSensorName, lookupUnits, +// hexToFloat, modbusToFloat_AB_CD_to_ABCD, modbusToFloat_AB_CD_to_DCBA, modbusToFloat_AB_CD_to_BADC, modbusToFloat_AB_CD_to_CDAB, printPayload +// } \ No newline at end of file diff --git a/decoders/connector/volley-boast/vobo-xp/v1.0.0/payload.test.ts b/decoders/connector/volley-boast/vobo-xp/v1.0.0/payload.test.ts new file mode 100644 index 0000000..b06cba4 --- /dev/null +++ b/decoders/connector/volley-boast/vobo-xp/v1.0.0/payload.test.ts @@ -0,0 +1,326 @@ +import { describe, test, expect } from "vitest"; +import { decoderRun } from "../../../../../src/functions/decoder-run"; + +const file_path = "decoders/connector/volley-boast/vobo-xp/v1.0.0/payload.js" + +const testPayloads = { + "data": [ + { "fport": 22, "bytes": "cc0d3900000c0000000400", "name": "Heartbeat 2.0 Payload" }, + { "fport": 32, "bytes": "0f2041d30000", "name": "One Analog Input Payload" }, + { "fport": 42, "bytes": "1227411ffefa3a4022147b", "name": "Two Analog Input Payload" }, + { "fport": 52, "bytes": "07000600", "name": "Digital Input Payload" }, + { "fport": 62, "bytes": "681ebd64ffff0000000000", "name": "Event Log Payload" }, + { "fport": 72, "bytes": "004ae6e23c002405200000", "name": "Configuration Payload" }, + { "fport": 102, "bytes": "0313020000000000000000", "name": "Modbus Generic Payload - One Register Group" }, + { "fport": 112, "bytes": "01fe3f80000002fe4000000003fe3f800000003a405810620f2041b60000", "name": "Analog Input Variable Length Payload" }, + { "fport": 122, "bytes": "0f270f270f270f270f270f270f2701", "name": "Modbus Standard Variable Length Payload" }, + ] +} + + +describe(`fPort: ${testPayloads.data[0].fport} - ${testPayloads.data[0].name}`, () => { // fPort 22 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[0].bytes }, + { variable: "fPort", value: testPayloads.data[0].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "batteryLevel": 3532, + "fatalErrorsTotal": 0, + "rssiAvg": 57, + "failedJoinAttemptsTotal": 0, + "configUpdateOccurred": 0, + "firmwareRevision": 0, + "rebootsTotal": 3, + "failedTransmitsTotal": 0, + "errorEventLogsTotal": 0, + "warningEventLogsTotal": 0, + "infoEventLogsTotal": 0, + "measurementPacketsTotal": 4, + "fport": 22, + "voboType": "VoBoXP", + "payloadType": "Heartbeat 2.0" + }, + "warnings": [], + "errors": [] + }); + }); +}); +describe(`fPort: ${testPayloads.data[1].fport} - ${testPayloads.data[1].name}`, () => { // fPort 32 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[1].bytes }, + { variable: "fPort", value: testPayloads.data[1].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorNum0": 15, + "sensorUnits0": 32, + "sensorData0": 26.375, + "analogSensorString0": "ADC Temperature ", + "engUnitsString0": "C", + "fport": 32, + "voboType": "VoBoXP", + "payloadType": "One Analog Input" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[2].fport} - ${testPayloads.data[2].name}`, () => { // fPort 42 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[2].bytes }, + { variable: "fPort", value: testPayloads.data[2].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorNum0": 1, + "sensorUnits0": 39, + "sensorData0": 9.999750137329102, + "sensorNum1": 2, + "sensorUnits1": 58, + "sensorData1": 2.5325000286102295, + "analogSensorString0": "AIN1 ", + "engUnitsString0": "mA", + "analogSensorString1": "AIN2 ", + "engUnitsString1": "V", + "fport": 42, + "voboType": "VoBoXP", + "payloadType": "Two Analog Inputs" + }, + "warnings": [], + "errors": [] + } + ); + }); +}); + +describe(`fPort: ${testPayloads.data[3].fport} - ${testPayloads.data[3].name}`, () => { // fPort 52 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[3].bytes }, + { variable: "fPort", value: testPayloads.data[3].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "sensorValid0": 7, + "sensorData0": 6, + "digitalSensorStrings": [ + "WKUP", + "DIN1", + "DIN2" + ], + "digitalSensorData": [ + 0, + 1, + 1 + ], + "fport": 52, + "voboType": "VoBoXP", + "payloadType": "Digital Inputs" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[4].fport} - ${testPayloads.data[4].name}`, () => { // fPort 62 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[4].bytes }, + { variable: "fPort", value: testPayloads.data[4].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "eventTimestamp": 1690115688, + "eventCode": 65535, + "metadata": [ + 0, + 0, + 0, + 0, + 0 + ], + "fport": 62, + "voboType": "VoBoXP", + "payloadType": "Event Log" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[5].fport} - ${testPayloads.data[5].name}`, () => { // fPort 72 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[5].bytes }, + { variable: "fPort", value: testPayloads.data[5].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "subgroupID": 0, + "sequenceNumber": 0, + "transRejoin": 10, + "ackFrequency": 4, + "lowBattery": 3.1, + "reserved1": 0, + "heartbeatAckEnable": true, + "operationMode": 1, + "cycleSubBands": true, + "ackRetries": 2, + "reservedLL": 4, + "ackEnable": true, + "heartbeatEnable": true, + "cycleTime": 60, + "backOffReset": 9, + "reservedRD": 5, + "reserved2": 0, + "resendAttempts": 0, + "freqSubBand": 2, + "fport": 72, + "voboType": "VoBoXP", + "payloadType": "Configuration" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[6].fport} - ${testPayloads.data[6].name}`, () => { // fPort 102 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[6].bytes }, + { variable: "fPort", value: testPayloads.data[6].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "group3register1": 531, + "fport": 102, + "voboType": "VoBoXP", + "payloadType": "Modbus Generic" + }, + "warnings": [], + "errors": [] + }); + }); +}); + + +describe(`fPort: ${testPayloads.data[7].fport} - ${testPayloads.data[7].name}`, () => { // fPort 112 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[7].bytes }, + { variable: "fPort", value: testPayloads.data[7].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "ainPayloads": [ + { + "sensorNum0": 1, + "sensorUnits0": 254, + "sensorData0": 1 + }, + { + "sensorNum0": 2, + "sensorUnits0": 254, + "sensorData0": 2 + }, + { + "sensorNum0": 3, + "sensorUnits0": 254, + "sensorData0": 1 + }, + { + "sensorNum0": 0, + "sensorUnits0": 58, + "sensorData0": 3.375999927520752 + }, + { + "sensorNum0": 15, + "sensorUnits0": 32, + "sensorData0": 22.75 + } + ], + "numOfAinPayloads": 5, + "analogSensorString0": "AIN1 ", + "engUnitsString0": "ADC code", + "analogSensorString1": "AIN2 ", + "engUnitsString1": "ADC code", + "analogSensorString2": "AIN3 ", + "engUnitsString2": "ADC code", + "analogSensorString3": "3.3V Supply Voltage ", + "engUnitsString3": "V", + "analogSensorString4": "ADC Temperature ", + "engUnitsString4": "C", + "fport": 112, + "voboType": "VoBoXP", + "payloadType": "Analog Input Variable Length" + }, + "warnings": [], + "errors": [] + }); + }); +}); + +describe(`fPort: ${testPayloads.data[8].fport} - ${testPayloads.data[8].name}`, () => { // fPort 122 + const raw_payload = [ + { variable: "bytes", value: testPayloads.data[8].bytes }, + { variable: "fPort", value: testPayloads.data[8].fport } + ]; + + const payload = decoderRun(file_path, { payload: raw_payload }); + + test("Check if data is correct", () => { + expect(payload).toStrictEqual({ + "data": { + "modbusSlots": { + "Modbus1": 9999, + "Modbus2": 9999, + "Modbus3": 9999, + "Modbus4": 9999, + "Modbus5": 9999, + "Modbus6": 9999, + "Modbus7": 9999 + }, + "firstSlotNum": 1, + "numModbusSlots": 7, + "fport": 122, + "voboType": "VoBoXP", + "payloadType": "Modbus Standard Variable Length" + }, + "warnings": [], + "errors": [] + }); + }); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1a8079e..fa5f775 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@types/validator": "13.11.10", "ajv": "8.13.0", "ajv-formats": "3.0.1", - "better-sqlite3": "11.6.0", + "better-sqlite3": "10.0.0", "knex": "3.1.0", "moment-timezone": "0.5.45", "sqlite3": "5.1.7", @@ -1359,12 +1359,11 @@ ] }, "node_modules/better-sqlite3": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.6.0.tgz", - "integrity": "sha512-2J6k/eVxcFYY2SsTxsXrj6XylzHWPxveCn4fKPKZFv/Vqn/Cd7lOuX4d7rGQXT5zL+97MkNL3nSbCrIoe3LkgA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-10.0.0.tgz", + "integrity": "sha512-rOz0JY8bt9oMgrFssP7GnvA5R3yln73y/NizzWqy3WlFth8Ux8+g4r/N9fjX97nn4X1YX6MTER2doNpTu5pqiA==", "dev": true, "hasInstallScript": true, - "license": "MIT", "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" diff --git a/package.json b/package.json index 664e8d5..72806c8 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@types/validator": "13.11.10", "ajv": "8.13.0", "ajv-formats": "3.0.1", - "better-sqlite3": "11.6.0", + "better-sqlite3": "10.0.0", "knex": "3.1.0", "moment-timezone": "0.5.45", "sqlite3": "5.1.7", @@ -27,4 +27,4 @@ "vm2": "3.9.19", "zod": "3.23.8" } -} \ No newline at end of file +}