From f9594022e1deadaa7cc9837790871bfae53d5762 Mon Sep 17 00:00:00 2001 From: mvujovic-vb Date: Fri, 27 Oct 2023 18:19:31 +0200 Subject: [PATCH] Volley Boast vendor and devices --- vendor/index.yaml | 10 + vendor/volley-boast/index.yaml | 7 + vendor/volley-boast/vobo-codec.yaml | 4 + vendor/volley-boast/vobo-decoder.js | 1368 +++++++++++++++++++++ vendor/volley-boast/vobo-gp-1.jpg | Bin 0 -> 15484 bytes vendor/volley-boast/vobo-gp-1.yaml | 82 ++ vendor/volley-boast/vobo-hl-1.jpg | Bin 0 -> 16778 bytes vendor/volley-boast/vobo-hl-1.yaml | 82 ++ vendor/volley-boast/vobo-sl-profile.yaml | 53 + vendor/volley-boast/vobo-sl.jpg | Bin 0 -> 9634 bytes vendor/volley-boast/vobo-sl.yaml | 76 ++ vendor/volley-boast/vobo-tc-profile.yaml | 53 + vendor/volley-boast/vobo-tc.jpg | Bin 0 -> 11637 bytes vendor/volley-boast/vobo-tc.yaml | 68 + vendor/volley-boast/vobo-xp-profile.yaml | 53 + vendor/volley-boast/vobo-xp.jpg | Bin 0 -> 16842 bytes vendor/volley-boast/vobo-xp.yaml | 74 ++ vendor/volley-boast/vobo-xx-profile.yaml | 53 + vendor/volley-boast/volley-boast-logo.png | Bin 0 -> 14247 bytes 19 files changed, 1983 insertions(+) create mode 100644 vendor/volley-boast/index.yaml create mode 100644 vendor/volley-boast/vobo-codec.yaml create mode 100644 vendor/volley-boast/vobo-decoder.js create mode 100644 vendor/volley-boast/vobo-gp-1.jpg create mode 100644 vendor/volley-boast/vobo-gp-1.yaml create mode 100644 vendor/volley-boast/vobo-hl-1.jpg create mode 100644 vendor/volley-boast/vobo-hl-1.yaml create mode 100644 vendor/volley-boast/vobo-sl-profile.yaml create mode 100644 vendor/volley-boast/vobo-sl.jpg create mode 100644 vendor/volley-boast/vobo-sl.yaml create mode 100644 vendor/volley-boast/vobo-tc-profile.yaml create mode 100644 vendor/volley-boast/vobo-tc.jpg create mode 100644 vendor/volley-boast/vobo-tc.yaml create mode 100644 vendor/volley-boast/vobo-xp-profile.yaml create mode 100644 vendor/volley-boast/vobo-xp.jpg create mode 100644 vendor/volley-boast/vobo-xp.yaml create mode 100644 vendor/volley-boast/vobo-xx-profile.yaml create mode 100644 vendor/volley-boast/volley-boast-logo.png diff --git a/vendor/index.yaml b/vendor/index.yaml index 39cecdb616..2b02b559d0 100644 --- a/vendor/index.yaml +++ b/vendor/index.yaml @@ -1969,3 +1969,13 @@ vendors: logo: koidra-logo.svg social: linkedin: https://www.linkedin.com/company/koidra/ + + - id: volley-boast + name: Volley Boast LLC + description: Volley Boast designs, deploys, and manages industrial IOT solutions. Combining extremely low-power LPWAN technology with the latest in cloud computing, Volley Boast delivers actionable data and analytics to customers' end-devices with minimal overhead and maintenance. + vendorID: 918 + website: https://volleyboast.com/ + logo: volley-boast-logo.png + social: + linkedin: https://www.linkedin.com/company/volley-boast-llc + github: VolleyBoast diff --git a/vendor/volley-boast/index.yaml b/vendor/volley-boast/index.yaml new file mode 100644 index 0000000000..0569b1c4e2 --- /dev/null +++ b/vendor/volley-boast/index.yaml @@ -0,0 +1,7 @@ +endDevices: + # Unique identifier of the end device (lowercase, alphanumeric with dashes, max 36 characters) + - vobo-gp-1 + - vobo-hl-1 + - vobo-tc + - vobo-xp + - vobo-sl diff --git a/vendor/volley-boast/vobo-codec.yaml b/vendor/volley-boast/vobo-codec.yaml new file mode 100644 index 0000000000..e7be19ae94 --- /dev/null +++ b/vendor/volley-boast/vobo-codec.yaml @@ -0,0 +1,4 @@ +# Uplink decoder decodes binary data uplink into a JSON object (optional) +# For documentation on writing encoders and decoders, see: https://www.thethingsindustries.com/docs/integrations/payload-formatters/javascript/ +uplinkDecoder: + fileName: vobo-decoder.js diff --git a/vendor/volley-boast/vobo-decoder.js b/vendor/volley-boast/vobo-decoder.js new file mode 100644 index 0000000000..9d641b54ea --- /dev/null +++ b/vendor/volley-boast/vobo-decoder.js @@ -0,0 +1,1368 @@ +/* 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 = 0; +const DECODER_PATCH_VERSION = 2; + +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 { + 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 portSecondDigit = fport % 10; + + if((portSecondDigit == 0) || ((fport >= 1) && (fport < 10))) voboType = "VoBoXX"; + else if(portSecondDigit == 1) voboType = "VoBoTC"; + + 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"; + + 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) + 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) + + 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[6] << 24) | (bytes[5] << 16) | (bytes[4] << 8) | bytes[3]; // 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] & 0x70) >> 4; // Reserved 1 Field (3-bit) + decoded.vbsEnable = Boolean((bytes[8] & 0x80) >> 7); // VoboSync Enable (1-bit) + decoded.reservedVTSIH = bytes[9] & 0x3F; // Reserved VTSIH (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.reserved1 = (bytes[4] >> 4) & 0x70; // Reserved 1 Field (3-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] & 0x01; // Modbus Payload Type (1-bit) + decoded.reserved1 = (bytes[4] >> 1) & 0x7F; // Reserved 1 Field (7-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 = bytes[1] & 0x01; // Group 1 First Cycle Enable + decoded.mbFirstCycleG2 = (bytes[1] >> 1) & 0x01; // Group 2 First Cycle Enable + decoded.mbFirstCycleG3 = (bytes[1] >> 2) & 0x01; // Group 3 First Cycle Enable + decoded.mbFirstCycleG4 = (bytes[1] >> 3) & 0x01; // Group 4 First Cycle Enable + decoded.mbFirstCycleG5 = (bytes[1] >> 4) & 0x01; // Group 5 First Cycle Enable + decoded.mbFirstCycleG6 = (bytes[1] >> 5) & 0x01; // Group 6 First Cycle Enable + decoded.mbFirstCycleG7 = (bytes[1] >> 6) & 0x01; // Group 7 First Cycle Enable + decoded.mbFirstCycleG8 = (bytes[1] >> 7) & 0x01; // Group 8 First Cycle Enable + decoded.mbFirstCycleG9 = bytes[2] & 0x01; // Group 9 First Cycle Enable + decoded.mbFirstCycleG10 = (bytes[2] >> 1) & 0x01; // Group 10 First Cycle Enable + decoded.mbFirstCycleG11 = (bytes[2] >> 2) & 0x01; // Group 11 First Cycle Enable + decoded.mbFirstCycleG12 = (bytes[2] >> 3) & 0x01; // Group 12 First Cycle Enable + decoded.mbFirstCycleG13 = (bytes[2] >> 4) & 0x01; // Group 13 First Cycle Enable + decoded.mbFirstCycleG14 = (bytes[2] >> 5) & 0x01; // Group 14 First Cycle Enable + decoded.mbFirstCycleG15 = (bytes[2] >> 6) & 0x01; // Group 15 First Cycle Enable + decoded.mbFirstCycleG16 = (bytes[2] >> 7) & 0x01; // Group 16 First Cycle Enable + decoded.mbFirstCycleG17 = bytes[3] & 0x01; // Group 17 First Cycle Enable + decoded.mbFirstCycleG18 = (bytes[3] >> 1) & 0x01; // Group 18 First Cycle Enable + decoded.mbFirstCycleG19 = (bytes[3] >> 2) & 0x01; // Group 19 First Cycle Enable + decoded.mbFirstCycleG20 = (bytes[3] >> 3) & 0x01; // Group 20 First Cycle Enable + decoded.mbFirstCycleG21 = (bytes[3] >> 4) & 0x01; // Group 21 First Cycle Enable + decoded.mbFirstCycleG22 = (bytes[3] >> 5) & 0x01; // Group 22 First Cycle Enable + decoded.mbFirstCycleG23 = (bytes[3] >> 6) & 0x01; // Group 23 First Cycle Enable + decoded.mbFirstCycleG24 = (bytes[3] >> 7) & 0x01; // Group 24 First Cycle Enable + decoded.mbFirstCycleG25 = bytes[4] & 0x01; // Group 25 First Cycle Enable + decoded.mbFirstCycleG26 = (bytes[4] >> 1) & 0x01; // Group 26 First Cycle Enable + decoded.mbFirstCycleG27 = (bytes[4] >> 2) & 0x01; // Group 27 First Cycle Enable + decoded.mbFirstCycleG28 = (bytes[4] >> 3) & 0x01; // Group 28 First Cycle Enable + decoded.mbFirstCycleG29 = (bytes[4] >> 4) & 0x01; // Group 29 First Cycle Enable + decoded.mbFirstCycleG30 = (bytes[4] >> 5) & 0x01; // Group 30 First Cycle Enable + decoded.mbFirstCycleG31 = (bytes[4] >> 6) & 0x01; // Group 31 First Cycle Enable + decoded.mbFirstCycleG32 = (bytes[4] >> 7) & 0x01; // Group 32 First Cycle Enable + decoded.mbFirstCycleG33 = bytes[5] & 0x01; // Group 33 First Cycle Enable + decoded.mbFirstCycleG34 = (bytes[5] >> 1) & 0x01; // Group 34 First Cycle Enable + decoded.mbFirstCycleG35 = (bytes[5] >> 2) & 0x01; // Group 35 First Cycle Enable + decoded.mbFirstCycleG36 = (bytes[5] >> 3) & 0x01; // Group 36 First Cycle Enable + decoded.mbFirstCycleG37 = (bytes[5] >> 4) & 0x01; // Group 37 First Cycle Enable + decoded.mbFirstCycleG38 = (bytes[5] >> 5) & 0x01; // Group 38 First Cycle Enable + decoded.mbFirstCycleG39 = (bytes[5] >> 6) & 0x01; // Group 39 First Cycle Enable + decoded.mbFirstCycleG40 = (bytes[5] >> 7) & 0x01; // Group 40 First Cycle Enable + decoded.mbFirstCycleG41 = bytes[6] & 0x01; // Group 41 First Cycle Enable + decoded.reserved1 = (bytes[6] >> 1) & 0x7F; // Reserved 1 Field (7-bit) + } + if (decoded.subgroupID == 7) + { + decoded.mbSubseqCycleG1 = bytes[1] & 0x01; // Group 1 Subsequent Cycle Enable + decoded.mbSubseqCycleG2 = (bytes[1] >> 1) & 0x01; // Group 2 Subsequent Cycle Enable + decoded.mbSubseqCycleG3 = (bytes[1] >> 2) & 0x01; // Group 3 Subsequent Cycle Enable + decoded.mbSubseqCycleG4 = (bytes[1] >> 3) & 0x01; // Group 4 Subsequent Cycle Enable + decoded.mbSubseqCycleG5 = (bytes[1] >> 4) & 0x01; // Group 5 Subsequent Cycle Enable + decoded.mbSubseqCycleG6 = (bytes[1] >> 5) & 0x01; // Group 6 Subsequent Cycle Enable + decoded.mbSubseqCycleG7 = (bytes[1] >> 6) & 0x01; // Group 7 Subsequent Cycle Enable + decoded.mbSubseqCycleG8 = (bytes[1] >> 7) & 0x01; // Group 8 Subsequent Cycle Enable + decoded.mbSubseqCycleG9 = bytes[2] & 0x01; // Group 9 Subsequent Cycle Enable + decoded.mbSubseqCycleG10 = (bytes[2] >> 1) & 0x01; // Group 10 Subsequent Cycle Enable + decoded.mbSubseqCycleG11 = (bytes[2] >> 2) & 0x01; // Group 11 Subsequent Cycle Enable + decoded.mbSubseqCycleG12 = (bytes[2] >> 3) & 0x01; // Group 12 Subsequent Cycle Enable + decoded.mbSubseqCycleG13 = (bytes[2] >> 4) & 0x01; // Group 13 Subsequent Cycle Enable + decoded.mbSubseqCycleG14 = (bytes[2] >> 5) & 0x01; // Group 14 Subsequent Cycle Enable + decoded.mbSubseqCycleG15 = (bytes[2] >> 6) & 0x01; // Group 15 Subsequent Cycle Enable + decoded.mbSubseqCycleG16 = (bytes[2] >> 7) & 0x01; // Group 16 Subsequent Cycle Enable + decoded.mbSubseqCycleG17 = bytes[3] & 0x01; // Group 17 Subsequent Cycle Enable + decoded.mbSubseqCycleG18 = (bytes[3] >> 1) & 0x01; // Group 18 Subsequent Cycle Enable + decoded.mbSubseqCycleG19 = (bytes[3] >> 2) & 0x01; // Group 19 Subsequent Cycle Enable + decoded.mbSubseqCycleG20 = (bytes[3] >> 3) & 0x01; // Group 20 Subsequent Cycle Enable + decoded.mbSubseqCycleG21 = (bytes[3] >> 4) & 0x01; // Group 21 Subsequent Cycle Enable + decoded.mbSubseqCycleG22 = (bytes[3] >> 5) & 0x01; // Group 22 Subsequent Cycle Enable + decoded.mbSubseqCycleG23 = (bytes[3] >> 6) & 0x01; // Group 23 Subsequent Cycle Enable + decoded.mbSubseqCycleG24 = (bytes[3] >> 7) & 0x01; // Group 24 Subsequent Cycle Enable + decoded.mbSubseqCycleG25 = bytes[4] & 0x01; // Group 25 Subsequent Cycle Enable + decoded.mbSubseqCycleG26 = (bytes[4] >> 1) & 0x01; // Group 26 Subsequent Cycle Enable + decoded.mbSubseqCycleG27 = (bytes[4] >> 2) & 0x01; // Group 27 Subsequent Cycle Enable + decoded.mbSubseqCycleG28 = (bytes[4] >> 3) & 0x01; // Group 28 Subsequent Cycle Enable + decoded.mbSubseqCycleG29 = (bytes[4] >> 4) & 0x01; // Group 29 Subsequent Cycle Enable + decoded.mbSubseqCycleG30 = (bytes[4] >> 5) & 0x01; // Group 30 Subsequent Cycle Enable + decoded.mbSubseqCycleG31 = (bytes[4] >> 6) & 0x01; // Group 31 Subsequent Cycle Enable + decoded.mbSubseqCycleG32 = (bytes[4] >> 7) & 0x01; // Group 32 Subsequent Cycle Enable + decoded.mbSubseqCycleG33 = bytes[5] & 0x01; // Group 33 Subsequent Cycle Enable + decoded.mbSubseqCycleG34 = (bytes[5] >> 1) & 0x01; // Group 34 Subsequent Cycle Enable + decoded.mbSubseqCycleG35 = (bytes[5] >> 2) & 0x01; // Group 35 Subsequent Cycle Enable + decoded.mbSubseqCycleG36 = (bytes[5] >> 3) & 0x01; // Group 36 Subsequent Cycle Enable + decoded.mbSubseqCycleG37 = (bytes[5] >> 4) & 0x01; // Group 37 Subsequent Cycle Enable + decoded.mbSubseqCycleG38 = (bytes[5] >> 5) & 0x01; // Group 38 Subsequent Cycle Enable + decoded.mbSubseqCycleG39 = (bytes[5] >> 6) & 0x01; // Group 39 Subsequent Cycle Enable + decoded.mbSubseqCycleG40 = (bytes[5] >> 7) & 0x01; // Group 40 Subsequent Cycle Enable + decoded.mbSubseqCycleG41 = 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] = bytes[1] & 0x01; // Modbus Group First Cycle Enable (1-bit) + decoded["mbSubseqCyclesG" + groupIdxStr] = (bytes[1] >> 1) & 0x01; // Modbus Group Subsequent Cycle Enable (1-bit) + decoded["mbByteSwapG" + groupIdxStr] = (bytes[1] >> 2) & 0x01; // Modbus Group Byte Swap Enable (1-bit) + decoded["mbWordSwapG" + groupIdxStr] = (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 = parseFloat(((bytes[5] << 24) | (bytes[4] << 16) | (bytes[3] << 8) | bytes[2]).toFixed(3)); // AIN1 Minimum Value (32-bit) + decoded.ain1MaxValue = parseFloat(((bytes[9] << 24) | (bytes[8] << 16) | (bytes[7] << 8) | bytes[6]).toFixed(3)); // 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 = parseFloat(((bytes[4] << 24) | (bytes[3] << 16) | (bytes[2] << 8) | bytes[1]).toFixed(3)); // AIN1 Series Resistance (32-bit) + } + // AIN1 Calibration + if (decoded.sequenceNumber == 2) + { + decoded.ain1Gain = parseFloat(((bytes[4] << 24) | (bytes[3] << 16) | (bytes[2] << 8) | bytes[1]).toFixed(3)); // AIN1 Calibration Gain (32-bit) + decoded.ain1Offset = parseFloat(((bytes[8] << 24) | (bytes[7] << 16) | (bytes[6] << 8) | bytes[5]).toFixed(3)); // AIN1 Calibration Offset (32-bit) + } + + // AIN2 General + if (decoded.sequenceNumber == 3) + { + decoded.ain2UnitsCode = bytes[1]; // AIN2 Units Code (8-bit) + decoded.ain2MinValue = parseFloat(((bytes[5] << 24) | (bytes[4] << 16) | (bytes[3] << 8) | bytes[2]).toFixed(3)); // AIN2 Minimum Value (32-bit) + decoded.ain2MaxValue = parseFloat(((bytes[9] << 24) | (bytes[8] << 16) | (bytes[7] << 8) | bytes[6]).toFixed(3)); // 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 = parseFloat(((bytes[4] << 24) | (bytes[3] << 16) | (bytes[2] << 8) | bytes[1]).toFixed(3)); // AIN2 Series Resistance (32-bit) + } + // AIN2 Calibration + if (decoded.sequenceNumber == 5) + { + decoded.ain2Gain = parseFloat(((bytes[4] << 24) | (bytes[3] << 16) | (bytes[2] << 8) | bytes[1]).toFixed(3)); // AIN2 Calibration Gain (32-bit) + decoded.ain2Offset = parseFloat(((bytes[8] << 24) | (bytes[7] << 16) | (bytes[6] << 8) | bytes[5]).toFixed(3)); // AIN2 Calibration Offset (32-bit) + } + + // AIN3 General + if (decoded.sequenceNumber == 6) + { + decoded.ain3UnitsCode = bytes[1]; // AIN3 Units Code (8-bit) + decoded.ain3MinValue = parseFloat(((bytes[5] << 24) | (bytes[4] << 16) | (bytes[3] << 8) | bytes[2]).toFixed(3)); // AIN3 Minimum Value (32-bit) + decoded.ain3MaxValue = parseFloat(((bytes[9] << 24) | (bytes[8] << 16) | (bytes[7] << 8) | bytes[6]).toFixed(3)); // 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 = parseFloat(((bytes[4] << 24) | (bytes[3] << 16) | (bytes[2] << 8) | bytes[1]).toFixed(3)); // AIN3 Series Resistance (32-bit) + } + // AIN3 Calibration + if (decoded.sequenceNumber == 8) + { + decoded.ain3Gain = parseFloat(((bytes[4] << 24) | (bytes[3] << 16) | (bytes[2] << 8) | bytes[1]).toFixed(3)); // AIN3 Calibration Gain (32-bit) + decoded.ain3Offset = parseFloat(((bytes[8] << 24) | (bytes[7] << 16) | (bytes[6] << 8) | bytes[5]).toFixed(3)); // 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) + decoded.deviceType1 = bytes[1] & 0x07; // Device Type 1 (3-bit) + decoded.enabled1 = Boolean((bytes[1] & 0x08) >> 3); // Enabled 1 (1-bit) + decoded.deviceType2 = (bytes[1] & 0x70) >> 4; // Device Type 2 (3-bit) + decoded.enabled2 = Boolean((bytes[1] & 0x80) >> 7); // Enabled 2 (1-bit) + decoded.deviceType3 = bytes[2] & 0x07; // Device Type 3 (3-bit) + decoded.enabled3 = Boolean((bytes[2] & 0x08) >> 3); // Enabled 3 (1-bit) + decoded.deviceType4 = (bytes[2] & 0x70) >> 4; // Device Type 4 (3-bit) + decoded.enabled4 = Boolean((bytes[2] & 0x80) >> 7); // Enabled 4 (1-bit) + decoded.deviceType5 = bytes[3] & 0x07; // Device Type 5 (3-bit) + decoded.enabled5 = Boolean((bytes[3] & 0x08) >> 3); // Enabled 5 (1-bit) + decoded.deviceType6 = (bytes[3] & 0x70) >> 4; // Device Type 6 (3-bit) + decoded.enabled6 = Boolean((bytes[3] & 0x80) >> 7); // Enabled 6 (1-bit) + decoded.deviceType7 = bytes[4] & 0x07; // Device Type 7 (3-bit) + decoded.enabled7 = Boolean((bytes[4] & 0x08) >> 3); // Enabled 7 (1-bit) + decoded.deviceType8 = (bytes[4] & 0x70) >> 4; // Device Type 8 (3-bit) + decoded.enabled8 = Boolean((bytes[4] & 0x80) >> 7); // Enabled 8 (1-bit) + decoded.deviceType9 = bytes[5] & 0x07; // Device Type 9 (3-bit) + decoded.enabled9 = Boolean((bytes[5] & 0x08) >> 3); // Enabled 9 (1-bit) + decoded.deviceType10 = (bytes[5] & 0x70) >> 4; // Device Type 10 (3-bit) + decoded.enabled10 = Boolean((bytes[5] & 0x80) >> 7); // Enabled 10 (1-bit) + decoded.deviceType11 = bytes[6] & 0x07; // Device Type 11 (3-bit) + decoded.enabled11 = Boolean((bytes[6] & 0x08) >> 3); // Enabled 11 (1-bit) + decoded.deviceType12 = (bytes[6] & 0x70) >> 4; // Device Type 12 (3-bit) + decoded.enabled12 = Boolean((bytes[6] & 0x80) >> 7); // Enabled 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) + + 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] & 0x3F; + 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 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"} + ]; + + var analogSensorsTable = []; + if(voboType == "VoBoXX") { + analogSensorsTable = analogSensorsTableXX; + } + else if(voboType == "VoBoTC") { + analogSensorsTable = analogSensorsTableTC; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\" or \"VoBoTC\""; + 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"]; + 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"} + ]; + + var digitalSensorsTable = []; + if(voboType == "VoBoXX") { + digitalSensorsTable = digitalSensorsTableXX; + } + else if(voboType == "VoBoTC") { + digitalSensorsTable = digitalSensorsTableTC; + } + else { + errorMsg = voboType + " -- " + "Invalid VoBo Type. Use \"VoBoXX\" or \"VoBoTC\""; + 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":"120","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":"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 == "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(""); +} + +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/vendor/volley-boast/vobo-gp-1.jpg b/vendor/volley-boast/vobo-gp-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ffa0d5158aab840fa3f1c0309ba9ab8c84c9a638 GIT binary patch literal 15484 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 literal 0 HcmV?d00001 diff --git a/vendor/volley-boast/vobo-gp-1.yaml b/vendor/volley-boast/vobo-gp-1.yaml new file mode 100644 index 0000000000..1230ae0b7d --- /dev/null +++ b/vendor/volley-boast/vobo-gp-1.yaml @@ -0,0 +1,82 @@ +name: VoBo-GP-1 +description: General purpose LoRaWAN bridge + +# Firmware versions (at least one is mandatory) +firmwareVersions: + - # Firmware version 1.03.10 + version: '1.03.10' + numeric: 10310 + profiles: + US902-928: + # Identifier of the profile (lowercase, alphanumeric with dashes, max 36 characters) + id: vobo-xx-profile + codec: vobo-codec + - # Firmware version 2.00.00 + version: '2.00.00' + numeric: 20000 + profiles: + US902-928: + # Identifier of the profile (lowercase, alphanumeric with dashes, max 36 characters) + id: vobo-xx-profile + codec: vobo-codec + +# Sensors that this device features (optional) +# Valid values are: +# 4-20 ma, accelerometer, altitude, analog input, auxiliary, barometer, battery, button, bvoc, co, co2, conductivity, +# current, digital input, dissolved oxygen, distance, dust, energy, gps, gyroscope, h2s, humidity, iaq, level, light, +# lightning, link, magnetometer, moisture, motion, no, no2, o3, particulate matter, ph, pir, pm2.5, pm10, potentiometer, +# power, precipitation, pressure, proximity, pulse count, pulse frequency, radar, rainfall, rssi, smart valve, snr, so2, +# solar radiation, sound, strain, surface temperature, temperature, tilt, time, tvoc, uv, vapor pressure, velocity, +# vibration, voltage, water potential, water, weight, wifi ssid, wind direction, wind speed. +sensors: + - 4-20 ma + - analog input + - battery + - button + - digital input + - voltage + +# Bridge interfaces (optional) +# Valid values are: modbus, m-bus, can bus, rs-485, sdi-12, analog. +bridgeInterfaces: + - analog + - modbus + - rs-485 + +# Dimensions in mm (optional) +# Use width, height, length and/or diameter +dimensions: + width: 79 + length: 193 + height: 117 + +# Weight in grams (optional) +weight: 681 + +# Battery information (optional) +battery: + replaceable: true + type: LSH20 (D size) + +# Operating conditions (optional) +operatingConditions: + # Temperature (Celsius) + temperature: + min: -35 + max: 80 + +# IP rating (optional) +ipCode: IP66 + +# Key provisioning (optional) +# Valid values are: custom (user can configure keys), join server and manifest. +keyProvisioning: + - join server + +# Product and data sheet URLs (optional) +productURL: https://volleyboast.com/products/vobo-endpoints/ +dataSheetURL: https://volleyboast.com/products/Latest/VoBoGP1/DCM-0018-A5_VoBo_GP1_DataSheet.pdf + +# Photos +photos: + main: vobo-gp-1.jpg diff --git a/vendor/volley-boast/vobo-hl-1.jpg b/vendor/volley-boast/vobo-hl-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a3fba94adba01a55932b0b89f282ee791cda3627 GIT binary patch literal 16778 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 B7K;D? literal 0 HcmV?d00001 diff --git a/vendor/volley-boast/vobo-hl-1.yaml b/vendor/volley-boast/vobo-hl-1.yaml new file mode 100644 index 0000000000..89c16938a2 --- /dev/null +++ b/vendor/volley-boast/vobo-hl-1.yaml @@ -0,0 +1,82 @@ +name: VoBo-HL-1 +description: General purpose LoRaWAN bridge certified for use in Class 1 Div 2 hazardous locations + +# Firmware versions (at least one is mandatory) +firmwareVersions: + - # Firmware version 1.03.10 + version: '1.03.10' + numeric: 10310 + profiles: + US902-928: + # Identifier of the profile (lowercase, alphanumeric with dashes, max 36 characters) + id: vobo-xx-profile + codec: vobo-codec + - # Firmware version 2.00.00 + version: '2.00.00' + numeric: 20000 + profiles: + US902-928: + # Identifier of the profile (lowercase, alphanumeric with dashes, max 36 characters) + id: vobo-xx-profile + codec: vobo-codec + +# Sensors that this device features (optional) +# Valid values are: +# 4-20 ma, accelerometer, altitude, analog input, auxiliary, barometer, battery, button, bvoc, co, co2, conductivity, +# current, digital input, dissolved oxygen, distance, dust, energy, gps, gyroscope, h2s, humidity, iaq, level, light, +# lightning, link, magnetometer, moisture, motion, no, no2, o3, particulate matter, ph, pir, pm2.5, pm10, potentiometer, +# power, precipitation, pressure, proximity, pulse count, pulse frequency, radar, rainfall, rssi, smart valve, snr, so2, +# solar radiation, sound, strain, surface temperature, temperature, tilt, time, tvoc, uv, vapor pressure, velocity, +# vibration, voltage, water potential, water, weight, wifi ssid, wind direction, wind speed. +sensors: + - 4-20 ma + - analog input + - battery + - button + - digital input + - voltage + +# Bridge interfaces (optional) +# Valid values are: modbus, m-bus, can bus, rs-485, sdi-12, analog. +bridgeInterfaces: + - analog + - modbus + - rs-485 + +# Dimensions in mm (optional) +# Use width, height, length and/or diameter +dimensions: + width: 79 + length: 193 + height: 117 + +# Weight in grams (optional) +weight: 681 + +# Battery information (optional) +battery: + replaceable: true + type: LSH20 (D size) + +# Operating conditions (optional) +operatingConditions: + # Temperature (Celsius) + temperature: + min: -35 + max: 80 + +# IP rating (optional) +ipCode: IP66 + +# Key provisioning (optional) +# Valid values are: custom (user can configure keys), join server and manifest. +keyProvisioning: + - join server + +# Product and data sheet URLs (optional) +productURL: https://volleyboast.com/products/vobo-endpoints/ +dataSheetURL: https://volleyboast.com/products/Latest/VoBoHL1/DCM-0017-A3_VoBo_HL1_DataSheet.pdf + +# Photos +photos: + main: vobo-hl-1.jpg diff --git a/vendor/volley-boast/vobo-sl-profile.yaml b/vendor/volley-boast/vobo-sl-profile.yaml new file mode 100644 index 0000000000..3024f1131d --- /dev/null +++ b/vendor/volley-boast/vobo-sl-profile.yaml @@ -0,0 +1,53 @@ +# Vendor profile ID, can be freely issued by the vendor +# This vendor profile ID is also used on the QR code for LoRaWAN devices, see +# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf +vendorProfileID: 918 + +# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1 +macVersion: '1.0.4' +# LoRaWAN Regional Parameters version. Values depend on the LoRaWAN version: +# 1.0: TS001-1.0 +# 1.0.1: TS001-1.0.1 +# 1.0.2: RP001-1.0.2 or RP001-1.0.2-RevB +# 1.0.3: RP001-1.0.3-RevA +# 1.0.4: RP002-1.0.0 or RP002-1.0.1 +# 1.1: RP001-1.1-RevA or RP001-1.1-RevB +regionalParametersVersion: 'RP002-1.0.1' +# regionalParametersVersion: 'RP002-1.0.3' # Supported, can't be assigned as there is no option for it. + +# Whether the end device supports join (OTAA) or not (ABP) +supportsJoin: true +# If your device is an ABP device (supportsJoin is false), uncomment the following fields: +# RX1 delay +#rx1Delay: 5 +# RX1 data rate offset +#rx1DataRateOffset: 0 +# RX2 data rate index +#rx2DataRateIndex: 0 +# RX2 frequency (MHz) +#rx2Frequency: 869.525 +# Factory preset frequencies (MHz) +#factoryPresetFrequencies: [868.1, 868.3, 868.5, 867.1, 867.3, 867.5, 867.7, 867.9] + +# Maximum EIRP +maxEIRP: 36 +# Whether the end device supports 32-bit frame counters +supports32bitFCnt: true + +# Whether the end device supports class B +supportsClassB: false +# If your device supports class B, uncomment the following fields: +# Maximum delay for the end device to answer a MAC request or confirmed downlink frame (seconds) +#classBTimeout: 60 +# Ping slot period (seconds) +#pingSlotPeriod: 128 +# Ping slot data rate index +#pingSlotDataRateIndex: 0 +# Ping slot frequency (MHz). Set to 0 if the band supports ping slot frequency hopping. +#pingSlotFrequency: 869.525 + +# Whether the end device supports class C +supportsClassC: false +# If your device supports class C, uncomment the following fields: +# Maximum delay for the end device to answer a MAC request or confirmed downlink frame (seconds) +#classCTimeout: 60 diff --git a/vendor/volley-boast/vobo-sl.jpg b/vendor/volley-boast/vobo-sl.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0935b2559956a7eb2326289490955d0e29c04669 GIT binary patch literal 9634 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%0A(2{)G``kYw0 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 V1I{y?|Nh@jMnX~ix2W-t{{c{zED8Vs literal 0 HcmV?d00001