From f9ff2ed7378bce46c8939c026a7bdb9f579fb013 Mon Sep 17 00:00:00 2001 From: Cory Callcott Date: Wed, 21 May 2025 10:28:24 +1000 Subject: [PATCH 1/8] started adding dragino devices --- vendors/dragino/codecs/ldds75.js | 26 ++ vendors/dragino/codecs/lds02.js | 65 ++++ vendors/dragino/codecs/lht65n-e31f.js | 320 ++++++++++++++++++ vendors/dragino/codecs/lt-22222-l.js | 162 +++++++++ vendors/dragino/codecs/lwl02.js | 65 ++++ .../dragino/codecs/test_decode_ldds75.json | 1 + vendors/dragino/codecs/test_decode_lds02.json | 1 + .../codecs/test_decode_lht65n-e31f.json | 1 + .../codecs/test_decode_lt-22222-l.json | 1 + vendors/dragino/codecs/test_decode_lwl02.json | 1 + .../dragino/codecs/test_encode_ldds75.json | 1 + vendors/dragino/codecs/test_encode_lds02.json | 1 + .../codecs/test_encode_lht65n-e31f.json | 1 + .../codecs/test_encode_lt-22222-l.json | 1 + vendors/dragino/codecs/test_encode_lwl02.json | 1 + vendors/dragino/devices/ldds75.toml | 8 + vendors/dragino/devices/lds02.toml | 8 + vendors/dragino/devices/lht65n-e31f.toml | 8 + vendors/dragino/devices/lt-22222-l.toml | 8 + vendors/dragino/devices/lwl02.toml | 8 + .../dragino/profiles/AS923-1_0_3-CLASS_C.toml | 24 ++ vendors/dragino/profiles/AS923-1_0_3.toml | 24 ++ .../dragino/profiles/AU915-1_0_3-CLASS_C.toml | 24 ++ vendors/dragino/profiles/AU915-1_0_3.toml | 24 ++ .../dragino/profiles/EU868-1_0_3-CLASS_C.toml | 24 ++ vendors/dragino/profiles/EU868-1_0_3.toml | 24 ++ .../dragino/profiles/US915-1_0_3-CLASS_C.toml | 24 ++ vendors/dragino/profiles/US915-1_0_3.toml | 24 ++ vendors/dragino/vendor.toml | 14 + 29 files changed, 894 insertions(+) create mode 100644 vendors/dragino/codecs/ldds75.js create mode 100644 vendors/dragino/codecs/lds02.js create mode 100644 vendors/dragino/codecs/lht65n-e31f.js create mode 100644 vendors/dragino/codecs/lt-22222-l.js create mode 100644 vendors/dragino/codecs/lwl02.js create mode 100644 vendors/dragino/codecs/test_decode_ldds75.json create mode 100644 vendors/dragino/codecs/test_decode_lds02.json create mode 100644 vendors/dragino/codecs/test_decode_lht65n-e31f.json create mode 100644 vendors/dragino/codecs/test_decode_lt-22222-l.json create mode 100644 vendors/dragino/codecs/test_decode_lwl02.json create mode 100644 vendors/dragino/codecs/test_encode_ldds75.json create mode 100644 vendors/dragino/codecs/test_encode_lds02.json create mode 100644 vendors/dragino/codecs/test_encode_lht65n-e31f.json create mode 100644 vendors/dragino/codecs/test_encode_lt-22222-l.json create mode 100644 vendors/dragino/codecs/test_encode_lwl02.json create mode 100644 vendors/dragino/devices/ldds75.toml create mode 100644 vendors/dragino/devices/lds02.toml create mode 100644 vendors/dragino/devices/lht65n-e31f.toml create mode 100644 vendors/dragino/devices/lt-22222-l.toml create mode 100644 vendors/dragino/devices/lwl02.toml create mode 100644 vendors/dragino/profiles/AS923-1_0_3-CLASS_C.toml create mode 100644 vendors/dragino/profiles/AS923-1_0_3.toml create mode 100644 vendors/dragino/profiles/AU915-1_0_3-CLASS_C.toml create mode 100644 vendors/dragino/profiles/AU915-1_0_3.toml create mode 100644 vendors/dragino/profiles/EU868-1_0_3-CLASS_C.toml create mode 100644 vendors/dragino/profiles/EU868-1_0_3.toml create mode 100644 vendors/dragino/profiles/US915-1_0_3-CLASS_C.toml create mode 100644 vendors/dragino/profiles/US915-1_0_3.toml create mode 100644 vendors/dragino/vendor.toml diff --git a/vendors/dragino/codecs/ldds75.js b/vendors/dragino/codecs/ldds75.js new file mode 100644 index 0000000..83d8e79 --- /dev/null +++ b/vendors/dragino/codecs/ldds75.js @@ -0,0 +1,26 @@ +function Decode(fPort, bytes, variables) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var len=bytes.length; + var value=(bytes[0]<<8 | bytes[1]) & 0x3FFF; + var batV=value/1000;//Battery,units:V + + var distance = 0; + if(len==5) + { + value=bytes[2]<<8 | bytes[3]; + distance=(value);//distance,units:mm + if(value<20) + distance = "Invalid Reading"; + } + else + distance = "No Sensor"; + + var interrupt = bytes[len-1]; + return { + Node_type:"LDDS75", + Bat:batV , + Distance:distance, + Interrupt_status:interrupt + }; +} diff --git a/vendors/dragino/codecs/lds02.js b/vendors/dragino/codecs/lds02.js new file mode 100644 index 0000000..f23e717 --- /dev/null +++ b/vendors/dragino/codecs/lds02.js @@ -0,0 +1,65 @@ +function decodeUplink(input) { + return { + data: Decode(input.fPort, input.bytes, input.variables) + }; +} +function Decode(fPort, bytes, variables) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var value=(bytes[0]<<8 | bytes[1])&0x3FFF; + var bat=value/1000;//Battery,units:V + + var door_open_status=bytes[0]&0x80?1:0;//1:open,0:close + var water_leak_status=bytes[0]&0x40?1:0; + + var mod=bytes[2]; + var alarm=bytes[9]&0x01; + + if(mod==1){ + var open_times=bytes[3]<<16 | bytes[4]<<8 | bytes[5]; + var open_duration=bytes[6]<<16 | bytes[7]<<8 | bytes[8];//units:min + if(bytes.length==10 && 0x07>bytes[0]< 0x0f) + return { + Node_type:"LWL02", + BAT_V:bat, + MOD:mod, + DOOR_OPEN_STATUS:door_open_status, + DOOR_OPEN_TIMES:open_times, + LAST_DOOR_OPEN_DURATION:open_duration, + ALARM:alarm + }; + } + else if(mod==2) + { + var leak_times=bytes[3]<<16 | bytes[4]<<8 | bytes[5]; + var leak_duration=bytes[6]<<16 | bytes[7]<<8 | bytes[8];//units:min + if(bytes.length==10 && 0x07>bytes[0]< 0x0f) + return { + Node_type:"LWL02", + BAT_V:bat, + MOD:mod, + WATER_LEAK_STATUS:water_leak_status, + WATER_LEAK_TIMES:leak_times, + LAST_WATER_LEAK_DURATION:leak_duration + }; + } + else if(mod==3) + if(bytes.length==10 && 0x07>bytes[0]< 0x0f) + { + return { + Node_type:"LWL02", + BAT_V:bat, + MOD:mod, + DOOR_OPEN_STATUS:door_open_status, + WATER_LEAK_STATUS:water_leak_status, + ALARM:alarm + }; + } + else{ + return { + Node_type:"LWL02", + BAT_V:bat, + MOD:mod, + }; + } +} \ No newline at end of file diff --git a/vendors/dragino/codecs/lht65n-e31f.js b/vendors/dragino/codecs/lht65n-e31f.js new file mode 100644 index 0000000..b78d5a2 --- /dev/null +++ b/vendors/dragino/codecs/lht65n-e31f.js @@ -0,0 +1,320 @@ +function decodeUplink(input) { + return { + data: Decode(input.fPort, input.bytes, input.variables), + }; +} + +function Str1(str2) { + var str3 = ""; + for (var i = 0; i < str2.length; i++) { + if (str2[i] <= 0x0f) { + str2[i] = "0" + str2[i].toString(16) + ""; + } + str3 += str2[i].toString(16) + ""; + } + return str3; +} + +function str_pad(byte) { + var zero = "00"; + var hex = byte.toString(16); + var tmp = 2 - hex.length; + return zero.substr(0, tmp) + hex + " "; +} + +function datalog(i, bytes) { + var Ext = bytes[6] & 0x0f; + var bb; + if (Ext == "1" || Ext == "9") { + bb = parseFloat( + ((((bytes[0 + i] << 24) >> 16) | bytes[1 + i]) / 100).toFixed(2) + ); + } else if (Ext == "2") { + bb = parseFloat( + ((((bytes[0 + i] << 24) >> 16) | bytes[1 + i]) / 100).toFixed(2) + ); + } else if (Ext == "4") { + var Exti_pin_level = bytes[0 + i] ? "High" : "Low"; + var Exti_status = bytes[1 + i] ? "True" : "False"; + bb = Exti_pin_level + Exti_status; + } else if (Ext == "5") { + bb = (bytes[0 + i] << 8) | bytes[1 + i]; + } else if (Ext == "6") { + bb = ((bytes[0 + i] << 8) | bytes[1 + i]) / 1000; + } else if (Ext == "7") { + bb = (bytes[0 + i] << 8) | bytes[1 + i]; + } else if (Ext == "8" || Ext == "14") { + bb = (bytes[0 + i] << 8) | bytes[1 + i]; + } else if (Ext == "11") { + bb = parseFloat( + ((((bytes[0 + i] << 24) >> 16) | bytes[1 + i]) / 100).toFixed(2) + ); + } + var cc = parseFloat( + ((((bytes[2 + i] << 24) >> 16) | bytes[3 + i]) / 100).toFixed(2) + ); + var dd = parseFloat( + ((((bytes[4 + i] << 8) | bytes[5 + i]) & 0xfff) / 10).toFixed(1) + ); + var ee = getMyDate( + ( + (bytes[7 + i] << 24) | + (bytes[8 + i] << 16) | + (bytes[9 + i] << 8) | + bytes[10 + i] + ).toString(10) + ); + var string = "[" + bb + "," + cc + "," + dd + "," + ee + "]" + ","; + + return string; +} + +function getzf(c_num) { + if (parseInt(c_num) < 10) c_num = "0" + c_num; + + return c_num; +} + +function getMyDate(str) { + var c_Date; + if (str > 9999999999) c_Date = new Date(parseInt(str)); + else c_Date = new Date(parseInt(str) * 1000); + + var c_Year = c_Date.getFullYear(), + c_Month = c_Date.getMonth() + 1, + c_Day = c_Date.getDate(), + c_Hour = c_Date.getHours(), + c_Min = c_Date.getMinutes(), + c_Sen = c_Date.getSeconds(); + var c_Time = + c_Year + + "-" + + getzf(c_Month) + + "-" + + getzf(c_Day) + + " " + + getzf(c_Hour) + + ":" + + getzf(c_Min) + + ":" + + getzf(c_Sen); + + return c_Time; +} + +function Decode(fPort, bytes, variables) { + var Ext = bytes[6] & 0x0f; + var poll_message_status = (bytes[6] >> 6) & 0x01; + var retransmission_Status = (bytes[6] >> 7) & 0x01; + var Connect = (bytes[6] & 0x80) >> 7; + var decode = {}; + var data = {}; + if ( + fPort == 3 && + (bytes[2] == 0x01 || + bytes[2] == 0x02 || + bytes[2] == 0x03 || + bytes[2] == 0x04) + ) { + var array1 = []; + var bytes1 = "0x"; + var str1 = Str1(bytes); + var str2 = str1.substring(0, 6); + var str3 = str1.substring(6); + var reg = /.{4}/g; + var rs = str3.match(reg); + rs.push(str3.substring(rs.join("").length)); + rs.pop(); + var new_arr = [...rs]; + var data1 = new_arr; + decode.bat = parseInt((bytes1 + str2.substring(0, 4)) & 0x3fff); + if (parseInt(bytes1 + str2.substring(4)) == 1) { + decode.sensor = "ds18b20"; + } else if (parseInt(bytes1 + str2.substring(4)) == 2) { + decode.sensor = "tmp117"; + } else if (parseInt(bytes1 + str2.substring(4)) == 3) { + decode.sensor = "gxht30"; + } else if (parseInt(bytes1 + str2.substring(4)) == 4) { + decode.sensor = "sht31"; + } + for (var i = 0; i < data1.length; i++) { + var temp = parseInt(bytes1 + data1[i].substring(0, 4)) / 100; + array1[i] = temp; + } + decode.Temp = array1; + { + return decode; + } + } else if (fPort == 5) { + var sub_band; + var freq_band; + var sensor; + + if (bytes[0] == 0x0b) sensor = "LHT65N"; + else if (bytes[0] == 0x1a) sensor = "LHT65N-PIR"; + + if (bytes[4] == 0xff) sub_band = "NULL"; + else sub_band = bytes[4]; + + if (bytes[3] == 0x01) freq_band = "EU868"; + else if (bytes[3] == 0x02) freq_band = "US915"; + else if (bytes[3] == 0x03) freq_band = "IN865"; + else if (bytes[3] == 0x04) freq_band = "AU915"; + else if (bytes[3] == 0x05) freq_band = "KZ865"; + else if (bytes[3] == 0x06) freq_band = "RU864"; + else if (bytes[3] == 0x07) freq_band = "AS923"; + else if (bytes[3] == 0x08) freq_band = "AS923_1"; + else if (bytes[3] == 0x09) freq_band = "AS923_2"; + else if (bytes[3] == 0x0a) freq_band = "AS923_3"; + else if (bytes[3] == 0x0b) freq_band = "CN470"; + else if (bytes[3] == 0x0c) freq_band = "EU433"; + else if (bytes[3] == 0x0d) freq_band = "KR920"; + else if (bytes[3] == 0x0e) freq_band = "MA869"; + + var firm_ver = + (bytes[1] & 0x0f) + + "." + + ((bytes[2] >> 4) & 0x0f) + + "." + + (bytes[2] & 0x0f); + var bat = ((bytes[5] << 8) | bytes[6]) / 1000; + + return { + SENSOR_MODEL: sensor, + FIRMWARE_VERSION: firm_ver, + FREQUENCY_BAND: freq_band, + SUB_BAND: sub_band, + BAT: bat, + }; + } + if (retransmission_Status == 0) { + switch (poll_message_status) { + case 0: + { + if (Ext == 0x09) { + decode.TempC_DS = parseFloat( + ((((bytes[0] << 24) >> 16) | bytes[1]) / 100).toFixed(2) + ); + decode.Bat_status = bytes[4] >> 6; + } else { + decode.BatV = (((bytes[0] << 8) | bytes[1]) & 0x3fff) / 1000; + decode.Bat_status = bytes[0] >> 6; + } + + if (Ext != 0x0f) { + decode.TempC_SHT = parseFloat( + ((((bytes[2] << 24) >> 16) | bytes[3]) / 100).toFixed(2) + ); + decode.Hum_SHT = parseFloat( + ((((bytes[4] << 8) | bytes[5]) & 0xfff) / 10).toFixed(1) + ); + } + if (Connect == "1") { + decode.No_connect = "Sensor no connection"; + } + + if (Ext == "0") { + decode.Ext_sensor = "No external sensor"; + } else if (Ext == "1") { + decode.Ext_sensor = "Temperature Sensor"; + decode.TempC_DS = parseFloat( + ((((bytes[7] << 24) >> 16) | bytes[8]) / 100).toFixed(2) + ); + } else if (Ext == "2") { + decode.Ext_sensor = "Temperature Sensor"; + decode.TempC_TMP117 = parseFloat( + ((((bytes[7] << 24) >> 16) | bytes[8]) / 100).toFixed(2) + ); + } else if (Ext == "4") { + decode.Work_mode = "Interrupt Sensor send"; + decode.Exti_pin_level = bytes[7] ? "High" : "Low"; + decode.Exti_status = bytes[8] ? "True" : "False"; + decode.Exit_count = (bytes[9] << 16) | (bytes[10] << 8) | bytes[11]; + decode.Exit_duration = + (bytes[12] << 16) | (bytes[13] << 8) | bytes[14]; + } else if (Ext == "5") { + decode.Work_mode = "Illumination Sensor"; + decode.ILL_lx = (bytes[7] << 8) | bytes[8]; + } else if (Ext == "6") { + decode.Work_mode = "ADC Sensor"; + decode.ADC_V = ((bytes[7] << 8) | bytes[8]) / 1000; + } else if (Ext == "7") { + decode.Work_mode = "Interrupt Sensor count"; + decode.Exit_count = (bytes[7] << 8) | bytes[8]; + } else if (Ext == "8") { + decode.Work_mode = "Interrupt Sensor count"; + decode.Exit_count = + (bytes[7] << 24) | (bytes[8] << 16) | (bytes[9] << 8) | bytes[10]; + } else if (Ext == "9") { + decode.Work_mode = "DS18B20 & timestamp"; + decode.Systimestamp = + (bytes[7] << 24) | (bytes[8] << 16) | (bytes[9] << 8) | bytes[10]; + } else if (Ext == "11") { + decode.Work_mode = "SHT31 Sensor"; + decode.Ext_TempC_SHT = parseFloat( + ((((bytes[7] << 24) >> 16) | bytes[8]) / 100).toFixed(2) + ); + decode.Ext_Hum_SHT = parseFloat( + ((((bytes[9] << 8) | bytes[10]) & 0xfff) / 10).toFixed(1) + ); + } else if (Ext == "14") { + decode.Work_mode = "PIR Sensor"; + decode.Exti_pin_level = + bytes[7] & 0x01 ? "Activity" : "No activity"; + decode.Move_count = (bytes[8] << 16) | (bytes[9] << 8) | bytes[10]; + } else if (Ext == "15") { + decode.Work_mode = "DS18B20ID"; + decode.ID = + str_pad(bytes[2]) + + str_pad(bytes[3]) + + str_pad(bytes[4]) + + str_pad(bytes[5]) + + str_pad(bytes[7]) + + str_pad(bytes[8]) + + str_pad(bytes[9]) + + str_pad(bytes[10]); + } + } + if (bytes.length == 11 || bytes.length == 15) { + return decode; + } + break; + + case 1: + { + for (var i = 0; i < bytes.length; i = i + 11) { + var da = datalog(i, bytes); + if (i == "0") decode.DATALOG = da; + else decode.DATALOG += da; + } + } + { + return decode; + } + break; + default: + return { + errors: ["unknown"], + }; + } + } else { + switch (retransmission_Status) { + case 1: + { + for (var i = 0; i < bytes.length; i = i + 11) { + var da = datalog(i, bytes); + if (i == "0") data.retransmission_message = da; + else data.retransmission_message += da; + } + } + { + return data; + } + break; + default: + return { + errors: ["unknown"], + }; + } + } +} diff --git a/vendors/dragino/codecs/lt-22222-l.js b/vendors/dragino/codecs/lt-22222-l.js new file mode 100644 index 0000000..9a48258 --- /dev/null +++ b/vendors/dragino/codecs/lt-22222-l.js @@ -0,0 +1,162 @@ +function decodeUplink(input) { + return { + data: Decode(input.fPort, input.bytes, input.variables) + }; +} + +function Decode(fPort, bytes, variables) { + //LT33222-L or LT22222-L Decode + if(fPort==0x02) + { + var hardware= (bytes[10] & 0xC0)>>6; + var mode0= bytes[10] & 0xff; + var mode= bytes[10] & 0x3f; + var decode = {}; + + if(hardware=='0') + { + decode.Hardware_mode="LT33222"; + decode.DO3_status=(bytes[8] &0x04)? "L":"H"; + if(mode0=='1') + { + decode.DI3_status= (bytes[8] &0x20)?"H":"L"; + } + } + else if(hardware=='1') + { + decode.Hardware_mode= "LT22222"; + } + + if(mode!=6) + { + decode.DO1_status= (bytes[8] &0x01)? "L":"H"; + decode.DO2_status= (bytes[8] &0x02)? "L":"H"; + decode.RO1_status= (bytes[8] &0x80)? "ON":"OFF"; + decode.RO2_status= (bytes[8] &0x40)? "ON":"OFF"; + if(mode!=1) + { + if(mode!=5) + { + decode.Count1_times= (bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3])>>>0; + } + decode.First_status= (bytes[8] &0x20)? "Yes":"No"; + } + } + + if(mode=='1') + { + decode.Work_mode= "2ACI+2AVI"; + decode.AVI1_V= parseFloat(((bytes[0]<<24>>16 | bytes[1])/1000).toFixed(3)); + decode.AVI2_V= parseFloat(((bytes[2]<<24>>16 | bytes[3])/1000).toFixed(3)); + decode.ACI1_mA= parseFloat(((bytes[4]<<24>>16 | bytes[5])/1000).toFixed(3)); + decode.ACI2_mA= parseFloat(((bytes[6]<<24>>16 | bytes[7])/1000).toFixed(3)); + decode.DI1_status= (bytes[8] &0x08)? "H":"L"; + decode.DI2_status= (bytes[8] &0x10)? "H":"L"; + } + else if(mode=='2') + { + decode.Work_mode= "Count mode 1"; + decode.Count2_times= (bytes[4]<<24 | bytes[5]<<16 | bytes[6]<<8 | bytes[7]) >>>0; + } + else if(mode=='3') + { + decode.Work_mode= "2ACI+1Count"; + decode.ACI1_mA= parseFloat(((bytes[4]<<24>>16 | bytes[5])/1000).toFixed(3)); + decode.ACI2_mA= parseFloat(((bytes[6]<<24>>16 | bytes[7])/1000).toFixed(3)); + } + else if(mode=='4') + { + decode.Work_mode= "Count mode 2"; + decode.Acount_times= (bytes[4]<<24 | bytes[5]<<16 | bytes[6]<<8 | bytes[7]) >>>0; + } + else if(mode=='5') + { + decode.Work_mode= " 1ACI+2AVI+1Count"; + decode.AVI1_V= parseFloat(((bytes[0]<<24>>16 | bytes[1])/1000).toFixed(3)); + decode.AVI2_V= parseFloat(((bytes[2]<<24>>16 | bytes[3])/1000).toFixed(3)); + decode.ACI1_mA= parseFloat(((bytes[4]<<24>>16 | bytes[5])/1000).toFixed(3)); + decode.Count1_times= bytes[6]<<8 | bytes[7]; + } + else if(mode=='6') + { + decode.Work_mode= "Trigger mode"; + decode.Mode_status= bytes[9] ? "True":"False"; + decode.AV1L_flag= (bytes[0] &0x80)? "True":"False"; + decode.AV1H_flag= (bytes[0] &0x40)? "True":"False"; + decode.AV2L_flag= (bytes[0] &0x20)? "True":"False"; + decode.AV2H_flag= (bytes[0] &0x10)? "True":"False"; + decode.AC1L_flag= (bytes[0] &0x08)? "True":"False"; + decode.AC1H_flag= (bytes[0] &0x04)? "True":"False"; + decode.AC2L_flag= (bytes[0] &0x02)? "True":"False"; + decode.AC2H_flag= (bytes[0] &0x01)? "True":"False"; + decode.AV1L_status= (bytes[1] &0x80)? "True":"False"; + decode.AV1H_status= (bytes[1] &0x40)? "True":"False"; + decode.AV2L_status= (bytes[1] &0x20)? "True":"False"; + decode.AV2H_status= (bytes[1] &0x10)? "True":"False"; + decode.AC1L_status= (bytes[1] &0x08)? "True":"False"; + decode.AC1H_status= (bytes[1] &0x04)? "True":"False"; + decode.AC2L_status= (bytes[1] &0x02)? "True":"False"; + decode.AC2H_status= (bytes[1] &0x01)? "True":"False"; + decode.DI2_status= (bytes[2] &0x08)? "True":"False"; + decode.DI2_flag= (bytes[2] &0x04)? "True":"False"; + decode.DI1_status= (bytes[2] &0x02)? "True":"False"; + decode.DI1_flag= (bytes[2] &0x01)? "True":"False"; + } + + if(bytes.length!=1) + return decode; + } + + else if(fPort==5) + { + var freq_band; + var sub_band; + + if(bytes[0]==0x01) + freq_band="EU868"; + else if(bytes[0]==0x02) + freq_band="US915"; + else if(bytes[0]==0x03) + freq_band="IN865"; + else if(bytes[0]==0x04) + freq_band="AU915"; + else if(bytes[0]==0x05) + freq_band="KZ865"; + else if(bytes[0]==0x06) + freq_band="RU864"; + else if(bytes[0]==0x07) + freq_band="AS923"; + else if(bytes[0]==0x08) + freq_band="AS923_1"; + else if(bytes[0]==0x09) + freq_band="AS923_2"; + else if(bytes[0]==0x0A) + freq_band="AS923_3"; + else if(bytes[0]==0x0F) + freq_band="AS923_4"; + else if(bytes[0]==0x0B) + freq_band="CN470"; + else if(bytes[0]==0x0C) + freq_band="EU433"; + else if(bytes[0]==0x0D) + freq_band="KR920"; + else if(bytes[0]==0x0E) + freq_band="MA869"; + + if(bytes[1]==0xff) + sub_band="NULL"; + else + sub_band=bytes[1]; + + var firm_ver= (bytes[2]&0x0f)+'.'+(bytes[3]>>4&0x0f)+'.'+(bytes[3]&0x0f); + + var tdc_time= bytes[4]<<16 | bytes[5]<<8 | bytes[6]; + + return { + FIRMWARE_VERSION:firm_ver, + FREQUENCY_BAND:freq_band, + SUB_BAND:sub_band, + TDC_sec:tdc_time, + } + } +} diff --git a/vendors/dragino/codecs/lwl02.js b/vendors/dragino/codecs/lwl02.js new file mode 100644 index 0000000..796e37d --- /dev/null +++ b/vendors/dragino/codecs/lwl02.js @@ -0,0 +1,65 @@ +function decodeUplink(input) { + return { + data: Decode(input.fPort, input.bytes, input.variables) + }; +} +function Decode(fPort, bytes, variables) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var value=(bytes[0]<<8 | bytes[1])&0x3FFF; + var bat=value/1000;//Battery,units:V + + var door_open_status=bytes[0]&0x80?1:0;//1:open,0:close + var water_leak_status=bytes[0]&0x40?1:0; + + var mod=bytes[2]; + var alarm=bytes[9]&0x01; + + if(mod==1){ + var open_times=bytes[3]<<16 | bytes[4]<<8 | bytes[5]; + var open_duration=bytes[6]<<16 | bytes[7]<<8 | bytes[8];//units:min + if(bytes.length==10 && 0x07>bytes[0]< 0x0f) + return { + Node_type:"LWL02", + BAT_V:bat, + MOD:mod, + DOOR_OPEN_STATUS:door_open_status, + DOOR_OPEN_TIMES:open_times, + LAST_DOOR_OPEN_DURATION:open_duration, + ALARM:alarm + }; + } + else if(mod==2) + { + var leak_times=bytes[3]<<16 | bytes[4]<<8 | bytes[5]; + var leak_duration=bytes[6]<<16 | bytes[7]<<8 | bytes[8];//units:min + if(bytes.length==10 && 0x07>bytes[0]< 0x0f) + return { + Node_type:"LWL02", + BAT_V:bat, + MOD:mod, + WATER_LEAK_STATUS:water_leak_status, + WATER_LEAK_TIMES:leak_times, + LAST_WATER_LEAK_DURATION:leak_duration + }; + } + else if(mod==3) + if(bytes.length==10 && 0x07>bytes[0]< 0x0f) + { + return { + Node_type:"LWL02", + BAT_V:bat, + MOD:mod, + DOOR_OPEN_STATUS:door_open_status, + WATER_LEAK_STATUS:water_leak_status, + ALARM:alarm + }; + } + else{ + return { + Node_type:"LWL02", + BAT_V:bat, + MOD:mod, + }; + } +} diff --git a/vendors/dragino/codecs/test_decode_ldds75.json b/vendors/dragino/codecs/test_decode_ldds75.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_decode_ldds75.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_decode_lds02.json b/vendors/dragino/codecs/test_decode_lds02.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_decode_lds02.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_decode_lht65n-e31f.json b/vendors/dragino/codecs/test_decode_lht65n-e31f.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_decode_lht65n-e31f.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_decode_lt-22222-l.json b/vendors/dragino/codecs/test_decode_lt-22222-l.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_decode_lt-22222-l.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_decode_lwl02.json b/vendors/dragino/codecs/test_decode_lwl02.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_decode_lwl02.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_encode_ldds75.json b/vendors/dragino/codecs/test_encode_ldds75.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_encode_ldds75.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_encode_lds02.json b/vendors/dragino/codecs/test_encode_lds02.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_encode_lds02.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_encode_lht65n-e31f.json b/vendors/dragino/codecs/test_encode_lht65n-e31f.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_encode_lht65n-e31f.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_encode_lt-22222-l.json b/vendors/dragino/codecs/test_encode_lt-22222-l.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_encode_lt-22222-l.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_encode_lwl02.json b/vendors/dragino/codecs/test_encode_lwl02.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/dragino/codecs/test_encode_lwl02.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/dragino/devices/ldds75.toml b/vendors/dragino/devices/ldds75.toml new file mode 100644 index 0000000..98c59b4 --- /dev/null +++ b/vendors/dragino/devices/ldds75.toml @@ -0,0 +1,8 @@ +[device] +name = "LDDS75" +description = "Distance Detection Sensor" +firmware = [] + +[device.metadata] +product_url = "https://dragino.com/products/distance-level-sensor/item/161-ldds75.html" +documentation_url = "https://dragino.com/products/distance-level-sensor/item/161-ldds75.html" diff --git a/vendors/dragino/devices/lds02.toml b/vendors/dragino/devices/lds02.toml new file mode 100644 index 0000000..39a40d9 --- /dev/null +++ b/vendors/dragino/devices/lds02.toml @@ -0,0 +1,8 @@ +[device] +name = "LDS02" +description = "Door Sensor" +firmware = [] + +[device.metadata] +product_url = "https://dragino.com/products/lorawan-nb-iot-door-sensor-water-leak/item/181-lds02.html" +documentation_url = "https://dragino.com/products/lorawan-nb-iot-door-sensor-water-leak/item/181-lds02.html" diff --git a/vendors/dragino/devices/lht65n-e31f.toml b/vendors/dragino/devices/lht65n-e31f.toml new file mode 100644 index 0000000..510a9fb --- /dev/null +++ b/vendors/dragino/devices/lht65n-e31f.toml @@ -0,0 +1,8 @@ +[device] +name = "LHT65N-E31F" +description = "Temperature & Humidity Sensor, optional external temperature sensor." +firmware = [] + +[device.metadata] +product_url = "https://dragino.com/products/temperature-humidity-sensor/item/266-lht65n-e31f.html" +documentation_url = "https://dragino.com/products/temperature-humidity-sensor/item/266-lht65n-e31f.html" diff --git a/vendors/dragino/devices/lt-22222-l.toml b/vendors/dragino/devices/lt-22222-l.toml new file mode 100644 index 0000000..df43c0f --- /dev/null +++ b/vendors/dragino/devices/lt-22222-l.toml @@ -0,0 +1,8 @@ +[device] +name = "LT-22222-L" +description = "LoRa I/O Controller" +firmware = [] + +[device.metadata] +product_url = "https://dragino.com/products/lora-lorawan-end-node/item/156-lt-22222-l.html" +documentation_url = "https://dragino.com/products/lora-lorawan-end-node/item/156-lt-22222-l.html" diff --git a/vendors/dragino/devices/lwl02.toml b/vendors/dragino/devices/lwl02.toml new file mode 100644 index 0000000..236b4d1 --- /dev/null +++ b/vendors/dragino/devices/lwl02.toml @@ -0,0 +1,8 @@ +[device] +name = "LWL02" +description = "Water Leak Sensor" +firmware = [] + +[device.metadata] +product_url = "https://dragino.com/products/lorawan-nb-iot-door-sensor-water-leak/item/180-lwl02.html" +documentation_url = "https://dragino.com/products/lorawan-nb-iot-door-sensor-water-leak/item/180-lwl02.html" diff --git a/vendors/dragino/profiles/AS923-1_0_3-CLASS_C.toml b/vendors/dragino/profiles/AS923-1_0_3-CLASS_C.toml new file mode 100644 index 0000000..2e912fb --- /dev/null +++ b/vendors/dragino/profiles/AS923-1_0_3-CLASS_C.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "AS923" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = true +max_eirp = 16 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 120 diff --git a/vendors/dragino/profiles/AS923-1_0_3.toml b/vendors/dragino/profiles/AS923-1_0_3.toml new file mode 100644 index 0000000..ab20972 --- /dev/null +++ b/vendors/dragino/profiles/AS923-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "AS923" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 16 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/dragino/profiles/AU915-1_0_3-CLASS_C.toml b/vendors/dragino/profiles/AU915-1_0_3-CLASS_C.toml new file mode 100644 index 0000000..5648fa4 --- /dev/null +++ b/vendors/dragino/profiles/AU915-1_0_3-CLASS_C.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "AU915" +mac_version = "1.0.0" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = true +max_eirp = 27 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 120 diff --git a/vendors/dragino/profiles/AU915-1_0_3.toml b/vendors/dragino/profiles/AU915-1_0_3.toml new file mode 100644 index 0000000..041a821 --- /dev/null +++ b/vendors/dragino/profiles/AU915-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "AU915" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 27 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/dragino/profiles/EU868-1_0_3-CLASS_C.toml b/vendors/dragino/profiles/EU868-1_0_3-CLASS_C.toml new file mode 100644 index 0000000..89160f5 --- /dev/null +++ b/vendors/dragino/profiles/EU868-1_0_3-CLASS_C.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "EU868" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = true +max_eirp = 16 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 120 diff --git a/vendors/dragino/profiles/EU868-1_0_3.toml b/vendors/dragino/profiles/EU868-1_0_3.toml new file mode 100644 index 0000000..6595d6e --- /dev/null +++ b/vendors/dragino/profiles/EU868-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "EU868" +mac_version = "1.0.0" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 16 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/dragino/profiles/US915-1_0_3-CLASS_C.toml b/vendors/dragino/profiles/US915-1_0_3-CLASS_C.toml new file mode 100644 index 0000000..a667e56 --- /dev/null +++ b/vendors/dragino/profiles/US915-1_0_3-CLASS_C.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "EU868" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = true +max_eirp = 27 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 120 diff --git a/vendors/dragino/profiles/US915-1_0_3.toml b/vendors/dragino/profiles/US915-1_0_3.toml new file mode 100644 index 0000000..5213dfb --- /dev/null +++ b/vendors/dragino/profiles/US915-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "US915" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 27 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/dragino/vendor.toml b/vendors/dragino/vendor.toml new file mode 100644 index 0000000..587f0fd --- /dev/null +++ b/vendors/dragino/vendor.toml @@ -0,0 +1,14 @@ +[vendor] +name = "Dragino" +id = 676 +ouis = ["a84041"] +devices = [ + "lht65n-e31f.toml", + "lt-22222-l.toml", + "lwl02.toml", + "lds02.toml", + "ldds75.toml", +] + +[vendor.metadata] +homepage = "https://dragino.com/" From 6c8183ccc7fa493d4b23b5eca15113e56f090c66 Mon Sep 17 00:00:00 2001 From: Cory Callcott Date: Thu, 22 May 2025 20:18:13 +1000 Subject: [PATCH 2/8] inital addition of milesight em300 range --- vendors/milesight/codecs/em300-cl.js | 330 ++++++++++++++++ vendors/milesight/codecs/em300-di.js | 223 +++++++++++ vendors/milesight/codecs/em300-mcs.js | 90 +++++ vendors/milesight/codecs/em300-mld.js | 73 ++++ vendors/milesight/codecs/em300-sld-zld.js | 373 ++++++++++++++++++ vendors/milesight/codecs/em300-th.js | 85 ++++ vendors/milesight/codecs/em320-th.js | 168 ++++++++ vendors/milesight/codecs/em320-tilt.js | 64 +++ .../codecs/test_decode_em300-cl.json | 1 + .../codecs/test_decode_em300-di.json | 1 + .../codecs/test_decode_em300-mcs.json | 1 + .../codecs/test_decode_em300-mld.json | 1 + .../codecs/test_decode_em300-sld-zld.json | 1 + .../codecs/test_decode_em300-th.json | 14 + .../codecs/test_decode_em320-th.json | 1 + .../codecs/test_decode_em320-tilt.json | 1 + .../codecs/test_encode_em300-cl.json | 1 + .../codecs/test_encode_em300-di.json | 1 + .../codecs/test_encode_em300-mcs.json | 1 + .../codecs/test_encode_em300-mld.json | 1 + .../codecs/test_encode_em300-sld-zld.json | 1 + .../codecs/test_encode_em300-th.json | 1 + .../codecs/test_encode_em320-th.json | 1 + .../codecs/test_encode_em320-tilt.json | 1 + .../milesight/devices/milesight-em300-cl.toml | 8 + .../milesight/devices/milesight-em300-di.toml | 8 + .../devices/milesight-em300-mcs.toml | 8 + .../devices/milesight-em300-mld.toml | 8 + .../devices/milesight-em300-sld-zld.toml | 8 + .../milesight/devices/milesight-em300-th.toml | 8 + .../devices/milesight-em310-tilt.toml | 8 + .../milesight/devices/milesight-em320-th.toml | 8 + vendors/milesight/profiles/AS923-1_0_3.toml | 24 ++ vendors/milesight/profiles/AU915-1_0_3.toml | 24 ++ vendors/milesight/profiles/EU868-1_0_3.toml | 24 ++ vendors/milesight/profiles/US915-1_0_3.toml | 24 ++ vendors/milesight/vendor.toml | 17 + 37 files changed, 1612 insertions(+) create mode 100644 vendors/milesight/codecs/em300-cl.js create mode 100644 vendors/milesight/codecs/em300-di.js create mode 100644 vendors/milesight/codecs/em300-mcs.js create mode 100644 vendors/milesight/codecs/em300-mld.js create mode 100644 vendors/milesight/codecs/em300-sld-zld.js create mode 100644 vendors/milesight/codecs/em300-th.js create mode 100644 vendors/milesight/codecs/em320-th.js create mode 100644 vendors/milesight/codecs/em320-tilt.js create mode 100644 vendors/milesight/codecs/test_decode_em300-cl.json create mode 100644 vendors/milesight/codecs/test_decode_em300-di.json create mode 100644 vendors/milesight/codecs/test_decode_em300-mcs.json create mode 100644 vendors/milesight/codecs/test_decode_em300-mld.json create mode 100644 vendors/milesight/codecs/test_decode_em300-sld-zld.json create mode 100644 vendors/milesight/codecs/test_decode_em300-th.json create mode 100644 vendors/milesight/codecs/test_decode_em320-th.json create mode 100644 vendors/milesight/codecs/test_decode_em320-tilt.json create mode 100644 vendors/milesight/codecs/test_encode_em300-cl.json create mode 100644 vendors/milesight/codecs/test_encode_em300-di.json create mode 100644 vendors/milesight/codecs/test_encode_em300-mcs.json create mode 100644 vendors/milesight/codecs/test_encode_em300-mld.json create mode 100644 vendors/milesight/codecs/test_encode_em300-sld-zld.json create mode 100644 vendors/milesight/codecs/test_encode_em300-th.json create mode 100644 vendors/milesight/codecs/test_encode_em320-th.json create mode 100644 vendors/milesight/codecs/test_encode_em320-tilt.json create mode 100644 vendors/milesight/devices/milesight-em300-cl.toml create mode 100644 vendors/milesight/devices/milesight-em300-di.toml create mode 100644 vendors/milesight/devices/milesight-em300-mcs.toml create mode 100644 vendors/milesight/devices/milesight-em300-mld.toml create mode 100644 vendors/milesight/devices/milesight-em300-sld-zld.toml create mode 100644 vendors/milesight/devices/milesight-em300-th.toml create mode 100644 vendors/milesight/devices/milesight-em310-tilt.toml create mode 100644 vendors/milesight/devices/milesight-em320-th.toml create mode 100644 vendors/milesight/profiles/AS923-1_0_3.toml create mode 100644 vendors/milesight/profiles/AU915-1_0_3.toml create mode 100644 vendors/milesight/profiles/EU868-1_0_3.toml create mode 100644 vendors/milesight/profiles/US915-1_0_3.toml create mode 100644 vendors/milesight/vendor.toml diff --git a/vendors/milesight/codecs/em300-cl.js b/vendors/milesight/codecs/em300-cl.js new file mode 100644 index 0000000..2504850 --- /dev/null +++ b/vendors/milesight/codecs/em300-cl.js @@ -0,0 +1,330 @@ +/** + * Payload Decoder + * + * Copyright 2025 Milesight IoT + * + * @product EM300-CL + */ +var RAW_VALUE = 0x00; + +// Chirpstack v4 +function decodeUplink(input) { + var decoded = milesightDeviceDecode(input.bytes); + return { data: decoded }; +} + +// Chirpstack v3 +function Decode(fPort, bytes) { + return milesightDeviceDecode(bytes); +} + +// The Things Network +function Decoder(bytes, port) { + return milesightDeviceDecode(bytes); +} + +function milesightDeviceDecode(bytes) { + var decoded = {}; + + for (var i = 0; i < bytes.length;) { + var channel_id = bytes[i++]; + var channel_type = bytes[i++]; + + // IPSO VERSION + if (channel_id === 0xff && channel_type === 0x01) { + decoded.ipso_version = readProtocolVersion(bytes[i]); + i += 1; + } + // HARDWARE VERSION + else if (channel_id === 0xff && channel_type === 0x09) { + decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2)); + i += 2; + } + // FIRMWARE VERSION + else if (channel_id === 0xff && channel_type === 0x0a) { + decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2)); + i += 2; + } + // TSL VERSION + else if (channel_id === 0xff && channel_type === 0xff) { + decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2)); + i += 2; + } + // SERIAL NUMBER + else if (channel_id === 0xff && channel_type === 0x16) { + decoded.sn = readSerialNumber(bytes.slice(i, i + 8)); + i += 8; + } + // LORAWAN CLASS TYPE + else if (channel_id === 0xff && channel_type === 0x0f) { + decoded.lorawan_class = readLoRaWANClass(bytes[i]); + i += 1; + } + // RESET EVENT + else if (channel_id === 0xff && channel_type === 0xfe) { + decoded.reset_event = readResetEvent(1); + i += 1; + } + // DEVICE STATUS + else if (channel_id === 0xff && channel_type === 0x0b) { + decoded.device_status = readDeviceStatus(1); + i += 1; + } + // BATTERY + else if (channel_id === 0x01 && channel_type === 0x75) { + decoded.battery = readUInt8(bytes[i]); + i += 1; + } + // LIQUID + else if (channel_id === 0x03 && channel_type === 0xed) { + decoded.liquid = readLiquidStatus(bytes[i]); + i += 1; + } + // CALIBRATION RESULT + else if (channel_id === 0x04 && channel_type === 0xee) { + decoded.calibration_result = readCalibrationResult(bytes[i]); + i += 1; + } + // LIQUID ALARM + else if (channel_id === 0x83 && channel_type === 0xed) { + decoded.liquid = readLiquidStatus(bytes[i]); + decoded.liquid_alarm = readAlarmType(bytes[i + 1]); + i += 2; + } + // DOWNLINK RESPONSE + else if (channel_id === 0xfe || channel_id === 0xff) { + var result = handle_downlink_response(channel_type, bytes, i); + decoded = Object.assign(decoded, result.data); + i = result.offset; + } + else { + break; + } + } + + return decoded; +} + +function handle_downlink_response(channel_type, bytes, offset) { + var decoded = {}; + + switch (channel_type) { + case 0x10: + decoded.reboot = readYesNoStatus(1); + offset += 1; + break; + case 0x28: + decoded.report_status = readYesNoStatus(1); + offset += 1; + break; + case 0x62: + decoded.calibrate = readYesNoStatus(1); + offset += 1; + break; + case 0x7e: + decoded.alarm_config = {}; + decoded.alarm_config.enable = readEnableStatus(bytes[offset] & 0x01); + decoded.alarm_config.alarm_release_enable = readEnableStatus((bytes[offset] >> 7) & 0x01); + decoded.alarm_config.alarm_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3)); + decoded.alarm_config.alarm_counts = readUInt16LE(bytes.slice(offset + 3, offset + 5)); + offset += 5; + break; + case 0x8e: + // ignore first byte + decoded.report_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3)); + offset += 3; + break; + case 0xbb: + // ignore first byte + decoded.collection_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3)); + offset += 3; + break; + case 0xbe: + var data = bytes[offset]; + if (data === 0x00) { + decoded.query_capacitor_calibration_value = readYesNoStatus(1); + } else if (data === 0x01) { + decoded.query_capacitor_value = readYesNoStatus(1); + } else if (data === 0x02) { + decoded.query_capacitor_judge_value = readYesNoStatus(1); + } + offset += 1; + break; + case 0xbf: + var data = bytes[offset]; + if (data === 0x00) { + decoded.capacitor_config = {}; + decoded.capacitor_config.c1 = readUInt16LE(bytes.slice(offset + 1, offset + 3)) / 100; + decoded.capacitor_config.c2 = readUInt16LE(bytes.slice(offset + 3, offset + 5)) / 100; + decoded.capacitor_config.delta = readUInt16LE(bytes.slice(offset + 5, offset + 7)) / 100; + } else if (data === 0x01) { + decoded.capacitor_judge_config = {}; + decoded.capacitor_judge_config.c1 = readUInt16LE(bytes.slice(offset + 1, offset + 3)) / 100; + decoded.capacitor_judge_config.c2 = readUInt16LE(bytes.slice(offset + 3, offset + 5)) / 100; + decoded.capacitor_judge_config.delta = readUInt16LE(bytes.slice(offset + 5, offset + 7)) / 100; + } + offset += 9; + break; + case 0xc0: + decoded.calibrate_delay_time = readUInt16LE(bytes.slice(offset, offset + 2)); + offset += 2; + break; + default: + throw new Error("unknown downlink response"); + } + + return { data: decoded, offset: offset }; +} + +function readProtocolVersion(bytes) { + var major = (bytes & 0xf0) >> 4; + var minor = bytes & 0x0f; + return "v" + major + "." + minor; +} + +function readHardwareVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = (bytes[1] & 0xff) >> 4; + return "v" + major + "." + minor; +} + +function readFirmwareVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = bytes[1] & 0xff; + return "v" + major + "." + minor; +} + +function readTslVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = bytes[1] & 0xff; + return "v" + major + "." + minor; +} + +function readSerialNumber(bytes) { + var temp = []; + for (var idx = 0; idx < bytes.length; idx++) { + temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2)); + } + return temp.join(""); +} + +function readLoRaWANClass(type) { + var class_map = { + 0: "Class A", + 1: "Class B", + 2: "Class C", + 3: "Class CtoB", + }; + return getValue(class_map, type); +} + +function readResetEvent(status) { + var status_map = { 0: "normal", 1: "reset" }; + return getValue(status_map, status); +} + +function readDeviceStatus(status) { + var status_map = { 0: "off", 1: "on" }; + return getValue(status_map, status); +} + +function readEnableStatus(status) { + var status_map = { 0: "disable", 1: "enable" }; + return getValue(status_map, status); +} + +function readYesNoStatus(status) { + var status_map = { 0: "no", 1: "yes" }; + return getValue(status_map, status); +} + +function readLiquidStatus(type) { + var liquid_status_map = { 0: "uncalibrated", 1: "full", 2: "critical liquid level alert", 255: "error" }; + return getValue(liquid_status_map, type); +} + +function readAlarmType(type) { + var alarm_type_map = { 0: "critical liquid level alarm release", 1: "critical liquid level alarm" }; + return getValue(alarm_type_map, type); +} + +function readCalibrationResult(type) { + var calibration_result_map = { 0: "failed", 1: "success" }; + return getValue(calibration_result_map, type); +} + + +function readUInt8(bytes) { + return bytes & 0xff; +} + +function readInt8(bytes) { + var ref = readUInt8(bytes); + return ref > 0x7f ? ref - 0x100 : ref; +} + +function readUInt16LE(bytes) { + var value = (bytes[1] << 8) + bytes[0]; + return value & 0xffff; +} + +function readInt16LE(bytes) { + var ref = readUInt16LE(bytes); + return ref > 0x7fff ? ref - 0x10000 : ref; +} + +function readUInt32LE(bytes) { + var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]; + return (value & 0xffffffff) >>> 0; +} + +function readInt32LE(bytes) { + var ref = readUInt32LE(bytes); + return ref > 0x7fffffff ? ref - 0x100000000 : ref; +} + +function getValue(map, key) { + if (RAW_VALUE) return key; + + var value = map[key]; + if (!value) value = "unknown"; + return value; +} + +if (!Object.assign) { + Object.defineProperty(Object, "assign", { + enumerable: false, + configurable: true, + writable: true, + value: function (target) { + "use strict"; + if (target == null) { + throw new TypeError("Cannot convert first argument to object"); + } + + var to = Object(target); + for (var i = 1; i < arguments.length; i++) { + var nextSource = arguments[i]; + if (nextSource == null) { + continue; + } + nextSource = Object(nextSource); + + var keysArray = Object.keys(Object(nextSource)); + for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { + var nextKey = keysArray[nextIndex]; + var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); + if (desc !== undefined && desc.enumerable) { + // concat array + if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) { + to[nextKey] = to[nextKey].concat(nextSource[nextKey]); + } else { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }, + }); +} diff --git a/vendors/milesight/codecs/em300-di.js b/vendors/milesight/codecs/em300-di.js new file mode 100644 index 0000000..ecfd90d --- /dev/null +++ b/vendors/milesight/codecs/em300-di.js @@ -0,0 +1,223 @@ +/** + * Payload Decoder + * + * Copyright 2024 Milesight IoT + * + * @product EM300-DI + */ +// Chirpstack v4 +function decodeUplink(input) { + var decoded = milesightDeviceDecode(input.bytes); + return { data: decoded }; +} + +// Chirpstack v3 +function Decode(fPort, bytes) { + return milesightDeviceDecode(bytes); +} + +// The Things Network +function Decoder(bytes, port) { + return milesightDeviceDecode(bytes); +} + +function milesightDeviceDecode(bytes) { + var decoded = {}; + + for (var i = 0; i < bytes.length; ) { + var channel_id = bytes[i++]; + var channel_type = bytes[i++]; + + // BATTERY + if (channel_id === 0x01 && channel_type === 0x75) { + decoded.battery = bytes[i]; + i += 1; + } + // TEMPERATURE + else if (channel_id === 0x03 && channel_type === 0x67) { + // ℃ + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; + i += 2; + + // ℉ + // decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10 * 1.8 + 32; + // i +=2; + } + // HUMIDITY + else if (channel_id === 0x04 && channel_type === 0x68) { + decoded.humidity = bytes[i] / 2; + i += 1; + } + // GPIO + else if (channel_id === 0x05 && channel_type === 0x00) { + decoded.gpio = readGPIOStatus(bytes[i]); + i += 1; + } + // PULSE COUNTER + else if (channel_id === 0x05 && channel_type === 0xc8) { + decoded.pulse = readUInt32LE(bytes.slice(i, i + 4)); + i += 4; + } + // PULSE COUNTER (v1.3+) + else if (channel_id === 0x05 && channel_type === 0xe1) { + decoded.water_conv = readUInt16LE(bytes.slice(i, i + 2)) / 10; + decoded.pulse_conv = readUInt16LE(bytes.slice(i + 2, i + 4)) / 10; + decoded.water = readFloatLE(bytes.slice(i + 4, i + 8)); + i += 8; + } + // GPIO ALARM + else if (channel_id === 0x85 && channel_type === 0x00) { + decoded.gpio = readGPIOStatus(bytes[i]); + decoded.gpio_alarm = readGPIOAlarm(bytes[i + 1]); + i += 2; + } + // WATER ALARM + else if (channel_id === 0x85 && channel_type === 0xe1) { + decoded.water_conv = readUInt16LE(bytes.slice(i, i + 2)) / 10; + decoded.pulse_conv = readUInt16LE(bytes.slice(i + 2, i + 4)) / 10; + decoded.water = readFloatLE(bytes.slice(i + 4, i + 8)); + decoded.water_alarm = readWaterAlarm(bytes[i + 8]); + i += 9; + } + // HISTORICAL DATA + else if (channel_id === 0x20 && channel_type === 0xce) { + // maybe not historical raw data + if (bytes.slice(i).length < 12) break; + + var point = {}; + point.timestamp = readUInt32LE(bytes.slice(i, i + 4)); + point.temperature = readInt16LE(bytes.slice(i + 4, i + 6)) / 10; + point.humidity = bytes[i + 6] / 2; + var mode = bytes[i + 7]; + if (mode === 1) { + point.gpio_type = "gpio"; + point.gpio = bytes[i + 8]; + } else if (mode === 2) { + point.gpio_type = "pulse"; + point.pulse = readUInt32LE(bytes.slice(i + 9, i + 13)); + } + decoded.history = decoded.history || []; + decoded.history.push(point); + i += 13; + } + // HISTORICAL DATA (v2) + else if (channel_id === 0x21 && channel_type === 0xce) { + var point = {}; + point.timestamp = readUInt32LE(bytes.slice(i, i + 4)); + point.temperature = readInt16LE(bytes.slice(i + 4, i + 6)) / 10; + point.humidity = bytes[i + 6] / 2; + point.alarm = readAlarm(bytes[i + 7]); + var mode = bytes[i + 8]; + if (mode === 1) { + point.gpio_type = "gpio"; + point.gpio = readGPIOStatus(bytes[i + 9]); + } else if (mode === 2) { + point.gpio_type = "pulse"; + point.water_conv = readUInt16LE(bytes.slice(i + 10, i + 12)) / 10; + point.pulse_conv = readUInt16LE(bytes.slice(i + 12, i + 14)) / 10; + point.water = readFloatLE(bytes.slice(i + 14, i + 18)); + } + + decoded.history = decoded.history || []; + decoded.history.push(point); + i += 18; + } else { + break; + } + } + + return decoded; +} + +/* ****************************************** + * bytes to number + ********************************************/ +function readUInt16LE(bytes) { + var value = (bytes[1] << 8) + bytes[0]; + return value & 0xffff; +} + +function readInt16LE(bytes) { + var ref = readUInt16LE(bytes); + return ref > 0x7fff ? ref - 0x10000 : ref; +} + +function readUInt32LE(bytes) { + var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]; + return (value & 0xffffffff) >>> 0; +} + +function readFloatLE(bytes) { + // JavaScript bitwise operators yield a 32 bits integer, not a float. + // Assume LSB (least significant byte first). + var bits = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]; + var sign = bits >>> 31 === 0 ? 1.0 : -1.0; + var e = (bits >>> 23) & 0xff; + var m = e === 0 ? (bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000; + var f = sign * m * Math.pow(2, e - 150); + + var v = Number(f.toFixed(2)); + return v; +} + +function readGPIOStatus(bytes) { + // 0: low, 1: high + switch (bytes) { + case 0: + return "low"; + case 1: + return "high"; + default: + return "unknown"; + } +} + +function readGPIOAlarm(bytes) { + // 1: gpio alarm, 0: gpio alarm release + switch (bytes) { + case 0: + return "gpio alarm release"; + case 1: + return "gpio alarm"; + default: + return "unknown"; + } +} + +function readWaterAlarm(bytes) { + // 1: water outage timeout alarm, 2: water outage timeout alarm release, 3: water flow timeout alarm, 4: water flow timeout alarm release + switch (bytes) { + case 1: + return "water outage timeout alarm"; + case 2: + return "water outage timeout alarm release"; + case 3: + return "water flow timeout alarm"; + case 4: + return "water flow timeout alarm release"; + default: + return "unknown"; + } +} + +function readAlarm(bytes) { + // 0: none, 1: water outage timeout alarm, 2: water outage timeout alarm release, 3: water flow timeout alarm, 4: water flow timeout alarm release, 5: gpio alarm, 6: gpio alarm release + switch (bytes) { + case 0: + return "none"; + case 1: + return "water outage timeout alarm"; + case 2: + return "water outage timeout alarm release"; + case 3: + return "water flow timeout alarm"; + case 4: + return "water flow timeout alarm release"; + case 5: + return "gpio alarm"; + case 6: + return "gpio alarm release"; + default: + return "unknown"; + } +} diff --git a/vendors/milesight/codecs/em300-mcs.js b/vendors/milesight/codecs/em300-mcs.js new file mode 100644 index 0000000..b8e02ef --- /dev/null +++ b/vendors/milesight/codecs/em300-mcs.js @@ -0,0 +1,90 @@ +/** + * Payload Decoder + * + * Copyright 2024 Milesight IoT + * + * @product EM300-MCS + */ +// Chirpstack v4 +function decodeUplink(input) { + var decoded = milesightDeviceDecode(input.bytes); + return { data: decoded }; +} + +// Chirpstack v3 +function Decode(fPort, bytes) { + return milesightDeviceDecode(bytes); +} + +// The Things Network +function Decoder(bytes, port) { + return milesightDeviceDecode(bytes); +} + +function milesightDeviceDecode(bytes) { + var decoded = {}; + + for (var i = 0; i < bytes.length; ) { + var channel_id = bytes[i++]; + var channel_type = bytes[i++]; + // BATTERY + if (channel_id === 0x01 && channel_type === 0x75) { + decoded.battery = bytes[i]; + i += 1; + } + // TEMPERATURE + else if (channel_id === 0x03 && channel_type === 0x67) { + // ℃ + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; + i += 2; + + // ℉ + // decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10 * 1.8 + 32; + // i +=2; + } + // HUMIDITY + else if (channel_id === 0x04 && channel_type === 0x68) { + decoded.humidity = bytes[i] / 2; + i += 1; + } + // MAGNET STATUS + else if (channel_id === 0x06 && channel_type === 0x00) { + decoded.magnet_status = bytes[i] === 0 ? "close" : "open"; + i += 1; + } + // TEMPERATURE、HUMIDITY & MAGNET STATUS HISTROY + else if (channel_id === 0x20 && channel_type === 0xce) { + var point = {}; + point.timestamp = readUInt32LE(bytes.slice(i, i + 4)); + point.temperature = readInt16LE(bytes.slice(i + 4, i + 6)) / 10; + point.humidity = bytes[i + 6] / 2; + point.magnet_status = bytes[i + 7] === 0 ? "close" : "open"; + + decoded.history = decoded.history || []; + decoded.history.push(point); + i += 8; + } else { + break; + } + } + + return decoded; +} + +/* ****************************************** + * bytes to number + ********************************************/ +function readUInt16LE(bytes) { + var value = (bytes[1] << 8) + bytes[0]; + return value & 0xffff; +} + +function readInt16LE(bytes) { + var ref = readUInt16LE(bytes); + return ref > 0x7fff ? ref - 0x10000 : ref; +} + +function readUInt32LE(bytes) { + var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]; + return value & 0xffffffff; +} diff --git a/vendors/milesight/codecs/em300-mld.js b/vendors/milesight/codecs/em300-mld.js new file mode 100644 index 0000000..852366c --- /dev/null +++ b/vendors/milesight/codecs/em300-mld.js @@ -0,0 +1,73 @@ +/** + * Payload Decoder + * + * Copyright 2024 Milesight IoT + * + * @product EM300-MLD + */ +// Chirpstack v4 +function decodeUplink(input) { + var decoded = milesightDeviceDecode(input.bytes); + return { data: decoded }; +} + +// Chirpstack v3 +function Decode(fPort, bytes) { + return milesightDeviceDecode(bytes); +} + +// The Things Network +function Decoder(bytes, port) { + return milesightDeviceDecode(bytes); +} + +function milesightDeviceDecode(bytes) { + var decoded = {}; + + for (var i = 0; i < bytes.length; ) { + var channel_id = bytes[i++]; + var channel_type = bytes[i++]; + // BATTERY + if (channel_id === 0x01 && channel_type === 0x75) { + decoded.battery = bytes[i]; + i += 1; + } + // LEAKAGE STATUS + else if (channel_id === 0x05 && channel_type === 0x00) { + decoded.leakage_status = bytes[i] === 0 ? "normal" : "leak"; + i += 1; + } + // TEMPERATURE, HUMIDITY & LEAKAGE STATUS HISTROY + else if (channel_id === 0x20 && channel_type === 0xce) { + var point = {}; + point.timestamp = readUInt32LE(bytes.slice(i, i + 4)); + point.leakage_status = bytes[i + 7] === 0 ? "normal" : "leak"; + + decoded.history = decoded.history || []; + decoded.history.push(point); + i += 8; + } else { + break; + } + } + + return decoded; +} + +/* ****************************************** + * bytes to number + ********************************************/ +function readUInt16LE(bytes) { + var value = (bytes[1] << 8) + bytes[0]; + return value & 0xffff; +} + +function readInt16LE(bytes) { + var ref = readUInt16LE(bytes); + return ref > 0x7fff ? ref - 0x10000 : ref; +} + +function readUInt32LE(bytes) { + var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]; + return (value & 0xffffffff) >>> 0; +} diff --git a/vendors/milesight/codecs/em300-sld-zld.js b/vendors/milesight/codecs/em300-sld-zld.js new file mode 100644 index 0000000..6953f30 --- /dev/null +++ b/vendors/milesight/codecs/em300-sld-zld.js @@ -0,0 +1,373 @@ +/** + * Payload Decoder + * + * Copyright 2025 Milesight IoT + * + * @product EM300-SLD / EM300-ZLD + */ +var RAW_VALUE = 0x00; + +// Chirpstack v4 +function decodeUplink(input) { + var decoded = milesightDeviceDecode(input.bytes); + return { data: decoded }; +} + +// Chirpstack v3 +function Decode(fPort, bytes) { + return milesightDeviceDecode(bytes); +} + +// The Things Network +function Decoder(bytes, port) { + return milesightDeviceDecode(bytes); +} + +function milesightDeviceDecode(bytes) { + var decoded = {}; + + for (var i = 0; i < bytes.length;) { + var channel_id = bytes[i++]; + var channel_type = bytes[i++]; + + // IPSO VERSION + if (channel_id === 0xff && channel_type === 0x01) { + decoded.ipso_version = readProtocolVersion(bytes[i]); + i += 1; + } + // HARDWARE VERSION + else if (channel_id === 0xff && channel_type === 0x09) { + decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2)); + i += 2; + } + // FIRMWARE VERSION + else if (channel_id === 0xff && channel_type === 0x0a) { + decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2)); + i += 2; + } + // TSL VERSION + else if (channel_id === 0xff && channel_type === 0xff) { + decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2)); + i += 2; + } + // SERIAL NUMBER + else if (channel_id === 0xff && channel_type === 0x16) { + decoded.sn = readSerialNumber(bytes.slice(i, i + 8)); + i += 8; + } + // LORAWAN CLASS TYPE + else if (channel_id === 0xff && channel_type === 0x0f) { + decoded.lorawan_class = readLoRaWANClass(bytes[i]); + i += 1; + } + // RESET EVENT + else if (channel_id === 0xff && channel_type === 0xfe) { + decoded.reset_event = readResetEvent(1); + i += 1; + } + // DEVICE STATUS + else if (channel_id === 0xff && channel_type === 0x0b) { + decoded.device_status = readDeviceStatus(1); + i += 1; + } + // BATTERY + else if (channel_id === 0x01 && channel_type === 0x75) { + decoded.battery = readUInt8(bytes[i]); + i += 1; + } + // TEMPERATURE + else if (channel_id === 0x03 && channel_type === 0x67) { + // ℃ + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; + i += 2; + } + // HUMIDITY + else if (channel_id === 0x04 && channel_type === 0x68) { + decoded.humidity = readUInt8(bytes[i]) / 2; + i += 1; + } + // LEAKAGE STATUS + else if (channel_id === 0x05 && channel_type === 0x00) { + decoded.leakage_status = readLeakageStatus(bytes[i]); + i += 1; + } + // TEMPERATURE, HUMIDITY & LEAKAGE STATUS HISTORY + else if (channel_id === 0x20 && channel_type === 0xce) { + var data = {}; + data.timestamp = readUInt32LE(bytes.slice(i, i + 4)); + data.temperature = readInt16LE(bytes.slice(i + 4, i + 6)) / 10; + data.humidity = readUInt8(bytes[i + 6]) / 2; + data.leakage_status = readLeakageStatus(bytes[i + 7]); + + decoded.history = decoded.history || []; + decoded.history.push(data); + i += 8; + } + // DOWNLINK RESPONSE + else if (channel_id === 0xfe || channel_id === 0xff) { + var result = handle_downlink_response(channel_type, bytes, i); + decoded = Object.assign(decoded, result.data); + i = result.offset; + } else { + break; + } + } + + return decoded; +} + +function handle_downlink_response(channel_type, bytes, offset) { + var decoded = {}; + + switch (channel_type) { + case 0x02: + decoded.collection_interval = readUInt16LE(bytes.slice(offset, offset + 2)); + offset += 2; + break; + case 0x03: + decoded.report_interval = readUInt16LE(bytes.slice(offset, offset + 2)); + offset += 2; + break; + case 0x06: + var data = readUInt8(bytes[offset]); + var channel = (data >>> 3) & 0x07; + var value = data & 0x07; + 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; + } else if (channel === 0x02) { + decoded.leakage_alarm_config = {}; + decoded.leakage_alarm_config.enable = readEnableStatus(value); + // skip 4 bytes + decoded.leakage_alarm_config.report_interval = readUInt16LE(bytes.slice(offset + 5, offset + 7)); + decoded.leakage_alarm_config.report_times = readUInt16LE(bytes.slice(offset + 7, offset + 9)); + } + offset += 9; + break; + case 0x10: + decoded.reboot = readYesNoStatus(1); + offset += 1; + break; + case 0x27: + decoded.clear_history = readYesNoStatus(1); + offset += 1; + break; + case 0x28: + decoded.report_status = readYesNoStatus(1); + offset += 1; + break; + case 0x68: + decoded.history_enable = readEnableStatus(bytes[offset]); + offset += 1; + break; + case 0x69: + decoded.retransmit_enable = readEnableStatus(bytes[offset]); + offset += 1; + break; + case 0x6a: + var interval_type = readUInt8(bytes[offset]); + switch (interval_type) { + case 0: + decoded.retransmit_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3)); + break; + case 1: + decoded.resend_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3)); + break; + } + offset += 3; + break; + case 0x6d: + decoded.stop_transmit = readYesNoStatus(1); + 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)); + offset += 6; + 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; + } 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; + } + offset += 3; + break; + default: + throw new Error("unknown downlink response"); + } + + return { data: decoded, offset: offset }; +} + +function readProtocolVersion(bytes) { + var major = (bytes & 0xf0) >> 4; + var minor = bytes & 0x0f; + return "v" + major + "." + minor; +} + +function readHardwareVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = (bytes[1] & 0xff) >> 4; + return "v" + major + "." + minor; +} + +function readFirmwareVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = bytes[1] & 0xff; + return "v" + major + "." + minor; +} + +function readTslVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = bytes[1] & 0xff; + return "v" + major + "." + minor; +} + +function readSerialNumber(bytes) { + var temp = []; + for (var idx = 0; idx < bytes.length; idx++) { + temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2)); + } + return temp.join(""); +} + +function readLoRaWANClass(type) { + var class_map = { + 0: "Class A", + 1: "Class B", + 2: "Class C", + 3: "Class CtoB", + }; + return getValue(class_map, type); +} + +function readResetEvent(status) { + var status_map = { 0: "normal", 1: "reset" }; + return getValue(status_map, status); +} + +function readDeviceStatus(status) { + var status_map = { 0: "off", 1: "on" }; + return getValue(status_map, status); +} + +function readEnableStatus(status) { + var status_map = { 0: "disable", 1: "enable" }; + return getValue(status_map, status); +} + +function readYesNoStatus(status) { + var status_map = { 0: "no", 1: "yes" }; + return getValue(status_map, status); +} + +function readLeakageStatus(status) { + var status_map = { 0: "normal", 1: "leak" }; + return getValue(status_map, status); +} + +function readConditionType(value) { + var condition_map = { 0: "disable", 1: "below", 2: "above", 3: "between", 4: "outside" }; + return getValue(condition_map, value); +} + +function readTriggerEvent(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); +} + +function readReportType(value) { + var report_map = { 0: "lora", 1: "d2d", 3: "d2d_and_lora" }; + return getValue(report_map, value); +} + +function readD2DCommand(bytes) { + return ("0" + (bytes[1] & 0xff).toString(16)).slice(-2) + ("0" + (bytes[0] & 0xff).toString(16)).slice(-2); +} + +function readUInt8(bytes) { + return bytes & 0xff; +} + +function readInt8(bytes) { + var ref = readUInt8(bytes); + return ref > 0x7f ? ref - 0x100 : ref; +} + +function readUInt16LE(bytes) { + var value = (bytes[1] << 8) + bytes[0]; + return value & 0xffff; +} + +function readInt16LE(bytes) { + var ref = readUInt16LE(bytes); + return ref > 0x7fff ? ref - 0x10000 : ref; +} + +function readUInt32LE(bytes) { + var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]; + return (value & 0xffffffff) >>> 0; +} + +function readInt32LE(bytes) { + var ref = readUInt32LE(bytes); + return ref > 0x7fffffff ? ref - 0x100000000 : ref; +} + +function getValue(map, key) { + if (RAW_VALUE) return key; + + var value = map[key]; + if (!value) value = "unknown"; + return value; +} + +if (!Object.assign) { + Object.defineProperty(Object, "assign", { + enumerable: false, + configurable: true, + writable: true, + value: function (target) { + "use strict"; + if (target == null) { + throw new TypeError("Cannot convert first argument to object"); + } + + var to = Object(target); + for (var i = 1; i < arguments.length; i++) { + var nextSource = arguments[i]; + if (nextSource == null) { + continue; + } + nextSource = Object(nextSource); + + var keysArray = Object.keys(Object(nextSource)); + for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { + var nextKey = keysArray[nextIndex]; + var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); + if (desc !== undefined && desc.enumerable) { + // concat array + if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) { + to[nextKey] = to[nextKey].concat(nextSource[nextKey]); + } else { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }, + }); +} diff --git a/vendors/milesight/codecs/em300-th.js b/vendors/milesight/codecs/em300-th.js new file mode 100644 index 0000000..5a7590f --- /dev/null +++ b/vendors/milesight/codecs/em300-th.js @@ -0,0 +1,85 @@ +/** + * Payload Decoder + * + * Copyright 2024 Milesight IoT + * + * @product EM300-TH + */ +// Chirpstack v4 +function decodeUplink(input) { + var decoded = milesightDeviceDecode(input.bytes); + return { data: decoded }; +} + +// Chirpstack v3 +function Decode(fPort, bytes) { + return milesightDeviceDecode(bytes); +} + +// The Things Network +function Decoder(bytes, port) { + return milesightDeviceDecode(bytes); +} + +function milesightDeviceDecode(bytes) { + var decoded = {}; + + for (var i = 0; i < bytes.length; ) { + var channel_id = bytes[i++]; + var channel_type = bytes[i++]; + + // BATTERY + if (channel_id === 0x01 && channel_type === 0x75) { + decoded.battery = bytes[i]; + i += 1; + } + // TEMPERATURE + else if (channel_id === 0x03 && channel_type === 0x67) { + // ℃ + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; + i += 2; + + // ℉ + // decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10 * 1.8 + 32; + // i +=2; + } + // HUMIDITY + else if (channel_id === 0x04 && channel_type === 0x68) { + decoded.humidity = bytes[i] / 2; + i += 1; + } + // TEMPERATURE & HUMIDITY HISTROY + else if (channel_id === 0x20 && channel_type === 0xce) { + var point = {}; + point.timestamp = readUInt32LE(bytes.slice(i, i + 4)); + point.temperature = readInt16LE(bytes.slice(i + 4, i + 6)) / 10; + point.humidity = bytes[i + 6] / 2; + + decoded.history = decoded.history || []; + decoded.history.push(point); + i += 8; + } else { + break; + } + } + + return decoded; +} + +/* ****************************************** + * bytes to number + ********************************************/ +function readUInt16LE(bytes) { + var value = (bytes[1] << 8) + bytes[0]; + return value & 0xffff; +} + +function readInt16LE(bytes) { + var ref = readUInt16LE(bytes); + return ref > 0x7fff ? ref - 0x10000 : ref; +} + +function readUInt32LE(bytes) { + var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]; + return value & 0xffffffff; +} diff --git a/vendors/milesight/codecs/em320-th.js b/vendors/milesight/codecs/em320-th.js new file mode 100644 index 0000000..942684b --- /dev/null +++ b/vendors/milesight/codecs/em320-th.js @@ -0,0 +1,168 @@ +/** + * Payload Decoder + * + * Copyright 2024 Milesight IoT + * + * @product EM320-TH + */ +// Chirpstack v4 +function decodeUplink(input) { + var decoded = milesightDeviceDecode(input.bytes); + return { data: decoded }; +} + +// Chirpstack v3 +function Decode(fPort, bytes) { + return milesightDeviceDecode(bytes); +} + +// The Things Network +function Decoder(bytes, port) { + return milesightDeviceDecode(bytes); +} + +function milesightDeviceDecode(bytes) { + var decoded = {}; + + for (var i = 0; i < bytes.length; ) { + var channel_id = bytes[i++]; + var channel_type = bytes[i++]; + + // IPSO VERSION + if (channel_id === 0xff && channel_type === 0x01) { + decoded.ipso_version = readProtocolVersion(bytes[i]); + i += 1; + } + // HARDWARE VERSION + else if (channel_id === 0xff && channel_type === 0x09) { + decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2)); + i += 2; + } + // FIRMWARE VERSION + else if (channel_id === 0xff && channel_type === 0x0a) { + decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2)); + i += 2; + } + // DEVICE STATUS + else if (channel_id === 0xff && channel_type === 0x0b) { + decoded.device_status = "on"; + i += 1; + } + // LORAWAN CLASS TYPE + else if (channel_id === 0xff && channel_type === 0x0f) { + decoded.lorawan_class = readLoRaWANType(bytes[i]); + i += 1; + } + // SERIAL NUMBER + else if (channel_id === 0xff && channel_type === 0x16) { + decoded.sn = readSerialNumber(bytes.slice(i, i + 8)); + i += 8; + } + // TSL VERSION + else if (channel_id === 0xff && channel_type === 0xff) { + decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2)); + i += 2; + } + // BATTERY + else if (channel_id === 0x01 && channel_type === 0x75) { + decoded.battery = readUInt8(bytes[i]); + i += 1; + } + // TEMPERATURE + else if (channel_id === 0x03 && channel_type === 0x67) { + decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10; + i += 2; + } + // HUMIDITY + else if (channel_id === 0x04 && channel_type === 0x68) { + decoded.humidity = readUInt8(bytes[i]) / 2; + i += 1; + } + // HISTORICAL DATA + else if (channel_id === 0x20 && channel_type === 0xce) { + var data = {}; + data.timestamp = readUInt32LE(bytes.slice(i, i + 4)); + data.temperature = readInt16LE(bytes.slice(i + 4, i + 6)) / 10; + data.humidity = readUInt8(bytes[i + 6]) / 2; + + decoded.history = decoded.history || []; + decoded.history.push(data); + i += 7; + } else { + break; + } + } + + return decoded; +} + +function readUInt8(bytes) { + return bytes & 0xff; +} + +function readInt8(bytes) { + var ref = readUInt8(bytes); + return ref > 0x7f ? ref - 0x100 : ref; +} + +function readUInt16LE(bytes) { + var value = (bytes[1] << 8) + bytes[0]; + return value & 0xffff; +} + +function readInt16LE(bytes) { + var ref = readUInt16LE(bytes); + return ref > 0x7fff ? ref - 0x10000 : ref; +} + +function readUInt32LE(bytes) { + var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]; + return (value & 0xffffffff) >>> 0; +} + +function readProtocolVersion(bytes) { + var major = (bytes & 0xf0) >> 4; + var minor = bytes & 0x0f; + return "v" + major + "." + minor; +} + +function readHardwareVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = (bytes[1] & 0xff) >> 4; + return "v" + major + "." + minor; +} + +function readFirmwareVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = bytes[1] & 0xff; + return "v" + major + "." + minor; +} + +function readTslVersion(bytes) { + var major = bytes[0] & 0xff; + var minor = bytes[1] & 0xff; + return "v" + major + "." + minor; +} + +function readSerialNumber(bytes) { + var temp = []; + for (var idx = 0; idx < bytes.length; idx++) { + temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2)); + } + return temp.join(""); +} + +function readLoRaWANType(type) { + switch (type) { + case 0x00: + return "ClassA"; + case 0x01: + return "ClassB"; + case 0x02: + return "ClassC"; + case 0x03: + return "ClassCtoB"; + default: + return "Unknown"; + } +} diff --git a/vendors/milesight/codecs/em320-tilt.js b/vendors/milesight/codecs/em320-tilt.js new file mode 100644 index 0000000..1eee3c0 --- /dev/null +++ b/vendors/milesight/codecs/em320-tilt.js @@ -0,0 +1,64 @@ +/** + * Payload Decoder + * + * Copyright 2024 Milesight IoT + * + * @product EM310-TILT + */ +// Chirpstack v4 +function decodeUplink(input) { + var decoded = milesightDeviceDecode(input.bytes); + return { data: decoded }; +} + +// Chirpstack v3 +function Decode(fPort, bytes) { + return milesightDeviceDecode(bytes); +} + +// The Things Network +function Decoder(bytes, port) { + return milesightDeviceDecode(bytes); +} + +function milesightDeviceDecode(bytes) { + var decoded = {}; + + for (var i = 0; i < bytes.length; ) { + var channel_id = bytes[i++]; + var channel_type = bytes[i++]; + + // BATTERY + if (channel_id === 0x01 && channel_type === 0x75) { + decoded.battery = bytes[i]; + i += 1; + } + // ANGLE + else if (channel_id === 0x03 && channel_type === 0xcf) { + decoded.angle_x = readInt16LE(bytes.slice(i, i + 2)) / 100; + decoded.angle_y = readInt16LE(bytes.slice(i + 2, i + 4)) / 100; + decoded.angle_z = readInt16LE(bytes.slice(i + 4, i + 6)) / 100; + decoded.threshold_x = (bytes[i + 6] & 0x01) === 0x01 ? "trigger" : "normal"; + decoded.threshold_y = (bytes[i + 6] & 0x02) === 0x02 ? "trigger" : "normal"; + decoded.threshold_z = (bytes[i + 6] & 0x04) === 0x04 ? "trigger" : "normal"; + i += 7; + } else { + break; + } + } + + return decoded; +} + +/* ****************************************** + * bytes to number + ********************************************/ +function readUInt16LE(bytes) { + var value = (bytes[1] << 8) + bytes[0]; + return value & 0xffff; +} + +function readInt16LE(bytes) { + var ref = readUInt16LE(bytes); + return ref > 0x7fff ? ref - 0x10000 : ref; +} diff --git a/vendors/milesight/codecs/test_decode_em300-cl.json b/vendors/milesight/codecs/test_decode_em300-cl.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_decode_em300-cl.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_decode_em300-di.json b/vendors/milesight/codecs/test_decode_em300-di.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_decode_em300-di.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_decode_em300-mcs.json b/vendors/milesight/codecs/test_decode_em300-mcs.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_decode_em300-mcs.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_decode_em300-mld.json b/vendors/milesight/codecs/test_decode_em300-mld.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_decode_em300-mld.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_decode_em300-sld-zld.json b/vendors/milesight/codecs/test_decode_em300-sld-zld.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_decode_em300-sld-zld.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_decode_em300-th.json b/vendors/milesight/codecs/test_decode_em300-th.json new file mode 100644 index 0000000..0d7b7c5 --- /dev/null +++ b/vendors/milesight/codecs/test_decode_em300-th.json @@ -0,0 +1,14 @@ +[ + { + "name": "Test decode Milesight EM300-TH", + "input": { + "bytes": [3, 103, 44, 0, 4, 104, 23] + }, + "expected": { + "data": { + "temperature": 4.4, + "humidity": 11.5 + } + } + } +] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_decode_em320-th.json b/vendors/milesight/codecs/test_decode_em320-th.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_decode_em320-th.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_decode_em320-tilt.json b/vendors/milesight/codecs/test_decode_em320-tilt.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_decode_em320-tilt.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_encode_em300-cl.json b/vendors/milesight/codecs/test_encode_em300-cl.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_encode_em300-cl.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_encode_em300-di.json b/vendors/milesight/codecs/test_encode_em300-di.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_encode_em300-di.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_encode_em300-mcs.json b/vendors/milesight/codecs/test_encode_em300-mcs.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_encode_em300-mcs.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_encode_em300-mld.json b/vendors/milesight/codecs/test_encode_em300-mld.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_encode_em300-mld.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_encode_em300-sld-zld.json b/vendors/milesight/codecs/test_encode_em300-sld-zld.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_encode_em300-sld-zld.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_encode_em300-th.json b/vendors/milesight/codecs/test_encode_em300-th.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_encode_em300-th.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_encode_em320-th.json b/vendors/milesight/codecs/test_encode_em320-th.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_encode_em320-th.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/codecs/test_encode_em320-tilt.json b/vendors/milesight/codecs/test_encode_em320-tilt.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/milesight/codecs/test_encode_em320-tilt.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/milesight/devices/milesight-em300-cl.toml b/vendors/milesight/devices/milesight-em300-cl.toml new file mode 100644 index 0000000..b90e5ea --- /dev/null +++ b/vendors/milesight/devices/milesight-em300-cl.toml @@ -0,0 +1,8 @@ +[device] +name = "Milesight EM300-CL" +description = "Capacitive Level Sensor " +firmware = [] + +[device.metadata] +product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-cl" +documentation_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-cl" diff --git a/vendors/milesight/devices/milesight-em300-di.toml b/vendors/milesight/devices/milesight-em300-di.toml new file mode 100644 index 0000000..599557e --- /dev/null +++ b/vendors/milesight/devices/milesight-em300-di.toml @@ -0,0 +1,8 @@ +[device] +name = "Milesight EM300-DI" +description = "Pulse Counter" +firmware = [] + +[device.metadata] +product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-di" +documentation_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-di" diff --git a/vendors/milesight/devices/milesight-em300-mcs.toml b/vendors/milesight/devices/milesight-em300-mcs.toml new file mode 100644 index 0000000..1e70a88 --- /dev/null +++ b/vendors/milesight/devices/milesight-em300-mcs.toml @@ -0,0 +1,8 @@ +[device] +name = "Milesight EM300-MCS" +description = "Magnetic Contact Switch" +firmware = [] + +[device.metadata] +product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-mcs" +documentation_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-mcs" diff --git a/vendors/milesight/devices/milesight-em300-mld.toml b/vendors/milesight/devices/milesight-em300-mld.toml new file mode 100644 index 0000000..0609cc6 --- /dev/null +++ b/vendors/milesight/devices/milesight-em300-mld.toml @@ -0,0 +1,8 @@ +[device] +name = "Milesight EM300-MLD" +description = "Membrane Leakage Detection Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-mld" +documentation_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-mld" diff --git a/vendors/milesight/devices/milesight-em300-sld-zld.toml b/vendors/milesight/devices/milesight-em300-sld-zld.toml new file mode 100644 index 0000000..814af9d --- /dev/null +++ b/vendors/milesight/devices/milesight-em300-sld-zld.toml @@ -0,0 +1,8 @@ +[device] +name = "Milesight EM300-SLD-ZLD" +description = "Leak Detection Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-sld-zld" +documentation_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-sld-zld" diff --git a/vendors/milesight/devices/milesight-em300-th.toml b/vendors/milesight/devices/milesight-em300-th.toml new file mode 100644 index 0000000..a3da6bd --- /dev/null +++ b/vendors/milesight/devices/milesight-em300-th.toml @@ -0,0 +1,8 @@ +[device] +name = "Milesight EM300-TH" +description = "Temperature & Humidity Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-th" +documentation_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-th" diff --git a/vendors/milesight/devices/milesight-em310-tilt.toml b/vendors/milesight/devices/milesight-em310-tilt.toml new file mode 100644 index 0000000..a1a9907 --- /dev/null +++ b/vendors/milesight/devices/milesight-em310-tilt.toml @@ -0,0 +1,8 @@ +[device] +name = "Milesight EM310-TILT" +description = "Tilt Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em320-tilt" +documentation_url = "https://www.milesight.com/iot/product/lorawan-sensor/em320-tilt" diff --git a/vendors/milesight/devices/milesight-em320-th.toml b/vendors/milesight/devices/milesight-em320-th.toml new file mode 100644 index 0000000..4b72ed4 --- /dev/null +++ b/vendors/milesight/devices/milesight-em320-th.toml @@ -0,0 +1,8 @@ +[device] +name = "Milesight EM320-TH" +description = "Temperature & Humidity Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em320-th" +documentation_url = "https://www.milesight.com/iot/product/lorawan-sensor/em320-th" diff --git a/vendors/milesight/profiles/AS923-1_0_3.toml b/vendors/milesight/profiles/AS923-1_0_3.toml new file mode 100644 index 0000000..ab20972 --- /dev/null +++ b/vendors/milesight/profiles/AS923-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "AS923" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 16 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/milesight/profiles/AU915-1_0_3.toml b/vendors/milesight/profiles/AU915-1_0_3.toml new file mode 100644 index 0000000..decaa91 --- /dev/null +++ b/vendors/milesight/profiles/AU915-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "AU915" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 20 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/milesight/profiles/EU868-1_0_3.toml b/vendors/milesight/profiles/EU868-1_0_3.toml new file mode 100644 index 0000000..6595d6e --- /dev/null +++ b/vendors/milesight/profiles/EU868-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "EU868" +mac_version = "1.0.0" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 16 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/milesight/profiles/US915-1_0_3.toml b/vendors/milesight/profiles/US915-1_0_3.toml new file mode 100644 index 0000000..5211df4 --- /dev/null +++ b/vendors/milesight/profiles/US915-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "US915" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 20 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/milesight/vendor.toml b/vendors/milesight/vendor.toml new file mode 100644 index 0000000..3eba3fb --- /dev/null +++ b/vendors/milesight/vendor.toml @@ -0,0 +1,17 @@ +[vendor] +name = "Milesight" +id = 0 +ouis = ["24e124"] +devices = [ + "milesight-em300-di.toml", + "milesight-em300-mcs.toml", + "milesight-em300-th.toml", + "milesight-em300-cl.toml", + "milesight-em300-mld.toml", + "milesight-em300-sld-zld.toml", + "milesight-em310-tilt.toml", + "milesight-em320-th.toml", +] + +[vendor.metadata] +homepage = "https://www.milesight.com/" From e006f0fa135004ed2928b5a7decef87c8e46f325 Mon Sep 17 00:00:00 2001 From: Cory Callcott Date: Mon, 26 May 2025 17:24:44 +1000 Subject: [PATCH 3/8] add lora alliance vendor id to milesight --- vendors/milesight/vendor.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendors/milesight/vendor.toml b/vendors/milesight/vendor.toml index 3eba3fb..9421af4 100644 --- a/vendors/milesight/vendor.toml +++ b/vendors/milesight/vendor.toml @@ -1,6 +1,6 @@ [vendor] name = "Milesight" -id = 0 +id = 601 ouis = ["24e124"] devices = [ "milesight-em300-di.toml", From 1c02c8b8825fe0e5881f635e06e9f44cdf9b2aa1 Mon Sep 17 00:00:00 2001 From: Cory Callcott Date: Wed, 28 May 2025 18:27:27 +1000 Subject: [PATCH 4/8] init addition of some sensecap devices --- .../codecs/SenseCAP_LoRaWAN_V2_Decoder.js | 630 +++++++++++++ .../codecs/SenseCAP_LoRaWAN_V4_Decoder.js | 511 +++++++++++ .../codecs/SenseCAP_S2120_Weather_Station.js | 391 ++++++++ .../codecs/SenseCAP_T1000_mapper.js | 862 ++++++++++++++++++ ...st_decode_SenseCAP_LoRaWAN_V2_Decoder.json | 1 + ...st_decode_SenseCAP_LoRaWAN_V4_Decoder.json | 1 + ...decode_SenseCAP_S2120_Weather_Station.json | 1 + .../test_decode_SenseCAP_T1000_mapper.json | 1 + ...st_encode_SenseCAP_LoRaWAN_V2_Decoder.json | 1 + ...st_encode_SenseCAP_LoRaWAN_V4_Decoder.json | 1 + ...encode_SenseCAP_S2120_Weather_Station.json | 1 + .../test_encode_SenseCAP_T1000_mapper.json | 1 + .../devices/sensecap-a1101.toml | 8 + .../devices/sensecap-s2100.toml | 8 + .../devices/sensecap-s2101.toml | 8 + .../devices/sensecap-s2102.toml | 8 + .../devices/sensecap-s2103.toml | 8 + .../devices/sensecap-s2104.toml | 8 + .../devices/sensecap-s2105.toml | 8 + .../devices/sensecap-s2106.toml | 8 + .../devices/sensecap-s2107.toml | 8 + .../devices/sensecap-s2120.toml | 8 + .../devices/sensecap-t1000-a.toml | 8 + .../devices/sensecap-t1000-b.toml | 8 + .../profiles/AS923-1_0_3.toml | 24 + .../profiles/AU915-1_0_3.toml | 24 + .../profiles/EU868-1_0_3.toml | 24 + .../profiles/US915-1_0_3.toml | 24 + vendors/seeed-technology-co-ltd/vendor.toml | 21 + 29 files changed, 2615 insertions(+) create mode 100644 vendors/seeed-technology-co-ltd/codecs/SenseCAP_LoRaWAN_V2_Decoder.js create mode 100644 vendors/seeed-technology-co-ltd/codecs/SenseCAP_LoRaWAN_V4_Decoder.js create mode 100644 vendors/seeed-technology-co-ltd/codecs/SenseCAP_S2120_Weather_Station.js create mode 100644 vendors/seeed-technology-co-ltd/codecs/SenseCAP_T1000_mapper.js create mode 100644 vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_LoRaWAN_V2_Decoder.json create mode 100644 vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_LoRaWAN_V4_Decoder.json create mode 100644 vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_S2120_Weather_Station.json create mode 100644 vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_T1000_mapper.json create mode 100644 vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_LoRaWAN_V2_Decoder.json create mode 100644 vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_LoRaWAN_V4_Decoder.json create mode 100644 vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_S2120_Weather_Station.json create mode 100644 vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_T1000_mapper.json create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-a1101.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-s2100.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-s2101.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-s2102.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-s2103.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-s2104.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-s2105.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-s2106.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-s2107.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-s2120.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-t1000-a.toml create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-t1000-b.toml create mode 100644 vendors/seeed-technology-co-ltd/profiles/AS923-1_0_3.toml create mode 100644 vendors/seeed-technology-co-ltd/profiles/AU915-1_0_3.toml create mode 100644 vendors/seeed-technology-co-ltd/profiles/EU868-1_0_3.toml create mode 100644 vendors/seeed-technology-co-ltd/profiles/US915-1_0_3.toml create mode 100644 vendors/seeed-technology-co-ltd/vendor.toml diff --git a/vendors/seeed-technology-co-ltd/codecs/SenseCAP_LoRaWAN_V2_Decoder.js b/vendors/seeed-technology-co-ltd/codecs/SenseCAP_LoRaWAN_V2_Decoder.js new file mode 100644 index 0000000..ff72631 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/SenseCAP_LoRaWAN_V2_Decoder.js @@ -0,0 +1,630 @@ +// Decode uplink function. +// +// Input is an object with the following fields: +// - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0] +// - fPort = Uplink fPort. +// - variables = Object containing the configured device variables. +// +// Output must be an object with the following fields: +// - data = Object representing the decoded payload. +function decodeUplink(input) { + // data split + + var bytes = input['bytes'] + // init + bytes = bytes2HexString(bytes) + .toLocaleUpperCase() + + let result = { + 'err': 0, 'payload': bytes, 'valid': true, messages: [] + } + let splitArray = dataSplit(bytes) + // data decoder + let decoderArray = [] + for (let i = 0; i < splitArray.length; i++) { + let item = splitArray[i] + let dataId = item.dataId + let dataValue = item.dataValue + let messages = dataIdAndDataValueJudge(dataId, dataValue) + decoderArray.push(messages) + } + result.messages = decoderArray + return { data: result } +} + +/** + * data splits + * @param bytes + * @returns {*[]} + */ +function dataSplit (bytes) { + let frameArray = [] + + for (let i = 0; i < bytes.length; i++) { + let remainingValue = bytes + let dataId = remainingValue.substring(0, 2) + let dataValue + let dataObj = {} + switch (dataId) { + case '01' : + case '20' : + case '21' : + case '30' : + case '31' : + case '33' : + case '40' : + case '41' : + case '42' : + case '43' : + case '44' : + case '45' : + dataValue = remainingValue.substring(2, 22) + bytes = remainingValue.substring(22) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '02': + dataValue = remainingValue.substring(2, 18) + bytes = remainingValue.substring(18) + dataObj = { + 'dataId': '02', 'dataValue': dataValue + } + break + case '03' : + case '06': + dataValue = remainingValue.substring(2, 4) + bytes = remainingValue.substring(4) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '05' : + case '34': + dataValue = bytes.substring(2, 10) + bytes = remainingValue.substring(10) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '04': + case '10': + case '32': + case '35': + case '36': + case '37': + case '38': + case '39': + dataValue = bytes.substring(2, 20) + bytes = remainingValue.substring(20) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + default: + dataValue = '9' + break + } + if (dataValue.length < 2) { + break + } + frameArray.push(dataObj) + } + return frameArray +} + +function dataIdAndDataValueJudge (dataId, dataValue) { + let messages = [] + switch (dataId) { + case '01': + let temperature = dataValue.substring(0, 4) + let humidity = dataValue.substring(4, 6) + let illumination = dataValue.substring(6, 14) + let uv = dataValue.substring(14, 16) + let windSpeed = dataValue.substring(16, 20) + messages = [{ + measurementValue: loraWANV2DataFormat(temperature, 10), measurementId: '4097', type: 'Air Temperature' + }, { + measurementValue: loraWANV2DataFormat(humidity), measurementId: '4098', type: 'Air Humidity' + }, { + measurementValue: loraWANV2DataFormat(illumination), measurementId: '4099', type: 'Light Intensity' + }, { + measurementValue: loraWANV2DataFormat(uv, 10), measurementId: '4190', type: 'UV Index' + }, { + measurementValue: loraWANV2DataFormat(windSpeed, 10), measurementId: '4105', type: 'Wind Speed' + }] + break + case '02': + let windDirection = dataValue.substring(0, 4) + let rainfall = dataValue.substring(4, 12) + let airPressure = dataValue.substring(12, 16) + messages = [{ + measurementValue: loraWANV2DataFormat(windDirection), measurementId: '4104', type: 'Wind Direction Sensor' + }, { + measurementValue: loraWANV2DataFormat(rainfall, 1000), measurementId: '4113', type: 'Rain Gauge' + }, { + + measurementValue: loraWANV2DataFormat(airPressure, 0.1), measurementId: '4101', type: 'Barometric Pressure' + }] + break + case '03': + let Electricity = dataValue + messages = [{ + 'Battery(%)': loraWANV2DataFormat(Electricity) + }] + break + case '04': + let electricityWhether = dataValue.substring(0, 2) + let hwv = dataValue.substring(2, 6) + let bdv = dataValue.substring(6, 10) + let sensorAcquisitionInterval = dataValue.substring(10, 14) + let gpsAcquisitionInterval = dataValue.substring(14, 18) + messages = [{ + 'Battery(%)': loraWANV2DataFormat(electricityWhether), + 'Hardware Version': `${loraWANV2DataFormat(hwv.substring(0, 2))}.${loraWANV2DataFormat(hwv.substring(2, 4))}`, + 'Firmware Version': `${loraWANV2DataFormat(bdv.substring(0, 2))}.${loraWANV2DataFormat(bdv.substring(2, 4))}`, + 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionInterval)) * 60, + 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionInterval)) * 60 + }] + break + case '05': + let sensorAcquisitionIntervalFive = dataValue.substring(0, 4) + let gpsAcquisitionIntervalFive = dataValue.substring(4, 8) + messages = [{ + 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalFive)) * 60, + 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalFive)) * 60 + }] + break + case '06': + let errorCode = dataValue + let descZh + switch (errorCode) { + case '00': + descZh = 'CCL_SENSOR_ERROR_NONE' + break + case '01': + descZh = 'CCL_SENSOR_NOT_FOUND' + break + case '02': + descZh = 'CCL_SENSOR_WAKEUP_ERROR' + break + case '03': + descZh = 'CCL_SENSOR_NOT_RESPONSE' + break + case '04': + descZh = 'CCL_SENSOR_DATA_EMPTY' + break + case '05': + descZh = 'CCL_SENSOR_DATA_HEAD_ERROR' + break + case '06': + descZh = 'CCL_SENSOR_DATA_CRC_ERROR' + break + case '07': + descZh = 'CCL_SENSOR_DATA_B1_NO_VALID' + break + case '08': + descZh = 'CCL_SENSOR_DATA_B2_NO_VALID' + break + case '09': + descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH' + break + case '0A': + descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED' + break + case '0B': + descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED' + break + case '0C': + descZh = 'CCL_SENSOR_DATA_VALUE_HI' + break + case '0D': + descZh = 'CCL_SENSOR_DATA_VALUE_LOW' + break + case '0E': + descZh = 'CCL_SENSOR_DATA_VALUE_MISSED' + break + case '0F': + descZh = 'CCL_SENSOR_ARG_INVAILD' + break + case '10': + descZh = 'CCL_SENSOR_RS485_MASTER_BUSY' + break + case '11': + descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR' + break + case '12': + descZh = 'CCL_SENSOR_RS485_REG_MISSED' + break + case '13': + descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR' + break + case '14': + descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR' + break + case '15': + descZh = 'CCL_SENSOR_CONFIG_ERROR' + break + case 'FF': + descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW' + break + default: + descZh = 'CC_OTHER_FAILED' + break + } + messages = [{ + measurementId: '4101', type: 'sensor_error_event', errCode: errorCode, descZh + }] + break + case '10': + let statusValue = dataValue.substring(0, 2) + let { status, type } = loraWANV2BitDataFormat(statusValue) + let sensecapId = dataValue.substring(2) + messages = [{ + status: status, channelType: type, sensorEui: sensecapId + }] + break + case '20': + let initmeasurementId = 4175 + let sensor = [] + for (let i = 0; i < dataValue.length; i += 4) { + let modelId = loraWANV2DataFormat(dataValue.substring(i, i + 2)) + let detectionType = loraWANV2DataFormat(dataValue.substring(i + 2, i + 4)) + let aiHeadValues = `${modelId}.${detectionType}` + sensor.push({ + measurementValue: aiHeadValues, measurementId: initmeasurementId + }) + initmeasurementId++ + } + messages = sensor + break + case '21': + // Vision AI: + // AI 识别输出帧 + let tailValueArray = [] + let initTailmeasurementId = 4180 + for (let i = 0; i < dataValue.length; i += 4) { + let modelId = loraWANV2DataFormat(dataValue.substring(i, i + 2)) + let detectionType = loraWANV2DataFormat(dataValue.substring(i + 2, i + 4)) + let aiTailValues = `${modelId}.${detectionType}` + tailValueArray.push({ + measurementValue: aiTailValues, measurementId: initTailmeasurementId, type: `AI Detection ${i}` + }) + initTailmeasurementId++ + } + messages = tailValueArray + break + case '30': + case '31': + // 首帧或者首帧输出帧 + let channelInfoOne = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)) + let dataOne = { + measurementValue: loraWANV2DataFormat(dataValue.substring(4, 12), 1000), + measurementId: parseInt(channelInfoOne.one), + type: 'Measurement' + } + let dataTwo = { + measurementValue: loraWANV2DataFormat(dataValue.substring(12, 20), 1000), + measurementId: parseInt(channelInfoOne.two), + type: 'Measurement' + } + let cacheArrayInfo = [] + if (parseInt(channelInfoOne.one)) { + cacheArrayInfo.push(dataOne) + } + if (parseInt(channelInfoOne.two)) { + cacheArrayInfo.push(dataTwo) + } + cacheArrayInfo.forEach(item => { + messages.push(item) + }) + break + case '32': + let channelInfoTwo = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)) + let dataThree = { + measurementValue: loraWANV2DataFormat(dataValue.substring(2, 10), 1000), + measurementId: parseInt(channelInfoTwo.one), + type: 'Measurement' + } + let dataFour = { + measurementValue: loraWANV2DataFormat(dataValue.substring(10, 18), 1000), + measurementId: parseInt(channelInfoTwo.two), + type: 'Measurement' + } + if (parseInt(channelInfoTwo.one)) { + messages.push(dataThree) + } + if (parseInt(channelInfoTwo.two)) { + messages.push(dataFour) + } + break + case '33': + let channelInfoThree = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)) + let dataFive = { + measurementValue: loraWANV2DataFormat(dataValue.substring(4, 12), 1000), + measurementId: parseInt(channelInfoThree.one), + type: 'Measurement' + } + let dataSix = { + measurementValue: loraWANV2DataFormat(dataValue.substring(12, 20), 1000), + measurementId: parseInt(channelInfoThree.two), + type: 'Measurement' + } + if (parseInt(channelInfoThree.one)) { + messages.push(dataFive) + } + if (parseInt(channelInfoThree.two)) { + messages.push(dataSix) + } + + break + case '34': + let model = loraWANV2DataFormat(dataValue.substring(0, 2)) + let GPIOInput = loraWANV2DataFormat(dataValue.substring(2, 4)) + let simulationModel = loraWANV2DataFormat(dataValue.substring(4, 6)) + let simulationInterface = loraWANV2DataFormat(dataValue.substring(6, 8)) + messages = [{ + 'dataloggerProtocol': model, + 'dataloggerGPIOInput': GPIOInput, + 'dataloggerAnalogType': simulationModel, + 'dataloggerAnalogInterface': simulationInterface + }] + break + case '35': + case '36': + let channelTDOne = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)) + let channelSortTDOne = 3920 + (parseInt(channelTDOne.one) - 1) * 2 + let channelSortTDTWO = 3921 + (parseInt(channelTDOne.one) - 1) * 2 + messages = [{ + [channelSortTDOne]: loraWANV2DataFormat(dataValue.substring(2, 10), 1000), + [channelSortTDTWO]: loraWANV2DataFormat(dataValue.substring(10, 18), 1000) + }] + break + case '37': + let channelTDInfoTwo = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)) + let channelSortOne = 3920 + (parseInt(channelTDInfoTwo.one) - 1) * 2 + let channelSortTWO = 3921 + (parseInt(channelTDInfoTwo.one) - 1) * 2 + messages = [{ + [channelSortOne]: loraWANV2DataFormat(dataValue.substring(2, 10), 1000), + [channelSortTWO]: loraWANV2DataFormat(dataValue.substring(10, 18), 1000) + }] + break + case '38': + let channelTDInfoThree = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)) + let channelSortThreeOne = 3920 + (parseInt(channelTDInfoThree.one) - 1) * 2 + let channelSortThreeTWO = 3921 + (parseInt(channelTDInfoThree.one) - 1) * 2 + messages = [{ + [channelSortThreeOne]: loraWANV2DataFormat(dataValue.substring(2, 10), 1000), + [channelSortThreeTWO]: loraWANV2DataFormat(dataValue.substring(10, 18), 1000) + }] + break + case '39': + let electricityWhetherTD = dataValue.substring(0, 2) + let hwvTD = dataValue.substring(2, 6) + let bdvTD = dataValue.substring(6, 10) + let sensorAcquisitionIntervalTD = dataValue.substring(10, 14) + let gpsAcquisitionIntervalTD = dataValue.substring(14, 18) + messages = [{ + 'Battery(%)': loraWANV2DataFormat(electricityWhetherTD), + 'Hardware Version': `${loraWANV2DataFormat(hwvTD.substring(0, 2))}.${loraWANV2DataFormat(hwvTD.substring(2, 4))}`, + 'Firmware Version': `${loraWANV2DataFormat(bdvTD.substring(0, 2))}.${loraWANV2DataFormat(bdvTD.substring(2, 4))}`, + 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalTD)) * 60, + 'thresholdMeasureInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalTD)) + }] + break + case '40': + case '41': + let lightIntensity = dataValue.substring(0, 4) + let loudness = dataValue.substring(4, 8) + // X + let accelerateX = dataValue.substring(8, 12) + // Y + let accelerateY = dataValue.substring(12, 16) + // Z + let accelerateZ = dataValue.substring(16, 20) + messages = [{ + measurementValue: loraWANV2DataFormat(lightIntensity), measurementId: '4193', type: 'Light Intensity' + }, { + measurementValue: loraWANV2DataFormat(loudness), measurementId: '4192', type: 'Sound Intensity' + }, { + + measurementValue: loraWANV2DataFormat(accelerateX, 100), measurementId: '4150', type: 'AccelerometerX' + }, { + + measurementValue: loraWANV2DataFormat(accelerateY, 100), measurementId: '4151', type: 'AccelerometerY' + }, { + + measurementValue: loraWANV2DataFormat(accelerateZ, 100), measurementId: '4152', type: 'AccelerometerZ' + }] + break + case '42': + let airTemperature = dataValue.substring(0, 4) + let AirHumidity = dataValue.substring(4, 8) + let tVOC = dataValue.substring(8, 12) + let CO2eq = dataValue.substring(12, 16) + let soilMoisture = dataValue.substring(16, 20) + messages = [{ + measurementValue: loraWANV2DataFormat(airTemperature, 100), measurementId: '4097', type: 'Air Temperature' + }, { + measurementValue: loraWANV2DataFormat(AirHumidity, 100), measurementId: '4098', type: 'Air Humidity' + }, { + measurementValue: loraWANV2DataFormat(tVOC), measurementId: '4195', type: 'Total Volatile Organic Compounds' + }, { + measurementValue: loraWANV2DataFormat(CO2eq), measurementId: '4100', type: 'CO2' + }, { + measurementValue: loraWANV2DataFormat(soilMoisture), measurementId: '4196', type: 'Soil moisture intensity' + }] + break + case '43': + case '44': + let headerDevKitValueArray = [] + let initDevkitmeasurementId = 4175 + for (let i = 0; i < dataValue.length; i += 4) { + let modelId = loraWANV2DataFormat(dataValue.substring(i, i + 2)) + let detectionType = loraWANV2DataFormat(dataValue.substring(i + 2, i + 4)) + let aiHeadValues = `${modelId}.${detectionType}` + headerDevKitValueArray.push({ + measurementValue: aiHeadValues, measurementId: initDevkitmeasurementId, type: `AI Detection ${i}` + }) + initDevkitmeasurementId++ + } + messages = headerDevKitValueArray + break + case '45': + let initTailDevKitmeasurementId = 4180 + for (let i = 0; i < dataValue.length; i += 4) { + let modelId = loraWANV2DataFormat(dataValue.substring(i, i + 2)) + let detectionType = loraWANV2DataFormat(dataValue.substring(i + 2, i + 4)) + let aiTailValues = `${modelId}.${detectionType}` + messages.push({ + measurementValue: aiTailValues, measurementId: initTailDevKitmeasurementId, type: `AI Detection ${i}` + }) + initTailDevKitmeasurementId++ + } + break + default: + break + } + return messages +} + +/** + * + * data formatting + * @param str + * @param divisor + * @returns {string|number} + */ +function loraWANV2DataFormat (str, divisor = 1) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + if (str2.substring(0, 1) === '1') { + let arr = str2.split('') + let reverseArr = arr.map((item) => { + if (parseInt(item) === 1) { + return 0 + } else { + return 1 + } + }) + str2 = parseInt(reverseArr.join(''), 2) + 1 + return '-' + str2 / divisor + } + return parseInt(str2, 2) / divisor +} + +/** + * Handling big-endian data formats + * @param data + * @returns {*[]} + */ +function bigEndianTransform (data) { + let dataArray = [] + for (let i = 0; i < data.length; i += 2) { + dataArray.push(data.substring(i, i + 2)) + } + // array of hex + return dataArray +} + +/** + * Convert to an 8-digit binary number with 0s in front of the number + * @param arr + * @returns {string} + */ +function toBinary (arr) { + let binaryData = arr.map((item) => { + let data = parseInt(item, 16) + .toString(2) + let dataLength = data.length + if (data.length !== 8) { + for (let i = 0; i < 8 - dataLength; i++) { + data = `0` + data + } + } + return data + }) + let ret = binaryData.toString() + .replace(/,/g, '') + return ret +} + +/** + * sensor + * @param str + * @returns {{channel: number, type: number, status: number}} + */ +function loraWANV2BitDataFormat (str) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + let channel = parseInt(str2.substring(0, 4), 2) + let status = parseInt(str2.substring(4, 5), 2) + let type = parseInt(str2.substring(5), 2) + return { channel, status, type } +} + +/** + * channel info + * @param str + * @returns {{channelTwo: number, channelOne: number}} + */ +function loraWANV2ChannelBitFormat (str) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + let one = parseInt(str2.substring(0, 4), 2) + let two = parseInt(str2.substring(4, 8), 2) + let resultInfo = { + one: one, two: two + } + return resultInfo +} + +/** + * data log status bit + * @param str + * @returns {{total: number, level: number, isTH: number}} + */ +function loraWANV2DataLogBitFormat (str) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + let isTH = parseInt(str2.substring(0, 1), 2) + let total = parseInt(str2.substring(1, 5), 2) + let left = parseInt(str2.substring(5), 2) + let resultInfo = { + isTH: isTH, total: total, left: left + } + return resultInfo +} + +function bytes2HexString (arrBytes) { + var str = '' + for (var i = 0; i < arrBytes.length; i++) { + var tmp + var num = arrBytes[i] + if (num < 0) { + tmp = (255 + num + 1).toString(16) + } else { + tmp = num.toString(16) + } + if (tmp.length === 1) { + tmp = '0' + tmp + } + str += tmp + } + return str +} + + +// Encode downlink function. +// +// Input is an object with the following fields: +// - data = Object representing the payload that must be encoded. +// - variables = Object containing the configured device variables. +// +// Output must be an object with the following fields: +// - bytes = Byte array containing the downlink payload. +function encodeDownlink(input) { + return { + bytes: [225, 230, 255, 0] + }; +} diff --git a/vendors/seeed-technology-co-ltd/codecs/SenseCAP_LoRaWAN_V4_Decoder.js b/vendors/seeed-technology-co-ltd/codecs/SenseCAP_LoRaWAN_V4_Decoder.js new file mode 100644 index 0000000..8cb1819 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/SenseCAP_LoRaWAN_V4_Decoder.js @@ -0,0 +1,511 @@ +function decodeUplink(input) { + var bytes = input['bytes'] + var bytesString = bytes2HexString(bytes).toLocaleUpperCase() + var decoded = { + valid: true, + err: 0, + payload: bytesString, + messages: [] + } + let measurement = messageAnalyzed(bytesString) + decoded.messages = measurement + return { data: decoded } +} + +function messageAnalyzed (messageValue) { + try { + let frames = unpack(messageValue) + let measurementResultArray = [] + for (let i = 0; i < frames.length; i++) { + let item = frames[i] + let dataId = item.dataId + let dataValue = item.dataValue + let measurementArray = deserialize(dataId, dataValue) + measurementResultArray.push(measurementArray) + } + return measurementResultArray + } catch (e) { + return e.toString() + } +} + +function unpack (messageValue) { + let frameArray = [] + + for (let i = 0; i < messageValue.length; i++) { + let remainMessage = messageValue + let dataId = remainMessage.substring(0, 2).toUpperCase() + let dataValue + let dataObj = {} + let packageLen + switch (dataId) { + case '01': + packageLen = 94 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '02': + packageLen = 32 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '03': + packageLen = 64 + if (remainMessage.length < packageLen) { + return frameArray + } + break + case '04': + packageLen = 20 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '05': + packageLen = 10 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '06': + packageLen = 44 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '07': + packageLen = 84 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '08': + packageLen = 70 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '09': + packageLen = 36 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '0A': + packageLen = 76 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '0B': + packageLen = 62 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '0C': + packageLen = 2 + if (remainMessage.length < packageLen) { + return frameArray + } + break + case '0D': + packageLen = 10 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + default: + return frameArray + } + if (dataValue.length < 2) { + break + } + frameArray.push(dataObj) + } + return frameArray +} + +function deserialize (dataId, dataValue) { + let measurementArray = [] + let eventList = [] + let collectTime = 0 + switch (dataId) { + case '01': + measurementArray = getUpShortInfo(dataValue) + break + case '02': + measurementArray = getUpShortInfo(dataValue) + break + case '03': + break + case '04': + measurementArray = [ + {measurementId: '3940', type: 'Work Mode', measurementValue: getWorkingMode(dataValue.substring(0, 2))}, + {measurementId: '3942', type: 'Heartbeat Interval', measurementValue: getOneWeekInterval(dataValue.substring(4, 8))}, + {measurementId: '3943', type: 'Periodic Interval', measurementValue: getOneWeekInterval(dataValue.substring(8, 12))}, + {measurementId: '3944', type: 'Event Interval', measurementValue: getOneWeekInterval(dataValue.substring(12, 16))}, + {measurementId: '3941', type: 'SOS Mode', measurementValue: getSOSMode(dataValue.substring(16, 18))} + ] + break; + case '05': + measurementArray = [ + {measurementId: '3000', type: 'Battery', measurementValue: getBattery(dataValue.substring(0, 2))}, + {measurementId: '3940', type: 'Work Mode', measurementValue: getWorkingMode(dataValue.substring(2, 4))}, + {measurementId: '3941', type: 'SOS Mode', measurementValue: getSOSMode(dataValue.substring(6, 8))} + ] + break + case '06': + eventList = this.getEventStatus(dataValue.substring(0, 6)) + collectTime = this.getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', type: 'SOS Event', measurementValue: eventList[6]}, + {measurementId: '4197', type: 'Longitude', measurementValue: getSensorValue(dataValue.substring(16, 24), 1000000)}, + {measurementId: '4198', type: 'Latitude', measurementValue: getSensorValue(dataValue.substring(24, 32), 1000000)}, + {measurementId: '4097', type: 'Air Temperature', measurementValue: getSensorValue(dataValue.substring(32, 36), 10)}, + {measurementId: '4199', type: 'Light', measurementValue: getSensorValue(dataValue.substring(36, 40))}, + {measurementId: '3000', type: 'Battery', measurementValue: getBattery(dataValue.substring(40, 42))}, + {type: 'Timestamp', measurementValue: collectTime} + ] + break + case '07': + eventList = this.getEventStatus(dataValue.substring(0, 6)) + collectTime = this.getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', type: 'SOS Event', measurementValue: eventList[6]}, + {measurementId: '5001', type: 'Wi-Fi Scan', measurementValue: getMacAndRssiObj(dataValue.substring(16, 72))}, + {measurementId: '4097', type: 'Air Temperature', measurementValue: getSensorValue(dataValue.substring(72, 76), 10)}, + {measurementId: '4199', type: 'Light', measurementValue: getSensorValue(dataValue.substring(76, 80))}, + {measurementId: '3000', type: 'Battery', measurementValue: getBattery(dataValue.substring(80, 82))}, + {type: 'Timestamp', measurementValue: collectTime} + ] + break + case '08': + eventList = this.getEventStatus(dataValue.substring(0, 6)) + collectTime = this.getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', type: 'SOS Event', measurementValue: eventList[6]}, + {measurementId: '5002', type: 'BLE Scan', measurementValue: getMacAndRssiObj(dataValue.substring(16, 58))}, + {measurementId: '4097', type: 'Air Temperature', measurementValue: getSensorValue(dataValue.substring(58, 62), 10)}, + {measurementId: '4199', type: 'Light', measurementValue: getSensorValue(dataValue.substring(62, 66))}, + {measurementId: '3000', type: 'Battery', measurementValue: getBattery(dataValue.substring(66, 68))}, + {type: 'Timestamp', measurementValue: collectTime} + ] + break + case '09': + eventList = this.getEventStatus(dataValue.substring(0, 6)) + collectTime = this.getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', type: 'SOS Event', measurementValue: eventList[6]}, + {measurementId: '4197', type: 'Longitude', measurementValue: getSensorValue(dataValue.substring(16, 24), 1000000)}, + {measurementId: '4198', type: 'Latitude', measurementValue: getSensorValue(dataValue.substring(24, 32), 1000000)}, + {measurementId: '3000', type: 'Battery', measurementValue: getBattery(dataValue.substring(32, 34))}, + {type: 'Timestamp', measurementValue: collectTime} + ] + break + case '0A': + eventList = this.getEventStatus(dataValue.substring(0, 6)) + collectTime = this.getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', type: 'SOS Event', measurementValue: eventList[6]}, + {measurementId: '5001', type: 'Wi-Fi Scan', measurementValue: getMacAndRssiObj(dataValue.substring(16, 72))}, + {measurementId: '3000', type: 'Battery', measurementValue: getBattery(dataValue.substring(72, 74))}, + {type: 'Timestamp', measurementValue: collectTime} + ] + break + case '0B': + eventList = this.getEventStatus(dataValue.substring(0, 6)) + collectTime = this.getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', type: 'SOS Event', measurementValue: eventList[6]}, + {measurementId: '5002', type: 'BLE Scan', measurementValue: getMacAndRssiObj(dataValue.substring(16, 58))}, + {measurementId: '3000', type: 'Battery', measurementValue: getBattery(dataValue.substring(58, 60))}, + {type: 'Timestamp', measurementValue: collectTime} + ] + break + case '0D': + let errorCode = this.getInt(dataValue) + let error = '' + switch (errorCode) { + case 0: + error = 'THE GNSS SCAN TIME OUT' + break + case 1: + error = 'THE WI-FI SCAN TIME OUT' + break + case 2: + error = 'THE WI-FI+GNSS SCAN TIME OUT' + break + case 3: + error = 'THE GNSS+WI-FI SCAN TIME OUT' + break + case 4: + error = 'THE BEACON SCAN TIME OUT' + break + case 5: + error = 'THE BEACON+WI-FI SCAN TIME OUT' + break + case 6: + error = 'THE BEACON+GNSS SCAN TIME OUT' + break + case 7: + error = 'THE BEACON+WI-FI+GNSS SCAN TIME OUT' + break + case 8: + error = 'FAILED TO OBTAIN THE UTC TIMESTAMP' + break + } + measurementArray.push({errorCode, error}) + } + return measurementArray +} + +function getUpShortInfo (messageValue) { + return [ + { + measurementId: '3000', type: 'Battery', measurementValue: getBattery(messageValue.substring(0, 2)) + }, { + measurementId: '3502', type: 'Firmware Version', measurementValue: getSoftVersion(messageValue.substring(2, 6)) + }, { + measurementId: '3001', type: 'Hardware Version', measurementValue: getHardVersion(messageValue.substring(6, 10)) + }, { + measurementId: '3940', type: 'Work Mode', measurementValue: getWorkingMode(messageValue.substring(10, 12)) + }, { + measurementId: '3942', type: 'Heartbeat Interval', measurementValue: getOneWeekInterval(messageValue.substring(14, 18)) + }, { + measurementId: '3943', type: 'Periodic Interval', measurementValue: getOneWeekInterval(messageValue.substring(18, 22)) + }, { + measurementId: '3944', type: 'Event Interval', measurementValue: getOneWeekInterval(messageValue.substring(22, 26)) + }, { + measurementId: '3941', type: 'SOS Mode', measurementValue: getSOSMode(messageValue.substring(28, 30)) + } + ] +} +function getBattery (batteryStr) { + return loraWANV2DataFormat(batteryStr) +} +function getSoftVersion (softVersion) { + return `${loraWANV2DataFormat(softVersion.substring(0, 2))}.${loraWANV2DataFormat(softVersion.substring(2, 4))}` +} +function getHardVersion (hardVersion) { + return `${loraWANV2DataFormat(hardVersion.substring(0, 2))}.${loraWANV2DataFormat(hardVersion.substring(2, 4))}` +} + +function getOneWeekInterval (str) { + return loraWANV2DataFormat(str) * 60 +} +function getSensorValue (str, dig) { + if (str === '8000') { + return null + } else { + return loraWANV2DataFormat(str, dig) + } +} + +function bytes2HexString (arrBytes) { + var str = '' + for (var i = 0; i < arrBytes.length; i++) { + var tmp + var num = arrBytes[i] + if (num < 0) { + tmp = (255 + num + 1).toString(16) + } else { + tmp = num.toString(16) + } + if (tmp.length === 1) { + tmp = '0' + tmp + } + str += tmp + } + return str +} +function loraWANV2DataFormat (str, divisor = 1) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + if (str2.substring(0, 1) === '1') { + let arr = str2.split('') + let reverseArr = arr.map((item) => { + if (parseInt(item) === 1) { + return 0 + } else { + return 1 + } + }) + str2 = parseInt(reverseArr.join(''), 2) + 1 + return '-' + str2 / divisor + } + return parseInt(str2, 2) / divisor +} + +function bigEndianTransform (data) { + let dataArray = [] + for (let i = 0; i < data.length; i += 2) { + dataArray.push(data.substring(i, i + 2)) + } + return dataArray +} + +function toBinary (arr) { + let binaryData = arr.map((item) => { + let data = parseInt(item, 16) + .toString(2) + let dataLength = data.length + if (data.length !== 8) { + for (let i = 0; i < 8 - dataLength; i++) { + data = `0` + data + } + } + return data + }) + return binaryData.toString().replace(/,/g, '') +} + +function getSOSMode (str) { + return loraWANV2DataFormat(str) +} + +function getMacAndRssiObj (pair) { + let pairs = [] + if (pair.length % 14 === 0) { + for (let i = 0; i < pair.length; i += 14) { + let mac = getMacAddress(pair.substring(i, i + 12)) + if (mac) { + let rssi = getInt8RSSI(pair.substring(i + 12, i + 14)) + pairs.push({mac: mac, rssi: rssi}) + } else { + continue + } + } + } + return pairs +} + +function getMacAddress (str) { + if (str.toLowerCase() === 'ffffffffffff') { + return null + } + let macArr = [] + for (let i = 1; i < str.length; i++) { + if (i % 2 === 1) { + macArr.push(str.substring(i - 1, i + 1)) + } + } + let mac = '' + for (let i = 0; i < macArr.length; i++) { + mac = mac + macArr[i] + if (i < macArr.length - 1) { + mac = mac + ':' + } + } + return mac +} + +function getInt8RSSI (str) { + return this.loraWANV2DataFormat(str) +} + +function getInt (str) { + return parseInt(str) +} + +/** + * 1.MOVING_STARTING + * 2.MOVING_END + * 3.DEVICE_STATIC + * 4.SHOCK_EVENT + * 5.TEMP_EVENT + * 6.LIGHTING_EVENT + * 7.SOS_EVENT + * 8.CUSTOMER_EVENT + * */ +function getEventStatus (str) { + let bitStr = this.getByteArray(str) + let event = [] + for (let i = bitStr.length; i >= 0; i--) { + if (i === 0) { + event[i] = bitStr.substring(0) + } else { + event[i] = bitStr.substring(i - 1, i) + } + } + return event.reverse() +} + +function getByteArray (str) { + let bytes = [] + for (let i = 0; i < str.length; i += 2) { + bytes.push(str.substring(i, i + 2)) + } + return toBinary(bytes) +} + +function getWorkingMode (workingMode) { + return getInt(workingMode) +} + +function getUTCTimestamp(str){ + return parseInt(this.loraWANV2PositiveDataFormat(str)) * 1000 +} + +function loraWANV2PositiveDataFormat (str, divisor = 1) { + let strReverse = this.bigEndianTransform(str) + let str2 = this.toBinary(strReverse) + return parseInt(str2, 2) / divisor +} diff --git a/vendors/seeed-technology-co-ltd/codecs/SenseCAP_S2120_Weather_Station.js b/vendors/seeed-technology-co-ltd/codecs/SenseCAP_S2120_Weather_Station.js new file mode 100644 index 0000000..1353b6f --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/SenseCAP_S2120_Weather_Station.js @@ -0,0 +1,391 @@ +/** + * Entry, decoder.js + */ +function decodeUplink(input) { + // data split + + var bytes = input['bytes'] + // init + bytes = bytes2HexString(bytes) + .toLocaleUpperCase() + + let result = { + 'err': 0, 'payload': bytes, 'valid': true, messages: [] + } + let splitArray = dataSplit(bytes) + // data decoder + let decoderArray = [] + for (let i = 0; i < splitArray.length; i++) { + let item = splitArray[i] + let dataId = item.dataId + let dataValue = item.dataValue + let messages = dataIdAndDataValueJudge(dataId, dataValue) + decoderArray.push(messages) + } + result.messages = decoderArray + return { data: result } +} + +/** + * data splits + * @param bytes + * @returns {*[]} + */ +function dataSplit (bytes) { + let frameArray = [] + + for (let i = 0; i < bytes.length; i++) { + let remainingValue = bytes + let dataId = remainingValue.substring(0, 2) + let dataValue + let dataObj = {} + switch (dataId) { + case '01' : + case '20' : + case '21' : + case '30' : + case '31' : + case '33' : + case '40' : + case '41' : + case '42' : + case '43' : + case '44' : + case '45' : + dataValue = remainingValue.substring(2, 22) + bytes = remainingValue.substring(22) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '02': + dataValue = remainingValue.substring(2, 18) + bytes = remainingValue.substring(18) + dataObj = { + 'dataId': '02', 'dataValue': dataValue + } + break + case '03' : + case '06': + dataValue = remainingValue.substring(2, 4) + bytes = remainingValue.substring(4) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '05' : + case '34': + dataValue = bytes.substring(2, 10) + bytes = remainingValue.substring(10) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '04': + case '10': + case '32': + case '35': + case '36': + case '37': + case '38': + case '39': + dataValue = bytes.substring(2, 20) + bytes = remainingValue.substring(20) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + default: + dataValue = '9' + break + } + if (dataValue.length < 2) { + break + } + frameArray.push(dataObj) + } + return frameArray +} + +function dataIdAndDataValueJudge (dataId, dataValue) { + let messages = [] + switch (dataId) { + case '01': + let temperature = dataValue.substring(0, 4) + let humidity = dataValue.substring(4, 6) + let illumination = dataValue.substring(6, 14) + let uv = dataValue.substring(14, 16) + let windSpeed = dataValue.substring(16, 20) + messages = [{ + measurementValue: loraWANV2DataFormat(temperature, 10), measurementId: '4097', type: 'Air Temperature' + }, { + measurementValue: loraWANV2DataFormat(humidity), measurementId: '4098', type: 'Air Humidity' + }, { + measurementValue: loraWANV2DataFormat(illumination), measurementId: '4099', type: 'Light Intensity' + }, { + measurementValue: loraWANV2DataFormat(uv, 10), measurementId: '4190', type: 'UV Index' + }, { + measurementValue: loraWANV2DataFormat(windSpeed, 10), measurementId: '4105', type: 'Wind Speed' + }] + break + case '02': + let windDirection = dataValue.substring(0, 4) + let rainfall = dataValue.substring(4, 12) + let airPressure = dataValue.substring(12, 16) + messages = [{ + measurementValue: loraWANV2DataFormat(windDirection), measurementId: '4104', type: 'Wind Direction Sensor' + }, { + measurementValue: loraWANV2DataFormat(rainfall, 1000), measurementId: '4113', type: 'Rain Gauge' + }, { + + measurementValue: loraWANV2DataFormat(airPressure, 0.1), measurementId: '4101', type: 'Barometric Pressure' + }] + break + case '03': + let Electricity = dataValue + messages = [{ + 'Battery(%)': loraWANV2DataFormat(Electricity) + }] + break + case '04': + let electricityWhether = dataValue.substring(0, 2) + let hwv = dataValue.substring(2, 6) + let bdv = dataValue.substring(6, 10) + let sensorAcquisitionInterval = dataValue.substring(10, 14) + let gpsAcquisitionInterval = dataValue.substring(14, 18) + messages = [{ + 'Battery(%)': loraWANV2DataFormat(electricityWhether), + 'Hardware Version': `${loraWANV2DataFormat(hwv.substring(0, 2))}.${loraWANV2DataFormat(hwv.substring(2, 4))}`, + 'Firmware Version': `${loraWANV2DataFormat(bdv.substring(0, 2))}.${loraWANV2DataFormat(bdv.substring(2, 4))}`, + 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionInterval)) * 60, + 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionInterval)) * 60 + }] + break + case '05': + let sensorAcquisitionIntervalFive = dataValue.substring(0, 4) + let gpsAcquisitionIntervalFive = dataValue.substring(4, 8) + messages = [{ + 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalFive)) * 60, + 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalFive)) * 60 + }] + break + case '06': + let errorCode = dataValue + let descZh + switch (errorCode) { + case '00': + descZh = 'CCL_SENSOR_ERROR_NONE' + break + case '01': + descZh = 'CCL_SENSOR_NOT_FOUND' + break + case '02': + descZh = 'CCL_SENSOR_WAKEUP_ERROR' + break + case '03': + descZh = 'CCL_SENSOR_NOT_RESPONSE' + break + case '04': + descZh = 'CCL_SENSOR_DATA_EMPTY' + break + case '05': + descZh = 'CCL_SENSOR_DATA_HEAD_ERROR' + break + case '06': + descZh = 'CCL_SENSOR_DATA_CRC_ERROR' + break + case '07': + descZh = 'CCL_SENSOR_DATA_B1_NO_VALID' + break + case '08': + descZh = 'CCL_SENSOR_DATA_B2_NO_VALID' + break + case '09': + descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH' + break + case '0A': + descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED' + break + case '0B': + descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED' + break + case '0C': + descZh = 'CCL_SENSOR_DATA_VALUE_HI' + break + case '0D': + descZh = 'CCL_SENSOR_DATA_VALUE_LOW' + break + case '0E': + descZh = 'CCL_SENSOR_DATA_VALUE_MISSED' + break + case '0F': + descZh = 'CCL_SENSOR_ARG_INVAILD' + break + case '10': + descZh = 'CCL_SENSOR_RS485_MASTER_BUSY' + break + case '11': + descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR' + break + case '12': + descZh = 'CCL_SENSOR_RS485_REG_MISSED' + break + case '13': + descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR' + break + case '14': + descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR' + break + case '15': + descZh = 'CCL_SENSOR_CONFIG_ERROR' + break + case 'FF': + descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW' + break + default: + descZh = 'CC_OTHER_FAILED' + break + } + messages = [{ + measurementId: '4101', type: 'sensor_error_event', errCode: errorCode, descZh + }] + break + case '10': + let statusValue = dataValue.substring(0, 2) + let { status, type } = loraWANV2BitDataFormat(statusValue) + let sensecapId = dataValue.substring(2) + messages = [{ + status: status, channelType: type, sensorEui: sensecapId + }] + break + default: + break + } + return messages +} + +/** + * + * data formatting + * @param str + * @param divisor + * @returns {string|number} + */ +function loraWANV2DataFormat (str, divisor = 1) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + if (str2.substring(0, 1) === '1') { + let arr = str2.split('') + let reverseArr = arr.map((item) => { + if (parseInt(item) === 1) { + return 0 + } else { + return 1 + } + }) + str2 = parseInt(reverseArr.join(''), 2) + 1 + return '-' + str2 / divisor + } + return parseInt(str2, 2) / divisor +} + +/** + * Handling big-endian data formats + * @param data + * @returns {*[]} + */ +function bigEndianTransform (data) { + let dataArray = [] + for (let i = 0; i < data.length; i += 2) { + dataArray.push(data.substring(i, i + 2)) + } + // array of hex + return dataArray +} + +/** + * Convert to an 8-digit binary number with 0s in front of the number + * @param arr + * @returns {string} + */ +function toBinary (arr) { + let binaryData = arr.map((item) => { + let data = parseInt(item, 16) + .toString(2) + let dataLength = data.length + if (data.length !== 8) { + for (let i = 0; i < 8 - dataLength; i++) { + data = `0` + data + } + } + return data + }) + let ret = binaryData.toString() + .replace(/,/g, '') + return ret +} + +/** + * sensor + * @param str + * @returns {{channel: number, type: number, status: number}} + */ +function loraWANV2BitDataFormat (str) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + let channel = parseInt(str2.substring(0, 4), 2) + let status = parseInt(str2.substring(4, 5), 2) + let type = parseInt(str2.substring(5), 2) + return { channel, status, type } +} + +/** + * channel info + * @param str + * @returns {{channelTwo: number, channelOne: number}} + */ +function loraWANV2ChannelBitFormat (str) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + let one = parseInt(str2.substring(0, 4), 2) + let two = parseInt(str2.substring(4, 8), 2) + let resultInfo = { + one: one, two: two + } + return resultInfo +} + +/** + * data log status bit + * @param str + * @returns {{total: number, level: number, isTH: number}} + */ +function loraWANV2DataLogBitFormat (str) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + let isTH = parseInt(str2.substring(0, 1), 2) + let total = parseInt(str2.substring(1, 5), 2) + let left = parseInt(str2.substring(5), 2) + let resultInfo = { + isTH: isTH, total: total, left: left + } + return resultInfo +} + +function bytes2HexString (arrBytes) { + var str = '' + for (var i = 0; i < arrBytes.length; i++) { + var tmp + var num = arrBytes[i] + if (num < 0) { + tmp = (255 + num + 1).toString(16) + } else { + tmp = num.toString(16) + } + if (tmp.length === 1) { + tmp = '0' + tmp + } + str += tmp + } + return str +} diff --git a/vendors/seeed-technology-co-ltd/codecs/SenseCAP_T1000_mapper.js b/vendors/seeed-technology-co-ltd/codecs/SenseCAP_T1000_mapper.js new file mode 100644 index 0000000..44fe239 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/SenseCAP_T1000_mapper.js @@ -0,0 +1,862 @@ +/* + Sensecap T1000 decoder for mapping and more - 2024 1rabbit + https://github.com/1rabbit/T1000-Mapper-Decoder/blob/main/chirpstack_v4.js + decoder removes stale cached gps locations and adds gps coordinates to + enable tracker to be sent to regular mapping platforms. +*/ + +function decodeUplink(input) { + + let decoded = {} + + const decodedFull = original_decodeUplink(input).data + + if (typeof decodedFull.messages !== 'undefined') { + + let timestampMillis = 0 + + decodedFull.messages[0].forEach(message => { + + if (message.type === "Event Status") { + // Extract event name + if (typeof message.measurementValue[0]?.eventName !== 'undefined') decoded.event = message.measurementValue[0].eventName.replace('.', '') + } + else { + // Extract measurement values + if (typeof message.measurementValue !== 'undefined') { + decoded[message.type.toLowerCase().replace(/\s+/g, '_').replace('air_', '')] = parseFloat(message.measurementValue) + } + + timestampMillis = message.timestamp + } + + }) + + if (Date.now() - timestampMillis < 30 * 1000 && typeof decoded.latitude === 'number') { // data is less than 30s old + decoded.freshGPS = true + decoded.accuracy = 2 // adding mandatory values for mapping when gps is fresh + decoded.altitude = 0 + } + else decoded.freshGPS = false + + } + + return { data: decoded } +} + +function original_decodeUplink(input) { // Original function untouched, renamed to original_decodeUplink +// function decodeUplink (input) { +//// rest of the original function //// + + const bytes = input['bytes'] + const fport = parseInt(input['fPort']) + const bytesString = bytes2HexString(bytes) + const originMessage = bytesString.toLocaleUpperCase() + const decoded = { + valid: true, + err: 0, + payload: bytesString, + messages: [] + } + if (fport === 199 || fport === 192) { + decoded.messages.push({fport: fport, payload: bytesString}) + return { data: decoded } + } + if (fport !== 5) { + decoded.valid = false + return { data: decoded } + } + let measurement = messageAnalyzed(originMessage) + if (measurement.length === 0) { + decoded.valid = false + return { data: decoded } + } + + for (let message of measurement) { + if (message.length === 0) { + continue + } + let elements = [] + for (let element of message) { + if (element.errorCode) { + decoded.err = element.errorCode + decoded.errMessage = element.error + } else { + elements.push(element) + } + } + if (elements.length > 0) { + decoded.messages.push(elements) + } + } + // decoded.messages = measurement + return { data: decoded } +} + +function messageAnalyzed (messageValue) { + try { + let frames = unpack(messageValue) + let measurementResultArray = [] + for (let i = 0; i < frames.length; i++) { + let item = frames[i] + let dataId = item.dataId + let dataValue = item.dataValue + let measurementArray = deserialize(dataId, dataValue) + measurementResultArray.push(measurementArray) + } + return measurementResultArray + } catch (e) { + return e.toString() + } +} + +function unpack (messageValue) { + let frameArray = [] + + for (let i = 0; i < messageValue.length; i++) { + let remainMessage = messageValue + let dataId = remainMessage.substring(0, 2).toUpperCase() + let dataValue + let dataObj = {} + let packageLen + switch (dataId) { + case '01': + packageLen = 94 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '02': + packageLen = 32 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '03': + packageLen = 64 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '04': + packageLen = 20 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '05': + packageLen = 10 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '06': + packageLen = 44 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '07': + packageLen = 84 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '08': + packageLen = 70 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '09': + packageLen = 36 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '0A': + packageLen = 76 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '0B': + packageLen = 62 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '0C': + packageLen = 2 + if (remainMessage.length < packageLen) { + return frameArray + } + break + case '0D': + packageLen = 10 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '0E': + packageLen = getInt(remainMessage.substring(8, 10)) * 2 + 10 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, 8) + remainMessage.substring(10, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '0F': + packageLen = 34 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '10': + packageLen = 26 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + case '11': + packageLen = 28 + if (remainMessage.length < packageLen) { + return frameArray + } + dataValue = remainMessage.substring(2, packageLen) + messageValue = remainMessage.substring(packageLen) + dataObj = { + 'dataId': dataId, 'dataValue': dataValue + } + break + default: + return frameArray + } + if (dataValue.length < 2) { + break + } + frameArray.push(dataObj) + } + return frameArray +} + +function deserialize (dataId, dataValue) { + let measurementArray = [] + let eventList = [] + let measurement = {} + let collectTime = 0 + let groupId = 0 + let shardFlag = {} + let payload = '' + let result = [] + let dataArr = [] + switch (dataId) { + case '01': + measurementArray = getUpShortInfo(dataValue) + measurementArray.push(...getMotionSetting(dataValue.substring(30, 40))) + measurementArray.push(...getStaticSetting(dataValue.substring(40, 46))) + measurementArray.push(...getShockSetting(dataValue.substring(46, 52))) + measurementArray.push(...getTempSetting(dataValue.substring(52, 72))) + measurementArray.push(...getLightSetting(dataValue.substring(72, 92))) + break + case '02': + measurementArray = getUpShortInfo(dataValue) + break + case '03': + measurementArray.push(...getMotionSetting(dataValue.substring(0, 10))) + measurementArray.push(...getStaticSetting(dataValue.substring(10, 16))) + measurementArray.push(...getShockSetting(dataValue.substring(16, 22))) + measurementArray.push(...getTempSetting(dataValue.substring(22, 42))) + measurementArray.push(...getLightSetting(dataValue.substring(42, 62))) + break + case '04': + let interval = 0 + let workMode = getInt(dataValue.substring(0, 2)) + let heartbeatInterval = getMinsByMin(dataValue.substring(4, 8)) + let periodicInterval = getMinsByMin(dataValue.substring(8, 12)) + let eventInterval = getMinsByMin(dataValue.substring(12, 16)) + switch (workMode) { + case 0: + interval = heartbeatInterval + break + case 1: + interval = periodicInterval + break + case 2: + interval = eventInterval + break + } + measurementArray = [ + {measurementId: '3940', type: 'Work Mode', measurementValue: workMode}, + {measurementId: '3942', type: 'Heartbeat Interval', measurementValue: heartbeatInterval}, + {measurementId: '3943', type: 'Periodic Interval', measurementValue: periodicInterval}, + {measurementId: '3944', type: 'Event Interval', measurementValue: eventInterval}, + {measurementId: '3941', type: 'SOS Mode', measurementValue: getSOSMode(dataValue.substring(16, 18))}, + {measurementId: '3900', type: 'Uplink Interval', measurementValue: interval} + ] + break; + case '05': + measurementArray = [ + {measurementId: '3000', type: 'Battery', measurementValue: getBattery(dataValue.substring(0, 2))}, + {measurementId: '3940', type: 'Work Mode', measurementValue: getWorkingMode(dataValue.substring(2, 4))}, + {measurementId: '3965', type: 'Positioning Strategy', measurementValue: getPositioningStrategy(dataValue.substring(4, 6))}, + {measurementId: '3941', type: 'SOS Mode', measurementValue: getSOSMode(dataValue.substring(6, 8))} + ] + break + case '06': + collectTime = getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Event Status', measurementValue: getEventStatus(dataValue.substring(0, 6))}, + {measurementId: '4197', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Longitude', measurementValue: parseFloat(getSensorValue(dataValue.substring(16, 24), 1000000))}, + {measurementId: '4198', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Latitude', measurementValue: parseFloat(getSensorValue(dataValue.substring(24, 32), 1000000))}, + {measurementId: '4097', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Air Temperature', measurementValue: getSensorValue(dataValue.substring(32, 36), 10)}, + {measurementId: '4199', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Light', measurementValue: getSensorValue(dataValue.substring(36, 40))}, + {measurementId: '3000', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Battery', measurementValue: getBattery(dataValue.substring(40, 42))} + ] + break + case '07': + eventList = getEventStatus(dataValue.substring(0, 6)) + collectTime = getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Event Status', measurementValue: getEventStatus(dataValue.substring(0, 6))}, + {measurementId: '5001', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Wi-Fi Scan', measurementValue: getMacAndRssiObj(dataValue.substring(16, 72))}, + {measurementId: '4097', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Air Temperature', measurementValue: getSensorValue(dataValue.substring(72, 76), 10)}, + {measurementId: '4199', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Light', measurementValue: getSensorValue(dataValue.substring(76, 80))}, + {measurementId: '3000', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Battery', measurementValue: getBattery(dataValue.substring(80, 82))} + ] + break + case '08': + collectTime = getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Event Status', measurementValue: getEventStatus(dataValue.substring(0, 6))}, + {measurementId: '5002', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'BLE Scan', measurementValue: getMacAndRssiObj(dataValue.substring(16, 58))}, + {measurementId: '4097', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Air Temperature', measurementValue: getSensorValue(dataValue.substring(58, 62), 10)}, + {measurementId: '4199', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Light', measurementValue: getSensorValue(dataValue.substring(62, 66))}, + {measurementId: '3000', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Battery', measurementValue: getBattery(dataValue.substring(66, 68))} + ] + break + case '09': + collectTime = getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Event Status', measurementValue: getEventStatus(dataValue.substring(0, 6))}, + {measurementId: '4197', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Longitude', measurementValue: parseFloat(getSensorValue(dataValue.substring(16, 24), 1000000))}, + {measurementId: '4198', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Latitude', measurementValue: parseFloat(getSensorValue(dataValue.substring(24, 32), 1000000))}, + {measurementId: '3000', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Battery', measurementValue: getBattery(dataValue.substring(32, 34))} + ] + break + case '0A': + collectTime = getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Event Status', measurementValue: getEventStatus(dataValue.substring(0, 6))}, + {measurementId: '5001', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Wi-Fi Scan', measurementValue: getMacAndRssiObj(dataValue.substring(16, 72))}, + {measurementId: '3000', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Battery', measurementValue: getBattery(dataValue.substring(72, 74))} + ] + break + case '0B': + collectTime = getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray = [ + {measurementId: '4200', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Event Status', measurementValue: getEventStatus(dataValue.substring(0, 6))}, + {measurementId: '5002', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'BLE Scan', measurementValue: getMacAndRssiObj(dataValue.substring(16, 58))}, + {measurementId: '3000', timestamp: collectTime, motionId: getMotionId(dataValue.substring(6, 8)), type: 'Battery', measurementValue: getBattery(dataValue.substring(58, 60))}, + ] + break + case '0D': + let errorCode = getInt(dataValue) + let error = '' + switch (errorCode) { + case 1: + error = 'FAILED TO OBTAIN THE UTC TIMESTAMP' + break + case 2: + error = 'ALMANAC TOO OLD' + break + case 3: + error = 'DOPPLER ERROR' + break + } + measurementArray.push({errorCode, error}) + break + case '0E': + shardFlag = getShardFlag(dataValue.substring(0, 2)) + groupId = getInt(dataValue.substring(2, 6)) + payload = dataValue.substring(6) + measurement = { + measurementId: '6152', + groupId: groupId, + index: shardFlag.index, + count: shardFlag.count, + type: 'gnss-ng payload', + measurementValue: payload + } + measurementArray.push(measurement) + break + case '0F': + collectTime = getUTCTimestamp(dataValue.substring(8, 16)) + shardFlag = getShardFlag(dataValue.substring(26, 28)) + groupId = getInt(dataValue.substring(28, 32)) + measurementArray.push({ + measurementId: '4200', + timestamp: collectTime, + motionId: getMotionId(dataValue.substring(6, 8)), + groupId: groupId, + index: shardFlag.index, + count: shardFlag.count, + type: 'Event Status', + measurementValue: getEventStatus(dataValue.substring(0, 6)) + }) + measurementArray.push({ + measurementId: '4097', + timestamp: collectTime, + motionId: getMotionId(dataValue.substring(6, 8)), + groupId: groupId, + index: shardFlag.index, + count: shardFlag.count, + type: 'Air Temperature', + measurementValue: '' + getSensorValue(dataValue.substring(16, 20), 10) + }) + measurementArray.push({ + measurementId: '4199', + timestamp: collectTime, + motionId: getMotionId(dataValue.substring(6, 8)), + groupId: groupId, + index: shardFlag.index, + count: shardFlag.count, + type: 'Light', + measurementValue: '' + getSensorValue(dataValue.substring(20, 24)) + }) + measurementArray.push({ + measurementId: '3000', + timestamp: collectTime, + motionId: getMotionId(dataValue.substring(6, 8)), + groupId: groupId, + index: shardFlag.index, + count: shardFlag.count, + type: 'Battery', + measurementValue: '' + getBattery(dataValue.substring(24, 26)) + }) + break + case '10': + collectTime = getUTCTimestamp(dataValue.substring(8, 16)) + shardFlag = getShardFlag(dataValue.substring(18, 20)) + groupId = getInt(dataValue.substring(20, 24)) + measurementArray.push({ + measurementId: '4200', + timestamp: collectTime, + motionId: getMotionId(dataValue.substring(6, 8)), + groupId: groupId, + index: shardFlag.index, + count: shardFlag.count, + type: 'Event Status', + measurementValue: getEventStatus(dataValue.substring(0, 6)) + }) + measurementArray.push({ + measurementId: '3000', + timestamp: collectTime, + motionId: getMotionId(dataValue.substring(6, 8)), + groupId: groupId, + index: shardFlag.index, + count: shardFlag.count, + type: 'Battery', + measurementValue: '' + getBattery(dataValue.substring(16, 18)) + }) + break + case '11': + collectTime = getUTCTimestamp(dataValue.substring(8, 16)) + measurementArray.push({ + measurementId: '3576', + timestamp: collectTime, + type: 'Positing Status', + measurementValue: '' + getPositingStatus(dataValue.substring(0, 2)) + }) + measurementArray.push({ + timestamp: collectTime, + measurementId: '4200', + type: 'Event Status', + measurementValue: getEventStatus(dataValue.substring(2, 8)) + }) + if (!isNaN(parseFloat(getSensorValue(dataValue.substring(16, 20), 10)))) { + measurementArray.push({ + timestamp: collectTime, + measurementId: '4097', + type: 'Air Temperature', + measurementValue: '' + getSensorValue(dataValue.substring(16, 20), 10) + }) + } + if (!isNaN(parseFloat(getSensorValue(dataValue.substring(20, 24))))) { + measurementArray.push({ + timestamp: collectTime, + measurementId: '4199', + type: 'Light', + measurementValue: '' + getSensorValue(dataValue.substring(20, 24)) + }) + } + measurementArray.push({ + timestamp: collectTime, + measurementId: '3000', + type: 'Battery', + measurementValue: '' + getBattery(dataValue.substring(24, 26)) + }) + break + } + return measurementArray +} + +function getMotionId (str) { + return getInt(str) +} + +function getPositingStatus (str) { + return getInt(str) +} + +function getUpShortInfo (messageValue) { + return [ + { + measurementId: '3000', type: 'Battery', measurementValue: getBattery(messageValue.substring(0, 2)) + }, { + measurementId: '3502', type: 'Firmware Version', measurementValue: getSoftVersion(messageValue.substring(2, 6)) + }, { + measurementId: '3001', type: 'Hardware Version', measurementValue: getHardVersion(messageValue.substring(6, 10)) + }, { + measurementId: '3940', type: 'Work Mode', measurementValue: getWorkingMode(messageValue.substring(10, 12)) + }, { + measurementId: '3965', type: 'Positioning Strategy', measurementValue: getPositioningStrategy(messageValue.substring(12, 14)) + }, { + measurementId: '3942', type: 'Heartbeat Interval', measurementValue: getMinsByMin(messageValue.substring(14, 18)) + }, { + measurementId: '3943', type: 'Periodic Interval', measurementValue: getMinsByMin(messageValue.substring(18, 22)) + }, { + measurementId: '3944', type: 'Event Interval', measurementValue: getMinsByMin(messageValue.substring(22, 26)) + }, { + measurementId: '3945', type: 'Sensor Enable', measurementValue: getInt(messageValue.substring(26, 28)) + }, { + measurementId: '3941', type: 'SOS Mode', measurementValue: getSOSMode(messageValue.substring(28, 30)) + } + ] +} + +function getMotionSetting (str) { + return [ + {measurementId: '3946', type: 'Motion Enable', measurementValue: getInt(str.substring(0, 2))}, + {measurementId: '3947', type: 'Any Motion Threshold', measurementValue: getSensorValue(str.substring(2, 6), 1)}, + {measurementId: '3948', type: 'Motion Start Interval', measurementValue: getMinsByMin(str.substring(6, 10))}, + ] +} + +function getStaticSetting (str) { + return [ + {measurementId: '3949', type: 'Static Enable', measurementValue: getInt(str.substring(0, 2))}, + {measurementId: '3950', type: 'Device Static Timeout', measurementValue: getMinsByMin(str.substring(2, 6))} + ] +} + +function getShockSetting (str) { + return [ + {measurementId: '3951', type: 'Shock Enable', measurementValue: getInt(str.substring(0, 2))}, + {measurementId: '3952', type: 'Shock Threshold', measurementValue: getInt(str.substring(2, 6))} + ] +} + +function getTempSetting (str) { + return [ + {measurementId: '3953', type: 'Temp Enable', measurementValue: getInt(str.substring(0, 2))}, + {measurementId: '3954', type: 'Event Temp Interval', measurementValue: getMinsByMin(str.substring(2, 6))}, + {measurementId: '3955', type: 'Event Temp Sample Interval', measurementValue: getSecondsByInt(str.substring(6, 10))}, + {measurementId: '3956', type: 'Temp ThMax', measurementValue: getSensorValue(str.substring(10, 14), 10)}, + {measurementId: '3957', type: 'Temp ThMin', measurementValue: getSensorValue(str.substring(14, 18), 10)}, + {measurementId: '3958', type: 'Temp Warning Type', measurementValue: getInt(str.substring(18, 20))} + ] +} + +function getLightSetting (str) { + return [ + {measurementId: '3959', type: 'Light Enable', measurementValue: getInt(str.substring(0, 2))}, + {measurementId: '3960', type: 'Event Light Interval', measurementValue: getMinsByMin(str.substring(2, 6))}, + {measurementId: '3961', type: 'Event Light Sample Interval', measurementValue: getSecondsByInt(str.substring(6, 10))}, + {measurementId: '3962', type: 'Light ThMax', measurementValue: getSensorValue(str.substring(10, 14), 10)}, + {measurementId: '3963', type: 'Light ThMin', measurementValue: getSensorValue(str.substring(14, 18), 10)}, + {measurementId: '3964', type: 'Light Warning Type', measurementValue: getInt(str.substring(18, 20))} + ] +} + +function getShardFlag (str) { + let bitStr = getByteArray(str) + return { + count: parseInt(bitStr.substring(0, 4), 2), + index: parseInt(bitStr.substring(4), 2) + } +} + +function getBattery (batteryStr) { + return loraWANV2DataFormat(batteryStr) +} +function getSoftVersion (softVersion) { + return `${loraWANV2DataFormat(softVersion.substring(0, 2))}.${loraWANV2DataFormat(softVersion.substring(2, 4))}` +} +function getHardVersion (hardVersion) { + return `${loraWANV2DataFormat(hardVersion.substring(0, 2))}.${loraWANV2DataFormat(hardVersion.substring(2, 4))}` +} + +function getSecondsByInt (str) { + return getInt(str) +} + +function getMinsByMin (str) { + return getInt(str) +} + +function getSensorValue (str, dig) { + if (str === '8000') { + return null + } else { + return loraWANV2DataFormat(str, dig) + } +} + +function bytes2HexString (arrBytes) { + var str = '' + for (var i = 0; i < arrBytes.length; i++) { + var tmp + var num = arrBytes[i] + if (num < 0) { + tmp = (255 + num + 1).toString(16) + } else { + tmp = num.toString(16) + } + if (tmp.length === 1) { + tmp = '0' + tmp + } + str += tmp + } + return str +} +function loraWANV2DataFormat (str, divisor = 1) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + if (str2.substring(0, 1) === '1') { + let arr = str2.split('') + let reverseArr = arr.map((item) => { + if (parseInt(item) === 1) { + return 0 + } else { + return 1 + } + }) + str2 = parseInt(reverseArr.join(''), 2) + 1 + return '-' + str2 / divisor + } + return parseInt(str2, 2) / divisor +} + +function bigEndianTransform (data) { + let dataArray = [] + for (let i = 0; i < data.length; i += 2) { + dataArray.push(data.substring(i, i + 2)) + } + return dataArray +} + +function toBinary (arr) { + let binaryData = arr.map((item) => { + let data = parseInt(item, 16) + .toString(2) + let dataLength = data.length + if (data.length !== 8) { + for (let i = 0; i < 8 - dataLength; i++) { + data = `0` + data + } + } + return data + }) + return binaryData.toString().replace(/,/g, '') +} + +function getSOSMode (str) { + return loraWANV2DataFormat(str) +} + +function getMacAndRssiObj (pair) { + let pairs = [] + if (pair.length % 14 === 0) { + for (let i = 0; i < pair.length; i += 14) { + let mac = getMacAddress(pair.substring(i, i + 12)) + if (mac) { + let rssi = getInt8RSSI(pair.substring(i + 12, i + 14)) + pairs.push({mac: mac, rssi: rssi}) + } else { + continue + } + } + } + return pairs +} + +function getMacAddress (str) { + if (str.toLowerCase() === 'ffffffffffff') { + return null + } + let macArr = [] + for (let i = 1; i < str.length; i++) { + if (i % 2 === 1) { + macArr.push(str.substring(i - 1, i + 1)) + } + } + let mac = '' + for (let i = 0; i < macArr.length; i++) { + mac = mac + macArr[i] + if (i < macArr.length - 1) { + mac = mac + ':' + } + } + return mac +} + +function getInt8RSSI (str) { + return loraWANV2DataFormat(str) +} + +function getInt (str) { + return parseInt(str, 16) +} + +function getEventStatus (str) { + // return getInt(str) + let bitStr = getByteArray(str) + let bitArr = [] + for (let i = 0; i < bitStr.length; i++) { + bitArr[i] = bitStr.substring(i, i + 1) + } + bitArr = bitArr.reverse() + let event = [] + for (let i = 0; i < bitArr.length; i++) { + if (bitArr[i] !== '1') { + continue + } + switch (i){ + case 0: + event.push({id:1, eventName:"Start moving event."}) + break + case 1: + event.push({id:2, eventName:"End movement event."}) + break + case 2: + event.push({id:3, eventName:"Motionless event."}) + break + case 3: + event.push({id:4, eventName:"Shock event."}) + break + case 4: + event.push({id:5, eventName:"Temperature event."}) + break + case 5: + event.push({id:6, eventName:"Light event."}) + break + case 6: + event.push({id:7, eventName:"SOS event."}) + break + case 7: + event.push({id:8, eventName:"Press once event."}) + break + } + } + return event +} + +function getByteArray (str) { + let bytes = [] + for (let i = 0; i < str.length; i += 2) { + bytes.push(str.substring(i, i + 2)) + } + return toBinary(bytes) +} + +function getWorkingMode (workingMode) { + return getInt(workingMode) +} + +function getPositioningStrategy (strategy) { + return getInt(strategy) +} + +function getUTCTimestamp(str){ + return parseInt(loraWANV2PositiveDataFormat(str)) * 1000 +} + +function loraWANV2PositiveDataFormat (str, divisor = 1) { + let strReverse = bigEndianTransform(str) + let str2 = toBinary(strReverse) + return parseInt(str2, 2) / divisor +} diff --git a/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_LoRaWAN_V2_Decoder.json b/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_LoRaWAN_V2_Decoder.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_LoRaWAN_V2_Decoder.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_LoRaWAN_V4_Decoder.json b/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_LoRaWAN_V4_Decoder.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_LoRaWAN_V4_Decoder.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_S2120_Weather_Station.json b/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_S2120_Weather_Station.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_S2120_Weather_Station.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_T1000_mapper.json b/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_T1000_mapper.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/test_decode_SenseCAP_T1000_mapper.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_LoRaWAN_V2_Decoder.json b/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_LoRaWAN_V2_Decoder.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_LoRaWAN_V2_Decoder.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_LoRaWAN_V4_Decoder.json b/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_LoRaWAN_V4_Decoder.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_LoRaWAN_V4_Decoder.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_S2120_Weather_Station.json b/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_S2120_Weather_Station.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_S2120_Weather_Station.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_T1000_mapper.json b/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_T1000_mapper.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/codecs/test_encode_SenseCAP_T1000_mapper.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-a1101.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-a1101.toml new file mode 100644 index 0000000..d40b13e --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-a1101.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP A1101" +description = "Vision AI Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-A1101-LoRaWAN-Vision-AI-Sensor-p-5367.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-A1101-LoRaWAN-Vision-AI-Sensor-p-5367.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2100.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2100.toml new file mode 100644 index 0000000..e8207b9 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2100.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP S2100" +description = "Data Logger/DTU, Supports RS485/Analog/GPIO Interfaces" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-S2100-LoRaWAN-Data-Logger-p-5361.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-S2100-LoRaWAN-Data-Logger-p-5361.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2101.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2101.toml new file mode 100644 index 0000000..e43ab65 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2101.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP S2101" +description = "Air Temperature and Humidity Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-S2101-LoRaWAN-Air-Temperature-and-Humidity-Sensor-p-5354.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-S2101-LoRaWAN-Air-Temperature-and-Humidity-Sensor-p-5354.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2102.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2102.toml new file mode 100644 index 0000000..2078089 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2102.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP S2102" +description = "Wireless Light Intensity Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-S2102-LoRaWAN-Light-Intensity-Sensor-p-5355.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-S2102-LoRaWAN-Light-Intensity-Sensor-p-5355.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2103.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2103.toml new file mode 100644 index 0000000..fd3fb3e --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2103.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP S2103" +description = " CO2, Temperature and Humidity Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-S2103-LoRaWAN-CO2-Temperature-and-Humidity-Sensor-p-5356.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-S2103-LoRaWAN-CO2-Temperature-and-Humidity-Sensor-p-5356.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2104.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2104.toml new file mode 100644 index 0000000..1bcb006 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2104.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP S2104" +description = "Soil Moisture and Temperature Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-S2104-LoRaWAN-Soil-Temperature-and-Moisture-Sensor-p-5357.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-S2104-LoRaWAN-Soil-Temperature-and-Moisture-Sensor-p-5357.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2105.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2105.toml new file mode 100644 index 0000000..c279b7a --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2105.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP S2105" +description = "Soil Moisture, Temperature and EC Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-S2105-LoRaWAN-Soil-Temperature-Moisture-and-EC-Sensor-p-5358.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-S2105-LoRaWAN-Soil-Temperature-Moisture-and-EC-Sensor-p-5358.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2106.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2106.toml new file mode 100644 index 0000000..1680f36 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2106.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP S2106" +description = "pH Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-S2106-p-5647.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-S2106-p-5647.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2107.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2107.toml new file mode 100644 index 0000000..9a92124 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2107.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP S2107" +description = "Temperature Sensor with PT1000" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-S2107-Temperature-Sensor-p-5807.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-S2107-Temperature-Sensor-p-5807.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2120.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2120.toml new file mode 100644 index 0000000..88a8fb1 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2120.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP S2120" +description = "8-in-1 LoRaWAN Weather Sensor" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/sensecap-s2120-lorawan-8-in-1-weather-sensor-p-5436.html" +documentation_url = "https://www.seeedstudio.com/sensecap-s2120-lorawan-8-in-1-weather-sensor-p-5436.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-a.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-a.toml new file mode 100644 index 0000000..82e28b2 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-a.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP T1000-A" +description = "Tracker for Indoor and Outdoor Positioning" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-A-p-5697.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-A-p-5697.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-b.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-b.toml new file mode 100644 index 0000000..30b62de --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-b.toml @@ -0,0 +1,8 @@ +[device] +name = "SenseCAP T1000-B" +description = "Tracker for Indoor and Outdoor Positioning" +firmware = [] + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-B-p-5698.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-B-p-5698.html" diff --git a/vendors/seeed-technology-co-ltd/profiles/AS923-1_0_3.toml b/vendors/seeed-technology-co-ltd/profiles/AS923-1_0_3.toml new file mode 100644 index 0000000..ab20972 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/profiles/AS923-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "AS923" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 16 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/seeed-technology-co-ltd/profiles/AU915-1_0_3.toml b/vendors/seeed-technology-co-ltd/profiles/AU915-1_0_3.toml new file mode 100644 index 0000000..f7acb29 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/profiles/AU915-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "AU915" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 21 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/seeed-technology-co-ltd/profiles/EU868-1_0_3.toml b/vendors/seeed-technology-co-ltd/profiles/EU868-1_0_3.toml new file mode 100644 index 0000000..bc7ae24 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/profiles/EU868-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "EU868" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 16 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/seeed-technology-co-ltd/profiles/US915-1_0_3.toml b/vendors/seeed-technology-co-ltd/profiles/US915-1_0_3.toml new file mode 100644 index 0000000..08a5f74 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/profiles/US915-1_0_3.toml @@ -0,0 +1,24 @@ +[profile] +id = 0 +region = "US915" +mac_version = "1.0.3" +reg_params_revision = "A" +supports_otaa = true +supports_class_b = false +supports_class_c = false +max_eirp = 21 + +[profile.abp] +rx1_delay = 0 +rx1_dr_offset = 0 +rx2_dr = 0 +rx2_freq = 0 + +[profile.class_b] +timeout_secs = 0 +ping_slot_nb_k = 0 +ping_slot_dr = 0 +ping_slot_freq = 0 + +[profile.class_c] +timeout_secs = 0 diff --git a/vendors/seeed-technology-co-ltd/vendor.toml b/vendors/seeed-technology-co-ltd/vendor.toml new file mode 100644 index 0000000..e14c1b0 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/vendor.toml @@ -0,0 +1,21 @@ +[vendor] +name = "Seeed Technology Co Ltd" +id = 744 +ouis = ["2cf7f1"] +devices = [ + "sensecap-a1101.toml", + "sensecap-s2100.toml", + "sensecap-s2101.toml", + "sensecap-s2102.toml", + "sensecap-s2103.toml", + "sensecap-s2104.toml", + "sensecap-s2105.toml", + "sensecap-s2106.toml", + "sensecap-s2107.toml", + "sensecap-s2120.toml", + "sensecap-t1000-a.toml", + "sensecap-t1000-b.toml", +] + +[vendor.metadata] +homepage = "https://www.seeedstudio.com/" From 1e4154e1c6b45368c192b8c6f7716db66db89655 Mon Sep 17 00:00:00 2001 From: Cory Callcott Date: Wed, 4 Jun 2025 18:49:28 +1000 Subject: [PATCH 5/8] milesight em300, add inital firmware versions, profile ids and codecs --- .../milesight/devices/milesight-em300-cl.toml | 11 +++++++++- .../milesight/devices/milesight-em300-di.toml | 11 +++++++++- .../devices/milesight-em300-mcs.toml | 21 ++++++++++++++++++- .../devices/milesight-em300-mld.toml | 11 +++++++++- .../devices/milesight-em300-sld-zld.toml | 21 ++++++++++++++++++- .../milesight/devices/milesight-em300-th.toml | 21 ++++++++++++++++++- .../devices/milesight-em310-tilt.toml | 11 +++++++++- .../milesight/devices/milesight-em320-th.toml | 11 +++++++++- 8 files changed, 110 insertions(+), 8 deletions(-) diff --git a/vendors/milesight/devices/milesight-em300-cl.toml b/vendors/milesight/devices/milesight-em300-cl.toml index b90e5ea..7e861d1 100644 --- a/vendors/milesight/devices/milesight-em300-cl.toml +++ b/vendors/milesight/devices/milesight-em300-cl.toml @@ -1,7 +1,16 @@ [device] name = "Milesight EM300-CL" description = "Capacitive Level Sensor " -firmware = [] + +[[device.firmware]] +version = "1.2" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em300-cl.js" [device.metadata] product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-cl" diff --git a/vendors/milesight/devices/milesight-em300-di.toml b/vendors/milesight/devices/milesight-em300-di.toml index 599557e..52d29af 100644 --- a/vendors/milesight/devices/milesight-em300-di.toml +++ b/vendors/milesight/devices/milesight-em300-di.toml @@ -1,7 +1,16 @@ [device] name = "Milesight EM300-DI" description = "Pulse Counter" -firmware = [] + +[[device.firmware]] +version = "1.3" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em300-di.js" [device.metadata] product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-di" diff --git a/vendors/milesight/devices/milesight-em300-mcs.toml b/vendors/milesight/devices/milesight-em300-mcs.toml index 1e70a88..8f0dacf 100644 --- a/vendors/milesight/devices/milesight-em300-mcs.toml +++ b/vendors/milesight/devices/milesight-em300-mcs.toml @@ -1,7 +1,26 @@ [device] name = "Milesight EM300-MCS" description = "Magnetic Contact Switch" -firmware = [] + +[[device.firmware]] +version = "1.22" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em300-mcs.js" + +[[device.firmware]] +version = "1.8" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "US915-1_0_3.toml", + "EU868-1_0_3.toml", +] +codec = "em300-mcs.js" [device.metadata] product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-mcs" diff --git a/vendors/milesight/devices/milesight-em300-mld.toml b/vendors/milesight/devices/milesight-em300-mld.toml index 0609cc6..eb7888a 100644 --- a/vendors/milesight/devices/milesight-em300-mld.toml +++ b/vendors/milesight/devices/milesight-em300-mld.toml @@ -1,7 +1,16 @@ [device] name = "Milesight EM300-MLD" description = "Membrane Leakage Detection Sensor" -firmware = [] + +[[device.firmware]] +version = "1.8" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em300-mld.js" [device.metadata] product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-mld" diff --git a/vendors/milesight/devices/milesight-em300-sld-zld.toml b/vendors/milesight/devices/milesight-em300-sld-zld.toml index 814af9d..6dd1d87 100644 --- a/vendors/milesight/devices/milesight-em300-sld-zld.toml +++ b/vendors/milesight/devices/milesight-em300-sld-zld.toml @@ -1,7 +1,26 @@ [device] name = "Milesight EM300-SLD-ZLD" description = "Leak Detection Sensor" -firmware = [] + +[[device.firmware]] +version = "1.22" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em300-sld-zld.js" + +[[device.firmware]] +version = "1.9" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em300-sld-zld.js" [device.metadata] product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-sld-zld" diff --git a/vendors/milesight/devices/milesight-em300-th.toml b/vendors/milesight/devices/milesight-em300-th.toml index a3da6bd..a37fb24 100644 --- a/vendors/milesight/devices/milesight-em300-th.toml +++ b/vendors/milesight/devices/milesight-em300-th.toml @@ -1,7 +1,26 @@ [device] name = "Milesight EM300-TH" description = "Temperature & Humidity Sensor" -firmware = [] + +[[device.firmware]] +version = "1.22" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em300-th.js" + +[[device.firmware]] +version = "1.8" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em300-th.js" [device.metadata] product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em300-th" diff --git a/vendors/milesight/devices/milesight-em310-tilt.toml b/vendors/milesight/devices/milesight-em310-tilt.toml index a1a9907..2959954 100644 --- a/vendors/milesight/devices/milesight-em310-tilt.toml +++ b/vendors/milesight/devices/milesight-em310-tilt.toml @@ -1,7 +1,16 @@ [device] name = "Milesight EM310-TILT" description = "Tilt Sensor" -firmware = [] + +[[device.firmware]] +version = "1.3" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em320-tilt.js" [device.metadata] product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em320-tilt" diff --git a/vendors/milesight/devices/milesight-em320-th.toml b/vendors/milesight/devices/milesight-em320-th.toml index 4b72ed4..4d9e690 100644 --- a/vendors/milesight/devices/milesight-em320-th.toml +++ b/vendors/milesight/devices/milesight-em320-th.toml @@ -1,7 +1,16 @@ [device] name = "Milesight EM320-TH" description = "Temperature & Humidity Sensor" -firmware = [] + +[[device.firmware]] +version = "1.4" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "em320-th.js" [device.metadata] product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em320-th" From 35a5680be2a10c483169647e0be98277a0bcc857 Mon Sep 17 00:00:00 2001 From: Cory Callcott Date: Wed, 4 Jun 2025 18:50:30 +1000 Subject: [PATCH 6/8] sensecap s210x, add inital firmware versions, profile ids and codecs --- .../devices/sensecap-a1101.toml | 10 ++++++- .../devices/sensecap-s2100.toml | 10 ++++++- .../devices/sensecap-s2101.toml | 11 +++++++- .../devices/sensecap-s2102.toml | 11 +++++++- .../devices/sensecap-s2103.toml | 11 +++++++- .../devices/sensecap-s2104.toml | 11 +++++++- .../devices/sensecap-s2105.toml | 11 +++++++- .../devices/sensecap-s2106.toml | 11 +++++++- .../devices/sensecap-s2107.toml | 11 +++++++- .../devices/sensecap-s2120.toml | 11 +++++++- .../devices/sensecap-t1000-a.toml | 21 ++++++++++++++- .../devices/sensecap-t1000-ab-mapper.toml | 27 +++++++++++++++++++ .../devices/sensecap-t1000-b.toml | 21 ++++++++++++++- 13 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 vendors/seeed-technology-co-ltd/devices/sensecap-t1000-ab-mapper.toml diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-a1101.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-a1101.toml index d40b13e..04e07b2 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-a1101.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-a1101.toml @@ -1,7 +1,15 @@ [device] name = "SenseCAP A1101" description = "Vision AI Sensor" -firmware = [] + +[[device.firmware]] +version = "1.0.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-A1101-LoRaWAN-Vision-AI-Sensor-p-5367.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2100.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2100.toml index e8207b9..04124e2 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-s2100.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2100.toml @@ -1,7 +1,15 @@ [device] name = "SenseCAP S2100" description = "Data Logger/DTU, Supports RS485/Analog/GPIO Interfaces" -firmware = [] + +[[device.firmware]] +version = "1.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-S2100-LoRaWAN-Data-Logger-p-5361.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2101.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2101.toml index e43ab65..45b3294 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-s2101.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2101.toml @@ -1,7 +1,16 @@ [device] name = "SenseCAP S2101" description = "Air Temperature and Humidity Sensor" -firmware = [] + +[[device.firmware]] +version = "1.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-S2101-LoRaWAN-Air-Temperature-and-Humidity-Sensor-p-5354.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2102.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2102.toml index 2078089..1ad09ed 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-s2102.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2102.toml @@ -1,7 +1,16 @@ [device] name = "SenseCAP S2102" description = "Wireless Light Intensity Sensor" -firmware = [] + +[[device.firmware]] +version = "1.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-S2102-LoRaWAN-Light-Intensity-Sensor-p-5355.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2103.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2103.toml index fd3fb3e..48d9006 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-s2103.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2103.toml @@ -1,7 +1,16 @@ [device] name = "SenseCAP S2103" description = " CO2, Temperature and Humidity Sensor" -firmware = [] + +[[device.firmware]] +version = "1.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-S2103-LoRaWAN-CO2-Temperature-and-Humidity-Sensor-p-5356.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2104.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2104.toml index 1bcb006..b144b51 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-s2104.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2104.toml @@ -1,7 +1,16 @@ [device] name = "SenseCAP S2104" description = "Soil Moisture and Temperature Sensor" -firmware = [] + +[[device.firmware]] +version = "1.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-S2104-LoRaWAN-Soil-Temperature-and-Moisture-Sensor-p-5357.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2105.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2105.toml index c279b7a..7204ded 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-s2105.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2105.toml @@ -1,7 +1,16 @@ [device] name = "SenseCAP S2105" description = "Soil Moisture, Temperature and EC Sensor" -firmware = [] + +[[device.firmware]] +version = "1.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-S2105-LoRaWAN-Soil-Temperature-Moisture-and-EC-Sensor-p-5358.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2106.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2106.toml index 1680f36..050f6d1 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-s2106.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2106.toml @@ -1,7 +1,16 @@ [device] name = "SenseCAP S2106" description = "pH Sensor" -firmware = [] + +[[device.firmware]] +version = "1.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-S2106-p-5647.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2107.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2107.toml index 9a92124..8d154b4 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-s2107.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2107.toml @@ -1,7 +1,16 @@ [device] name = "SenseCAP S2107" description = "Temperature Sensor with PT1000" -firmware = [] + +[[device.firmware]] +version = "1.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-S2107-Temperature-Sensor-p-5807.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-s2120.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-s2120.toml index 88a8fb1..02e612d 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-s2120.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-s2120.toml @@ -1,7 +1,16 @@ [device] name = "SenseCAP S2120" description = "8-in-1 LoRaWAN Weather Sensor" -firmware = [] + +[[device.firmware]] +version = "1.0" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_S2120_Weather_Station.js" [device.metadata] product_url = "https://www.seeedstudio.com/sensecap-s2120-lorawan-8-in-1-weather-sensor-p-5436.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-a.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-a.toml index 82e28b2..6024ba8 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-a.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-a.toml @@ -1,7 +1,26 @@ [device] name = "SenseCAP T1000-A" description = "Tracker for Indoor and Outdoor Positioning" -firmware = [] + +[[device.firmware]] +version = "2.2" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" + +[[device.firmware]] +version = "2.5" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-A-p-5697.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-ab-mapper.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-ab-mapper.toml new file mode 100644 index 0000000..f320f21 --- /dev/null +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-ab-mapper.toml @@ -0,0 +1,27 @@ +[device] +name = "SenseCAP T1000-AB-Mapper" +description = "Profile for Helium & TTN Mapper mapping platforms" + +[[device.firmware]] +version = "2.2" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_T1000_mapper.js" + +[[device.firmware]] +version = "2.5" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_T1000_mapper.js" + +[device.metadata] +product_url = "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-A-p-5697.html" +documentation_url = "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-A-p-5697.html" diff --git a/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-b.toml b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-b.toml index 30b62de..80ff2bc 100644 --- a/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-b.toml +++ b/vendors/seeed-technology-co-ltd/devices/sensecap-t1000-b.toml @@ -1,7 +1,26 @@ [device] name = "SenseCAP T1000-B" description = "Tracker for Indoor and Outdoor Positioning" -firmware = [] + +[[device.firmware]] +version = "2.2" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" + +[[device.firmware]] +version = "2.5" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "SenseCAP_LoRaWAN_V4_Decoder.js" [device.metadata] product_url = "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-B-p-5698.html" From 56dd14c4ff73369aed4759208df27984e8f83bf7 Mon Sep 17 00:00:00 2001 From: Cory Callcott Date: Wed, 4 Jun 2025 20:50:10 +1000 Subject: [PATCH 7/8] dragino, add inital firmware versions, profile ids and codecs --- vendors/dragino/codecs/ldds75.js | 53 ++++++++++--------- vendors/dragino/codecs/lds02.js | 9 ++-- .../codecs/{lht65n-e31f.js => lht65n.js} | 6 +-- vendors/dragino/codecs/lt-22222-l.js | 6 +-- vendors/dragino/codecs/lwl02.js | 9 ++-- .../dragino/codecs/test_decode_ldds75.json | 19 ++++++- vendors/dragino/codecs/test_decode_lds02.json | 21 +++++++- .../dragino/codecs/test_decode_lht65n.json | 19 +++++++ .../codecs/test_decode_lt-22222-l.json | 26 ++++++++- vendors/dragino/codecs/test_decode_lwl02.json | 20 ++++++- .../codecs/test_encode_lht65n-e31f.json | 1 - ...t65n-e31f.json => test_encode_lht65n.json} | 0 vendors/dragino/devices/ldds75.toml | 11 +++- vendors/dragino/devices/lds02.toml | 11 +++- vendors/dragino/devices/lht65n-e31f.toml | 11 +++- vendors/dragino/devices/lt-22222-l.toml | 15 +++++- vendors/dragino/devices/lwl02.toml | 11 +++- 17 files changed, 200 insertions(+), 48 deletions(-) rename vendors/dragino/codecs/{lht65n-e31f.js => lht65n.js} (99%) create mode 100644 vendors/dragino/codecs/test_decode_lht65n.json delete mode 100644 vendors/dragino/codecs/test_encode_lht65n-e31f.json rename vendors/dragino/codecs/{test_decode_lht65n-e31f.json => test_encode_lht65n.json} (100%) diff --git a/vendors/dragino/codecs/ldds75.js b/vendors/dragino/codecs/ldds75.js index 83d8e79..6dd10ee 100644 --- a/vendors/dragino/codecs/ldds75.js +++ b/vendors/dragino/codecs/ldds75.js @@ -1,26 +1,31 @@ -function Decode(fPort, bytes, variables) { - // Decode an uplink message from a buffer - // (array) of bytes to an object of fields. - var len=bytes.length; - var value=(bytes[0]<<8 | bytes[1]) & 0x3FFF; - var batV=value/1000;//Battery,units:V - - var distance = 0; - if(len==5) - { - value=bytes[2]<<8 | bytes[3]; - distance=(value);//distance,units:mm - if(value<20) - distance = "Invalid Reading"; +function decodeUplink(input) { + let decoded = {}; + let len = input.bytes.length; + let value = (input.bytes[0]<<8 | input.bytes[1]) & 0x3FFF; + + switch (input.fPort) { + case 2: + decoded.Bat=value/1000; + value=input.bytes[2]<<8 | input.bytes[3]; + decoded.Distance=(value); //+" mm"; distance in mm + + if(value === 0) + decoded.Distance = "No Sensor"; + else if (value === 20) + decoded.Distance = "Invalid Reading"; + decoded.Interrupt_flag = input.bytes[4]; + + value = input.bytes[5]<<8 | input.bytes[6]; + if (input.bytes[5] & 0x80) + {value |= 0xFFFF0000;} + decoded.TempC_DS18B20=(value/10).toFixed(2); //DS18B20,temperature + decoded.Sensor_flag = input.bytes[7]; + + return {data: decoded}; + + default: + return { + errors: ["unknown FPort"] + } } - else - distance = "No Sensor"; - - var interrupt = bytes[len-1]; - return { - Node_type:"LDDS75", - Bat:batV , - Distance:distance, - Interrupt_status:interrupt - }; } diff --git a/vendors/dragino/codecs/lds02.js b/vendors/dragino/codecs/lds02.js index f23e717..f8baca8 100644 --- a/vendors/dragino/codecs/lds02.js +++ b/vendors/dragino/codecs/lds02.js @@ -1,8 +1,9 @@ function decodeUplink(input) { - return { - data: Decode(input.fPort, input.bytes, input.variables) - }; + return { + data: Decode(input.fPort, input.bytes, input.variables) + }; } + function Decode(fPort, bytes, variables) { // Decode an uplink message from a buffer // (array) of bytes to an object of fields. @@ -20,7 +21,7 @@ function Decode(fPort, bytes, variables) { var open_duration=bytes[6]<<16 | bytes[7]<<8 | bytes[8];//units:min if(bytes.length==10 && 0x07>bytes[0]< 0x0f) return { - Node_type:"LWL02", + Node_type:"LDS02", BAT_V:bat, MOD:mod, DOOR_OPEN_STATUS:door_open_status, diff --git a/vendors/dragino/codecs/lht65n-e31f.js b/vendors/dragino/codecs/lht65n.js similarity index 99% rename from vendors/dragino/codecs/lht65n-e31f.js rename to vendors/dragino/codecs/lht65n.js index b78d5a2..badb54a 100644 --- a/vendors/dragino/codecs/lht65n-e31f.js +++ b/vendors/dragino/codecs/lht65n.js @@ -1,7 +1,7 @@ function decodeUplink(input) { - return { - data: Decode(input.fPort, input.bytes, input.variables), - }; + return { + data: Decode(input.fPort, input.bytes, input.variables), + }; } function Str1(str2) { diff --git a/vendors/dragino/codecs/lt-22222-l.js b/vendors/dragino/codecs/lt-22222-l.js index 9a48258..72404eb 100644 --- a/vendors/dragino/codecs/lt-22222-l.js +++ b/vendors/dragino/codecs/lt-22222-l.js @@ -1,7 +1,7 @@ function decodeUplink(input) { - return { - data: Decode(input.fPort, input.bytes, input.variables) - }; + return { + data: Decode(input.fPort, input.bytes, input.variables) + }; } function Decode(fPort, bytes, variables) { diff --git a/vendors/dragino/codecs/lwl02.js b/vendors/dragino/codecs/lwl02.js index 796e37d..351befa 100644 --- a/vendors/dragino/codecs/lwl02.js +++ b/vendors/dragino/codecs/lwl02.js @@ -1,8 +1,9 @@ function decodeUplink(input) { - return { - data: Decode(input.fPort, input.bytes, input.variables) - }; + return { + data: Decode(input.fPort, input.bytes, input.variables) + }; } + function Decode(fPort, bytes, variables) { // Decode an uplink message from a buffer // (array) of bytes to an object of fields. @@ -20,7 +21,7 @@ function Decode(fPort, bytes, variables) { var open_duration=bytes[6]<<16 | bytes[7]<<8 | bytes[8];//units:min if(bytes.length==10 && 0x07>bytes[0]< 0x0f) return { - Node_type:"LWL02", + Node_type:"LDS02", BAT_V:bat, MOD:mod, DOOR_OPEN_STATUS:door_open_status, diff --git a/vendors/dragino/codecs/test_decode_ldds75.json b/vendors/dragino/codecs/test_decode_ldds75.json index 0637a08..8fd9fb7 100644 --- a/vendors/dragino/codecs/test_decode_ldds75.json +++ b/vendors/dragino/codecs/test_decode_ldds75.json @@ -1 +1,18 @@ -[] \ No newline at end of file +[ + { + "name": "Test decode Dragino LDDS75", + "input": { + "fPort": 0x02, + "bytes": [13,10,7,233,0,0,0,1] + }, + "expected": { + "data": { + "Bat":3.338, + "Distance":2025, + "Interrupt_flag":0, + "TempC_DS18B20":"0.00", + "Sensor_flag":1 + } + } + } +] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_decode_lds02.json b/vendors/dragino/codecs/test_decode_lds02.json index 0637a08..abac642 100644 --- a/vendors/dragino/codecs/test_decode_lds02.json +++ b/vendors/dragino/codecs/test_decode_lds02.json @@ -1 +1,20 @@ -[] \ No newline at end of file +[ + { + "name": "Test decode Dragino LDS02", + "input": { + "fPort": 0xa, + "bytes": [11,136,1,0,27,179,0,0,0,0] + }, + "expected": { + "data": { + "Node_type":"LDS02", + "BAT_V":2.952, + "MOD":1, + "DOOR_OPEN_STATUS":0, + "DOOR_OPEN_TIMES":7091, + "LAST_DOOR_OPEN_DURATION":0, + "ALARM":0 + } + } + } +] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_decode_lht65n.json b/vendors/dragino/codecs/test_decode_lht65n.json new file mode 100644 index 0000000..f12a986 --- /dev/null +++ b/vendors/dragino/codecs/test_decode_lht65n.json @@ -0,0 +1,19 @@ +[ + { + "name": "Test decode Dragino LHT65N", + "input": { + "fPort": 0x02, + "bytes": [203,198,0,130,0,242,1,127,255,127,255] + }, + "expected": { + "data": { + "BatV":3.014, + "Bat_status":3, + "TempC_SHT":1.3, + "Hum_SHT":24.2, + "Ext_sensor":"Temperature Sensor", + "TempC_DS":327.67 + } + } + } +] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_decode_lt-22222-l.json b/vendors/dragino/codecs/test_decode_lt-22222-l.json index 0637a08..f96fac8 100644 --- a/vendors/dragino/codecs/test_decode_lt-22222-l.json +++ b/vendors/dragino/codecs/test_decode_lt-22222-l.json @@ -1 +1,25 @@ -[] \ No newline at end of file +[ + { + "name": "Test decode Dragino LT-22222-L", + "input": { + "fPort": 0x02, + "bytes": [0,0,0,0,0,0,0,0,60,255,65] + }, + "expected": { + "data": { + "Hardware_mode":"LT22222", + "DO1_status":"H", + "DO2_status":"H", + "RO1_status":"OFF", + "RO2_status":"OFF", + "Work_mode":"2ACI+2AVI", + "AVI1_V":0, + "AVI2_V":0, + "ACI1_mA":0, + "ACI2_mA":0, + "DI1_status":"H", + "DI2_status":"H" + } + } + } +] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_decode_lwl02.json b/vendors/dragino/codecs/test_decode_lwl02.json index 0637a08..d07b63e 100644 --- a/vendors/dragino/codecs/test_decode_lwl02.json +++ b/vendors/dragino/codecs/test_decode_lwl02.json @@ -1 +1,19 @@ -[] \ No newline at end of file +[ + { + "name": "Test decode Dragino LWL02", + "input": { + "fPort": 0xa, + "bytes": [12,30,2,0,0,4,0,0,0,0] + }, + "expected": { + "data": { + Node_type:"LWL02", + BAT_V:3.102, + MOD:2, + WATER_LEAK_STATUS:0, + WATER_LEAK_TIMES:4, + LAST_WATER_LEAK_DURATION:0 + } + } + } +] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_encode_lht65n-e31f.json b/vendors/dragino/codecs/test_encode_lht65n-e31f.json deleted file mode 100644 index 0637a08..0000000 --- a/vendors/dragino/codecs/test_encode_lht65n-e31f.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/vendors/dragino/codecs/test_decode_lht65n-e31f.json b/vendors/dragino/codecs/test_encode_lht65n.json similarity index 100% rename from vendors/dragino/codecs/test_decode_lht65n-e31f.json rename to vendors/dragino/codecs/test_encode_lht65n.json diff --git a/vendors/dragino/devices/ldds75.toml b/vendors/dragino/devices/ldds75.toml index 98c59b4..c3b39c0 100644 --- a/vendors/dragino/devices/ldds75.toml +++ b/vendors/dragino/devices/ldds75.toml @@ -1,7 +1,16 @@ [device] name = "LDDS75" description = "Distance Detection Sensor" -firmware = [] + +[[device.firmware]] +version = "1.1.4" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "ldds75.js" [device.metadata] product_url = "https://dragino.com/products/distance-level-sensor/item/161-ldds75.html" diff --git a/vendors/dragino/devices/lds02.toml b/vendors/dragino/devices/lds02.toml index 39a40d9..10f11f5 100644 --- a/vendors/dragino/devices/lds02.toml +++ b/vendors/dragino/devices/lds02.toml @@ -1,7 +1,16 @@ [device] name = "LDS02" description = "Door Sensor" -firmware = [] + +[[device.firmware]] +version = "1.5" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "lds02.js" [device.metadata] product_url = "https://dragino.com/products/lorawan-nb-iot-door-sensor-water-leak/item/181-lds02.html" diff --git a/vendors/dragino/devices/lht65n-e31f.toml b/vendors/dragino/devices/lht65n-e31f.toml index 510a9fb..4b08904 100644 --- a/vendors/dragino/devices/lht65n-e31f.toml +++ b/vendors/dragino/devices/lht65n-e31f.toml @@ -1,7 +1,16 @@ [device] name = "LHT65N-E31F" description = "Temperature & Humidity Sensor, optional external temperature sensor." -firmware = [] + +[[device.firmware]] +version = "1.8" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "lht65n-e31f.js" [device.metadata] product_url = "https://dragino.com/products/temperature-humidity-sensor/item/266-lht65n-e31f.html" diff --git a/vendors/dragino/devices/lt-22222-l.toml b/vendors/dragino/devices/lt-22222-l.toml index df43c0f..b3ec79a 100644 --- a/vendors/dragino/devices/lt-22222-l.toml +++ b/vendors/dragino/devices/lt-22222-l.toml @@ -1,7 +1,20 @@ [device] name = "LT-22222-L" description = "LoRa I/O Controller" -firmware = [] + +[[device.firmware]] +version = "1.6" +profiles = [ + "AS923-1_0_3-CLASS_C.toml", + "AS923-1_0_3.toml", + "AU915-1_0_3-CLASS_C.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3-CLASS_C.toml", + "EU868-1_0_3.toml", + "US915-1_0_3-CLASS_C.toml", + "US915-1_0_3.toml", +] +codec = "lt-22222-l.js" [device.metadata] product_url = "https://dragino.com/products/lora-lorawan-end-node/item/156-lt-22222-l.html" diff --git a/vendors/dragino/devices/lwl02.toml b/vendors/dragino/devices/lwl02.toml index 236b4d1..e740080 100644 --- a/vendors/dragino/devices/lwl02.toml +++ b/vendors/dragino/devices/lwl02.toml @@ -1,7 +1,16 @@ [device] name = "LWL02" description = "Water Leak Sensor" -firmware = [] + +[[device.firmware]] +version = "1.5" +profiles = [ + "AS923-1_0_3.toml", + "AU915-1_0_3.toml", + "EU868-1_0_3.toml", + "US915-1_0_3.toml", +] +codec = "lwl02.js" [device.metadata] product_url = "https://dragino.com/products/lorawan-nb-iot-door-sensor-water-leak/item/180-lwl02.html" From 1e15a039213ca4fdd0f20be6aba62550ac907b03 Mon Sep 17 00:00:00 2001 From: Cory Callcott Date: Thu, 5 Jun 2025 19:19:48 +1000 Subject: [PATCH 8/8] remove failing lht65n-e31f.toml forgot to commit rm for and replace with lht65n.toml --- vendors/dragino/devices/{lht65n-e31f.toml => lht65n.toml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename vendors/dragino/devices/{lht65n-e31f.toml => lht65n.toml} (90%) diff --git a/vendors/dragino/devices/lht65n-e31f.toml b/vendors/dragino/devices/lht65n.toml similarity index 90% rename from vendors/dragino/devices/lht65n-e31f.toml rename to vendors/dragino/devices/lht65n.toml index 4b08904..ac3aa59 100644 --- a/vendors/dragino/devices/lht65n-e31f.toml +++ b/vendors/dragino/devices/lht65n.toml @@ -1,5 +1,5 @@ [device] -name = "LHT65N-E31F" +name = "LHT65N" description = "Temperature & Humidity Sensor, optional external temperature sensor." [[device.firmware]] @@ -10,7 +10,7 @@ profiles = [ "EU868-1_0_3.toml", "US915-1_0_3.toml", ] -codec = "lht65n-e31f.js" +codec = "lht65n.js" [device.metadata] product_url = "https://dragino.com/products/temperature-humidity-sensor/item/266-lht65n-e31f.html"