diff --git a/vendors/milesight/codecs/em300-sld-zld.js b/vendors/milesight/codecs/em300-sld-zld.js index 6953f30..689ad43 100644 --- a/vendors/milesight/codecs/em300-sld-zld.js +++ b/vendors/milesight/codecs/em300-sld-zld.js @@ -26,7 +26,7 @@ function Decoder(bytes, port) { function milesightDeviceDecode(bytes) { var decoded = {}; - for (var i = 0; i < bytes.length;) { + for (var i = 0; i < bytes.length; ) { var channel_id = bytes[i++]; var channel_type = bytes[i++]; @@ -77,7 +77,7 @@ function milesightDeviceDecode(bytes) { } // TEMPERATURE else if (channel_id === 0x03 && channel_type === 0x67) { - // ℃ + // °C decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; i += 2; } @@ -98,10 +98,9 @@ function milesightDeviceDecode(bytes) { data.temperature = readInt16LE(bytes.slice(i + 4, i + 6)) / 10; data.humidity = readUInt8(bytes[i + 6]) / 2; data.leakage_status = readLeakageStatus(bytes[i + 7]); - + i += 8; decoded.history = decoded.history || []; decoded.history.push(data); - i += 8; } // DOWNLINK RESPONSE else if (channel_id === 0xfe || channel_id === 0xff) { @@ -135,8 +134,8 @@ function handle_downlink_response(channel_type, bytes, offset) { if (channel === 0x01) { decoded.temperature_alarm_config = {}; decoded.temperature_alarm_config.condition = readConditionType(value); - decoded.temperature_alarm_config.min_threshold = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 10; - decoded.temperature_alarm_config.max_threshold = readInt16LE(bytes.slice(offset + 3, offset + 5)) / 10; + decoded.temperature_alarm_config.threshold_min = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 10; + decoded.temperature_alarm_config.threshold_max = readInt16LE(bytes.slice(offset + 3, offset + 5)) / 10; } else if (channel === 0x02) { decoded.leakage_alarm_config = {}; decoded.leakage_alarm_config.enable = readEnableStatus(value); @@ -183,24 +182,26 @@ function handle_downlink_response(channel_type, bytes, offset) { offset += 1; break; case 0x79: - decoded.d2d_config = {}; - decoded.d2d_config.trigger_event = readTriggerEvent(bytes[offset]); - decoded.d2d_config.report_type = readReportType(bytes[offset + 1]); - decoded.d2d_config.d2d_cmd = readD2DCommand(bytes.slice(offset + 2, offset + 6)); + var d2d_master_config = {}; + d2d_master_config.mode = readD2DMode(bytes[offset]); + d2d_master_config.report_type = readReportType(bytes[offset + 1]); + d2d_master_config.d2d_cmd = readD2DCommand(bytes.slice(offset + 2, offset + 6)); offset += 6; + decoded.d2d_master_config = decoded.d2d_master_config || []; + decoded.d2d_master_config.push(d2d_master_config); break; case 0xea: var data = readUInt8(bytes[offset]); var channel = data & 0x03; var enable_value = (data >>> 7) & 0x01; if (channel === 0x00) { - decoded.temperature_calibration_config = {}; - decoded.temperature_calibration_config.enable = readEnableStatus(enable_value); - decoded.temperature_calibration_config.value = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 10; + decoded.temperature_calibration_settings = {}; + decoded.temperature_calibration_settings.enable = readEnableStatus(enable_value); + decoded.temperature_calibration_settings.calibration_value = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 10; } else if (channel === 0x01) { - decoded.humidity_calibration_config = {}; - decoded.humidity_calibration_config.enable = readEnableStatus(enable_value); - decoded.humidity_calibration_config.value = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 2; + decoded.humidity_calibration_settings = {}; + decoded.humidity_calibration_settings.enable = readEnableStatus(enable_value); + decoded.humidity_calibration_settings.calibration_value = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 2; } offset += 3; break; @@ -218,14 +219,14 @@ function readProtocolVersion(bytes) { } function readHardwareVersion(bytes) { - var major = bytes[0] & 0xff; + var major = (bytes[0] & 0xff).toString(16); var minor = (bytes[1] & 0xff) >> 4; return "v" + major + "." + minor; } function readFirmwareVersion(bytes) { - var major = bytes[0] & 0xff; - var minor = bytes[1] & 0xff; + var major = (bytes[0] & 0xff).toString(16); + var minor = (bytes[1] & 0xff).toString(16); return "v" + major + "." + minor; } @@ -283,7 +284,7 @@ function readConditionType(value) { return getValue(condition_map, value); } -function readTriggerEvent(value) { +function readD2DMode(value) { var event_map = { 0: "disable", 1: "temperature_alarm", 2: "temperature_alarm_release", 3: "leakage_alarm", 4: "leakage_alarm_release" }; return getValue(event_map, value); } @@ -334,7 +335,7 @@ function getValue(map, key) { return value; } -if (!Object.assign) { +//if (!Object.assign) { Object.defineProperty(Object, "assign", { enumerable: false, configurable: true, @@ -370,4 +371,545 @@ if (!Object.assign) { return to; }, }); +//} + +/** + * Payload Encoder + * + * Copyright 2025 Milesight IoT + * + * @product EM300-SLD / EM300-ZLD + */ + +// Chirpstack v4 +function encodeDownlink(input) { + var encoded = milesightDeviceEncode(input.data); + return { bytes: encoded }; +} + +// Chirpstack v3 +function Encode(fPort, obj) { + return milesightDeviceEncode(obj); +} + +// The Things Network +function Encoder(obj, port) { + return milesightDeviceEncode(obj); +} + +function milesightDeviceEncode(payload) { + var encoded = []; + + if ("reboot" in payload) { + encoded = encoded.concat(reboot(payload.reboot)); + } + if ("report_interval" in payload) { + encoded = encoded.concat(setReportInterval(payload.report_interval)); + } + if ("collection_interval" in payload) { + encoded = encoded.concat(setCollectionInterval(payload.collection_interval)); + } + if ("report_status" in payload) { + encoded = encoded.concat(reportStatus(payload.report_status)); + } + if ("history_enable" in payload) { + encoded = encoded.concat(setHistoryEnable(payload.history_enable)); + } + if ("fetch_history" in payload) { + encoded = encoded.concat(fetchHistory(payload.fetch_history)); + } + if ("stop_transmit" in payload) { + encoded = encoded.concat(stopTransmit(payload.stop_transmit)); + } + if ("clear_history" in payload) { + encoded = encoded.concat(clearHistory(payload.clear_history)); + } + if ("retransmit_enable" in payload) { + encoded = encoded.concat(setRetransmitEnable(payload.retransmit_enable)); + } + if ("retransmit_interval" in payload) { + encoded = encoded.concat(setRetransmitInterval(payload.retransmit_interval)); + } + if ("resend_interval" in payload) { + encoded = encoded.concat(setResendInterval(payload.resend_interval)); + } + if ("temperature_alarm_config" in payload) { + encoded = encoded.concat(setTemperatureAlarmConfig(payload.temperature_alarm_config)); + } + if ("leakage_alarm_config" in payload) { + encoded = encoded.concat(setLeakageAlarm(payload.leakage_alarm_config)); + } + if ("temperature_calibration_settings" in payload) { + encoded = encoded.concat(setTemperatureCalibration(payload.temperature_calibration_settings)); + } + if ("humidity_calibration_settings" in payload) { + encoded = encoded.concat(setHumidityCalibration(payload.humidity_calibration_settings)); + } + if ("d2d_master_config" in payload) { + for (var i = 0; i < payload.d2d_master_config.length; i++) { + encoded = encoded.concat(setD2DMasterConfig(payload.d2d_master_config[i])); + } + } + + return encoded; +} + +/** + * reboot device + * @param {number} reboot values: (0: no, 1: yes) + * @example { "reboot": 1 } + */ +function reboot(reboot) { + var yes_no_map = { 0: "no", 1: "yes" }; + var yes_no_values = getEncoderValues(yes_no_map); + if (yes_no_values.indexOf(reboot) === -1) { + throw new Error("reboot must be one of " + yes_no_values.join(", ")); + } + + if (getEncoderValue(yes_no_map, reboot) === 0) { + return []; + } + return [0xff, 0x10, 0xff]; +} + +/** + * report interval configuration + * @param {number} report_interval uint: second, range: [1, 64800] + * @example payload: { "report_interval": 600 } + */ +function setReportInterval(report_interval) { + if (typeof report_interval !== "number") { + throw new Error("report_interval must be a number"); + } + if (report_interval < 1 || report_interval > 64800) { + throw new Error("report_interval must be in range [1, 64800]"); + } + + var buffer = new EncoderBuffer(4); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0x03); + buffer.writeUInt16LE(report_interval); + return buffer.toBytes(); +} + +/** + * set collection interval + * @param {number} collection_interval unit: second + * @example { "collection_interval": 300 } + */ +function setCollectionInterval(collection_interval) { + if (typeof collection_interval !== "number") { + throw new Error("collection_interval must be a number"); + } + + var buffer = new EncoderBuffer(4); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0x02); + buffer.writeUInt16LE(collection_interval); + return buffer.toBytes(); +} + +/** + * report status + * @param {number} report_status values: (0: no, 1: yes) + * @example { "report_status": 1 } + */ +function reportStatus(report_status) { + var yes_no_map = { 0: "no", 1: "yes" }; + var yes_no_values = getEncoderValues(yes_no_map); + if (yes_no_values.indexOf(report_status) === -1) { + throw new Error("report_status must be one of " + yes_no_values.join(", ")); + } + + if (getEncoderValue(yes_no_map, report_status) === 0) { + return []; + } + return [0xff, 0x28, 0xff]; +} + +/** + * history enable + * @param {number} history_enable values: (0: disable, 1: enable) + * @example { "history_enable": 1 } + */ +function setHistoryEnable(history_enable) { + var enable_map = { 0: "disable", 1: "enable" }; + var enable_values = getEncoderValues(enable_map); + if (enable_values.indexOf(history_enable) === -1) { + throw new Error("history_enable must be one of " + enable_values.join(", ")); + } + + var buffer = new EncoderBuffer(3); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0x68); + buffer.writeUInt8(getEncoderValue(enable_map, history_enable)); + return buffer.toBytes(); +} + +/** + * fetch history + * @param {object} fetch_history + * @param {number} fetch_history.start_time unit: second + * @param {number} fetch_history.end_time unit: second + * @example { "fetch_history": { "start_time": 1609459200, "end_time": 1609545600 } } + */ +function fetchHistory(fetch_history) { + var start_time = fetch_history.start_time; + var end_time = fetch_history.end_time; + + if (typeof start_time !== "number") { + throw new Error("start_time must be a number"); + } + if ("end_time" in fetch_history && typeof end_time !== "number") { + throw new Error("end_time must be a number"); + } + if ("end_time" in fetch_history && start_time > end_time) { + throw new Error("start_time must be less than end_time"); + } + + var buffer; + if ("end_time" in fetch_history || end_time === 0) { + buffer = new EncoderBuffer(6); + buffer.writeUInt8(0xfd); + buffer.writeUInt8(0x6b); + buffer.writeUInt32LE(start_time); + } else { + buffer = new EncoderBuffer(10); + buffer.writeUInt8(0xfd); + buffer.writeUInt8(0x6c); + buffer.writeUInt32LE(start_time); + buffer.writeUInt32LE(end_time); + } + return buffer.toBytes(); } + +/** + * history stop transmit + * @param {number} stop_transmit values: (0: no, 1: yes) + * @example { "stop_transmit": 1 } + */ +function stopTransmit(stop_transmit) { + var yes_no_map = { 0: "no", 1: "yes" }; + var yes_no_values = getEncoderValues(yes_no_map); + if (yes_no_values.indexOf(stop_transmit) === -1) { + throw new Error("stop_transmit must be one of " + yes_no_values.join(", ")); + } + + if (getEncoderValue(yes_no_map, stop_transmit) === 0) { + return []; + } + return [0xfd, 0x6d, 0xff]; +} + +/** + * clear history + * @param {number} clear_history values: (0: no, 1: yes) + * @example { "clear_history": 1 } + */ +function clearHistory(clear_history) { + var yes_no_map = { 0: "no", 1: "yes" }; + var yes_no_values = getEncoderValues(yes_no_map); + if (yes_no_values.indexOf(clear_history) === -1) { + throw new Error("clear_history must be one of " + yes_no_values.join(", ")); + } + + if (getEncoderValue(yes_no_map, clear_history) === 0) { + return []; + } + return [0xff, 0x27, 0x01]; +} + +/** + * set retransmit enable + * @param {number} retransmit_enable values: (0: disable, 1: enable) + * @example { "retransmit_enable": 1 } + */ +function setRetransmitEnable(retransmit_enable) { + var enable_map = { 0: "disable", 1: "enable" }; + var enable_values = getEncoderValues(enable_map); + if (enable_values.indexOf(retransmit_enable) === -1) { + throw new Error("retransmit_enable must be one of " + enable_values.join(", ")); + } + + var buffer = new EncoderBuffer(3); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0x69); + buffer.writeUInt8(getEncoderValue(enable_map, retransmit_enable)); + return buffer.toBytes(); +} + +/** + * set retransmit interval + * @param {number} retransmit_interval unit: second, range: [1, 64800] + * @example { "retransmit_interval": 600 } + */ +function setRetransmitInterval(retransmit_interval) { + if (typeof retransmit_interval !== "number") { + throw new Error("retransmit_interval must be a number"); + } + if (retransmit_interval < 1 || retransmit_interval > 64800) { + throw new Error("retransmit_interval must be between 1 and 64800"); + } + + var buffer = new EncoderBuffer(5); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0x6a); + buffer.writeUInt8(0x00); + buffer.writeUInt16LE(retransmit_interval); + return buffer.toBytes(); +} + +/** + * set resend interval + * @param {number} resend_interval unit: second, range: [1, 64800] + * @example { "resend_interval": 600 } + */ +function setResendInterval(resend_interval) { + if (typeof resend_interval !== "number") { + throw new Error("resend_interval must be a number"); + } + if (resend_interval < 1 || resend_interval > 64800) { + throw new Error("resend_interval must be between 1 and 64800"); + } + + var buffer = new EncoderBuffer(5); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0x6a); + buffer.writeUInt8(0x01); + buffer.writeUInt16LE(resend_interval); + return buffer.toBytes(); +} + +/** + * set temperature threshold alarm + * @param {object} temperature_alarm_config + * @param {number} temperature_alarm_config.condition values: (0: disable, 1: below, 2: above, 3: between, 4: outside) + * @param {number} temperature_alarm_config.threshold_min condition=(below, within, outside) + * @param {number} temperature_alarm_config.threshold_max condition=(above, within, outside) + * @example { "temperature_alarm_config": { "condition": 2, "threshold_min": 10, "threshold_max": 30 } } + */ +function setTemperatureAlarmConfig(temperature_alarm_config) { + var condition = temperature_alarm_config.condition; + var threshold_min = temperature_alarm_config.threshold_min; + var threshold_max = temperature_alarm_config.threshold_max; + + var condition_map = { 0: "disable", 1: "below", 2: "above", 3: "between", 4: "outside" }; + var condition_values = getEncoderValues(condition_map); + if (condition_values.indexOf(condition) === -1) { + throw new Error("condition must be one of " + condition_values.join(", ")); + } + + var data = getEncoderValue(condition_map, condition) | (1 << 3); + + var buffer = new EncoderBuffer(11); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0x06); + buffer.writeUInt8(data); + buffer.writeInt16LE(threshold_min * 10); + buffer.writeInt16LE(threshold_max * 10); + buffer.writeUInt16LE(0x00); + buffer.writeUInt16LE(0x00); + return buffer.toBytes(); +} + +/** + * set leakage alarm + * @param {object} leakage_alarm_config + * @param {number} leakage_alarm_config.enable values: (0: disable, 1: enable) + * @param {number} leakage_alarm_config.report_interval unit: second + * @param {number} leakage_alarm_config.report_times + * @example { "leakage_alarm_config": { "enable": 1, "report_interval": 600, "report_times": 10 } } + */ +function setLeakageAlarm(leakage_alarm_config) { + var enable = leakage_alarm_config.enable; + var report_interval = leakage_alarm_config.report_interval; + var report_times = leakage_alarm_config.report_times; + + var enable_map = { 0: "disable", 1: "enable" }; + var enable_values = getEncoderValues(enable_map); + if (enable_values.indexOf(enable) === -1) { + throw new Error("enable must be one of " + enable_values.join(", ")); + } + + var data = getEncoderValue(enable_map, enable) | (2 << 3); + + var buffer = new EncoderBuffer(11); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0x06); + buffer.writeUInt8(data); + buffer.writeUInt16LE(0x00); + buffer.writeUInt16LE(0x00); + buffer.writeUInt16LE(report_interval); + buffer.writeUInt16LE(report_times); + return buffer.toBytes(); +} + +/** + * set temperature calibration + * @since v1.8 + * @param {object} temperature_calibration_settings + * @param {number} temperature_calibration_settings.enable values: (0: disable, 1: enable) + * @param {number} temperature_calibration_settings.calibration_value unit: °C + * @example { "temperature_calibration_settings": { "enable": 1, "calibration_value": 10 } } + */ +function setTemperatureCalibration(temperature_calibration_settings) { + var enable = temperature_calibration_settings.enable; + var calibration_value = temperature_calibration_settings.calibration_value; + + var enable_map = { 0: "disable", 1: "enable" }; + var enable_values = getEncoderValues(enable_map); + if (enable_values.indexOf(enable) === -1) { + throw new Error("temperature_calibration_settings.enable must be one of " + enable_values.join(", ")); + } + + var data = (getEncoderValue(enable_map, enable) << 7) | 0x00; + var buffer = new EncoderBuffer(5); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0xea); + buffer.writeUInt8(data); + buffer.writeInt16LE(calibration_value * 10); + return buffer.toBytes(); +} + +/** + * set humidity calibration + * @since v1.8 + * @param {object} humidity_calibration_settings + * @param {number} humidity_calibration_settings.enable values: (0: disable, 1: enable) + * @param {number} humidity_calibration_settings.calibration_value unit: %.R.H + * @example { "humidity_calibration_settings": { "enable": 1, "calibration_value": 1.5 } } + */ +function setHumidityCalibration(humidity_calibration_settings) { + var enable = humidity_calibration_settings.enable; + var calibration_value = humidity_calibration_settings.calibration_value; + + var enable_map = { 0: "disable", 1: "enable" }; + var enable_values = getEncoderValues(enable_map); + if (enable_values.indexOf(enable) === -1) { + throw new Error("humidity_calibration_settings.enable must be one of " + enable_values.join(", ")); + } + + var data = (getEncoderValue(enable_map, enable) << 7) | 0x01; + var buffer = new EncoderBuffer(5); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0xea); + buffer.writeUInt8(data); + buffer.writeUInt16LE(calibration_value * 2); + return buffer.toBytes(); +} + +/** + * set D2D configuration + * @param {object} d2d_master_config + * @param {number} d2d_master_config.mode values: (1: temperature_alarm, 2: temperature_alarm_release, 3: leakage_alarm, 4: leakage_alarm_release) + * @param {number} d2d_master_config.report_type values: (0: lora, 1: d2d, 3: d2d_and_lora) + * @param {string} d2d_master_config.d2d_cmd + * @example { "d2d_master_config": { "mode": 1, "report_type": 1, "d2d_cmd": "0000" } } + */ +function setD2DMasterConfig(d2d_master_config) { + var mode = d2d_master_config.mode; + var report_type = d2d_master_config.report_type; + var d2d_cmd = d2d_master_config.d2d_cmd; + + var event_map = { 1: "temperature_alarm", 2: "temperature_alarm_release", 3: "leakage_alarm", 4: "leakage_alarm_release" }; + var event_values = getEncoderValues(event_map); + if (event_values.indexOf(mode) === -1) { + throw new Error("d2d_master_config.mode must be one of " + event_values.join(", ")); + } + var report_map = { 0: "lora", 1: "d2d", 3: "d2d_and_lora" }; + var report_values = getEncoderValues(report_map); + if (report_values.indexOf(report_type) === -1) { + throw new Error("d2d_master_config.report_type must be one of " + report_values.join(", ")); + } + + var buffer = new EncoderBuffer(6); + buffer.writeUInt8(0xff); + buffer.writeUInt8(0x79); + buffer.writeUInt8(getEncoderValue(event_map, mode)); + buffer.writeUInt8(getEncoderValue(report_map, report_type)); + buffer.writeD2DCommand(d2d_cmd, "0000"); + return buffer.toBytes(); +} + +function getEncoderValues(map) { + var values = []; + for (var key in map) { + values.push(RAW_VALUE ? parseInt(key) : map[key]); + } + return values; +} + +function getEncoderValue(map, value) { + if (RAW_VALUE) return value; + + for (var key in map) { + if (map[key] === value) { + return parseInt(key); + } + } + + throw new Error("not match in " + JSON.stringify(map)); +} + +function EncoderBuffer(size) { + this.buffer = new Array(size); + this.offset = 0; + + for (var i = 0; i < size; i++) { + this.buffer[i] = 0; + } +} + +EncoderBuffer.prototype._write = function (value, byteLength, isLittleEndian) { + var offset = 0; + for (var index = 0; index < byteLength; index++) { + offset = isLittleEndian ? index << 3 : (byteLength - 1 - index) << 3; + this.buffer[this.offset + index] = (value >> offset) & 0xff; + } +}; + +EncoderBuffer.prototype.writeUInt8 = function (value) { + this._write(value, 1, true); + this.offset += 1; +}; + +EncoderBuffer.prototype.writeInt8 = function (value) { + this._write(value < 0 ? value + 0x100 : value, 1, true); + this.offset += 1; +}; + +EncoderBuffer.prototype.writeUInt16LE = function (value) { + this._write(value, 2, true); + this.offset += 2; +}; + +EncoderBuffer.prototype.writeInt16LE = function (value) { + this._write(value < 0 ? value + 0x10000 : value, 2, true); + this.offset += 2; +}; + +EncoderBuffer.prototype.writeUInt32LE = function (value) { + this._write(value, 4, true); + this.offset += 4; +}; + +EncoderBuffer.prototype.writeInt32LE = function (value) { + this._write(value < 0 ? value + 0x100000000 : value, 4, true); + this.offset += 4; +}; + +EncoderBuffer.prototype.writeD2DCommand = function (value, defaultValue) { + if (typeof value !== "string") { + value = defaultValue; + } + if (value.length !== 4) { + throw new Error("d2d_cmd length must be 4"); + } + this.buffer[this.offset] = parseInt(value.substr(2, 2), 16); + this.buffer[this.offset + 1] = parseInt(value.substr(0, 2), 16); + this.offset += 2; +}; + +EncoderBuffer.prototype.toBytes = function () { + return this.buffer; +}; diff --git a/vendors/milesight/codecs/test_decode_em300-sld-zld.json b/vendors/milesight/codecs/test_decode_em300-sld-zld.json index 0637a08..0f79805 100644 --- a/vendors/milesight/codecs/test_decode_em300-sld-zld.json +++ b/vendors/milesight/codecs/test_decode_em300-sld-zld.json @@ -1 +1,56 @@ -[] \ No newline at end of file +[ + { + "name": "Test decode periodic uplink (battery, temperature, humidity, leakage)", + "input": { + "bytes": [1, 117, 92, 3, 103, 52, 1, 4, 104, 101, 5, 0, 0] + }, + "expected": { + "data": { + "battery": 92, + "temperature": 30.8, + "humidity": 50.5, + "leakage_status": "normal" + } + } + }, + { + "name": "Test decode leakage alarm", + "input": { + "bytes": [5, 0, 1] + }, + "expected": { + "data": { + "leakage_status": "leak" + } + } + }, + { + "name": "Test decode history", + "input": { + "bytes": [32, 206, 0, 241, 83, 101, 255, 0, 120, 1] + }, + "expected": { + "data": { + "history": [ + { + "timestamp": 1700000000, + "temperature": 25.5, + "humidity": 60, + "leakage_status": "leak" + } + ] + } + } + }, + { + "name": "Test decode downlink response (report interval)", + "input": { + "bytes": [255, 3, 88, 2] + }, + "expected": { + "data": { + "report_interval": 600 + } + } + } +] diff --git a/vendors/milesight/codecs/test_encode_em300-sld-zld.json b/vendors/milesight/codecs/test_encode_em300-sld-zld.json index 0637a08..141a01a 100644 --- a/vendors/milesight/codecs/test_encode_em300-sld-zld.json +++ b/vendors/milesight/codecs/test_encode_em300-sld-zld.json @@ -1 +1,141 @@ -[] \ No newline at end of file +[ + { + "name": "Test encode reboot", + "input": { + "data": { + "reboot": "yes" + } + }, + "expected": { + "bytes": [255, 16, 255] + } + }, + { + "name": "Test encode report interval (600 s)", + "input": { + "data": { + "report_interval": 600 + } + }, + "expected": { + "bytes": [255, 3, 88, 2] + } + }, + { + "name": "Test encode collection interval (300 s)", + "input": { + "data": { + "collection_interval": 300 + } + }, + "expected": { + "bytes": [255, 2, 44, 1] + } + }, + { + "name": "Test encode multiple commands", + "input": { + "data": { + "report_interval": 600, + "collection_interval": 300 + } + }, + "expected": { + "bytes": [255, 3, 88, 2, 255, 2, 44, 1] + } + }, + { + "name": "Test encode history enable", + "input": { + "data": { + "history_enable": "enable" + } + }, + "expected": { + "bytes": [255, 104, 1] + } + }, + { + "name": "Test encode retransmit enable", + "input": { + "data": { + "retransmit_enable": "enable" + } + }, + "expected": { + "bytes": [255, 105, 1] + } + }, + { + "name": "Test encode retransmit interval (600 s)", + "input": { + "data": { + "retransmit_interval": 600 + } + }, + "expected": { + "bytes": [255, 106, 0, 88, 2] + } + }, + { + "name": "Test encode temperature alarm (between 10 and 30)", + "input": { + "data": { + "temperature_alarm_config": { + "condition": "between", + "threshold_min": 10, + "threshold_max": 30 + } + } + }, + "expected": { + "bytes": [255, 6, 11, 100, 0, 44, 1, 0, 0, 0, 0] + } + }, + { + "name": "Test encode leakage alarm (every 600 s, 10 times)", + "input": { + "data": { + "leakage_alarm_config": { + "enable": "enable", + "report_interval": 600, + "report_times": 10 + } + } + }, + "expected": { + "bytes": [255, 6, 17, 0, 0, 0, 0, 88, 2, 10, 0] + } + }, + { + "name": "Test encode temperature calibration (+1.5)", + "input": { + "data": { + "temperature_calibration_settings": { + "enable": "enable", + "calibration_value": 1.5 + } + } + }, + "expected": { + "bytes": [255, 234, 128, 15, 0] + } + }, + { + "name": "Test encode D2D master config", + "input": { + "data": { + "d2d_master_config": [ + { + "mode": "leakage_alarm", + "report_type": "d2d", + "d2d_cmd": "1234" + } + ] + } + }, + "expected": { + "bytes": [255, 121, 3, 1, 52, 18] + } + } +]