UNPKG

bt-seneca-msc

Version:

A pure Javascript API for the Seneca Multi Smart Calibrator (MSC) device, using web bluetooth.

688 lines (646 loc) 21.8 kB
"use strict"; /******************************* MODBUS RTU FUNCTIONS FOR SENECA **********************/ var modbus = require("./modbusRtu"); var constants = require("./constants"); var utils = require("./utils"); const { Command } = require("./meterApi"); const { btState } = require("./classes/APIState"); var CommandType = constants.CommandType; const SENECA_MB_SLAVE_ID = modbus.SENECA_MB_SLAVE_ID; // Modbus RTU slave ID /* * Modbus registers map. Each register is 2 bytes wide. */ const MSCRegisters = { SerialNumber: 10, CurrentMode: 100, MeasureFlags: 102, CMD: 107, AUX1: 108, LoadCellMeasure: 114, TempMeasure: 120, RtdTemperatureMeasure: 128, RtdResistanceMeasure: 130, FrequencyMeasure: 164, MinMeasure: 132, MaxMeasure: 134, InstantMeasure: 136, PowerOffDelay: 142, PowerOffRemaining: 146, PulseOFFMeasure: 150, PulseONMeasure: 152, Sensibility_uS_OFF: 166, Sensibility_uS_ON: 168, BatteryMeasure: 174, ColdJunction: 190, ThresholdU_Freq: 192, GenerationFlags: 202, GEN_CMD: 207, GEN_AUX1: 208, CurrentSetpoint: 210, VoltageSetpoint: 212, LoadCellSetpoint: 216, ThermoTemperatureSetpoint: 220, RTDTemperatureSetpoint: 228, PulsesCount: 252, FrequencyTICK1: 254, FrequencyTICK2: 256, GenUhighPerc: 262, GenUlowPerc: 264 }; /** * Generate the modbus RTU packet to read the serial number * */ function makeSerialNumber() { return modbus.makeFC3(SENECA_MB_SLAVE_ID, 2, MSCRegisters.SerialNumber); } /** * Generate the modbus RTU packet to read the current mode * */ function makeCurrentMode() { return modbus.makeFC3(SENECA_MB_SLAVE_ID, 1, MSCRegisters.CurrentMode); } /** * Generate the modbus RTU packet to read the current battery level * */ function makeBatteryLevel() { return modbus.makeFC3(SENECA_MB_SLAVE_ID, 2, MSCRegisters.BatteryMeasure); } /** * Parses the register with battery level * @param {ArrayBuffer} buffer FC3 answer * @returns {number} battery level in V */ function parseBattery(buffer) { var registers = modbus.parseFC3(buffer); return modbus.getFloat32LEBS(registers, 0); } /** * Parse the Seneca MSC serial as per the UI interface * @param {ArrayBuffer} buffer modbus answer packet (FC3) */ function parseSerialNumber(buffer) { var registers = modbus.parseFC3(buffer); if (registers.length < 4) { throw new Error("Invalid serial number response"); } const val1 = registers.getUint16(0, false); const val2 = registers.getUint16(2, false); const serial = ((val2 << 16) + val1).toString(); if (serial.length > 5) { return serial.substr(0, 5) + "_" + serial.substr(5, serial.length - 5); } return serial; } /** * Parses the state of the meter. May throw. * @param {ArrayBuffer} buffer modbus answer packet (FC3) * @param {CommandType} currentMode if the registers contains an IGNORE value, returns the current mode * @returns {CommandType} meter mode */ function parseCurrentMode(buffer, currentMode) { var registers = modbus.parseFC3(buffer); if (registers.length < 2) { throw new Error("Invalid mode response"); } const val1 = registers.getUint16(0, false); if (val1 == CommandType.RESERVED || val1 == CommandType.GEN_RESERVED || val1 == CommandType.RESERVED_2) { // Must be ignored, internal states of the meter return currentMode; } const value = utils.Parse(CommandType, val1); if (value == null) throw new Error("Unknown meter mode : " + value); if (val1 == constants.ContinuityImpl && btState.continuity) { return CommandType.Continuity; } return val1; } /** * Sets the current mode. * @param {number} mode * @returns {ArrayBuffer|null} */ function makeModeRequest(mode) { const value = utils.Parse(CommandType, mode); const CHANGE_STATUS = 1; // Filter invalid commands if (value == null || value == CommandType.NONE_UNKNOWN) { return null; } btState.continuity = false; if (mode > CommandType.NONE_UNKNOWN && mode <= CommandType.OFF) { // Measurements if (mode == CommandType.Continuity) { mode = constants.ContinuityImpl; btState.continuity = true; } return modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.CMD, [CHANGE_STATUS, mode]); } else if (mode > CommandType.OFF && mode < CommandType.GEN_RESERVED) { // Generations switch (mode) { case CommandType.GEN_THERMO_B: case CommandType.GEN_THERMO_E: case CommandType.GEN_THERMO_J: case CommandType.GEN_THERMO_K: case CommandType.GEN_THERMO_L: case CommandType.GEN_THERMO_N: case CommandType.GEN_THERMO_R: case CommandType.GEN_THERMO_S: case CommandType.GEN_THERMO_T: // Cold junction not configured return modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.GEN_CMD, [CHANGE_STATUS, mode]); case CommandType.GEN_Cu50_3W: case CommandType.GEN_Cu50_2W: case CommandType.GEN_Cu100_2W: case CommandType.GEN_Ni100_2W: case CommandType.GEN_Ni120_2W: case CommandType.GEN_PT100_2W: case CommandType.GEN_PT500_2W: case CommandType.GEN_PT1000_2W: default: // All the simple cases return modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.GEN_CMD, [CHANGE_STATUS, mode]); } } return null; } /** * When the meter is measuring, make the modbus request of the value * @param {CommandType} mode * @returns {ArrayBuffer} modbus RTU packet */ function makeMeasureRequest(mode) { switch (mode) { case CommandType.OFF: return null; case CommandType.THERMO_B: case CommandType.THERMO_E: case CommandType.THERMO_J: case CommandType.THERMO_K: case CommandType.THERMO_L: case CommandType.THERMO_N: case CommandType.THERMO_R: case CommandType.THERMO_S: case CommandType.THERMO_T: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 2, MSCRegisters.TempMeasure); case CommandType.Continuity: case CommandType.Cu50_2W: case CommandType.Cu50_3W: case CommandType.Cu50_4W: case CommandType.Cu100_2W: case CommandType.Cu100_3W: case CommandType.Cu100_4W: case CommandType.Ni100_2W: case CommandType.Ni100_3W: case CommandType.Ni100_4W: case CommandType.Ni120_2W: case CommandType.Ni120_3W: case CommandType.Ni120_4W: case CommandType.PT100_2W: case CommandType.PT100_3W: case CommandType.PT100_4W: case CommandType.PT500_2W: case CommandType.PT500_3W: case CommandType.PT500_4W: case CommandType.PT1000_2W: case CommandType.PT1000_3W: case CommandType.PT1000_4W: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 4, MSCRegisters.RtdTemperatureMeasure); // Temp-Ohm case CommandType.Frequency: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 2, MSCRegisters.FrequencyMeasure); case CommandType.PulseTrain: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 4, MSCRegisters.PulseOFFMeasure); // ON-OFF case CommandType.LoadCell: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 4, MSCRegisters.LoadCell); case CommandType.mA_passive: case CommandType.mA_active: case CommandType.V: case CommandType.mV: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 6, MSCRegisters.MinMeasure); // Min-Max-Meas default: throw new Error("Mode not managed :" + btState.meter.mode); } } /** * Parse the measure read from the meter * @param {ArrayBuffer} buffer modbus rtu answer (FC3) * @param {CommandType} mode current mode of the meter * @returns {array} an array with first element "Measure name (units)":Value, second Timestamp:acquisition */ function parseMeasure(buffer, mode) { var responseFC3 = modbus.parseFC3(buffer); var meas, meas2, min, max; // All measures are float if (responseFC3 == null) return {}; switch (mode) { case CommandType.THERMO_B: case CommandType.THERMO_E: case CommandType.THERMO_J: case CommandType.THERMO_K: case CommandType.THERMO_L: case CommandType.THERMO_N: case CommandType.THERMO_R: case CommandType.THERMO_S: meas = modbus.getFloat32LEBS(responseFC3, 0); var value = Math.round(meas * 100) / 100; return { "Description": "Temperature", "Value": value, "Unit": "°C", "Timestamp": new Date().toISOString() }; case CommandType.Cu50_2W: case CommandType.Cu50_3W: case CommandType.Cu50_4W: case CommandType.Cu100_2W: case CommandType.Cu100_3W: case CommandType.Cu100_4W: case CommandType.Ni100_2W: case CommandType.Ni100_3W: case CommandType.Ni100_4W: case CommandType.Ni120_2W: case CommandType.Ni120_3W: case CommandType.Ni120_4W: case CommandType.PT100_2W: case CommandType.PT100_3W: case CommandType.PT100_4W: case CommandType.PT500_2W: case CommandType.PT500_3W: case CommandType.PT500_4W: case CommandType.PT1000_2W: case CommandType.PT1000_3W: case CommandType.PT1000_4W: meas = modbus.getFloat32LEBS(responseFC3, 0); meas2 = modbus.getFloat32LEBS(responseFC3, 4); return { "Description": "Temperature", "Value": Math.round(meas * 10) / 10, "Unit": "°C", "SecondaryDescription": "Resistance", "SecondaryValue": Math.round(meas2 * 10) / 10, "SecondaryUnit": "Ohms", "Timestamp": new Date().toISOString() }; case CommandType.Continuity: meas2 = modbus.getFloat32LEBS(responseFC3, 4); return { "Description": "Continuity", "Value": (meas2 < constants.ContinuityThresholdOhms) ? 1 : 0, "Unit": "None", "SecondaryDescription": "Resistance", "SecondaryValue": Math.round(meas2 * 10) / 10, "SecondaryUnit": "Ohms", "Timestamp": new Date().toISOString() }; case CommandType.Frequency: meas = modbus.getFloat32LEBS(responseFC3, 0); // Sensibilità mancanti return { "Description": "Frequency", "Value": Math.round(meas * 10) / 10, "Unit": "Hz", "Timestamp": new Date().toISOString() }; case CommandType.mA_active: case CommandType.mA_passive: min = modbus.getFloat32LEBS(responseFC3, 0); max = modbus.getFloat32LEBS(responseFC3, 4); meas = modbus.getFloat32LEBS(responseFC3, 8); return { "Description": "Current", "Value": Math.round(meas * 100) / 100, "Unit": "mA", "Minimum": Math.round(min * 100) / 100, "Maximum": Math.round(max * 100) / 100, "Timestamp": new Date().toISOString() }; case CommandType.V: min = modbus.getFloat32LEBS(responseFC3, 0); max = modbus.getFloat32LEBS(responseFC3, 4); meas = modbus.getFloat32LEBS(responseFC3, 8); return { "Description": "Voltage", "Value": Math.round(meas * 100) / 100, "Unit": "V", "Minimum": Math.round(min * 100) / 100, "Maximum": Math.round(max * 100) / 100, "Timestamp": new Date().toISOString() }; case CommandType.mV: min = modbus.getFloat32LEBS(responseFC3, 0); max = modbus.getFloat32LEBS(responseFC3, 4); meas = modbus.getFloat32LEBS(responseFC3, 8); return { "Description": "Voltage", "Value": Math.round(meas * 100) / 100, "Unit": "mV", "Minimum": Math.round(min * 100) / 100, "Maximum": Math.round(max * 100) / 100, "Timestamp": new Date().toISOString() }; case CommandType.PulseTrain: meas = modbus.getUint32LEBS(responseFC3, 0); meas2 = modbus.getUint32LEBS(responseFC3, 4); // Soglia e sensibilità mancanti return { "Description": "Pulse ON", "Value": meas, "Unit": "", "SecondaryDescription": "Pulse OFF", "SecondaryValue": meas2, "SecondaryUnit": "", "Timestamp": new Date().toISOString() }; case CommandType.LoadCell: meas = Math.round(modbus.getFloat32LEBS(responseFC3, 0) * 1000) / 1000; // Kg mancanti // Sensibilità, tara, portata mancanti return { "Description": "Imbalance", "Value": meas, "Unit": "mV/V", "Timestamp": new Date().toISOString() }; default: return { "Description": "Unknown", "Value": Math.round(meas * 1000) / 1000, "Unit": "?", "Timestamp": new Date().toISOString() }; } } /** * Reads the status flags from measurement mode * @param {CommandType} mode * @returns {ArrayBuffer} modbus RTU request to send */ function makeQualityBitRequest(mode) { return modbus.makeFC3(SENECA_MB_SLAVE_ID, 1, MSCRegisters.MeasureFlags); } /** * Checks if the error bit status * @param {ArrayBuffer} buffer * @returns {boolean} true if there is no error */ function isQualityValid(buffer) { var responseFC3 = modbus.parseFC3(buffer); return ((responseFC3.getUint16(0, false) & (1 << 13)) == 0); } /** * Reads the generation flags status from the meter * @param {CommandType} mode * @returns {ArrayBuffer} modbus RTU request to send */ function makeGenStatusRead(mode) { return modbus.makeFC3(SENECA_MB_SLAVE_ID, 1, MSCRegisters.GenerationFlags); } /** * Checks if the error bit is NOT set in the generation flags * @param {ArrayBuffer} responseFC3 * @returns {boolean} true if there is no error */ function parseGenStatus(buffer, mode) { var responseFC3 = modbus.parseFC3(buffer); switch (mode) { case CommandType.GEN_mA_active: case CommandType.GEN_mA_passive: return ((responseFC3.getUint16(0, false) & (1 << 15)) == 0) && // Gen error ((responseFC3.getUint16(0, false) & (1 << 14)) == 0); // Self generation I check default: return (responseFC3.getUint16(0, false) & (1 << 15)) == 0; // Gen error } } /** * Returns a buffer with the modbus-rtu request to be sent to Seneca * @param {CommandType} mode generation mode * @param {number} setpoint the value to set (mV/V/A/Hz/°C) except for pulses num_pulses * @param {number} setpoint2 frequency in Hz */ function makeSetpointRequest(mode, setpoint, setpoint2) { var TEMP, registers; var dt = new ArrayBuffer(4); var dv = new DataView(dt); modbus.setFloat32LEBS(dv, 0, setpoint); const sp = [dv.getUint16(0, false), dv.getUint16(2, false)]; var dtInt = new ArrayBuffer(4); var dvInt = new DataView(dtInt); modbus.setUint32LEBS(dvInt, 0, setpoint); const spInt = [dvInt.getUint16(0, false), dvInt.getUint16(2, false)]; switch (mode) { case CommandType.GEN_V: case CommandType.GEN_mV: return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.VoltageSetpoint, sp)]; // V / mV setpoint case CommandType.GEN_mA_active: case CommandType.GEN_mA_passive: return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.CurrentSetpoint, sp)]; // I setpoint case CommandType.GEN_Cu50_3W: case CommandType.GEN_Cu50_2W: case CommandType.GEN_Cu100_2W: case CommandType.GEN_Ni100_2W: case CommandType.GEN_Ni120_2W: case CommandType.GEN_PT100_2W: case CommandType.GEN_PT500_2W: case CommandType.GEN_PT1000_2W: return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.RTDTemperatureSetpoint, sp)]; // °C setpoint case CommandType.GEN_THERMO_B: case CommandType.GEN_THERMO_E: case CommandType.GEN_THERMO_J: case CommandType.GEN_THERMO_K: case CommandType.GEN_THERMO_L: case CommandType.GEN_THERMO_N: case CommandType.GEN_THERMO_R: case CommandType.GEN_THERMO_S: case CommandType.GEN_THERMO_T: return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.ThermoTemperatureSetpoint, sp)]; // °C setpoint case CommandType.GEN_LoadCell: return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.LoadCellSetpoint, sp)]; // mV/V setpoint case CommandType.GEN_Frequency: dt = new ArrayBuffer(8); // 2 Uint32 dv = new DataView(dt); // See Senecal manual manual // Max 20kHZ gen TEMP = Math.round(20000 / setpoint, 0); dv.setUint32(0, Math.floor(TEMP / 2), false); // TICK1 dv.setUint32(4, TEMP - Math.floor(TEMP / 2), false); // TICK2 // Byte-swapped little endian registers = [dv.getUint16(2, false), dv.getUint16(0, false), dv.getUint16(6, false), dv.getUint16(4, false)]; return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.FrequencyTICK1, registers)]; case CommandType.GEN_PulseTrain: dt = new ArrayBuffer(12); // 3 Uint32 dv = new DataView(dt); // See Senecal manual manual // Max 20kHZ gen TEMP = Math.round(20000 / setpoint2, 0); dv.setUint32(0, setpoint, false); // NUM_PULSES dv.setUint32(4, Math.floor(TEMP / 2), false); // TICK1 dv.setUint32(8, TEMP - Math.floor(TEMP / 2), false); // TICK2 registers = [dv.getUint16(2, false), dv.getUint16(0, false)]; var p1 = modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.PulsesCount, registers); // must split in two to stay <= 20 bytes for the full rtu packet registers = [ dv.getUint16(6, false), dv.getUint16(4, false), dv.getUint16(10, false), dv.getUint16(8, false)]; var p2 = modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.FrequencyTICK1, registers); return [p1, p2]; case CommandType.SET_UThreshold_F: return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.ThresholdU_Freq, sp)]; // U min for freq measurement case CommandType.SET_Sensitivity_uS: return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.Sensibility_uS_OFF, [spInt[0], spInt[1], spInt[0], spInt[1]])]; // uV for pulse train measurement to ON / OFF case CommandType.SET_ColdJunction: return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.ColdJunction, sp)]; // unclear unit case CommandType.SET_Ulow: modbus.setFloat32LEBS(dv, 0, setpoint / constants.MAX_U_GEN); // Must convert V into a % 0..MAX_U_GEN var sp2 = [dv.getUint16(0, false), dv.getUint16(2, false)]; return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.GenUlowPerc, sp2)]; // U low for freq / pulse gen case CommandType.SET_Uhigh: modbus.setFloat32LEBS(dv, 0, setpoint / constants.MAX_U_GEN); // Must convert V into a % 0..MAX_U_GEN var sp3 = [dv.getUint16(0, false), dv.getUint16(2, false)]; return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.GenUhighPerc, sp3)]; // U high for freq / pulse gen case CommandType.SET_ShutdownDelay: return [modbus.makeFC16(SENECA_MB_SLAVE_ID, MSCRegisters.PowerOffDelay, setpoint)]; // delay in sec case CommandType.OFF: return []; // No setpoint default: throw new Error("Not handled"); } return []; } /** * Reads the setpoint * @param {CommandType} mode * @returns {ArrayBuffer} modbus RTU request */ function makeSetpointRead(mode) { switch (mode) { case CommandType.GEN_V: case CommandType.GEN_mV: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 2, MSCRegisters.VoltageSetpoint); // mV or V setpoint case CommandType.GEN_mA_active: case CommandType.GEN_mA_passive: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 2, MSCRegisters.CurrentSetpoint); // A setpoint case CommandType.GEN_Cu50_3W: case CommandType.GEN_Cu50_2W: case CommandType.GEN_Cu100_2W: case CommandType.GEN_Ni100_2W: case CommandType.GEN_Ni120_2W: case CommandType.GEN_PT100_2W: case CommandType.GEN_PT500_2W: case CommandType.GEN_PT1000_2W: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 2, MSCRegisters.RTDTemperatureSetpoint); // °C setpoint case CommandType.GEN_THERMO_B: case CommandType.GEN_THERMO_E: case CommandType.GEN_THERMO_J: case CommandType.GEN_THERMO_K: case CommandType.GEN_THERMO_L: case CommandType.GEN_THERMO_N: case CommandType.GEN_THERMO_R: case CommandType.GEN_THERMO_S: case CommandType.GEN_THERMO_T: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 2, MSCRegisters.ThermoTemperatureSetpoint); // °C setpoint case CommandType.GEN_Frequency: case CommandType.GEN_PulseTrain: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 4, MSCRegisters.FrequencyTICK1); // Frequency setpoint (TICKS) case CommandType.GEN_LoadCell: return modbus.makeFC3(SENECA_MB_SLAVE_ID, 2, MSCRegisters.LoadCellSetpoint); // mV/V setpoint case CommandType.NONE_UNKNOWN: case CommandType.OFF: return null; } throw new Error("Not handled"); } /** * Parses the answer about SetpointRead * @param {ArrayBuffer} registers FC3 parsed answer * @returns {number} the last setpoint */ function parseSetpointRead(buffer, mode) { // Round to two digits var registers = modbus.parseFC3(buffer); var rounded = Math.round(modbus.getFloat32LEBS(registers, 0) * 100) / 100; switch (mode) { case CommandType.GEN_mA_active: case CommandType.GEN_mA_passive: return { "Description": "Current", "Value": rounded, "Unit": "mA", "Timestamp": new Date().toISOString() }; case CommandType.GEN_V: return { "Description": "Voltage", "Value": rounded, "Unit": "V", "Timestamp": new Date().toISOString() }; case CommandType.GEN_mV: return { "Description": "Voltage", "Value": rounded, "Unit": "mV", "Timestamp": new Date().toISOString() }; case CommandType.GEN_LoadCell: return { "Description": "Imbalance", "Value": rounded, "Unit": "mV/V", "Timestamp": new Date().toISOString() }; case CommandType.GEN_Frequency: case CommandType.GEN_PulseTrain: var tick1 = modbus.getUint32LEBS(registers, 0); var tick2 = modbus.getUint32LEBS(registers, 4); var fON = 0.0; var fOFF = 0.0; if (tick1 != 0) fON = Math.round(1 / (tick1 * 2 / 20000.0) * 10.0) / 10; // Need one decimal place for HZ if (tick2 != 0) fOFF = Math.round(1 / (tick2 * 2 / 20000.0) * 10.0) / 10; // Need one decimal place for HZ return { "Description": "Frequency ON", "Value": fON, "Unit": "Hz", "SecondaryDescription": "Frequency OFF", "SecondaryValue": fOFF, "SecondaryUnit": "Hz", "Timestamp": new Date().toISOString() }; case CommandType.GEN_Cu50_2W: case CommandType.GEN_Cu100_2W: case CommandType.GEN_Ni100_2W: case CommandType.GEN_Ni120_2W: case CommandType.GEN_PT100_2W: case CommandType.GEN_PT500_2W: case CommandType.GEN_PT1000_2W: case CommandType.GEN_THERMO_B: case CommandType.GEN_THERMO_E: case CommandType.GEN_THERMO_J: case CommandType.GEN_THERMO_K: case CommandType.GEN_THERMO_L: case CommandType.GEN_THERMO_N: case CommandType.GEN_THERMO_R: case CommandType.GEN_THERMO_S: case CommandType.GEN_THERMO_T: return { "Description": "Temperature", "Value": rounded, "Unit": "°C", "Timestamp": new Date().toISOString() }; default: return { "Description": "Unknown", "Value": rounded, "Unit": "?", "Timestamp": new Date().toISOString() }; } } module.exports = { MSCRegisters, makeSerialNumber, makeCurrentMode, makeBatteryLevel, parseBattery, parseSerialNumber, parseCurrentMode, makeModeRequest, makeMeasureRequest, parseMeasure, makeQualityBitRequest, isQualityValid, makeGenStatusRead, parseGenStatus, makeSetpointRequest, makeSetpointRead, parseSetpointRead };