├── A1101 ├── ChirpStack │ └── SenseCAP_A1101_ChirpStackV3_Decoder.js ├── Helium │ └── SenseCAP_A1101_Helium_Decoder.js └── TTN │ └── SenseCAP_A1101_TTN_Decoder.js ├── A1102 ├── Chirpstack │ └── SenseCAP_A1102_TTN_Decoder.js └── TTN │ └── SenseCAP_A1102_TTN_Decoder.js ├── README.md ├── S2100 ├── Helium │ └── SenseCAP_S2100_Helium_Decoder.js ├── TTN │ └── SenseCAP_S2100_TTN_Decoder.js └── datacake │ └── SenseCAP_S2100_Datacake_Decoder.js ├── S2107 ├── AWS │ └── SenseCAP_S2107_AWS_Decoder.js ├── ChirpStack │ └── SenseCAP_S2107_ChirpStackV3_Decoder.js ├── Helium │ └── SenseCAP_S2107_Helium_Decoder.js └── TTN │ └── SenseCAP_S2107_TTN_Decoder.js ├── S210X ├── AWS │ └── SenseCAP_S210x_AWS_Decoder.js ├── ChirpStack │ └── SenseCAP_S210x_ChirpStackV3_Decoder.js ├── Helium │ └── SenseCAP_S210X_Helium_Decoder.js └── TTN │ └── SenseCAP_S210X_TTN_Decoder.js ├── S2120 ├── ChirpStack │ └── SenseCAP_S2120_ChirpStackV3_Decoder.js ├── Helium │ └── SenseCAP_S2120_Helium_Decoder.js └── TTN │ └── SenseCAP_S2120_TTN_Decoder.js ├── SenseCAP Gen1 Sensor └── SenseCAP_WirelessSensor_TTN_Decoder.js ├── T1000 ├── AWS │ └── SenseCAP_T1000_AWS_Decoder.js ├── ChirpStack │ └── SenseCAP_T1000_ChirpStackV3_Decoder.js ├── Helium │ ├── SenseCAP_T1000E_Helium_Decoder.js │ └── SenseCAP_T1000_Helium_Decoder.js └── TTN │ ├── SenseCAP_T1000E_TTN_Decoder.js │ └── SenseCAP_T1000_TTN_Decoder.js └── WM1110 ├── ChirpStack └── SenseCAP_T1000_ChirpStackV3_Decoder.js ├── Helium └── SenseCAP_WM1110_Helium_Decoder.js └── TTN └── SenseCAP_WM1110_TTN_Decoder.js /A1101/ChirpStack/SenseCAP_A1101_ChirpStackV3_Decoder.js: -------------------------------------------------------------------------------- 1 | function Decode(fPort, bytes, variables) { 2 | var bytesString = bytes2HexString(bytes).toLocaleUpperCase(); 3 | var decoded = { 4 | // valid 5 | valid: true, 6 | err: 0, 7 | // bytes 8 | payload: bytesString, 9 | // messages array 10 | messages: [] 11 | }; 12 | 13 | // CRC check 14 | if (!crc16Check(bytesString)) { 15 | decoded['valid'] = false; 16 | decoded['err'] = -1; // "crc check fail." 17 | return { 18 | data: decoded 19 | }; 20 | } 21 | 22 | // Length Check 23 | if ((bytesString.length / 2 - 2) % 7 !== 0) { 24 | decoded['valid'] = false; 25 | decoded['err'] = -2; // "length check fail." 26 | return { 27 | data: decoded 28 | }; 29 | } 30 | 31 | // Cache sensor id 32 | var sensorEuiLowBytes; 33 | var sensorEuiHighBytes; 34 | 35 | // Handle each frame 36 | var frameArray = divideBy7Bytes(bytesString); 37 | for (var forFrame = 0; forFrame < frameArray.length; forFrame++) { 38 | var frame = frameArray[forFrame]; 39 | // Extract key parameters 40 | var channel = strTo10SysNub(frame.substring(0, 2)); 41 | var dataID = strTo10SysNub(frame.substring(2, 6)); 42 | var dataValue = frame.substring(6, 14); 43 | var realDataValue = isSpecialDataId(dataID) ? ttnDataSpecialFormat(dataID, dataValue) : ttnDataFormat(dataValue); 44 | if (checkDataIdIsMeasureUpload(dataID)) { 45 | // if telemetry. 46 | decoded.messages.push({ 47 | type: 'report_telemetry', 48 | measurementId: dataID, 49 | measurementValue: realDataValue 50 | }); 51 | } else if (isSpecialDataId(dataID) || dataID === 5 || dataID === 6) { 52 | // if special order, except "report_sensor_id". 53 | switch (dataID) { 54 | case 0x00: 55 | // node version 56 | var versionData = sensorAttrForVersion(realDataValue); 57 | decoded.messages.push({ 58 | type: 'upload_version', 59 | hardwareVersion: versionData.ver_hardware, 60 | softwareVersion: versionData.ver_software 61 | }); 62 | break; 63 | case 1: 64 | // sensor version 65 | break; 66 | case 2: 67 | // sensor eui, low bytes 68 | sensorEuiLowBytes = realDataValue; 69 | break; 70 | case 3: 71 | // sensor eui, high bytes 72 | sensorEuiHighBytes = realDataValue; 73 | break; 74 | case 7: 75 | // battery power && interval 76 | decoded.messages.push({ 77 | type: 'upload_battery', 78 | battery: realDataValue.power 79 | }, { 80 | type: 'upload_interval', 81 | interval: parseInt(realDataValue.interval) * 60 82 | }); 83 | break; 84 | case 9: 85 | decoded.messages.push({ 86 | type: 'model_info', 87 | detectionType: realDataValue.detectionType, 88 | modelId: realDataValue.modelId, 89 | modelVer: realDataValue.modelVer 90 | }); 91 | break; 92 | case 0x120: 93 | // remove sensor 94 | decoded.messages.push({ 95 | type: 'report_remove_sensor', 96 | channel: 1 97 | }); 98 | break; 99 | default: 100 | break; 101 | } 102 | } else { 103 | decoded.messages.push({ 104 | type: 'unknown_message', 105 | dataID: dataID, 106 | dataValue: dataValue 107 | }); 108 | } 109 | } 110 | 111 | // if the complete id received, as "upload_sensor_id" 112 | if (sensorEuiHighBytes && sensorEuiLowBytes) { 113 | decoded.messages.unshift({ 114 | type: 'upload_sensor_id', 115 | channel: 1, 116 | sensorId: (sensorEuiHighBytes + sensorEuiLowBytes).toUpperCase() 117 | }); 118 | } 119 | // return 120 | return { 121 | data: decoded 122 | }; 123 | } 124 | function crc16Check(data) { 125 | return true; 126 | } 127 | 128 | // util 129 | function bytes2HexString(arrBytes) { 130 | var str = ''; 131 | for (var i = 0; i < arrBytes.length; i++) { 132 | var tmp; 133 | var num = arrBytes[i]; 134 | if (num < 0) { 135 | tmp = (255 + num + 1).toString(16); 136 | } else { 137 | tmp = num.toString(16); 138 | } 139 | if (tmp.length === 1) { 140 | tmp = '0' + tmp; 141 | } 142 | str += tmp; 143 | } 144 | return str; 145 | } 146 | 147 | // util 148 | function divideBy7Bytes(str) { 149 | var frameArray = []; 150 | for (var i = 0; i < str.length - 4; i += 14) { 151 | var data = str.substring(i, i + 14); 152 | frameArray.push(data); 153 | } 154 | return frameArray; 155 | } 156 | 157 | // util 158 | function littleEndianTransform(data) { 159 | var dataArray = []; 160 | for (var i = 0; i < data.length; i += 2) { 161 | dataArray.push(data.substring(i, i + 2)); 162 | } 163 | dataArray.reverse(); 164 | return dataArray; 165 | } 166 | 167 | // util 168 | function strTo10SysNub(str) { 169 | var arr = littleEndianTransform(str); 170 | return parseInt(arr.toString().replace(/,/g, ''), 16); 171 | } 172 | 173 | // util 174 | function checkDataIdIsMeasureUpload(dataId) { 175 | return parseInt(dataId) > 4096; 176 | } 177 | 178 | // configurable. 179 | function isSpecialDataId(dataID) { 180 | switch (dataID) { 181 | case 0: 182 | case 1: 183 | case 2: 184 | case 3: 185 | case 4: 186 | case 7: 187 | case 9: 188 | case 0x120: 189 | return true; 190 | default: 191 | return false; 192 | } 193 | } 194 | 195 | // configurable 196 | function ttnDataSpecialFormat(dataId, str) { 197 | var strReverse = littleEndianTransform(str); 198 | if (dataId === 2 || dataId === 3) { 199 | return strReverse.join(''); 200 | } 201 | 202 | // handle unsigned number 203 | var str2 = toBinary(strReverse); 204 | var dataArray = []; 205 | switch (dataId) { 206 | case 0: // DATA_BOARD_VERSION 207 | case 1: 208 | // DATA_SENSOR_VERSION 209 | // Using point segmentation 210 | for (var k = 0; k < str2.length; k += 16) { 211 | var tmp146 = str2.substring(k, k + 16); 212 | tmp146 = (parseInt(tmp146.substring(0, 8), 2) || 0) + '.' + (parseInt(tmp146.substring(8, 16), 2) || 0); 213 | dataArray.push(tmp146); 214 | } 215 | return dataArray.join(','); 216 | case 4: 217 | for (var i = 0; i < str2.length; i += 8) { 218 | var item = parseInt(str2.substring(i, i + 8), 2); 219 | if (item < 10) { 220 | item = '0' + item.toString(); 221 | } else { 222 | item = item.toString(); 223 | } 224 | dataArray.push(item); 225 | } 226 | return dataArray.join(''); 227 | case 7: 228 | // battery && interval 229 | return { 230 | interval: parseInt(str2.substr(0, 16), 2), 231 | power: parseInt(str2.substr(-16, 16), 2) 232 | }; 233 | case 9: 234 | var dataValue = { 235 | detectionType: parseInt(str2.substring(0, 8), 2), 236 | modelId: parseInt(str2.substring(8, 16), 2), 237 | modelVer: parseInt(str2.substring(16, 24), 2) 238 | }; 239 | // 01010000 240 | return dataValue; 241 | } 242 | } 243 | 244 | // util 245 | function ttnDataFormat(str) { 246 | var strReverse = littleEndianTransform(str); 247 | var str2 = toBinary(strReverse); 248 | if (str2.substring(0, 1) === '1') { 249 | var arr = str2.split(''); 250 | var reverseArr = []; 251 | for (var forArr = 0; forArr < arr.length; forArr++) { 252 | var item = arr[forArr]; 253 | if (parseInt(item) === 1) { 254 | reverseArr.push(0); 255 | } else { 256 | reverseArr.push(1); 257 | } 258 | } 259 | str2 = parseInt(reverseArr.join(''), 2) + 1; 260 | return parseFloat('-' + str2 / 1000); 261 | } 262 | return parseInt(str2, 2) / 1000; 263 | } 264 | 265 | // util 266 | function sensorAttrForVersion(dataValue) { 267 | var dataValueSplitArray = dataValue.split(','); 268 | return { 269 | ver_hardware: dataValueSplitArray[0], 270 | ver_software: dataValueSplitArray[1] 271 | }; 272 | } 273 | 274 | // util 275 | function toBinary(arr) { 276 | var binaryData = []; 277 | for (var forArr = 0; forArr < arr.length; forArr++) { 278 | var item = arr[forArr]; 279 | var data = parseInt(item, 16).toString(2); 280 | var dataLength = data.length; 281 | if (data.length !== 8) { 282 | for (var i = 0; i < 8 - dataLength; i++) { 283 | data = '0' + data; 284 | } 285 | } 286 | binaryData.push(data); 287 | } 288 | return binaryData.toString().replace(/,/g, ''); 289 | } 290 | 291 | // Samples 292 | // var sample = Decoder(["00", "00", "00", "01", "01", "00", "01", "00", "07", "00", "64", "00", "3C", "00", "01", "20", "01", "00", "00", "00", "00", "28", "90"], null); 293 | // var sample = Decoder(["01", "01", "10", "98", "53", "00", "00", "01", "02", "10", "A8", "7A", "00", "00", "AF", "51"], null); 294 | // var sample = Decoder(["01", "01", "00", "01", "01", "00", "01", "01", "02", "00", "6A", "01", "00", "15", "01", "03", "00", "30", "F1", "F7", "2C", "01", "04", "00", "09", "0C", "13", "14", "01", "05", "00", "7F", "4D", "00", "00", "01", "06", "00", "00", "00", "00", "00", "4C", "BE"], null); 295 | // console.log(sample); -------------------------------------------------------------------------------- /A1101/TTN/SenseCAP_A1101_TTN_Decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SenseCAP & TTN (new v3) Converter 3 | * 4 | * @since 3.0 5 | * @return Object 6 | * @param Boolean valid Indicates whether the payload is a valid payload. 7 | * @param String err The reason for the payload to be invalid. 0 means valid, minus means invalid. 8 | * @param String payload Hexadecimal string, to show the payload. 9 | * @param Array messages One or more messages are parsed according to payload. 10 | * type // Enum: 11 | * // - "report_telemetry" 12 | * // - "upload_battery" 13 | * // - "upload_interval" 14 | * // - "upload_version" 15 | * // - "upload_sensor_id" 16 | * // - "report_remove_sensor" 17 | * // - "unknown_message" 18 | * 19 | * 20 | * 21 | * 22 | * @sample-1 23 | * var sample = Decoder(["00", "00", "00", "01", "01", "00", "01", "00", "07", "00", "64", "00", "3C", "00", "01", "20", "01", "00", "00", "00", "00", "28", "90"], null); 24 | * { 25 | * valid: true, 26 | * err: 0, 27 | * payload: '0000000101000100070064003C00012001000000002890', 28 | * messages: [ 29 | * { type: 'upload_version', 30 | * hardwareVersion: '1.0', 31 | * softwareVersion: '1.1' }, 32 | * { type: 'upload_battery', battery: 100 }, 33 | * { type: 'upload_interval', interval: 3600 }, 34 | * { type: 'report_remove_sensor', channel: 1 } 35 | * ] 36 | * } 37 | * @sample-2 38 | * var sample = Decoder(["01", "01", "10", "98", "53", "00", "00", "01", "02", "10", "A8", "7A", "00", "00", "AF", "51"], null); 39 | * { 40 | * valid: true, 41 | * err: 0, 42 | * payload: '01011098530000010210A87A0000AF51', 43 | * messages: [ 44 | * { type: 'report_telemetry', 45 | * measurementId: 4097, 46 | * measurementValue: 21.4 }, 47 | * { type: 'report_telemetry', 48 | * measurementId: 4098, 49 | * measurementValue: 31.4 } 50 | * ] 51 | * } 52 | * @sample-3 53 | * var sample = Decoder(["01", "01", "00", "01", "01", "00", "01", "01", "02", "00", "6A", "01", "00", "15", "01", "03", "00", "30", "F1", "F7", "2C", "01", "04", "00", "09", "0C", "13", "14", "01", "05", "00", "7F", "4D", "00", "00", "01", "06", "00", "00", "00", "00", "00", "4C", "BE"], null); 54 | * { 55 | * valid: true, 56 | * err: 0, 57 | * payload: '010100010100010102006A01001501030030F1F72C010400090C13140105007F4D0000010600000000004CBE', 58 | * messages: [ 59 | * { type: 'upload_sensor_id', sensorId: '30F1F72C6A010015', channel: 1 } 60 | * ] 61 | * } 62 | */ 63 | 64 | /** 65 | * Entry, decoder.js 66 | */ 67 | function decodeUplink (input) { 68 | var bytes = input['bytes']; 69 | // // init 70 | var bytesString = bytes2HexString(bytes) 71 | .toLocaleUpperCase(); 72 | // var bytesString = input 73 | var decoded = { 74 | // valid 75 | valid: true, err: 0, // bytes 76 | payload: bytesString, // messages array 77 | messages: [] 78 | } 79 | 80 | // CRC check 81 | if (!crc16Check(bytesString)) { 82 | decoded['valid'] = false 83 | decoded['err'] = -1 // "crc check fail." 84 | return { data: decoded } 85 | } 86 | 87 | // Length Check 88 | if ((((bytesString.length / 2) - 2) % 7) !== 0) { 89 | decoded['valid'] = false 90 | decoded['err'] = -2 // "length check fail." 91 | return { data: decoded } 92 | } 93 | 94 | // Cache sensor id 95 | var sensorEuiLowBytes 96 | var sensorEuiHighBytes 97 | 98 | // Handle each frame 99 | var frameArray = divideBy7Bytes(bytesString) 100 | for (var forFrame = 0; forFrame < frameArray.length; forFrame++) { 101 | var frame = frameArray[forFrame] 102 | // Extract key parameters 103 | var channel = strTo10SysNub(frame.substring(0, 2)) 104 | var dataID = strTo10SysNub(frame.substring(2, 6)) 105 | var dataValue = frame.substring(6, 14) 106 | var realDataValue = isSpecialDataId(dataID) ? ttnDataSpecialFormat(dataID, dataValue) : ttnDataFormat(dataValue) 107 | 108 | if (checkDataIdIsMeasureUpload(dataID)) { 109 | // if telemetry. 110 | decoded.messages.push({ 111 | type: 'report_telemetry', measurementId: dataID, measurementValue: realDataValue 112 | }) 113 | } else if (isSpecialDataId(dataID) || (dataID === 5) || (dataID === 6)) { 114 | // if special order, except "report_sensor_id". 115 | switch (dataID) { 116 | case 0x00: 117 | // node version 118 | var versionData = sensorAttrForVersion(realDataValue) 119 | decoded.messages.push({ 120 | type: 'upload_version', hardwareVersion: versionData.ver_hardware, softwareVersion: versionData.ver_software 121 | }) 122 | break 123 | case 1: 124 | // sensor version 125 | break 126 | case 2: 127 | // sensor eui, low bytes 128 | sensorEuiLowBytes = realDataValue 129 | break 130 | case 3: 131 | // sensor eui, high bytes 132 | sensorEuiHighBytes = realDataValue 133 | break 134 | case 7: 135 | // battery power && interval 136 | decoded.messages.push({ 137 | type: 'upload_battery', battery: realDataValue.power 138 | }, { 139 | type: 'upload_interval', interval: parseInt(realDataValue.interval) * 60 140 | }) 141 | break 142 | case 9: 143 | decoded.messages.push({ 144 | type: 'model_info', 145 | detectionType: realDataValue.detectionType, 146 | modelId: realDataValue.modelId, 147 | modelVer: realDataValue.modelVer 148 | }) 149 | break 150 | case 0x120: 151 | // remove sensor 152 | decoded.messages.push({ 153 | type: 'report_remove_sensor', channel: 1 154 | }) 155 | break 156 | default: 157 | break 158 | } 159 | } else { 160 | decoded.messages.push({ 161 | type: 'unknown_message', dataID: dataID, dataValue: dataValue 162 | }) 163 | } 164 | 165 | } 166 | 167 | // if the complete id received, as "upload_sensor_id" 168 | if (sensorEuiHighBytes && sensorEuiLowBytes) { 169 | decoded.messages.unshift({ 170 | type: 'upload_sensor_id', channel: 1, sensorId: (sensorEuiHighBytes + sensorEuiLowBytes).toUpperCase() 171 | }) 172 | } 173 | // return 174 | return { data: decoded } 175 | } 176 | 177 | function crc16Check (data) { 178 | return true 179 | } 180 | 181 | // util 182 | function bytes2HexString (arrBytes) { 183 | var str = '' 184 | for (var i = 0; i < arrBytes.length; i++) { 185 | var tmp 186 | var num = arrBytes[i] 187 | if (num < 0) { 188 | tmp = (255 + num + 1).toString(16) 189 | } else { 190 | tmp = num.toString(16) 191 | } 192 | if (tmp.length === 1) { 193 | tmp = '0' + tmp 194 | } 195 | str += tmp 196 | } 197 | return str 198 | } 199 | 200 | // util 201 | function divideBy7Bytes (str) { 202 | var frameArray = [] 203 | for (var i = 0; i < str.length - 4; i += 14) { 204 | var data = str.substring(i, i + 14) 205 | frameArray.push(data) 206 | } 207 | return frameArray 208 | } 209 | 210 | // util 211 | function littleEndianTransform (data) { 212 | var dataArray = [] 213 | for (var i = 0; i < data.length; i += 2) { 214 | dataArray.push(data.substring(i, i + 2)) 215 | } 216 | dataArray.reverse() 217 | return dataArray 218 | } 219 | 220 | // util 221 | function strTo10SysNub (str) { 222 | var arr = littleEndianTransform(str) 223 | return parseInt(arr.toString() 224 | .replace(/,/g, ''), 16) 225 | } 226 | 227 | // util 228 | function checkDataIdIsMeasureUpload (dataId) { 229 | return parseInt(dataId) > 4096 230 | } 231 | 232 | // configurable. 233 | function isSpecialDataId (dataID) { 234 | switch (dataID) { 235 | case 0: 236 | case 1: 237 | case 2: 238 | case 3: 239 | case 4: 240 | case 7: 241 | case 9: 242 | case 0x120: 243 | return true 244 | default: 245 | return false 246 | } 247 | } 248 | 249 | // configurable 250 | function ttnDataSpecialFormat (dataId, str) { 251 | var strReverse = littleEndianTransform(str) 252 | if (dataId === 2 || dataId === 3) { 253 | return strReverse.join('') 254 | } 255 | 256 | // handle unsigned number 257 | var str2 = toBinary(strReverse) 258 | var dataArray = [] 259 | switch (dataId) { 260 | case 0: // DATA_BOARD_VERSION 261 | case 1: // DATA_SENSOR_VERSION 262 | // Using point segmentation 263 | for (var k = 0; k < str2.length; k += 16) { 264 | var tmp146 = str2.substring(k, k + 16) 265 | tmp146 = (parseInt(tmp146.substring(0, 8), 2) || 0) + '.' + (parseInt(tmp146.substring(8, 16), 2) || 0) 266 | dataArray.push(tmp146) 267 | } 268 | return dataArray.join(',') 269 | case 4: 270 | for (var i = 0; i < str2.length; i += 8) { 271 | var item = parseInt(str2.substring(i, i + 8), 2) 272 | if (item < 10) { 273 | item = '0' + item.toString() 274 | } else { 275 | item = item.toString() 276 | } 277 | dataArray.push(item) 278 | } 279 | return dataArray.join('') 280 | case 7: 281 | // battery && interval 282 | return { 283 | interval: parseInt(str2.substr(0, 16), 2), power: parseInt(str2.substr(-16, 16), 2) 284 | } 285 | case 9: 286 | let dataValue = { 287 | detectionType: parseInt(str2.substring(0, 8), 2), 288 | modelId: parseInt(str2.substring(8, 16), 2), 289 | modelVer: parseInt(str2.substring(16, 24), 2) 290 | } 291 | // 01010000 292 | return dataValue 293 | } 294 | } 295 | 296 | // util 297 | function ttnDataFormat (str) { 298 | var strReverse = littleEndianTransform(str) 299 | var str2 = toBinary(strReverse) 300 | if (str2.substring(0, 1) === '1') { 301 | var arr = str2.split('') 302 | var reverseArr = [] 303 | for (var forArr = 0; forArr < arr.length; forArr++) { 304 | var item = arr[forArr] 305 | if (parseInt(item) === 1) { 306 | reverseArr.push(0) 307 | } else { 308 | reverseArr.push(1) 309 | } 310 | } 311 | str2 = parseInt(reverseArr.join(''), 2) + 1 312 | return parseFloat('-' + str2 / 1000) 313 | } 314 | return parseInt(str2, 2) / 1000 315 | } 316 | 317 | // util 318 | function sensorAttrForVersion (dataValue) { 319 | var dataValueSplitArray = dataValue.split(',') 320 | return { 321 | ver_hardware: dataValueSplitArray[0], ver_software: dataValueSplitArray[1] 322 | } 323 | } 324 | 325 | // util 326 | function toBinary (arr) { 327 | var binaryData = [] 328 | for (var forArr = 0; forArr < arr.length; forArr++) { 329 | var item = arr[forArr] 330 | var data = parseInt(item, 16) 331 | .toString(2) 332 | var dataLength = data.length 333 | if (data.length !== 8) { 334 | for (var i = 0; i < 8 - dataLength; i++) { 335 | data = '0' + data 336 | } 337 | } 338 | binaryData.push(data) 339 | } 340 | return binaryData.toString() 341 | .replace(/,/g, '') 342 | } 343 | 344 | // Samples 345 | // var sample = Decoder(["00", "00", "00", "01", "01", "00", "01", "00", "07", "00", "64", "00", "3C", "00", "01", "20", "01", "00", "00", "00", "00", "28", "90"], null); 346 | // var sample = Decoder(["01", "01", "10", "98", "53", "00", "00", "01", "02", "10", "A8", "7A", "00", "00", "AF", "51"], null); 347 | // var sample = Decoder(["01", "01", "00", "01", "01", "00", "01", "01", "02", "00", "6A", "01", "00", "15", "01", "03", "00", "30", "F1", "F7", "2C", "01", "04", "00", "09", "0C", "13", "14", "01", "05", "00", "7F", "4D", "00", "00", "01", "06", "00", "00", "00", "00", "00", "4C", "BE"], null); 348 | // console.log(sample); -------------------------------------------------------------------------------- /A1102/Chirpstack/SenseCAP_A1102_TTN_Decoder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } 4 | function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } 5 | function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } 6 | function Decode(fPort, bytes, variables) { 7 | var bytesString = bytes2HexString(bytes).toLocaleUpperCase(); 8 | var fport = parseInt(fPort); 9 | var result = { 10 | 'err': 0, 11 | 'payload': bytesString, 12 | 'valid': true, 13 | messages: [] 14 | }; 15 | var splitArray = dataSplit(bytesString); 16 | // data decoder 17 | var decoderArray = []; 18 | var modelName = 'unknown model'; 19 | var classes = null; 20 | var _iterator = _createForOfIteratorHelper(splitArray), 21 | _step; 22 | try { 23 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 24 | var fragment = _step.value; 25 | if (fragment.dataId !== '33') { 26 | continue; 27 | } 28 | fragment.modelId = parseInt(loraWANV2DataFormat(fragment.dataValue.substring(12, 20), 1000)) - 1000000; 29 | var modelInfo = getModelInfo(fragment.modelId + ''); 30 | if (modelInfo) { 31 | modelName = modelInfo.modelName; 32 | classes = modelInfo.classes; 33 | } 34 | } 35 | } catch (err) { 36 | _iterator.e(err); 37 | } finally { 38 | _iterator.f(); 39 | } 40 | for (var i = 0; i < splitArray.length; i++) { 41 | var item = splitArray[i]; 42 | var dataId = item.dataId; 43 | var dataValue = item.dataValue; 44 | var messages = dataIdAndDataValueJudge(dataId, dataValue, modelName, classes); 45 | if (!messages || messages.length === 0) { 46 | continue; 47 | } 48 | decoderArray.push(messages); 49 | } 50 | result.messages = decoderArray; 51 | return { 52 | data: result 53 | }; 54 | } 55 | 56 | /** 57 | * data splits 58 | * @param bytes 59 | * @returns {*[]} 60 | */ 61 | function dataSplit(bytes) { 62 | var frameArray = []; 63 | for (var i = 0; i < bytes.length; i++) { 64 | var remainingValue = bytes; 65 | var dataId = remainingValue.substring(0, 2); 66 | var dataValue = void 0; 67 | var dataObj = {}; 68 | switch (dataId) { 69 | case '01': 70 | case '20': 71 | case '21': 72 | case '30': 73 | case '31': 74 | case '33': 75 | case '40': 76 | case '41': 77 | case '42': 78 | case '43': 79 | case '44': 80 | case '45': 81 | dataValue = remainingValue.substring(2, 22); 82 | bytes = remainingValue.substring(22); 83 | dataObj = { 84 | 'dataId': dataId, 85 | 'dataValue': dataValue 86 | }; 87 | break; 88 | case '02': 89 | dataValue = remainingValue.substring(2, 18); 90 | bytes = remainingValue.substring(18); 91 | dataObj = { 92 | 'dataId': '02', 93 | 'dataValue': dataValue 94 | }; 95 | break; 96 | case '03': 97 | case '06': 98 | dataValue = remainingValue.substring(2, 4); 99 | bytes = remainingValue.substring(4); 100 | dataObj = { 101 | 'dataId': dataId, 102 | 'dataValue': dataValue 103 | }; 104 | break; 105 | case '05': 106 | case '34': 107 | dataValue = bytes.substring(2, 10); 108 | bytes = remainingValue.substring(10); 109 | dataObj = { 110 | 'dataId': dataId, 111 | 'dataValue': dataValue 112 | }; 113 | break; 114 | case '04': 115 | case '10': 116 | case '32': 117 | case '35': 118 | case '36': 119 | case '37': 120 | case '38': 121 | case '39': 122 | dataValue = bytes.substring(2, 20); 123 | bytes = remainingValue.substring(20); 124 | dataObj = { 125 | 'dataId': dataId, 126 | 'dataValue': dataValue 127 | }; 128 | break; 129 | default: 130 | dataValue = '9'; 131 | break; 132 | } 133 | if (dataValue.length < 2) { 134 | break; 135 | } 136 | frameArray.push(dataObj); 137 | } 138 | return frameArray; 139 | } 140 | function dataIdAndDataValueJudge(dataId, dataValue, modelName, classes) { 141 | var messages = []; 142 | var dataOne; 143 | var dataTwo; 144 | switch (dataId) { 145 | case '01': 146 | break; 147 | case '02': 148 | break; 149 | case '03': 150 | break; 151 | case '04': 152 | break; 153 | case '05': 154 | break; 155 | case '06': 156 | var errorCode = dataValue; 157 | var descZh; 158 | switch (errorCode) { 159 | case '00': 160 | descZh = 'CCL_SENSOR_ERROR_NONE'; 161 | break; 162 | case '01': 163 | descZh = 'CCL_SENSOR_NOT_FOUND'; 164 | break; 165 | case '02': 166 | descZh = 'CCL_SENSOR_WAKEUP_ERROR'; 167 | break; 168 | case '03': 169 | descZh = 'CCL_SENSOR_NOT_RESPONSE'; 170 | break; 171 | case '04': 172 | descZh = 'CCL_SENSOR_DATA_EMPTY'; 173 | break; 174 | case '05': 175 | descZh = 'CCL_SENSOR_DATA_HEAD_ERROR'; 176 | break; 177 | case '06': 178 | descZh = 'CCL_SENSOR_DATA_CRC_ERROR'; 179 | break; 180 | case '07': 181 | descZh = 'CCL_SENSOR_DATA_B1_NO_VALID'; 182 | break; 183 | case '08': 184 | descZh = 'CCL_SENSOR_DATA_B2_NO_VALID'; 185 | break; 186 | case '09': 187 | descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH'; 188 | break; 189 | case '0A': 190 | descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED'; 191 | break; 192 | case '0B': 193 | descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED'; 194 | break; 195 | case '0C': 196 | descZh = 'CCL_SENSOR_DATA_VALUE_HI'; 197 | break; 198 | case '0D': 199 | descZh = 'CCL_SENSOR_DATA_VALUE_LOW'; 200 | break; 201 | case '0E': 202 | descZh = 'CCL_SENSOR_DATA_VALUE_MISSED'; 203 | break; 204 | case '0F': 205 | descZh = 'CCL_SENSOR_ARG_INVAILD'; 206 | break; 207 | case '10': 208 | descZh = 'CCL_SENSOR_RS485_MASTER_BUSY'; 209 | break; 210 | case '11': 211 | descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR'; 212 | break; 213 | case '12': 214 | descZh = 'CCL_SENSOR_RS485_REG_MISSED'; 215 | break; 216 | case '13': 217 | descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR'; 218 | break; 219 | case '14': 220 | descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR'; 221 | break; 222 | case '15': 223 | descZh = 'CCL_SENSOR_CONFIG_ERROR'; 224 | break; 225 | case 'FF': 226 | descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW'; 227 | break; 228 | default: 229 | descZh = 'CC_OTHER_FAILED'; 230 | break; 231 | } 232 | messages = [{ 233 | measurementId: '4101', 234 | type: 'sensor_error_event', 235 | errCode: errorCode, 236 | descZh: descZh 237 | }]; 238 | break; 239 | case '10': 240 | var statusValue = dataValue.substring(0, 2); 241 | var _loraWANV2BitDataForm = loraWANV2BitDataFormat(statusValue), 242 | status = _loraWANV2BitDataForm.status, 243 | type = _loraWANV2BitDataForm.type; 244 | var sensecapId = dataValue.substring(2); 245 | messages = [{ 246 | status: status, 247 | channelType: type, 248 | sensorEui: sensecapId 249 | }]; 250 | break; 251 | case '30': 252 | case '31': 253 | var channelInfoOne = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)); 254 | dataOne = loraWANV2DataFormat(dataValue.substring(4, 12)); 255 | dataTwo = loraWANV2DataFormat(dataValue.substring(12, 20)); 256 | if (parseInt(dataOne) !== -1000) { 257 | if (modelName === 'Digital Meter Electricity') { 258 | var classId = parseInt(dataOne / 1000); 259 | var targetName = classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown'; 260 | messages.push({ 261 | measurementValue: dataOne / 1000, 262 | measurementId: '4165', 263 | type: "".concat(targetName, " value") 264 | }); 265 | } else { 266 | var _classId = parseInt(dataOne / 1000); 267 | var _targetName = classes !== null && classes[_classId + ''] ? classes[_classId + ''] : 'unknown'; 268 | messages.push({ 269 | measurementValue: dataOne / 1000 + 0.01, 270 | measurementId: '4165', 271 | type: "".concat(_targetName, " Conf") 272 | }); 273 | } 274 | } 275 | if (parseInt(dataTwo) !== -1000) { 276 | if (modelName === 'Digital Meter Electricity') { 277 | var _classId2 = parseInt(dataTwo / 1000); 278 | var _targetName2 = classes !== null && classes[_classId2 + ''] ? classes[_classId2 + ''] : 'unknown'; 279 | messages.push({ 280 | measurementValue: dataTwo / 1000, 281 | measurementId: '4166', 282 | type: "".concat(_targetName2, " value") 283 | }); 284 | } else { 285 | var _classId3 = parseInt(dataTwo / 1000); 286 | var _targetName3 = classes !== null && classes[_classId3 + ''] ? classes[_classId3 + ''] : 'unknown'; 287 | messages.push({ 288 | measurementValue: dataTwo / 1000 + 0.01, 289 | measurementId: '4166', 290 | type: "".concat(_targetName3, " Conf") 291 | }); 292 | } 293 | } 294 | break; 295 | case '32': 296 | var channelInfoTwo = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)); 297 | dataOne = loraWANV2DataFormat(dataValue.substring(2, 10)); 298 | dataTwo = loraWANV2DataFormat(dataValue.substring(10, 18)); 299 | if (parseInt(dataOne) !== -1000) { 300 | if (modelName === 'Digital Meter Electricity') { 301 | var _classId4 = parseInt(dataOne / 1000); 302 | var _targetName4 = classes !== null && classes[_classId4 + ''] ? classes[_classId4 + ''] : 'unknown'; 303 | messages.push({ 304 | measurementValue: dataOne / 1000, 305 | measurementId: 4164 + parseInt(channelInfoTwo.one) + '', 306 | type: "".concat(_targetName4, " value") 307 | }); 308 | } else { 309 | var _classId5 = parseInt(dataOne / 1000); 310 | var _targetName5 = classes !== null && classes[_classId5 + ''] ? classes[_classId5 + ''] : 'unknown'; 311 | messages.push({ 312 | measurementValue: dataOne / 1000 + 0.01, 313 | measurementId: 4164 + parseInt(channelInfoTwo.one) + '', 314 | type: "".concat(_targetName5, " Conf") 315 | }); 316 | } 317 | } 318 | if (parseInt(dataTwo) !== -1000) { 319 | if (modelName === 'Digital Meter Electricity') { 320 | var _classId6 = parseInt(dataTwo / 1000); 321 | var _targetName6 = classes !== null && classes[_classId6 + ''] ? classes[_classId6 + ''] : 'unknown'; 322 | messages.push({ 323 | measurementValue: dataTwo / 1000, 324 | measurementId: 4164 + parseInt(channelInfoTwo.two) + '', 325 | type: "".concat(_targetName6, " value") 326 | }); 327 | } else { 328 | var _classId7 = parseInt(dataTwo / 1000); 329 | var _targetName7 = classes !== null && classes[_classId7 + ''] ? classes[_classId7 + ''] : 'unknown'; 330 | messages.push({ 331 | measurementValue: dataTwo / 1000 + 0.01, 332 | measurementId: 4164 + parseInt(channelInfoTwo.two) + '', 333 | type: "".concat(_targetName7, " Conf") 334 | }); 335 | } 336 | } 337 | break; 338 | case '33': 339 | var channelInfoThree = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)); 340 | dataOne = loraWANV2DataFormat(dataValue.substring(4, 12)); 341 | dataTwo = loraWANV2DataFormat(dataValue.substring(12, 20)); 342 | if (parseInt(dataOne) !== -1000) { 343 | if (modelName === 'Digital Meter Electricity') { 344 | var _classId8 = parseInt(dataOne / 1000); 345 | var _targetName8 = classes !== null && classes[_classId8 + ''] ? classes[_classId8 + ''] : 'unknown'; 346 | messages.push({ 347 | measurementValue: dataOne / 1000, 348 | measurementId: 4164 + parseInt(channelInfoThree.one) + '', 349 | type: "".concat(_targetName8, " value") 350 | }); 351 | } else { 352 | var _classId9 = parseInt(dataOne / 1000); 353 | var _targetName9 = classes !== null && classes[_classId9 + ''] ? classes[_classId9 + ''] : 'unknown'; 354 | messages.push({ 355 | measurementValue: dataOne / 1000 + 0.01, 356 | measurementId: 4164 + parseInt(channelInfoThree.one) + '', 357 | type: "".concat(_targetName9, " Conf") 358 | }); 359 | } 360 | } 361 | if (parseInt(dataTwo) !== -1000 && parseInt(channelInfoThree.two) === 10) { 362 | messages.push({ 363 | measurementValue: modelName, 364 | measurementId: 4164 + parseInt(channelInfoThree.two) + '', 365 | type: "Model Type" 366 | }); 367 | } 368 | break; 369 | case '34': 370 | break; 371 | case '35': 372 | case '36': 373 | break; 374 | case '37': 375 | break; 376 | case '38': 377 | break; 378 | case '39': 379 | var electricityWhetherTD = dataValue.substring(0, 2); 380 | var hwvTD = dataValue.substring(2, 6); 381 | var bdvTD = dataValue.substring(6, 10); 382 | var sensorAcquisitionIntervalTD = dataValue.substring(10, 14); 383 | var gpsAcquisitionIntervalTD = dataValue.substring(14, 18); 384 | messages = [{ 385 | 'Battery(%)': loraWANV2DataFormat(electricityWhetherTD), 386 | 'Hardware Version': "".concat(loraWANV2DataFormat(hwvTD.substring(0, 2)), ".").concat(loraWANV2DataFormat(hwvTD.substring(2, 4))), 387 | 'Firmware Version': "".concat(loraWANV2DataFormat(bdvTD.substring(0, 2)), ".").concat(loraWANV2DataFormat(bdvTD.substring(2, 4))), 388 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalTD)) * 60, 389 | 'thresholdMeasureInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalTD)) 390 | }]; 391 | break; 392 | case '40': 393 | case '41': 394 | break; 395 | case '42': 396 | break; 397 | case '43': 398 | case '44': 399 | break; 400 | case '45': 401 | break; 402 | default: 403 | break; 404 | } 405 | return messages; 406 | } 407 | 408 | /** 409 | * 410 | * data formatting 411 | * @param str 412 | * @param divisor 413 | * @returns {string|number} 414 | */ 415 | function loraWANV2DataFormat(str) { 416 | var divisor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; 417 | var strReverse = bigEndianTransform(str); 418 | var str2 = toBinary(strReverse); 419 | if (str2.substring(0, 1) === '1') { 420 | var arr = str2.split(''); 421 | var reverseArr = arr.map(function (item) { 422 | if (parseInt(item) === 1) { 423 | return 0; 424 | } else { 425 | return 1; 426 | } 427 | }); 428 | str2 = parseInt(reverseArr.join(''), 2) + 1; 429 | return parseFloat('-' + str2 / divisor); 430 | } 431 | return parseInt(str2, 2) / divisor; 432 | } 433 | 434 | /** 435 | * Handling big-endian data formats 436 | * @param data 437 | * @returns {*[]} 438 | */ 439 | function bigEndianTransform(data) { 440 | var dataArray = []; 441 | for (var i = 0; i < data.length; i += 2) { 442 | dataArray.push(data.substring(i, i + 2)); 443 | } 444 | // array of hex 445 | return dataArray; 446 | } 447 | 448 | /** 449 | * Convert to an 8-digit binary number with 0s in front of the number 450 | * @param arr 451 | * @returns {string} 452 | */ 453 | function toBinary(arr) { 454 | var binaryData = arr.map(function (item) { 455 | var data = parseInt(item, 16).toString(2); 456 | var dataLength = data.length; 457 | if (data.length !== 8) { 458 | for (var i = 0; i < 8 - dataLength; i++) { 459 | data = "0" + data; 460 | } 461 | } 462 | return data; 463 | }); 464 | var ret = binaryData.toString().replace(/,/g, ''); 465 | return ret; 466 | } 467 | 468 | /** 469 | * sensor 470 | * @param str 471 | * @returns {{channel: number, type: number, status: number}} 472 | */ 473 | function loraWANV2BitDataFormat(str) { 474 | var strReverse = bigEndianTransform(str); 475 | var str2 = toBinary(strReverse); 476 | var channel = parseInt(str2.substring(0, 4), 2); 477 | var status = parseInt(str2.substring(4, 5), 2); 478 | var type = parseInt(str2.substring(5), 2); 479 | return { 480 | channel: channel, 481 | status: status, 482 | type: type 483 | }; 484 | } 485 | 486 | /** 487 | * channel info 488 | * @param str 489 | * @returns {{channelTwo: number, channelOne: number}} 490 | */ 491 | function loraWANV2ChannelBitFormat(str) { 492 | var strReverse = bigEndianTransform(str); 493 | var str2 = toBinary(strReverse); 494 | var one = parseInt(str2.substring(0, 4), 2); 495 | var two = parseInt(str2.substring(4, 8), 2); 496 | var resultInfo = { 497 | one: one, 498 | two: two 499 | }; 500 | return resultInfo; 501 | } 502 | 503 | /** 504 | * data log status bit 505 | * @param str 506 | * @returns {{total: number, level: number, isTH: number}} 507 | */ 508 | function loraWANV2DataLogBitFormat(str) { 509 | var strReverse = bigEndianTransform(str); 510 | var str2 = toBinary(strReverse); 511 | var isTH = parseInt(str2.substring(0, 1), 2); 512 | var total = parseInt(str2.substring(1, 5), 2); 513 | var left = parseInt(str2.substring(5), 2); 514 | var resultInfo = { 515 | isTH: isTH, 516 | total: total, 517 | left: left 518 | }; 519 | return resultInfo; 520 | } 521 | function bytes2HexString(arrBytes) { 522 | var str = ''; 523 | for (var i = 0; i < arrBytes.length; i++) { 524 | var tmp; 525 | var num = arrBytes[i]; 526 | if (num < 0) { 527 | tmp = (255 + num + 1).toString(16); 528 | } else { 529 | tmp = num.toString(16); 530 | } 531 | if (tmp.length === 1) { 532 | tmp = '0' + tmp; 533 | } 534 | str += tmp; 535 | } 536 | return str; 537 | } 538 | function getModelInfo(modelId) { 539 | var modelTable = { 540 | '60086': { 541 | modelName: 'Person Detection--Swift YOLO', 542 | task: 'Detection', 543 | classes: { 544 | "0": "person" 545 | } 546 | }, 547 | '60113': { 548 | modelName: 'Digital Meter Electricity', 549 | task: 'Detection', 550 | classes: { 551 | "0": "zero", 552 | "1": "one", 553 | "2": "two", 554 | "3": "three", 555 | "4": "four", 556 | "5": "five", 557 | "6": "six", 558 | "7": "seven", 559 | "8": "eight", 560 | "9": "nine" 561 | } 562 | }, 563 | '60242': { 564 | modelName: 'Person Detection', 565 | task: 'Detection', 566 | classes: { 567 | "0": "Person" 568 | } 569 | } 570 | }; 571 | return modelTable[modelId]; 572 | } -------------------------------------------------------------------------------- /A1102/TTN/SenseCAP_A1102_TTN_Decoder.js: -------------------------------------------------------------------------------- 1 | 2 | function decodeUplink (input, port) { 3 | var bytes = input['bytes'] 4 | bytes = bytes2HexString(bytes) 5 | .toLocaleUpperCase() 6 | 7 | let result = { 8 | 'err': 0, 'payload': bytes, 'valid': true, messages: [] 9 | } 10 | let splitArray = dataSplit(bytes) 11 | // data decoder 12 | let decoderArray = [] 13 | let modelName = 'unknown model' 14 | let classes = null 15 | for (let fragment of splitArray) { 16 | if (fragment.dataId !== '33') { 17 | continue 18 | } 19 | fragment.modelId = parseInt(loraWANV2DataFormat(fragment.dataValue.substring(12, 20), 1000)) - 1000000 20 | let modelInfo = getModelInfo(fragment.modelId + '') 21 | if (modelInfo) { 22 | modelName = modelInfo.modelName 23 | classes = modelInfo.classes 24 | } 25 | } 26 | for (let i = 0; i < splitArray.length; i++) { 27 | let item = splitArray[i] 28 | let dataId = item.dataId 29 | let dataValue = item.dataValue 30 | let messages = dataIdAndDataValueJudge(dataId, dataValue, modelName, classes) 31 | if (!messages || messages.length === 0) { 32 | continue 33 | } 34 | decoderArray.push(messages) 35 | } 36 | result.messages = decoderArray 37 | return { data: result } 38 | } 39 | 40 | /** 41 | * data splits 42 | * @param bytes 43 | * @returns {*[]} 44 | */ 45 | function dataSplit (bytes) { 46 | let frameArray = [] 47 | 48 | for (let i = 0; i < bytes.length; i++) { 49 | let remainingValue = bytes 50 | let dataId = remainingValue.substring(0, 2) 51 | let dataValue 52 | let dataObj = {} 53 | switch (dataId) { 54 | case '01' : 55 | case '20' : 56 | case '21' : 57 | case '30' : 58 | case '31' : 59 | case '33' : 60 | case '40' : 61 | case '41' : 62 | case '42' : 63 | case '43' : 64 | case '44' : 65 | case '45' : 66 | dataValue = remainingValue.substring(2, 22) 67 | bytes = remainingValue.substring(22) 68 | dataObj = { 69 | 'dataId': dataId, 'dataValue': dataValue 70 | } 71 | break 72 | case '02': 73 | dataValue = remainingValue.substring(2, 18) 74 | bytes = remainingValue.substring(18) 75 | dataObj = { 76 | 'dataId': '02', 'dataValue': dataValue 77 | } 78 | break 79 | case '03' : 80 | case '06': 81 | dataValue = remainingValue.substring(2, 4) 82 | bytes = remainingValue.substring(4) 83 | dataObj = { 84 | 'dataId': dataId, 'dataValue': dataValue 85 | } 86 | break 87 | case '05' : 88 | case '34': 89 | dataValue = bytes.substring(2, 10) 90 | bytes = remainingValue.substring(10) 91 | dataObj = { 92 | 'dataId': dataId, 'dataValue': dataValue 93 | } 94 | break 95 | case '04': 96 | case '10': 97 | case '32': 98 | case '35': 99 | case '36': 100 | case '37': 101 | case '38': 102 | case '39': 103 | dataValue = bytes.substring(2, 20) 104 | bytes = remainingValue.substring(20) 105 | dataObj = { 106 | 'dataId': dataId, 'dataValue': dataValue 107 | } 108 | break 109 | default: 110 | dataValue = '9' 111 | break 112 | } 113 | if (dataValue.length < 2) { 114 | break 115 | } 116 | frameArray.push(dataObj) 117 | } 118 | return frameArray 119 | } 120 | 121 | function dataIdAndDataValueJudge (dataId, dataValue, modelName, classes) { 122 | let messages = [] 123 | let dataOne 124 | let dataTwo 125 | switch (dataId) { 126 | case '01': 127 | break 128 | case '02': 129 | break 130 | case '03': 131 | break 132 | case '04': 133 | break 134 | case '05': 135 | break 136 | case '06': 137 | let errorCode = dataValue 138 | let descZh 139 | switch (errorCode) { 140 | case '00': 141 | descZh = 'CCL_SENSOR_ERROR_NONE' 142 | break 143 | case '01': 144 | descZh = 'CCL_SENSOR_NOT_FOUND' 145 | break 146 | case '02': 147 | descZh = 'CCL_SENSOR_WAKEUP_ERROR' 148 | break 149 | case '03': 150 | descZh = 'CCL_SENSOR_NOT_RESPONSE' 151 | break 152 | case '04': 153 | descZh = 'CCL_SENSOR_DATA_EMPTY' 154 | break 155 | case '05': 156 | descZh = 'CCL_SENSOR_DATA_HEAD_ERROR' 157 | break 158 | case '06': 159 | descZh = 'CCL_SENSOR_DATA_CRC_ERROR' 160 | break 161 | case '07': 162 | descZh = 'CCL_SENSOR_DATA_B1_NO_VALID' 163 | break 164 | case '08': 165 | descZh = 'CCL_SENSOR_DATA_B2_NO_VALID' 166 | break 167 | case '09': 168 | descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH' 169 | break 170 | case '0A': 171 | descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED' 172 | break 173 | case '0B': 174 | descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED' 175 | break 176 | case '0C': 177 | descZh = 'CCL_SENSOR_DATA_VALUE_HI' 178 | break 179 | case '0D': 180 | descZh = 'CCL_SENSOR_DATA_VALUE_LOW' 181 | break 182 | case '0E': 183 | descZh = 'CCL_SENSOR_DATA_VALUE_MISSED' 184 | break 185 | case '0F': 186 | descZh = 'CCL_SENSOR_ARG_INVAILD' 187 | break 188 | case '10': 189 | descZh = 'CCL_SENSOR_RS485_MASTER_BUSY' 190 | break 191 | case '11': 192 | descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR' 193 | break 194 | case '12': 195 | descZh = 'CCL_SENSOR_RS485_REG_MISSED' 196 | break 197 | case '13': 198 | descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR' 199 | break 200 | case '14': 201 | descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR' 202 | break 203 | case '15': 204 | descZh = 'CCL_SENSOR_CONFIG_ERROR' 205 | break 206 | case 'FF': 207 | descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW' 208 | break 209 | default: 210 | descZh = 'CC_OTHER_FAILED' 211 | break 212 | } 213 | messages = [{ 214 | measurementId: '4101', type: 'sensor_error_event', errCode: errorCode, descZh 215 | }] 216 | break 217 | case '10': 218 | let statusValue = dataValue.substring(0, 2) 219 | let { status, type } = loraWANV2BitDataFormat(statusValue) 220 | let sensecapId = dataValue.substring(2) 221 | messages = [{ 222 | status: status, channelType: type, sensorEui: sensecapId 223 | }] 224 | break 225 | case '30': 226 | case '31': 227 | let channelInfoOne = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)) 228 | dataOne = loraWANV2DataFormat(dataValue.substring(4, 12)) 229 | dataTwo = loraWANV2DataFormat(dataValue.substring(12, 20)) 230 | if (parseInt(dataOne) !== -1000) { 231 | if (modelName === 'Digital Meter Electricity') { 232 | let classId = parseInt(dataOne / 1000) 233 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 234 | messages.push({ 235 | measurementValue: dataOne / 1000, 236 | measurementId: '4165', 237 | type: `${targetName} value` 238 | }) 239 | } else { 240 | let classId = parseInt(dataOne / 1000) 241 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 242 | messages.push({ 243 | measurementValue: (dataOne / 1000) + 0.01, 244 | measurementId: '4165', 245 | type: `${targetName} Conf` 246 | }) 247 | } 248 | } 249 | if (parseInt(dataTwo) !== -1000) { 250 | if (modelName === 'Digital Meter Electricity') { 251 | let classId = parseInt(dataTwo / 1000) 252 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 253 | messages.push({ 254 | measurementValue: dataTwo / 1000, 255 | measurementId: '4166', 256 | type: `${targetName} value` 257 | }) 258 | } else { 259 | let classId = parseInt(dataTwo / 1000) 260 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 261 | messages.push({ 262 | measurementValue: (dataTwo / 1000) + 0.01, 263 | measurementId: '4166', 264 | type: `${targetName} Conf` 265 | }) 266 | } 267 | } 268 | break 269 | case '32': 270 | let channelInfoTwo = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)) 271 | dataOne = loraWANV2DataFormat(dataValue.substring(2, 10)) 272 | dataTwo = loraWANV2DataFormat(dataValue.substring(10, 18)) 273 | if (parseInt(dataOne) !== -1000) { 274 | if (modelName === 'Digital Meter Electricity') { 275 | let classId = parseInt(dataOne / 1000) 276 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 277 | messages.push({ 278 | measurementValue: dataOne / 1000, 279 | measurementId: 4164 + parseInt(channelInfoTwo.one) + '', 280 | type: `${targetName} value` 281 | }) 282 | } else { 283 | let classId = parseInt(dataOne / 1000) 284 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 285 | messages.push({ 286 | measurementValue: (dataOne / 1000) + 0.01, 287 | measurementId: 4164 + parseInt(channelInfoTwo.one) + '', 288 | type: `${targetName} Conf` 289 | }) 290 | } 291 | } 292 | if (parseInt(dataTwo) !== -1000) { 293 | if (modelName === 'Digital Meter Electricity') { 294 | let classId = parseInt(dataTwo / 1000) 295 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 296 | messages.push({ 297 | measurementValue: dataTwo / 1000, 298 | measurementId: 4164 + parseInt(channelInfoTwo.two) + '', 299 | type: `${targetName} value` 300 | }) 301 | } else { 302 | let classId = parseInt(dataTwo / 1000) 303 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 304 | messages.push({ 305 | measurementValue: (dataTwo / 1000) + 0.01, 306 | measurementId: 4164 + parseInt(channelInfoTwo.two) + '', 307 | type: `${targetName} Conf` 308 | }) 309 | } 310 | } 311 | break 312 | case '33': 313 | let channelInfoThree = loraWANV2ChannelBitFormat(dataValue.substring(0, 2)) 314 | dataOne = loraWANV2DataFormat(dataValue.substring(4, 12)) 315 | dataTwo = loraWANV2DataFormat(dataValue.substring(12, 20)) 316 | if (parseInt(dataOne) !== -1000) { 317 | if (modelName === 'Digital Meter Electricity') { 318 | let classId = parseInt(dataOne / 1000) 319 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 320 | messages.push({ 321 | measurementValue: dataOne / 1000, 322 | measurementId: 4164 + parseInt(channelInfoThree.one) + '', 323 | type: `${targetName} value` 324 | }) 325 | } else { 326 | let classId = parseInt(dataOne / 1000) 327 | let targetName = (classes !== null && classes[classId + ''] ? classes[classId + ''] : 'unknown') 328 | messages.push({ 329 | measurementValue: (dataOne / 1000) + 0.01, 330 | measurementId: 4164 + parseInt(channelInfoThree.one) + '', 331 | type: `${targetName} Conf` 332 | }) 333 | } 334 | } 335 | if (parseInt(dataTwo) !== -1000 && parseInt(channelInfoThree.two) === 10) { 336 | messages.push({ 337 | measurementValue: modelName, 338 | measurementId: 4164 + parseInt(channelInfoThree.two) + '', 339 | type: `Model Type` 340 | }) 341 | } 342 | break 343 | case '34': 344 | break 345 | case '35': 346 | case '36': 347 | break 348 | case '37': 349 | break 350 | case '38': 351 | break 352 | case '39': 353 | let electricityWhetherTD = dataValue.substring(0, 2) 354 | let hwvTD = dataValue.substring(2, 6) 355 | let bdvTD = dataValue.substring(6, 10) 356 | let sensorAcquisitionIntervalTD = dataValue.substring(10, 14) 357 | let gpsAcquisitionIntervalTD = dataValue.substring(14, 18) 358 | messages = [{ 359 | 'Battery(%)': loraWANV2DataFormat(electricityWhetherTD), 360 | 'Hardware Version': `${loraWANV2DataFormat(hwvTD.substring(0, 2))}.${loraWANV2DataFormat(hwvTD.substring(2, 4))}`, 361 | 'Firmware Version': `${loraWANV2DataFormat(bdvTD.substring(0, 2))}.${loraWANV2DataFormat(bdvTD.substring(2, 4))}`, 362 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalTD)) * 60, 363 | 'thresholdMeasureInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalTD)) 364 | }] 365 | break 366 | case '40': 367 | case '41': 368 | break 369 | case '42': 370 | break 371 | case '43': 372 | case '44': 373 | break 374 | case '45': 375 | break 376 | default: 377 | break 378 | } 379 | return messages 380 | } 381 | 382 | /** 383 | * 384 | * data formatting 385 | * @param str 386 | * @param divisor 387 | * @returns {string|number} 388 | */ 389 | function loraWANV2DataFormat (str, divisor = 1) { 390 | let strReverse = bigEndianTransform(str) 391 | let str2 = toBinary(strReverse) 392 | if (str2.substring(0, 1) === '1') { 393 | let arr = str2.split('') 394 | let reverseArr = arr.map((item) => { 395 | if (parseInt(item) === 1) { 396 | return 0 397 | } else { 398 | return 1 399 | } 400 | }) 401 | str2 = parseInt(reverseArr.join(''), 2) + 1 402 | return parseFloat('-' + str2 / divisor) 403 | } 404 | return parseInt(str2, 2) / divisor 405 | } 406 | 407 | /** 408 | * Handling big-endian data formats 409 | * @param data 410 | * @returns {*[]} 411 | */ 412 | function bigEndianTransform (data) { 413 | let dataArray = [] 414 | for (let i = 0; i < data.length; i += 2) { 415 | dataArray.push(data.substring(i, i + 2)) 416 | } 417 | // array of hex 418 | return dataArray 419 | } 420 | 421 | /** 422 | * Convert to an 8-digit binary number with 0s in front of the number 423 | * @param arr 424 | * @returns {string} 425 | */ 426 | function toBinary (arr) { 427 | let binaryData = arr.map((item) => { 428 | let data = parseInt(item, 16) 429 | .toString(2) 430 | let dataLength = data.length 431 | if (data.length !== 8) { 432 | for (let i = 0; i < 8 - dataLength; i++) { 433 | data = `0` + data 434 | } 435 | } 436 | return data 437 | }) 438 | let ret = binaryData.toString() 439 | .replace(/,/g, '') 440 | return ret 441 | } 442 | 443 | /** 444 | * sensor 445 | * @param str 446 | * @returns {{channel: number, type: number, status: number}} 447 | */ 448 | function loraWANV2BitDataFormat (str) { 449 | let strReverse = bigEndianTransform(str) 450 | let str2 = toBinary(strReverse) 451 | let channel = parseInt(str2.substring(0, 4), 2) 452 | let status = parseInt(str2.substring(4, 5), 2) 453 | let type = parseInt(str2.substring(5), 2) 454 | return { channel, status, type } 455 | } 456 | 457 | /** 458 | * channel info 459 | * @param str 460 | * @returns {{channelTwo: number, channelOne: number}} 461 | */ 462 | function loraWANV2ChannelBitFormat (str) { 463 | let strReverse = bigEndianTransform(str) 464 | let str2 = toBinary(strReverse) 465 | let one = parseInt(str2.substring(0, 4), 2) 466 | let two = parseInt(str2.substring(4, 8), 2) 467 | let resultInfo = { 468 | one: one, two: two 469 | } 470 | return resultInfo 471 | } 472 | 473 | /** 474 | * data log status bit 475 | * @param str 476 | * @returns {{total: number, level: number, isTH: number}} 477 | */ 478 | function loraWANV2DataLogBitFormat (str) { 479 | let strReverse = bigEndianTransform(str) 480 | let str2 = toBinary(strReverse) 481 | let isTH = parseInt(str2.substring(0, 1), 2) 482 | let total = parseInt(str2.substring(1, 5), 2) 483 | let left = parseInt(str2.substring(5), 2) 484 | let resultInfo = { 485 | isTH: isTH, total: total, left: left 486 | } 487 | return resultInfo 488 | } 489 | 490 | function bytes2HexString (arrBytes) { 491 | var str = '' 492 | for (var i = 0; i < arrBytes.length; i++) { 493 | var tmp 494 | var num = arrBytes[i] 495 | if (num < 0) { 496 | tmp = (255 + num + 1).toString(16) 497 | } else { 498 | tmp = num.toString(16) 499 | } 500 | if (tmp.length === 1) { 501 | tmp = '0' + tmp 502 | } 503 | str += tmp 504 | } 505 | return str 506 | } 507 | 508 | function getModelInfo (modelId) { 509 | let modelTable = { 510 | '60086':{ 511 | modelName:'Person Detection--Swift YOLO', 512 | task: 'Detection', 513 | classes: 514 | { 515 | "0": "person" 516 | } 517 | }, 518 | '60113':{ 519 | modelName:'Digital Meter Electricity', 520 | task: 'Detection', 521 | classes: 522 | { 523 | "0": "zero", 524 | "1": "one", 525 | "2": "two", 526 | "3": "three", 527 | "4": "four", 528 | "5": "five", 529 | "6": "six", 530 | "7": "seven", 531 | "8": "eight", 532 | "9": "nine" 533 | } 534 | }, 535 | '60242':{ 536 | modelName:'Person Detection', 537 | task: 'Detection', 538 | classes: 539 | { 540 | "0": "Person" 541 | } 542 | 543 | }, 544 | } 545 | return modelTable[modelId] 546 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SenseCAP-Decoder 2 | 3 | TTN payload decoding script for SenseCAP LoRaWAN messages 4 | This script is used for decoding the LoRaWAN messages sent from the SenseCAP nodes. After decoding, users' applications will get more friendly and readable messages from TTN data API. 5 | 6 | ## How to Use 7 | 8 | #### 1. Prerequisites 9 | 10 | We assume that you already setup your gateway and SenseCAP nodes correctly, and the SenseCAP nodes can send messages to your TTN application. You can already observe the data transmisstion through TTN console. 11 | 12 | ![image](https://user-images.githubusercontent.com/5130185/76604764-48b49180-654a-11ea-992b-761bf8179590.png) 13 | 14 | #### 2. Configure the Payload Decoder 15 | 16 | - Please navigate to the `Payload Formats` tab of your application to which your SenseCAP nodes send data. 17 | - Select `Custom` for `Payload Format` 18 | - Copy and paste the whole contents of `decoder.js` to the `decoder` textarea. 19 | > If you are using the latest version of TTN v3, you may encounter an error that exceeds the character limit. In this case, you will need to use `decoder_new-v3-uglifyjs.js` instead. 20 | - Click `save payload functions` 21 | 22 | ![image](https://user-images.githubusercontent.com/5130185/76605545-954c9c80-654b-11ea-984e-272c58492e85.png) 23 | 24 | 25 | #### 3. Check the Decoded Messages 26 | 27 | You may test the decoding script with sample payload first. To do this, just copy `01 01 10 98 53 00 00 01 02 10 A8 7A 00 00 AF 51` into the `Payload` text input, and click `Test` button. If succeeded, you will see a successfully parsed JSON structure below. 28 | 29 | ![image](https://user-images.githubusercontent.com/5130185/76605914-43584680-654c-11ea-9074-6e78d93059a7.png) 30 | 31 | Then let's check out the magic of the script. We navigate to the `Data` tab, and you can expand any uploaded message to check the `Fields` in the payload. These fields are just populated by the script. 32 | 33 | ![image](https://user-images.githubusercontent.com/5130185/76606268-ec9f3c80-654c-11ea-90e0-332f186475f3.png) 34 | 35 | If you're subscribing the messages with TTN's MQTT Data API, you will also get parsed JSON payload fields. 36 | 37 | ``` 38 | Client mosq-TCSlhYcKaRCn3cIePE received PUBLISH (d0, q0, r0, m0, 'lorawan868/devices/2cf7f12010700041/up', ... (719 bytes)) 39 | lorawan868/devices/2cf7f12010700041/up {"app_id":"lorawan868","dev_id":"2cf7f12010700041","hardware_serial":"2CF7F12010700041","port":2,"counter":1119,"confirmed":true,"payload_raw":"AQEQYG0AAAECEOj9AACWSA==","payload_fields":{"err":0,"messages":[{"measurementId":4097,"measurementValue":28,"type":"report_telemetry"},{"measurementId":4098,"measurementValue":65,"type":"report_telemetry"}],"payload":"010110606D0000010210E8FD00009648","valid":true},"metadata":{"time":"2020-03-13T09:09:45.834032725Z","frequency":867.3,"modulation":"LORA","data_rate":"SF7BW125","airtime":66816000,"coding_rate":"4/5","gateways":[{"gtw_id":"eui-2cf7f11014300001","timestamp":1779605971,"time":"2020-03-13T09:09:45.672666033Z","channel":4,"rssi":-66,"snr":8.8,"rf_chain":0}]}} 40 | ``` 41 | 42 | ## Reference 43 | 44 | How many message types and what's the structure of each message type? 45 | 46 | We will document the latest message types in the comment header of the script. Please check out [here](https://github.com/Seeed-Solution/TTN-Payload-Decoder/blob/master/decoder.js#L1). -------------------------------------------------------------------------------- /S2107/AWS/SenseCAP_S2107_AWS_Decoder.js: -------------------------------------------------------------------------------- 1 | const {IoTDataPlaneClient, PublishCommand} = require("@aws-sdk/client-iot-data-plane"); 2 | const client = new IoTDataPlaneClient({ 3 | //Replace the region according to your device. 4 | "region": "us-east-1" 5 | }); 6 | const topic_prefix = 'sensor/s2107/' 7 | 8 | function decodeUplink (input, port) { 9 | const bytes = Buffer.from(input, 'base64'); 10 | // // init 11 | var bytesString = bytes2HexString(bytes) 12 | .toLocaleUpperCase(); 13 | let result = { 14 | 'err': 0, 'payload': bytesString, 'valid': true, messages: [] 15 | } 16 | let splitArray = dataSplit(bytesString) 17 | // data decoder 18 | let decoderArray = [] 19 | for (let i = 0; i < splitArray.length; i++) { 20 | let item = splitArray[i] 21 | let dataId = item.dataId 22 | let dataValue = item.dataValue 23 | let messages = dataIdAndDataValueJudge(dataId, dataValue) 24 | decoderArray.push(messages) 25 | } 26 | result.messages = decoderArray 27 | return { data: result } 28 | } 29 | 30 | /** 31 | * data splits 32 | * @param bytes 33 | * @returns {*[]} 34 | */ 35 | function dataSplit (bytes) { 36 | let frameArray = [] 37 | bytes = bytes.toLowerCase() 38 | for (let i = 0; i < bytes.length; i++) { 39 | let remainingValue = bytes 40 | let dataId = remainingValue.substring(0, 2) 41 | let dataValue 42 | let dataObj = {} 43 | switch (dataId) { 44 | case '01' : 45 | case '20' : 46 | case '21' : 47 | case '30' : 48 | case '31' : 49 | case '33' : 50 | case '40' : 51 | case '41' : 52 | case '42' : 53 | case '43' : 54 | case '44' : 55 | case '45' : 56 | case '46' : 57 | case '48' : 58 | dataValue = remainingValue.substring(2, 22) 59 | bytes = remainingValue.substring(22) 60 | dataObj = { 61 | 'dataId': dataId, 'dataValue': dataValue 62 | } 63 | break 64 | case '02': 65 | dataValue = remainingValue.substring(2, 18) 66 | bytes = remainingValue.substring(18) 67 | dataObj = { 68 | 'dataId': '02', 'dataValue': dataValue 69 | } 70 | break 71 | case '03' : 72 | case '06': 73 | dataValue = remainingValue.substring(2, 4) 74 | bytes = remainingValue.substring(4) 75 | dataObj = { 76 | 'dataId': dataId, 'dataValue': dataValue 77 | } 78 | break 79 | case '05' : 80 | case '34' : 81 | dataValue = bytes.substring(2, 10) 82 | bytes = remainingValue.substring(10) 83 | dataObj = { 84 | 'dataId': dataId, 'dataValue': dataValue 85 | } 86 | break 87 | case '04' : 88 | case '10' : 89 | case '32' : 90 | case '35' : 91 | case '36' : 92 | case '37' : 93 | case '38' : 94 | case '39' : 95 | dataValue = bytes.substring(2, 20) 96 | bytes = remainingValue.substring(20) 97 | dataObj = { 98 | 'dataId': dataId, 'dataValue': dataValue 99 | } 100 | break 101 | case '47' : 102 | dataValue = bytes.substring(2, 14) 103 | bytes = remainingValue.substring(14) 104 | dataObj = { 105 | 'dataId': dataId, 'dataValue': dataValue 106 | } 107 | break 108 | case '49' : 109 | dataValue = bytes.substring(2, 16) 110 | bytes = remainingValue.substring(16) 111 | dataObj = { 112 | 'dataId': dataId, 'dataValue': dataValue 113 | } 114 | break 115 | case '7f' : 116 | bytes = remainingValue.substring(4) 117 | continue 118 | default: 119 | dataValue = '9' 120 | break 121 | } 122 | if (dataValue.length < 2) { 123 | break 124 | } 125 | frameArray.push(dataObj) 126 | } 127 | return frameArray 128 | } 129 | 130 | function dataIdAndDataValueJudge (dataId, dataValue) { 131 | let messages = [] 132 | switch (dataId) { 133 | case '01': 134 | let temperature = dataValue.substring(0, 4) 135 | let humidity = dataValue.substring(4, 6) 136 | let illumination = dataValue.substring(6, 14) 137 | let uv = dataValue.substring(14, 16) 138 | let windSpeed = dataValue.substring(16, 20) 139 | messages = [{ 140 | measurementValue: loraWANV2DataFormat(temperature, 10), measurementId: '4097', type: 'Air Temperature' 141 | }, { 142 | measurementValue: loraWANV2DataFormat(humidity), measurementId: '4098', type: 'Air Humidity' 143 | }, { 144 | measurementValue: loraWANV2DataFormat(illumination), measurementId: '4099', type: 'Light Intensity' 145 | }, { 146 | measurementValue: loraWANV2DataFormat(uv, 10), measurementId: '4190', type: 'UV Index' 147 | }, { 148 | measurementValue: loraWANV2DataFormat(windSpeed, 10), measurementId: '4105', type: 'Wind Speed' 149 | }] 150 | break 151 | case '02': 152 | let windDirection = dataValue.substring(0, 4) 153 | let rainfall = dataValue.substring(4, 12) 154 | let airPressure = dataValue.substring(12, 16) 155 | messages = [{ 156 | measurementValue: loraWANV2DataFormat(windDirection), measurementId: '4104', type: 'Wind Direction Sensor' 157 | }, { 158 | measurementValue: loraWANV2DataFormat(rainfall, 1000), measurementId: '4113', type: 'Rain Gauge' 159 | }, { 160 | 161 | measurementValue: loraWANV2DataFormat(airPressure, 0.1), measurementId: '4101', type: 'Barometric Pressure' 162 | }] 163 | break 164 | case '03': 165 | let Electricity = dataValue 166 | messages = [{ 167 | 'Battery(%)': loraWANV2DataFormat(Electricity) 168 | }] 169 | break 170 | case '04': 171 | let electricityWhether = dataValue.substring(0, 2) 172 | let hwv = dataValue.substring(2, 6) 173 | let bdv = dataValue.substring(6, 10) 174 | let sensorAcquisitionInterval = dataValue.substring(10, 14) 175 | let gpsAcquisitionInterval = dataValue.substring(14, 18) 176 | messages = [{ 177 | 'Battery(%)': loraWANV2DataFormat(electricityWhether), 178 | 'Hardware Version': `${loraWANV2DataFormat(hwv.substring(0, 2))}.${loraWANV2DataFormat(hwv.substring(2, 4))}`, 179 | 'Firmware Version': `${loraWANV2DataFormat(bdv.substring(0, 2))}.${loraWANV2DataFormat(bdv.substring(2, 4))}`, 180 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionInterval)) * 60, 181 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionInterval)) * 60 182 | }] 183 | break 184 | case '05': 185 | let sensorAcquisitionIntervalFive = dataValue.substring(0, 4) 186 | let gpsAcquisitionIntervalFive = dataValue.substring(4, 8) 187 | messages = [{ 188 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalFive)) * 60, 189 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalFive)) * 60 190 | }] 191 | break 192 | case '06': 193 | let errorCode = dataValue 194 | let descZh 195 | switch (errorCode) { 196 | case '00': 197 | descZh = 'CCL_SENSOR_ERROR_NONE' 198 | break 199 | case '01': 200 | descZh = 'CCL_SENSOR_NOT_FOUND' 201 | break 202 | case '02': 203 | descZh = 'CCL_SENSOR_WAKEUP_ERROR' 204 | break 205 | case '03': 206 | descZh = 'CCL_SENSOR_NOT_RESPONSE' 207 | break 208 | case '04': 209 | descZh = 'CCL_SENSOR_DATA_EMPTY' 210 | break 211 | case '05': 212 | descZh = 'CCL_SENSOR_DATA_HEAD_ERROR' 213 | break 214 | case '06': 215 | descZh = 'CCL_SENSOR_DATA_CRC_ERROR' 216 | break 217 | case '07': 218 | descZh = 'CCL_SENSOR_DATA_B1_NO_VALID' 219 | break 220 | case '08': 221 | descZh = 'CCL_SENSOR_DATA_B2_NO_VALID' 222 | break 223 | case '09': 224 | descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH' 225 | break 226 | case '0A': 227 | descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED' 228 | break 229 | case '0B': 230 | descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED' 231 | break 232 | case '0C': 233 | descZh = 'CCL_SENSOR_DATA_VALUE_HI' 234 | break 235 | case '0D': 236 | descZh = 'CCL_SENSOR_DATA_VALUE_LOW' 237 | break 238 | case '0E': 239 | descZh = 'CCL_SENSOR_DATA_VALUE_MISSED' 240 | break 241 | case '0F': 242 | descZh = 'CCL_SENSOR_ARG_INVAILD' 243 | break 244 | case '10': 245 | descZh = 'CCL_SENSOR_RS485_MASTER_BUSY' 246 | break 247 | case '11': 248 | descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR' 249 | break 250 | case '12': 251 | descZh = 'CCL_SENSOR_RS485_REG_MISSED' 252 | break 253 | case '13': 254 | descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR' 255 | break 256 | case '14': 257 | descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR' 258 | break 259 | case '15': 260 | descZh = 'CCL_SENSOR_CONFIG_ERROR' 261 | break 262 | case 'FF': 263 | descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW' 264 | break 265 | default: 266 | descZh = 'CC_OTHER_FAILED' 267 | break 268 | } 269 | messages = [{ 270 | measurementId: '4101', type: 'sensor_error_event', errCode: errorCode, descZh 271 | }] 272 | break 273 | case '10': 274 | let statusValue = dataValue.substring(0, 2) 275 | let { status, type } = loraWANV2BitDataFormat(statusValue) 276 | let sensecapId = dataValue.substring(2) 277 | messages = [{ 278 | status: status, channelType: type, sensorEui: sensecapId 279 | }] 280 | break 281 | case '46': 282 | let measurementTime = loraWANV2PositiveDataFormat(dataValue.substring(0, 8)) * 1000 283 | let offLineTmpOne = loraWanSensorFormat(dataValue.substring(8, 12), 100) 284 | let offLineTmpTwo = loraWanSensorFormat(dataValue.substring(12, 16), 100) 285 | let offLineTmpThree = loraWanSensorFormat(dataValue.substring(16, 20), 100) 286 | messages = [] 287 | if (offLineTmpOne) { 288 | messages.push({ 289 | measurementValue: '' + offLineTmpOne, 290 | measurementId: '4203', 291 | type:"Temperature", 292 | measurementChannel: '1', 293 | measureTime: measurementTime 294 | }) 295 | } 296 | if (offLineTmpTwo) { 297 | messages.push({ 298 | measurementValue: '' + offLineTmpTwo, 299 | measurementId: '4203', 300 | type:"Temperature", 301 | measurementChannel: '2', 302 | measureTime: measurementTime 303 | }) 304 | } 305 | if (offLineTmpThree) { 306 | messages.push({ 307 | measurementValue: '' + offLineTmpThree, 308 | measurementId: '4203', 309 | type:"Temperature", 310 | measurementChannel: '3', 311 | measureTime: measurementTime 312 | }) 313 | } 314 | break 315 | case '47': 316 | let tmpOne = loraWanSensorFormat(dataValue.substring(0, 4), 100) 317 | let tmpTwo = loraWanSensorFormat(dataValue.substring(4, 8), 100) 318 | let tmpThree = loraWanSensorFormat(dataValue.substring(8, 16), 100) 319 | if (tmpOne) { 320 | messages.push({ 321 | measurementValue: '' + tmpOne, 322 | measurementId: '4203', 323 | type:"Temperature", 324 | measurementChannel: '1' 325 | }) 326 | } 327 | if (tmpTwo) { 328 | messages.push({ 329 | measurementValue: '' + tmpTwo, 330 | measurementId: '4203', 331 | type:"Temperature", 332 | measurementChannel: '2' 333 | }) 334 | } 335 | if (tmpThree) { 336 | messages.push({ 337 | measurementValue: '' + tmpThree, 338 | measurementId: '4203', 339 | type:"Temperature", 340 | measurementChannel: '3' 341 | }) 342 | } 343 | break 344 | case '48': 345 | for (let i = 0; i < dataValue.length; i += 2) { 346 | let channelStatusHex = dataValue.substring(i, i + 2) 347 | if (channelStatusHex.toLowerCase() === 'ff') { 348 | continue 349 | } 350 | let channelStatus = loraWANV2DataFormat(channelStatusHex) 351 | let statusStr = 'normal' 352 | if (parseInt(channelStatus) === 0) { 353 | statusStr = 'idle' 354 | } else if (parseInt(channelStatus) === 2) { 355 | statusStr = 'abnormal' 356 | } 357 | messages.push({ 358 | channel_index: '' + (1 + i / 2), status: statusStr, channelType: '1' 359 | }) 360 | } 361 | break 362 | case '49': 363 | messages = [{ 364 | 'Battery(%)': loraWANV2DataFormat(dataValue.substring(0, 2)), 365 | 'Hardware Version': `${loraWANV2DataFormat(dataValue.substring(2, 4))}.${loraWANV2DataFormat(dataValue.substring(4, 6))}`, 366 | 'Firmware Version': `${loraWANV2DataFormat(dataValue.substring(6, 8))}.${loraWANV2DataFormat(dataValue.substring(8, 10))}`, 367 | 'measureInterval': parseInt(loraWANV2DataFormat(dataValue.substring(10, 14))) * 60 368 | }] 369 | break 370 | default: 371 | break 372 | } 373 | return messages 374 | } 375 | 376 | /** 377 | * 378 | * data formatting 379 | * @param str 380 | * @param divisor 381 | * @returns {string|number} 382 | */ 383 | function loraWANV2DataFormat (str, divisor = 1) { 384 | let strReverse = bigEndianTransform(str) 385 | let str2 = toBinary(strReverse) 386 | if (str2.substring(0, 1) === '1') { 387 | let arr = str2.split('') 388 | let reverseArr = arr.map((item) => { 389 | if (parseInt(item) === 1) { 390 | return 0 391 | } else { 392 | return 1 393 | } 394 | }) 395 | str2 = parseInt(reverseArr.join(''), 2) + 1 396 | return parseFloat('-' + str2 / divisor) 397 | } 398 | return parseInt(str2, 2) / divisor 399 | } 400 | 401 | function loraWanSensorFormat (str, divisor = 1) { 402 | if (str === '8000') { 403 | return null 404 | } 405 | return loraWANV2DataFormat(str, divisor) 406 | } 407 | 408 | /** 409 | * Handling big-endian data formats 410 | * @param data 411 | * @returns {*[]} 412 | */ 413 | function bigEndianTransform (data) { 414 | let dataArray = [] 415 | for (let i = 0; i < data.length; i += 2) { 416 | dataArray.push(data.substring(i, i + 2)) 417 | } 418 | // array of hex 419 | return dataArray 420 | } 421 | 422 | function loraWANV2PositiveDataFormat (str, divisor = 1) { 423 | let strReverse = bigEndianTransform(str) 424 | let str2 = toBinary(strReverse) 425 | return parseInt(str2, 2) / divisor 426 | } 427 | 428 | /** 429 | * Convert to an 8-digit binary number with 0s in front of the number 430 | * @param arr 431 | * @returns {string} 432 | */ 433 | function toBinary (arr) { 434 | let binaryData = arr.map((item) => { 435 | let data = parseInt(item, 16) 436 | .toString(2) 437 | let dataLength = data.length 438 | if (data.length !== 8) { 439 | for (let i = 0; i < 8 - dataLength; i++) { 440 | data = `0` + data 441 | } 442 | } 443 | return data 444 | }) 445 | let ret = binaryData.toString() 446 | .replace(/,/g, '') 447 | return ret 448 | } 449 | 450 | /** 451 | * sensor 452 | * @param str 453 | * @returns {{channel: number, type: number, status: number}} 454 | */ 455 | function loraWANV2BitDataFormat (str) { 456 | let strReverse = bigEndianTransform(str) 457 | let str2 = toBinary(strReverse) 458 | let channel = parseInt(str2.substring(0, 4), 2) 459 | let status = parseInt(str2.substring(4, 5), 2) 460 | let type = parseInt(str2.substring(5), 2) 461 | return { channel, status, type } 462 | } 463 | 464 | function bytes2HexString (arrBytes) { 465 | var str = '' 466 | for (var i = 0; i < arrBytes.length; i++) { 467 | var tmp 468 | var num = arrBytes[i] 469 | if (num < 0) { 470 | tmp = (255 + num + 1).toString(16) 471 | } else { 472 | tmp = num.toString(16) 473 | } 474 | if (tmp.length === 1) { 475 | tmp = '0' + tmp 476 | } 477 | str += tmp 478 | } 479 | return str 480 | } 481 | 482 | 483 | 484 | exports.handler = async (event) => { 485 | try { 486 | const lorawan_info = event["WirelessMetadata"]["LoRaWAN"]; 487 | const device_id = event["WirelessDeviceId"] 488 | const device_eui = lorawan_info["DevEui"] 489 | const lorawan_data = event["PayloadData"]; 490 | const resolved_data = decodeUplink(lorawan_data, lorawan_info["FPort"]); 491 | 492 | const input = { 493 | topic: `${topic_prefix}${device_eui}`, 494 | qos: 0, 495 | retain: false, 496 | payload: JSON.stringify({ 497 | eui: device_eui, 498 | device_id: device_id, 499 | timestamp: lorawan_info["Timestamp"], 500 | data: resolved_data.data 501 | }) 502 | }; 503 | const command = new PublishCommand(input); 504 | const response = await client.send(command); 505 | console.log("response: " + JSON.stringify(response)); 506 | return { 507 | statusCode: 200, 508 | body: 'Message published successfully' + JSON.stringify(event) 509 | }; 510 | } catch (error) { 511 | console.error('Error publishing message:', error); 512 | 513 | return { 514 | statusCode: 500, 515 | body: 'Error publishing message' 516 | }; 517 | } 518 | }; 519 | 520 | -------------------------------------------------------------------------------- /S2107/ChirpStack/SenseCAP_S2107_ChirpStackV3_Decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Entry, decoder.js 3 | */ 4 | function Decode(fPort, bytes, variables) { 5 | // data split 6 | 7 | bytes = bytes2HexString(bytes).toLocaleUpperCase(); 8 | var result = { 9 | 'err': 0, 10 | 'payload': bytes, 11 | 'valid': true, 12 | messages: [] 13 | }; 14 | var splitArray = dataSplit(bytes); 15 | // data decoder 16 | var decoderArray = []; 17 | for (var i = 0; i < splitArray.length; i++) { 18 | var item = splitArray[i]; 19 | var dataId = item.dataId; 20 | var dataValue = item.dataValue; 21 | var messages = dataIdAndDataValueJudge(dataId, dataValue); 22 | decoderArray.push(messages); 23 | } 24 | result.messages = decoderArray; 25 | return { 26 | data: result 27 | }; 28 | } 29 | 30 | /** 31 | * data splits 32 | * @param bytes 33 | * @returns {*[]} 34 | */ 35 | function dataSplit(bytes) { 36 | var frameArray = []; 37 | bytes = bytes.toLowerCase(); 38 | for (var i = 0; i < bytes.length; i++) { 39 | var remainingValue = bytes; 40 | var dataId = remainingValue.substring(0, 2); 41 | var dataValue = void 0; 42 | var dataObj = {}; 43 | switch (dataId) { 44 | case '01': 45 | case '20': 46 | case '21': 47 | case '30': 48 | case '31': 49 | case '33': 50 | case '40': 51 | case '41': 52 | case '42': 53 | case '43': 54 | case '44': 55 | case '45': 56 | case '46': 57 | case '48': 58 | dataValue = remainingValue.substring(2, 22); 59 | bytes = remainingValue.substring(22); 60 | dataObj = { 61 | 'dataId': dataId, 62 | 'dataValue': dataValue 63 | }; 64 | break; 65 | case '02': 66 | dataValue = remainingValue.substring(2, 18); 67 | bytes = remainingValue.substring(18); 68 | dataObj = { 69 | 'dataId': '02', 70 | 'dataValue': dataValue 71 | }; 72 | break; 73 | case '03': 74 | case '06': 75 | dataValue = remainingValue.substring(2, 4); 76 | bytes = remainingValue.substring(4); 77 | dataObj = { 78 | 'dataId': dataId, 79 | 'dataValue': dataValue 80 | }; 81 | break; 82 | case '05': 83 | case '34': 84 | dataValue = bytes.substring(2, 10); 85 | bytes = remainingValue.substring(10); 86 | dataObj = { 87 | 'dataId': dataId, 88 | 'dataValue': dataValue 89 | }; 90 | break; 91 | case '04': 92 | case '10': 93 | case '32': 94 | case '35': 95 | case '36': 96 | case '37': 97 | case '38': 98 | case '39': 99 | dataValue = bytes.substring(2, 20); 100 | bytes = remainingValue.substring(20); 101 | dataObj = { 102 | 'dataId': dataId, 103 | 'dataValue': dataValue 104 | }; 105 | break; 106 | case '47': 107 | dataValue = bytes.substring(2, 14); 108 | bytes = remainingValue.substring(14); 109 | dataObj = { 110 | 'dataId': dataId, 111 | 'dataValue': dataValue 112 | }; 113 | break; 114 | case '49': 115 | dataValue = bytes.substring(2, 16); 116 | bytes = remainingValue.substring(16); 117 | dataObj = { 118 | 'dataId': dataId, 119 | 'dataValue': dataValue 120 | }; 121 | break; 122 | case '7f': 123 | bytes = remainingValue.substring(4); 124 | continue; 125 | default: 126 | dataValue = '9'; 127 | break; 128 | } 129 | if (dataValue.length < 2) { 130 | break; 131 | } 132 | frameArray.push(dataObj); 133 | } 134 | return frameArray; 135 | } 136 | function dataIdAndDataValueJudge(dataId, dataValue) { 137 | var messages = []; 138 | switch (dataId) { 139 | case '01': 140 | var temperature = dataValue.substring(0, 4); 141 | var humidity = dataValue.substring(4, 6); 142 | var illumination = dataValue.substring(6, 14); 143 | var uv = dataValue.substring(14, 16); 144 | var windSpeed = dataValue.substring(16, 20); 145 | messages = [{ 146 | measurementValue: loraWANV2DataFormat(temperature, 10), 147 | measurementId: '4097', 148 | type: 'Air Temperature' 149 | }, { 150 | measurementValue: loraWANV2DataFormat(humidity), 151 | measurementId: '4098', 152 | type: 'Air Humidity' 153 | }, { 154 | measurementValue: loraWANV2DataFormat(illumination), 155 | measurementId: '4099', 156 | type: 'Light Intensity' 157 | }, { 158 | measurementValue: loraWANV2DataFormat(uv, 10), 159 | measurementId: '4190', 160 | type: 'UV Index' 161 | }, { 162 | measurementValue: loraWANV2DataFormat(windSpeed, 10), 163 | measurementId: '4105', 164 | type: 'Wind Speed' 165 | }]; 166 | break; 167 | case '02': 168 | var windDirection = dataValue.substring(0, 4); 169 | var rainfall = dataValue.substring(4, 12); 170 | var airPressure = dataValue.substring(12, 16); 171 | messages = [{ 172 | measurementValue: loraWANV2DataFormat(windDirection), 173 | measurementId: '4104', 174 | type: 'Wind Direction Sensor' 175 | }, { 176 | measurementValue: loraWANV2DataFormat(rainfall, 1000), 177 | measurementId: '4113', 178 | type: 'Rain Gauge' 179 | }, { 180 | measurementValue: loraWANV2DataFormat(airPressure, 0.1), 181 | measurementId: '4101', 182 | type: 'Barometric Pressure' 183 | }]; 184 | break; 185 | case '03': 186 | var Electricity = dataValue; 187 | messages = [{ 188 | 'Battery(%)': loraWANV2DataFormat(Electricity) 189 | }]; 190 | break; 191 | case '04': 192 | var electricityWhether = dataValue.substring(0, 2); 193 | var hwv = dataValue.substring(2, 6); 194 | var bdv = dataValue.substring(6, 10); 195 | var sensorAcquisitionInterval = dataValue.substring(10, 14); 196 | var gpsAcquisitionInterval = dataValue.substring(14, 18); 197 | messages = [{ 198 | 'Battery(%)': loraWANV2DataFormat(electricityWhether), 199 | 'Hardware Version': "".concat(loraWANV2DataFormat(hwv.substring(0, 2)), ".").concat(loraWANV2DataFormat(hwv.substring(2, 4))), 200 | 'Firmware Version': "".concat(loraWANV2DataFormat(bdv.substring(0, 2)), ".").concat(loraWANV2DataFormat(bdv.substring(2, 4))), 201 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionInterval)) * 60, 202 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionInterval)) * 60 203 | }]; 204 | break; 205 | case '05': 206 | var sensorAcquisitionIntervalFive = dataValue.substring(0, 4); 207 | var gpsAcquisitionIntervalFive = dataValue.substring(4, 8); 208 | messages = [{ 209 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalFive)) * 60, 210 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalFive)) * 60 211 | }]; 212 | break; 213 | case '06': 214 | var errorCode = dataValue; 215 | var descZh; 216 | switch (errorCode) { 217 | case '00': 218 | descZh = 'CCL_SENSOR_ERROR_NONE'; 219 | break; 220 | case '01': 221 | descZh = 'CCL_SENSOR_NOT_FOUND'; 222 | break; 223 | case '02': 224 | descZh = 'CCL_SENSOR_WAKEUP_ERROR'; 225 | break; 226 | case '03': 227 | descZh = 'CCL_SENSOR_NOT_RESPONSE'; 228 | break; 229 | case '04': 230 | descZh = 'CCL_SENSOR_DATA_EMPTY'; 231 | break; 232 | case '05': 233 | descZh = 'CCL_SENSOR_DATA_HEAD_ERROR'; 234 | break; 235 | case '06': 236 | descZh = 'CCL_SENSOR_DATA_CRC_ERROR'; 237 | break; 238 | case '07': 239 | descZh = 'CCL_SENSOR_DATA_B1_NO_VALID'; 240 | break; 241 | case '08': 242 | descZh = 'CCL_SENSOR_DATA_B2_NO_VALID'; 243 | break; 244 | case '09': 245 | descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH'; 246 | break; 247 | case '0A': 248 | descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED'; 249 | break; 250 | case '0B': 251 | descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED'; 252 | break; 253 | case '0C': 254 | descZh = 'CCL_SENSOR_DATA_VALUE_HI'; 255 | break; 256 | case '0D': 257 | descZh = 'CCL_SENSOR_DATA_VALUE_LOW'; 258 | break; 259 | case '0E': 260 | descZh = 'CCL_SENSOR_DATA_VALUE_MISSED'; 261 | break; 262 | case '0F': 263 | descZh = 'CCL_SENSOR_ARG_INVAILD'; 264 | break; 265 | case '10': 266 | descZh = 'CCL_SENSOR_RS485_MASTER_BUSY'; 267 | break; 268 | case '11': 269 | descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR'; 270 | break; 271 | case '12': 272 | descZh = 'CCL_SENSOR_RS485_REG_MISSED'; 273 | break; 274 | case '13': 275 | descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR'; 276 | break; 277 | case '14': 278 | descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR'; 279 | break; 280 | case '15': 281 | descZh = 'CCL_SENSOR_CONFIG_ERROR'; 282 | break; 283 | case 'FF': 284 | descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW'; 285 | break; 286 | default: 287 | descZh = 'CC_OTHER_FAILED'; 288 | break; 289 | } 290 | messages = [{ 291 | measurementId: '4101', 292 | type: 'sensor_error_event', 293 | errCode: errorCode, 294 | descZh: descZh 295 | }]; 296 | break; 297 | case '10': 298 | var statusValue = dataValue.substring(0, 2); 299 | var _loraWANV2BitDataForm = loraWANV2BitDataFormat(statusValue), 300 | status = _loraWANV2BitDataForm.status, 301 | type = _loraWANV2BitDataForm.type; 302 | var sensecapId = dataValue.substring(2); 303 | messages = [{ 304 | status: status, 305 | channelType: type, 306 | sensorEui: sensecapId 307 | }]; 308 | break; 309 | case '46': 310 | var measurementTime = loraWANV2PositiveDataFormat(dataValue.substring(0, 8)) * 1000; 311 | var offLineTmpOne = loraWanSensorFormat(dataValue.substring(8, 12), 100); 312 | var offLineTmpTwo = loraWanSensorFormat(dataValue.substring(12, 16), 100); 313 | var offLineTmpThree = loraWanSensorFormat(dataValue.substring(16, 20), 100); 314 | messages = []; 315 | if (offLineTmpOne) { 316 | messages.push({ 317 | measurementValue: '' + offLineTmpOne, 318 | measurementId: '4203', 319 | type: "Temperature", 320 | measurementChannel: '1', 321 | measureTime: measurementTime 322 | }); 323 | } 324 | if (offLineTmpTwo) { 325 | messages.push({ 326 | measurementValue: '' + offLineTmpTwo, 327 | measurementId: '4203', 328 | type: "Temperature", 329 | measurementChannel: '2', 330 | measureTime: measurementTime 331 | }); 332 | } 333 | if (offLineTmpThree) { 334 | messages.push({ 335 | measurementValue: '' + offLineTmpThree, 336 | measurementId: '4203', 337 | type: "Temperature", 338 | measurementChannel: '3', 339 | measureTime: measurementTime 340 | }); 341 | } 342 | break; 343 | case '47': 344 | var tmpOne = loraWanSensorFormat(dataValue.substring(0, 4), 100); 345 | var tmpTwo = loraWanSensorFormat(dataValue.substring(4, 8), 100); 346 | var tmpThree = loraWanSensorFormat(dataValue.substring(8, 16), 100); 347 | if (tmpOne) { 348 | messages.push({ 349 | measurementValue: '' + tmpOne, 350 | measurementId: '4203', 351 | type: "Temperature", 352 | measurementChannel: '1' 353 | }); 354 | } 355 | if (tmpTwo) { 356 | messages.push({ 357 | measurementValue: '' + tmpTwo, 358 | measurementId: '4203', 359 | type: "Temperature", 360 | measurementChannel: '2' 361 | }); 362 | } 363 | if (tmpThree) { 364 | messages.push({ 365 | measurementValue: '' + tmpThree, 366 | measurementId: '4203', 367 | type: "Temperature", 368 | measurementChannel: '3' 369 | }); 370 | } 371 | break; 372 | case '48': 373 | for (var i = 0; i < dataValue.length; i += 2) { 374 | var channelStatusHex = dataValue.substring(i, i + 2); 375 | if (channelStatusHex.toLowerCase() === 'ff') { 376 | continue; 377 | } 378 | var channelStatus = loraWANV2DataFormat(channelStatusHex); 379 | var statusStr = 'normal'; 380 | if (parseInt(channelStatus) === 0) { 381 | statusStr = 'idle'; 382 | } else if (parseInt(channelStatus) === 2) { 383 | statusStr = 'abnormal'; 384 | } 385 | messages.push({ 386 | channel_index: '' + (1 + i / 2), 387 | status: statusStr, 388 | channelType: '1' 389 | }); 390 | } 391 | break; 392 | case '49': 393 | messages = [{ 394 | 'Battery(%)': loraWANV2DataFormat(dataValue.substring(0, 2)), 395 | 'Hardware Version': "".concat(loraWANV2DataFormat(dataValue.substring(2, 4)), ".").concat(loraWANV2DataFormat(dataValue.substring(4, 6))), 396 | 'Firmware Version': "".concat(loraWANV2DataFormat(dataValue.substring(6, 8)), ".").concat(loraWANV2DataFormat(dataValue.substring(8, 10))), 397 | 'measureInterval': parseInt(loraWANV2DataFormat(dataValue.substring(10, 14))) * 60 398 | }]; 399 | break; 400 | default: 401 | break; 402 | } 403 | return messages; 404 | } 405 | 406 | /** 407 | * 408 | * data formatting 409 | * @param str 410 | * @param divisor 411 | * @returns {string|number} 412 | */ 413 | function loraWANV2DataFormat(str) { 414 | var divisor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; 415 | var strReverse = bigEndianTransform(str); 416 | var str2 = toBinary(strReverse); 417 | if (str2.substring(0, 1) === '1') { 418 | var arr = str2.split(''); 419 | var reverseArr = arr.map(function (item) { 420 | if (parseInt(item) === 1) { 421 | return 0; 422 | } else { 423 | return 1; 424 | } 425 | }); 426 | str2 = parseInt(reverseArr.join(''), 2) + 1; 427 | return parseFloat('-' + str2 / divisor); 428 | } 429 | return parseInt(str2, 2) / divisor; 430 | } 431 | function loraWanSensorFormat(str) { 432 | var divisor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; 433 | if (str === '8000') { 434 | return null; 435 | } 436 | return loraWANV2DataFormat(str, divisor); 437 | } 438 | 439 | /** 440 | * Handling big-endian data formats 441 | * @param data 442 | * @returns {*[]} 443 | */ 444 | function bigEndianTransform(data) { 445 | var dataArray = []; 446 | for (var i = 0; i < data.length; i += 2) { 447 | dataArray.push(data.substring(i, i + 2)); 448 | } 449 | // array of hex 450 | return dataArray; 451 | } 452 | function loraWANV2PositiveDataFormat(str) { 453 | var divisor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; 454 | var strReverse = bigEndianTransform(str); 455 | var str2 = toBinary(strReverse); 456 | return parseInt(str2, 2) / divisor; 457 | } 458 | 459 | /** 460 | * Convert to an 8-digit binary number with 0s in front of the number 461 | * @param arr 462 | * @returns {string} 463 | */ 464 | function toBinary(arr) { 465 | var binaryData = arr.map(function (item) { 466 | var data = parseInt(item, 16).toString(2); 467 | var dataLength = data.length; 468 | if (data.length !== 8) { 469 | for (var i = 0; i < 8 - dataLength; i++) { 470 | data = "0" + data; 471 | } 472 | } 473 | return data; 474 | }); 475 | var ret = binaryData.toString().replace(/,/g, ''); 476 | return ret; 477 | } 478 | 479 | /** 480 | * sensor 481 | * @param str 482 | * @returns {{channel: number, type: number, status: number}} 483 | */ 484 | function loraWANV2BitDataFormat(str) { 485 | var strReverse = bigEndianTransform(str); 486 | var str2 = toBinary(strReverse); 487 | var channel = parseInt(str2.substring(0, 4), 2); 488 | var status = parseInt(str2.substring(4, 5), 2); 489 | var type = parseInt(str2.substring(5), 2); 490 | return { 491 | channel: channel, 492 | status: status, 493 | type: type 494 | }; 495 | } 496 | 497 | /** 498 | * channel info 499 | * @param str 500 | * @returns {{channelTwo: number, channelOne: number}} 501 | */ 502 | function loraWANV2ChannelBitFormat(str) { 503 | var strReverse = bigEndianTransform(str); 504 | var str2 = toBinary(strReverse); 505 | var one = parseInt(str2.substring(0, 4), 2); 506 | var two = parseInt(str2.substring(4, 8), 2); 507 | var resultInfo = { 508 | one: one, 509 | two: two 510 | }; 511 | return resultInfo; 512 | } 513 | 514 | /** 515 | * data log status bit 516 | * @param str 517 | * @returns {{total: number, level: number, isTH: number}} 518 | */ 519 | function loraWANV2DataLogBitFormat(str) { 520 | var strReverse = bigEndianTransform(str); 521 | var str2 = toBinary(strReverse); 522 | var isTH = parseInt(str2.substring(0, 1), 2); 523 | var total = parseInt(str2.substring(1, 5), 2); 524 | var left = parseInt(str2.substring(5), 2); 525 | var resultInfo = { 526 | isTH: isTH, 527 | total: total, 528 | left: left 529 | }; 530 | return resultInfo; 531 | } 532 | function bytes2HexString(arrBytes) { 533 | var str = ''; 534 | for (var i = 0; i < arrBytes.length; i++) { 535 | var tmp; 536 | var num = arrBytes[i]; 537 | if (num < 0) { 538 | tmp = (255 + num + 1).toString(16); 539 | } else { 540 | tmp = num.toString(16); 541 | } 542 | if (tmp.length === 1) { 543 | tmp = '0' + tmp; 544 | } 545 | str += tmp; 546 | } 547 | return str; 548 | } -------------------------------------------------------------------------------- /S2107/Helium/SenseCAP_S2107_Helium_Decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Entry, decoder.js 3 | */ 4 | function Decoder (bytes, port) { 5 | // data split 6 | 7 | bytes = bytes2HexString(bytes) 8 | .toLocaleUpperCase() 9 | 10 | let result = { 11 | 'err': 0, 'payload': bytes, 'valid': true, messages: [] 12 | } 13 | let splitArray = dataSplit(bytes) 14 | // data decoder 15 | let decoderArray = [] 16 | for (let i = 0; i < splitArray.length; i++) { 17 | let item = splitArray[i] 18 | let dataId = item.dataId 19 | let dataValue = item.dataValue 20 | let messages = dataIdAndDataValueJudge(dataId, dataValue) 21 | decoderArray.push(messages) 22 | } 23 | result.messages = decoderArray 24 | return { data: result } 25 | } 26 | 27 | /** 28 | * data splits 29 | * @param bytes 30 | * @returns {*[]} 31 | */ 32 | function dataSplit (bytes) { 33 | let frameArray = [] 34 | bytes = bytes.toLowerCase() 35 | for (let i = 0; i < bytes.length; i++) { 36 | let remainingValue = bytes 37 | let dataId = remainingValue.substring(0, 2) 38 | let dataValue 39 | let dataObj = {} 40 | switch (dataId) { 41 | case '01' : 42 | case '20' : 43 | case '21' : 44 | case '30' : 45 | case '31' : 46 | case '33' : 47 | case '40' : 48 | case '41' : 49 | case '42' : 50 | case '43' : 51 | case '44' : 52 | case '45' : 53 | case '46' : 54 | case '48' : 55 | dataValue = remainingValue.substring(2, 22) 56 | bytes = remainingValue.substring(22) 57 | dataObj = { 58 | 'dataId': dataId, 'dataValue': dataValue 59 | } 60 | break 61 | case '02': 62 | dataValue = remainingValue.substring(2, 18) 63 | bytes = remainingValue.substring(18) 64 | dataObj = { 65 | 'dataId': '02', 'dataValue': dataValue 66 | } 67 | break 68 | case '03' : 69 | case '06': 70 | dataValue = remainingValue.substring(2, 4) 71 | bytes = remainingValue.substring(4) 72 | dataObj = { 73 | 'dataId': dataId, 'dataValue': dataValue 74 | } 75 | break 76 | case '05' : 77 | case '34' : 78 | dataValue = bytes.substring(2, 10) 79 | bytes = remainingValue.substring(10) 80 | dataObj = { 81 | 'dataId': dataId, 'dataValue': dataValue 82 | } 83 | break 84 | case '04' : 85 | case '10' : 86 | case '32' : 87 | case '35' : 88 | case '36' : 89 | case '37' : 90 | case '38' : 91 | case '39' : 92 | dataValue = bytes.substring(2, 20) 93 | bytes = remainingValue.substring(20) 94 | dataObj = { 95 | 'dataId': dataId, 'dataValue': dataValue 96 | } 97 | break 98 | case '47' : 99 | dataValue = bytes.substring(2, 14) 100 | bytes = remainingValue.substring(14) 101 | dataObj = { 102 | 'dataId': dataId, 'dataValue': dataValue 103 | } 104 | break 105 | case '49' : 106 | dataValue = bytes.substring(2, 16) 107 | bytes = remainingValue.substring(16) 108 | dataObj = { 109 | 'dataId': dataId, 'dataValue': dataValue 110 | } 111 | break 112 | case '7f' : 113 | bytes = remainingValue.substring(4) 114 | continue 115 | default: 116 | dataValue = '9' 117 | break 118 | } 119 | if (dataValue.length < 2) { 120 | break 121 | } 122 | frameArray.push(dataObj) 123 | } 124 | return frameArray 125 | } 126 | 127 | function dataIdAndDataValueJudge (dataId, dataValue) { 128 | let messages = [] 129 | switch (dataId) { 130 | case '01': 131 | let temperature = dataValue.substring(0, 4) 132 | let humidity = dataValue.substring(4, 6) 133 | let illumination = dataValue.substring(6, 14) 134 | let uv = dataValue.substring(14, 16) 135 | let windSpeed = dataValue.substring(16, 20) 136 | messages = [{ 137 | measurementValue: loraWANV2DataFormat(temperature, 10), measurementId: '4097', type: 'Air Temperature' 138 | }, { 139 | measurementValue: loraWANV2DataFormat(humidity), measurementId: '4098', type: 'Air Humidity' 140 | }, { 141 | measurementValue: loraWANV2DataFormat(illumination), measurementId: '4099', type: 'Light Intensity' 142 | }, { 143 | measurementValue: loraWANV2DataFormat(uv, 10), measurementId: '4190', type: 'UV Index' 144 | }, { 145 | measurementValue: loraWANV2DataFormat(windSpeed, 10), measurementId: '4105', type: 'Wind Speed' 146 | }] 147 | break 148 | case '02': 149 | let windDirection = dataValue.substring(0, 4) 150 | let rainfall = dataValue.substring(4, 12) 151 | let airPressure = dataValue.substring(12, 16) 152 | messages = [{ 153 | measurementValue: loraWANV2DataFormat(windDirection), measurementId: '4104', type: 'Wind Direction Sensor' 154 | }, { 155 | measurementValue: loraWANV2DataFormat(rainfall, 1000), measurementId: '4113', type: 'Rain Gauge' 156 | }, { 157 | 158 | measurementValue: loraWANV2DataFormat(airPressure, 0.1), measurementId: '4101', type: 'Barometric Pressure' 159 | }] 160 | break 161 | case '03': 162 | let Electricity = dataValue 163 | messages = [{ 164 | 'Battery(%)': loraWANV2DataFormat(Electricity) 165 | }] 166 | break 167 | case '04': 168 | let electricityWhether = dataValue.substring(0, 2) 169 | let hwv = dataValue.substring(2, 6) 170 | let bdv = dataValue.substring(6, 10) 171 | let sensorAcquisitionInterval = dataValue.substring(10, 14) 172 | let gpsAcquisitionInterval = dataValue.substring(14, 18) 173 | messages = [{ 174 | 'Battery(%)': loraWANV2DataFormat(electricityWhether), 175 | 'Hardware Version': `${loraWANV2DataFormat(hwv.substring(0, 2))}.${loraWANV2DataFormat(hwv.substring(2, 4))}`, 176 | 'Firmware Version': `${loraWANV2DataFormat(bdv.substring(0, 2))}.${loraWANV2DataFormat(bdv.substring(2, 4))}`, 177 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionInterval)) * 60, 178 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionInterval)) * 60 179 | }] 180 | break 181 | case '05': 182 | let sensorAcquisitionIntervalFive = dataValue.substring(0, 4) 183 | let gpsAcquisitionIntervalFive = dataValue.substring(4, 8) 184 | messages = [{ 185 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalFive)) * 60, 186 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalFive)) * 60 187 | }] 188 | break 189 | case '06': 190 | let errorCode = dataValue 191 | let descZh 192 | switch (errorCode) { 193 | case '00': 194 | descZh = 'CCL_SENSOR_ERROR_NONE' 195 | break 196 | case '01': 197 | descZh = 'CCL_SENSOR_NOT_FOUND' 198 | break 199 | case '02': 200 | descZh = 'CCL_SENSOR_WAKEUP_ERROR' 201 | break 202 | case '03': 203 | descZh = 'CCL_SENSOR_NOT_RESPONSE' 204 | break 205 | case '04': 206 | descZh = 'CCL_SENSOR_DATA_EMPTY' 207 | break 208 | case '05': 209 | descZh = 'CCL_SENSOR_DATA_HEAD_ERROR' 210 | break 211 | case '06': 212 | descZh = 'CCL_SENSOR_DATA_CRC_ERROR' 213 | break 214 | case '07': 215 | descZh = 'CCL_SENSOR_DATA_B1_NO_VALID' 216 | break 217 | case '08': 218 | descZh = 'CCL_SENSOR_DATA_B2_NO_VALID' 219 | break 220 | case '09': 221 | descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH' 222 | break 223 | case '0A': 224 | descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED' 225 | break 226 | case '0B': 227 | descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED' 228 | break 229 | case '0C': 230 | descZh = 'CCL_SENSOR_DATA_VALUE_HI' 231 | break 232 | case '0D': 233 | descZh = 'CCL_SENSOR_DATA_VALUE_LOW' 234 | break 235 | case '0E': 236 | descZh = 'CCL_SENSOR_DATA_VALUE_MISSED' 237 | break 238 | case '0F': 239 | descZh = 'CCL_SENSOR_ARG_INVAILD' 240 | break 241 | case '10': 242 | descZh = 'CCL_SENSOR_RS485_MASTER_BUSY' 243 | break 244 | case '11': 245 | descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR' 246 | break 247 | case '12': 248 | descZh = 'CCL_SENSOR_RS485_REG_MISSED' 249 | break 250 | case '13': 251 | descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR' 252 | break 253 | case '14': 254 | descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR' 255 | break 256 | case '15': 257 | descZh = 'CCL_SENSOR_CONFIG_ERROR' 258 | break 259 | case 'FF': 260 | descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW' 261 | break 262 | default: 263 | descZh = 'CC_OTHER_FAILED' 264 | break 265 | } 266 | messages = [{ 267 | measurementId: '4101', type: 'sensor_error_event', errCode: errorCode, descZh 268 | }] 269 | break 270 | case '10': 271 | let statusValue = dataValue.substring(0, 2) 272 | let { status, type } = loraWANV2BitDataFormat(statusValue) 273 | let sensecapId = dataValue.substring(2) 274 | messages = [{ 275 | status: status, channelType: type, sensorEui: sensecapId 276 | }] 277 | break 278 | case '46': 279 | let measurementTime = loraWANV2PositiveDataFormat(dataValue.substring(0, 8)) * 1000 280 | let offLineTmpOne = loraWanSensorFormat(dataValue.substring(8, 12), 100) 281 | let offLineTmpTwo = loraWanSensorFormat(dataValue.substring(12, 16), 100) 282 | let offLineTmpThree = loraWanSensorFormat(dataValue.substring(16, 20), 100) 283 | messages = [] 284 | if (offLineTmpOne) { 285 | messages.push({ 286 | measurementValue: '' + offLineTmpOne, 287 | measurementId: '4203', 288 | type:"Temperature", 289 | measurementChannel: '1', 290 | measureTime: measurementTime 291 | }) 292 | } 293 | if (offLineTmpTwo) { 294 | messages.push({ 295 | measurementValue: '' + offLineTmpTwo, 296 | measurementId: '4203', 297 | type:"Temperature", 298 | measurementChannel: '2', 299 | measureTime: measurementTime 300 | }) 301 | } 302 | if (offLineTmpThree) { 303 | messages.push({ 304 | measurementValue: '' + offLineTmpThree, 305 | measurementId: '4203', 306 | type:"Temperature", 307 | measurementChannel: '3', 308 | measureTime: measurementTime 309 | }) 310 | } 311 | break 312 | case '47': 313 | let tmpOne = loraWanSensorFormat(dataValue.substring(0, 4), 100) 314 | let tmpTwo = loraWanSensorFormat(dataValue.substring(4, 8), 100) 315 | let tmpThree = loraWanSensorFormat(dataValue.substring(8, 16), 100) 316 | if (tmpOne) { 317 | messages.push({ 318 | measurementValue: '' + tmpOne, 319 | measurementId: '4203', 320 | type:"Temperature", 321 | measurementChannel: '1' 322 | }) 323 | } 324 | if (tmpTwo) { 325 | messages.push({ 326 | measurementValue: '' + tmpTwo, 327 | measurementId: '4203', 328 | type:"Temperature", 329 | measurementChannel: '2' 330 | }) 331 | } 332 | if (tmpThree) { 333 | messages.push({ 334 | measurementValue: '' + tmpThree, 335 | measurementId: '4203', 336 | type:"Temperature", 337 | measurementChannel: '3' 338 | }) 339 | } 340 | break 341 | case '48': 342 | for (let i = 0; i < dataValue.length; i += 2) { 343 | let channelStatusHex = dataValue.substring(i, i + 2) 344 | if (channelStatusHex.toLowerCase() === 'ff') { 345 | continue 346 | } 347 | let channelStatus = loraWANV2DataFormat(channelStatusHex) 348 | let statusStr = 'normal' 349 | if (parseInt(channelStatus) === 0) { 350 | statusStr = 'idle' 351 | } else if (parseInt(channelStatus) === 2) { 352 | statusStr = 'abnormal' 353 | } 354 | messages.push({ 355 | channel_index: '' + (1 + i / 2), status: statusStr, channelType: '1' 356 | }) 357 | } 358 | break 359 | case '49': 360 | messages = [{ 361 | 'Battery(%)': loraWANV2DataFormat(dataValue.substring(0, 2)), 362 | 'Hardware Version': `${loraWANV2DataFormat(dataValue.substring(2, 4))}.${loraWANV2DataFormat(dataValue.substring(4, 6))}`, 363 | 'Firmware Version': `${loraWANV2DataFormat(dataValue.substring(6, 8))}.${loraWANV2DataFormat(dataValue.substring(8, 10))}`, 364 | 'measureInterval': parseInt(loraWANV2DataFormat(dataValue.substring(10, 14))) * 60 365 | }] 366 | break 367 | default: 368 | break 369 | } 370 | return messages 371 | } 372 | 373 | /** 374 | * 375 | * data formatting 376 | * @param str 377 | * @param divisor 378 | * @returns {string|number} 379 | */ 380 | function loraWANV2DataFormat (str, divisor = 1) { 381 | let strReverse = bigEndianTransform(str) 382 | let str2 = toBinary(strReverse) 383 | if (str2.substring(0, 1) === '1') { 384 | let arr = str2.split('') 385 | let reverseArr = arr.map((item) => { 386 | if (parseInt(item) === 1) { 387 | return 0 388 | } else { 389 | return 1 390 | } 391 | }) 392 | str2 = parseInt(reverseArr.join(''), 2) + 1 393 | return parseFloat('-' + str2 / divisor) 394 | } 395 | return parseInt(str2, 2) / divisor 396 | } 397 | 398 | function loraWanSensorFormat (str, divisor = 1) { 399 | if (str === '8000') { 400 | return null 401 | } 402 | return loraWANV2DataFormat(str, divisor) 403 | } 404 | 405 | /** 406 | * Handling big-endian data formats 407 | * @param data 408 | * @returns {*[]} 409 | */ 410 | function bigEndianTransform (data) { 411 | let dataArray = [] 412 | for (let i = 0; i < data.length; i += 2) { 413 | dataArray.push(data.substring(i, i + 2)) 414 | } 415 | // array of hex 416 | return dataArray 417 | } 418 | 419 | function loraWANV2PositiveDataFormat (str, divisor = 1) { 420 | let strReverse = bigEndianTransform(str) 421 | let str2 = toBinary(strReverse) 422 | return parseInt(str2, 2) / divisor 423 | } 424 | 425 | /** 426 | * Convert to an 8-digit binary number with 0s in front of the number 427 | * @param arr 428 | * @returns {string} 429 | */ 430 | function toBinary (arr) { 431 | let binaryData = arr.map((item) => { 432 | let data = parseInt(item, 16) 433 | .toString(2) 434 | let dataLength = data.length 435 | if (data.length !== 8) { 436 | for (let i = 0; i < 8 - dataLength; i++) { 437 | data = `0` + data 438 | } 439 | } 440 | return data 441 | }) 442 | let ret = binaryData.toString() 443 | .replace(/,/g, '') 444 | return ret 445 | } 446 | 447 | /** 448 | * sensor 449 | * @param str 450 | * @returns {{channel: number, type: number, status: number}} 451 | */ 452 | function loraWANV2BitDataFormat (str) { 453 | let strReverse = bigEndianTransform(str) 454 | let str2 = toBinary(strReverse) 455 | let channel = parseInt(str2.substring(0, 4), 2) 456 | let status = parseInt(str2.substring(4, 5), 2) 457 | let type = parseInt(str2.substring(5), 2) 458 | return { channel, status, type } 459 | } 460 | 461 | /** 462 | * channel info 463 | * @param str 464 | * @returns {{channelTwo: number, channelOne: number}} 465 | */ 466 | function loraWANV2ChannelBitFormat (str) { 467 | let strReverse = bigEndianTransform(str) 468 | let str2 = toBinary(strReverse) 469 | let one = parseInt(str2.substring(0, 4), 2) 470 | let two = parseInt(str2.substring(4, 8), 2) 471 | let resultInfo = { 472 | one: one, two: two 473 | } 474 | return resultInfo 475 | } 476 | 477 | /** 478 | * data log status bit 479 | * @param str 480 | * @returns {{total: number, level: number, isTH: number}} 481 | */ 482 | function loraWANV2DataLogBitFormat (str) { 483 | let strReverse = bigEndianTransform(str) 484 | let str2 = toBinary(strReverse) 485 | let isTH = parseInt(str2.substring(0, 1), 2) 486 | let total = parseInt(str2.substring(1, 5), 2) 487 | let left = parseInt(str2.substring(5), 2) 488 | let resultInfo = { 489 | isTH: isTH, total: total, left: left 490 | } 491 | return resultInfo 492 | } 493 | 494 | function bytes2HexString (arrBytes) { 495 | var str = '' 496 | for (var i = 0; i < arrBytes.length; i++) { 497 | var tmp 498 | var num = arrBytes[i] 499 | if (num < 0) { 500 | tmp = (255 + num + 1).toString(16) 501 | } else { 502 | tmp = num.toString(16) 503 | } 504 | if (tmp.length === 1) { 505 | tmp = '0' + tmp 506 | } 507 | str += tmp 508 | } 509 | return str 510 | } -------------------------------------------------------------------------------- /S2107/TTN/SenseCAP_S2107_TTN_Decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Entry, decoder.js 3 | */ 4 | function decodeUplink (input, port) { 5 | // data split 6 | 7 | var bytes = input['bytes'] 8 | // init 9 | bytes = bytes2HexString(bytes) 10 | .toLocaleUpperCase() 11 | 12 | let result = { 13 | 'err': 0, 'payload': bytes, 'valid': true, messages: [] 14 | } 15 | let splitArray = dataSplit(bytes) 16 | // data decoder 17 | let decoderArray = [] 18 | for (let i = 0; i < splitArray.length; i++) { 19 | let item = splitArray[i] 20 | let dataId = item.dataId 21 | let dataValue = item.dataValue 22 | let messages = dataIdAndDataValueJudge(dataId, dataValue) 23 | decoderArray.push(messages) 24 | } 25 | result.messages = decoderArray 26 | return { data: result } 27 | } 28 | 29 | /** 30 | * data splits 31 | * @param bytes 32 | * @returns {*[]} 33 | */ 34 | function dataSplit (bytes) { 35 | let frameArray = [] 36 | bytes = bytes.toLowerCase() 37 | for (let i = 0; i < bytes.length; i++) { 38 | let remainingValue = bytes 39 | let dataId = remainingValue.substring(0, 2) 40 | let dataValue 41 | let dataObj = {} 42 | switch (dataId) { 43 | case '01' : 44 | case '20' : 45 | case '21' : 46 | case '30' : 47 | case '31' : 48 | case '33' : 49 | case '40' : 50 | case '41' : 51 | case '42' : 52 | case '43' : 53 | case '44' : 54 | case '45' : 55 | case '46' : 56 | case '48' : 57 | dataValue = remainingValue.substring(2, 22) 58 | bytes = remainingValue.substring(22) 59 | dataObj = { 60 | 'dataId': dataId, 'dataValue': dataValue 61 | } 62 | break 63 | case '02': 64 | dataValue = remainingValue.substring(2, 18) 65 | bytes = remainingValue.substring(18) 66 | dataObj = { 67 | 'dataId': '02', 'dataValue': dataValue 68 | } 69 | break 70 | case '03' : 71 | case '06': 72 | dataValue = remainingValue.substring(2, 4) 73 | bytes = remainingValue.substring(4) 74 | dataObj = { 75 | 'dataId': dataId, 'dataValue': dataValue 76 | } 77 | break 78 | case '05' : 79 | case '34' : 80 | dataValue = bytes.substring(2, 10) 81 | bytes = remainingValue.substring(10) 82 | dataObj = { 83 | 'dataId': dataId, 'dataValue': dataValue 84 | } 85 | break 86 | case '04' : 87 | case '10' : 88 | case '32' : 89 | case '35' : 90 | case '36' : 91 | case '37' : 92 | case '38' : 93 | case '39' : 94 | dataValue = bytes.substring(2, 20) 95 | bytes = remainingValue.substring(20) 96 | dataObj = { 97 | 'dataId': dataId, 'dataValue': dataValue 98 | } 99 | break 100 | case '47' : 101 | dataValue = bytes.substring(2, 14) 102 | bytes = remainingValue.substring(14) 103 | dataObj = { 104 | 'dataId': dataId, 'dataValue': dataValue 105 | } 106 | break 107 | case '49' : 108 | dataValue = bytes.substring(2, 16) 109 | bytes = remainingValue.substring(16) 110 | dataObj = { 111 | 'dataId': dataId, 'dataValue': dataValue 112 | } 113 | break 114 | case '7f' : 115 | bytes = remainingValue.substring(4) 116 | continue 117 | default: 118 | dataValue = '9' 119 | break 120 | } 121 | if (dataValue.length < 2) { 122 | break 123 | } 124 | frameArray.push(dataObj) 125 | } 126 | return frameArray 127 | } 128 | 129 | function dataIdAndDataValueJudge (dataId, dataValue) { 130 | let messages = [] 131 | switch (dataId) { 132 | case '01': 133 | let temperature = dataValue.substring(0, 4) 134 | let humidity = dataValue.substring(4, 6) 135 | let illumination = dataValue.substring(6, 14) 136 | let uv = dataValue.substring(14, 16) 137 | let windSpeed = dataValue.substring(16, 20) 138 | messages = [{ 139 | measurementValue: loraWANV2DataFormat(temperature, 10), measurementId: '4097', type: 'Air Temperature' 140 | }, { 141 | measurementValue: loraWANV2DataFormat(humidity), measurementId: '4098', type: 'Air Humidity' 142 | }, { 143 | measurementValue: loraWANV2DataFormat(illumination), measurementId: '4099', type: 'Light Intensity' 144 | }, { 145 | measurementValue: loraWANV2DataFormat(uv, 10), measurementId: '4190', type: 'UV Index' 146 | }, { 147 | measurementValue: loraWANV2DataFormat(windSpeed, 10), measurementId: '4105', type: 'Wind Speed' 148 | }] 149 | break 150 | case '02': 151 | let windDirection = dataValue.substring(0, 4) 152 | let rainfall = dataValue.substring(4, 12) 153 | let airPressure = dataValue.substring(12, 16) 154 | messages = [{ 155 | measurementValue: loraWANV2DataFormat(windDirection), measurementId: '4104', type: 'Wind Direction Sensor' 156 | }, { 157 | measurementValue: loraWANV2DataFormat(rainfall, 1000), measurementId: '4113', type: 'Rain Gauge' 158 | }, { 159 | 160 | measurementValue: loraWANV2DataFormat(airPressure, 0.1), measurementId: '4101', type: 'Barometric Pressure' 161 | }] 162 | break 163 | case '03': 164 | let Electricity = dataValue 165 | messages = [{ 166 | 'Battery(%)': loraWANV2DataFormat(Electricity) 167 | }] 168 | break 169 | case '04': 170 | let electricityWhether = dataValue.substring(0, 2) 171 | let hwv = dataValue.substring(2, 6) 172 | let bdv = dataValue.substring(6, 10) 173 | let sensorAcquisitionInterval = dataValue.substring(10, 14) 174 | let gpsAcquisitionInterval = dataValue.substring(14, 18) 175 | messages = [{ 176 | 'Battery(%)': loraWANV2DataFormat(electricityWhether), 177 | 'Hardware Version': `${loraWANV2DataFormat(hwv.substring(0, 2))}.${loraWANV2DataFormat(hwv.substring(2, 4))}`, 178 | 'Firmware Version': `${loraWANV2DataFormat(bdv.substring(0, 2))}.${loraWANV2DataFormat(bdv.substring(2, 4))}`, 179 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionInterval)) * 60, 180 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionInterval)) * 60 181 | }] 182 | break 183 | case '05': 184 | let sensorAcquisitionIntervalFive = dataValue.substring(0, 4) 185 | let gpsAcquisitionIntervalFive = dataValue.substring(4, 8) 186 | messages = [{ 187 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalFive)) * 60, 188 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalFive)) * 60 189 | }] 190 | break 191 | case '06': 192 | let errorCode = dataValue 193 | let descZh 194 | switch (errorCode) { 195 | case '00': 196 | descZh = 'CCL_SENSOR_ERROR_NONE' 197 | break 198 | case '01': 199 | descZh = 'CCL_SENSOR_NOT_FOUND' 200 | break 201 | case '02': 202 | descZh = 'CCL_SENSOR_WAKEUP_ERROR' 203 | break 204 | case '03': 205 | descZh = 'CCL_SENSOR_NOT_RESPONSE' 206 | break 207 | case '04': 208 | descZh = 'CCL_SENSOR_DATA_EMPTY' 209 | break 210 | case '05': 211 | descZh = 'CCL_SENSOR_DATA_HEAD_ERROR' 212 | break 213 | case '06': 214 | descZh = 'CCL_SENSOR_DATA_CRC_ERROR' 215 | break 216 | case '07': 217 | descZh = 'CCL_SENSOR_DATA_B1_NO_VALID' 218 | break 219 | case '08': 220 | descZh = 'CCL_SENSOR_DATA_B2_NO_VALID' 221 | break 222 | case '09': 223 | descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH' 224 | break 225 | case '0A': 226 | descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED' 227 | break 228 | case '0B': 229 | descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED' 230 | break 231 | case '0C': 232 | descZh = 'CCL_SENSOR_DATA_VALUE_HI' 233 | break 234 | case '0D': 235 | descZh = 'CCL_SENSOR_DATA_VALUE_LOW' 236 | break 237 | case '0E': 238 | descZh = 'CCL_SENSOR_DATA_VALUE_MISSED' 239 | break 240 | case '0F': 241 | descZh = 'CCL_SENSOR_ARG_INVAILD' 242 | break 243 | case '10': 244 | descZh = 'CCL_SENSOR_RS485_MASTER_BUSY' 245 | break 246 | case '11': 247 | descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR' 248 | break 249 | case '12': 250 | descZh = 'CCL_SENSOR_RS485_REG_MISSED' 251 | break 252 | case '13': 253 | descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR' 254 | break 255 | case '14': 256 | descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR' 257 | break 258 | case '15': 259 | descZh = 'CCL_SENSOR_CONFIG_ERROR' 260 | break 261 | case 'FF': 262 | descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW' 263 | break 264 | default: 265 | descZh = 'CC_OTHER_FAILED' 266 | break 267 | } 268 | messages = [{ 269 | measurementId: '4101', type: 'sensor_error_event', errCode: errorCode, descZh 270 | }] 271 | break 272 | case '10': 273 | let statusValue = dataValue.substring(0, 2) 274 | let { status, type } = loraWANV2BitDataFormat(statusValue) 275 | let sensecapId = dataValue.substring(2) 276 | messages = [{ 277 | status: status, channelType: type, sensorEui: sensecapId 278 | }] 279 | break 280 | case '46': 281 | let measurementTime = loraWANV2PositiveDataFormat(dataValue.substring(0, 8)) * 1000 282 | let offLineTmpOne = loraWanSensorFormat(dataValue.substring(8, 12), 100) 283 | let offLineTmpTwo = loraWanSensorFormat(dataValue.substring(12, 16), 100) 284 | let offLineTmpThree = loraWanSensorFormat(dataValue.substring(16, 20), 100) 285 | messages = [] 286 | if (offLineTmpOne) { 287 | messages.push({ 288 | measurementValue: '' + offLineTmpOne, 289 | measurementId: '4203', 290 | type:"Temperature", 291 | measurementChannel: '1', 292 | measureTime: measurementTime 293 | }) 294 | } 295 | if (offLineTmpTwo) { 296 | messages.push({ 297 | measurementValue: '' + offLineTmpTwo, 298 | measurementId: '4203', 299 | type:"Temperature", 300 | measurementChannel: '2', 301 | measureTime: measurementTime 302 | }) 303 | } 304 | if (offLineTmpThree) { 305 | messages.push({ 306 | measurementValue: '' + offLineTmpThree, 307 | measurementId: '4203', 308 | type:"Temperature", 309 | measurementChannel: '3', 310 | measureTime: measurementTime 311 | }) 312 | } 313 | break 314 | case '47': 315 | let tmpOne = loraWanSensorFormat(dataValue.substring(0, 4), 100) 316 | let tmpTwo = loraWanSensorFormat(dataValue.substring(4, 8), 100) 317 | let tmpThree = loraWanSensorFormat(dataValue.substring(8, 16), 100) 318 | if (tmpOne) { 319 | messages.push({ 320 | measurementValue: '' + tmpOne, 321 | measurementId: '4203', 322 | type:"Temperature", 323 | measurementChannel: '1' 324 | }) 325 | } 326 | if (tmpTwo) { 327 | messages.push({ 328 | measurementValue: '' + tmpTwo, 329 | measurementId: '4203', 330 | type:"Temperature", 331 | measurementChannel: '2' 332 | }) 333 | } 334 | if (tmpThree) { 335 | messages.push({ 336 | measurementValue: '' + tmpThree, 337 | measurementId: '4203', 338 | type:"Temperature", 339 | measurementChannel: '3' 340 | }) 341 | } 342 | break 343 | case '48': 344 | for (let i = 0; i < dataValue.length; i += 2) { 345 | let channelStatusHex = dataValue.substring(i, i + 2) 346 | if (channelStatusHex.toLowerCase() === 'ff') { 347 | continue 348 | } 349 | let channelStatus = loraWANV2DataFormat(channelStatusHex) 350 | let statusStr = 'normal' 351 | if (parseInt(channelStatus) === 0) { 352 | statusStr = 'idle' 353 | } else if (parseInt(channelStatus) === 2) { 354 | statusStr = 'abnormal' 355 | } 356 | messages.push({ 357 | channel_index: '' + (1 + i / 2), status: statusStr, channelType: '1' 358 | }) 359 | } 360 | break 361 | case '49': 362 | messages = [{ 363 | 'Battery(%)': loraWANV2DataFormat(dataValue.substring(0, 2)), 364 | 'Hardware Version': `${loraWANV2DataFormat(dataValue.substring(2, 4))}.${loraWANV2DataFormat(dataValue.substring(4, 6))}`, 365 | 'Firmware Version': `${loraWANV2DataFormat(dataValue.substring(6, 8))}.${loraWANV2DataFormat(dataValue.substring(8, 10))}`, 366 | 'measureInterval': parseInt(loraWANV2DataFormat(dataValue.substring(10, 14))) * 60 367 | }] 368 | break 369 | default: 370 | break 371 | } 372 | return messages 373 | } 374 | 375 | /** 376 | * 377 | * data formatting 378 | * @param str 379 | * @param divisor 380 | * @returns {string|number} 381 | */ 382 | function loraWANV2DataFormat (str, divisor = 1) { 383 | let strReverse = bigEndianTransform(str) 384 | let str2 = toBinary(strReverse) 385 | if (str2.substring(0, 1) === '1') { 386 | let arr = str2.split('') 387 | let reverseArr = arr.map((item) => { 388 | if (parseInt(item) === 1) { 389 | return 0 390 | } else { 391 | return 1 392 | } 393 | }) 394 | str2 = parseInt(reverseArr.join(''), 2) + 1 395 | return parseFloat('-' + str2 / divisor) 396 | } 397 | return parseInt(str2, 2) / divisor 398 | } 399 | 400 | function loraWanSensorFormat (str, divisor = 1) { 401 | if (str === '8000') { 402 | return null 403 | } 404 | return loraWANV2DataFormat(str, divisor) 405 | } 406 | 407 | /** 408 | * Handling big-endian data formats 409 | * @param data 410 | * @returns {*[]} 411 | */ 412 | function bigEndianTransform (data) { 413 | let dataArray = [] 414 | for (let i = 0; i < data.length; i += 2) { 415 | dataArray.push(data.substring(i, i + 2)) 416 | } 417 | // array of hex 418 | return dataArray 419 | } 420 | 421 | function loraWANV2PositiveDataFormat (str, divisor = 1) { 422 | let strReverse = bigEndianTransform(str) 423 | let str2 = toBinary(strReverse) 424 | return parseInt(str2, 2) / divisor 425 | } 426 | 427 | /** 428 | * Convert to an 8-digit binary number with 0s in front of the number 429 | * @param arr 430 | * @returns {string} 431 | */ 432 | function toBinary (arr) { 433 | let binaryData = arr.map((item) => { 434 | let data = parseInt(item, 16) 435 | .toString(2) 436 | let dataLength = data.length 437 | if (data.length !== 8) { 438 | for (let i = 0; i < 8 - dataLength; i++) { 439 | data = `0` + data 440 | } 441 | } 442 | return data 443 | }) 444 | let ret = binaryData.toString() 445 | .replace(/,/g, '') 446 | return ret 447 | } 448 | 449 | /** 450 | * sensor 451 | * @param str 452 | * @returns {{channel: number, type: number, status: number}} 453 | */ 454 | function loraWANV2BitDataFormat (str) { 455 | let strReverse = bigEndianTransform(str) 456 | let str2 = toBinary(strReverse) 457 | let channel = parseInt(str2.substring(0, 4), 2) 458 | let status = parseInt(str2.substring(4, 5), 2) 459 | let type = parseInt(str2.substring(5), 2) 460 | return { channel, status, type } 461 | } 462 | 463 | /** 464 | * channel info 465 | * @param str 466 | * @returns {{channelTwo: number, channelOne: number}} 467 | */ 468 | function loraWANV2ChannelBitFormat (str) { 469 | let strReverse = bigEndianTransform(str) 470 | let str2 = toBinary(strReverse) 471 | let one = parseInt(str2.substring(0, 4), 2) 472 | let two = parseInt(str2.substring(4, 8), 2) 473 | let resultInfo = { 474 | one: one, two: two 475 | } 476 | return resultInfo 477 | } 478 | 479 | /** 480 | * data log status bit 481 | * @param str 482 | * @returns {{total: number, level: number, isTH: number}} 483 | */ 484 | function loraWANV2DataLogBitFormat (str) { 485 | let strReverse = bigEndianTransform(str) 486 | let str2 = toBinary(strReverse) 487 | let isTH = parseInt(str2.substring(0, 1), 2) 488 | let total = parseInt(str2.substring(1, 5), 2) 489 | let left = parseInt(str2.substring(5), 2) 490 | let resultInfo = { 491 | isTH: isTH, total: total, left: left 492 | } 493 | return resultInfo 494 | } 495 | 496 | function bytes2HexString (arrBytes) { 497 | var str = '' 498 | for (var i = 0; i < arrBytes.length; i++) { 499 | var tmp 500 | var num = arrBytes[i] 501 | if (num < 0) { 502 | tmp = (255 + num + 1).toString(16) 503 | } else { 504 | tmp = num.toString(16) 505 | } 506 | if (tmp.length === 1) { 507 | tmp = '0' + tmp 508 | } 509 | str += tmp 510 | } 511 | return str 512 | } -------------------------------------------------------------------------------- /S210X/AWS/SenseCAP_S210x_AWS_Decoder.js: -------------------------------------------------------------------------------- 1 | //Lambda function script 2 | 3 | const {IoTDataPlaneClient, PublishCommand} = require("@aws-sdk/client-iot-data-plane"); 4 | const client = new IoTDataPlaneClient({ 5 | //Replace the region according to your device. 6 | "region": "us-east-1" 7 | }); 8 | const topic_prefix = 'sensor/s210x/' 9 | 10 | function decodeUplink (input) { 11 | const bytes = Buffer.from(input, 'base64'); 12 | // // init 13 | var bytesString = bytes2HexString(bytes) 14 | .toLocaleUpperCase(); 15 | // var bytesString = input 16 | var decoded = { 17 | // valid 18 | valid: true, err: 0, // bytes 19 | payload: bytesString, // messages array 20 | messages: [] 21 | } 22 | 23 | // CRC check 24 | if (!crc16Check(bytesString)) { 25 | decoded['valid'] = false 26 | decoded['err'] = -1 // "crc check fail." 27 | return { data: decoded } 28 | } 29 | 30 | // Length Check 31 | if ((((bytesString.length / 2) - 2) % 7) !== 0) { 32 | decoded['valid'] = false 33 | decoded['err'] = -2 // "length check fail." 34 | return { data: decoded } 35 | } 36 | 37 | // Cache sensor id 38 | var sensorEuiLowBytes 39 | var sensorEuiHighBytes 40 | 41 | // Handle each frame 42 | var frameArray = divideBy7Bytes(bytesString) 43 | for (var forFrame = 0; forFrame < frameArray.length; forFrame++) { 44 | var frame = frameArray[forFrame] 45 | // Extract key parameters 46 | var channel = strTo10SysNub(frame.substring(0, 2)) 47 | var dataID = strTo10SysNub(frame.substring(2, 6)) 48 | var dataValue = frame.substring(6, 14) 49 | var realDataValue = isSpecialDataId(dataID) ? ttnDataSpecialFormat(dataID, dataValue) : ttnDataFormat(dataValue) 50 | 51 | if (checkDataIdIsMeasureUpload(dataID)) { 52 | // if telemetry. 53 | decoded.messages.push({ 54 | type: 'report_telemetry', measurementId: dataID, measurementValue: realDataValue 55 | }) 56 | } else if (isSpecialDataId(dataID) || (dataID === 5) || (dataID === 6)) { 57 | // if special order, except "report_sensor_id". 58 | switch (dataID) { 59 | case 0x00: 60 | // node version 61 | var versionData = sensorAttrForVersion(realDataValue) 62 | decoded.messages.push({ 63 | type: 'upload_version', hardwareVersion: versionData.ver_hardware, softwareVersion: versionData.ver_software 64 | }) 65 | break 66 | case 1: 67 | // sensor version 68 | break 69 | case 2: 70 | // sensor eui, low bytes 71 | sensorEuiLowBytes = realDataValue 72 | break 73 | case 3: 74 | // sensor eui, high bytes 75 | sensorEuiHighBytes = realDataValue 76 | break 77 | case 7: 78 | // battery power && interval 79 | decoded.messages.push({ 80 | type: 'upload_battery', battery: realDataValue.power 81 | }, { 82 | type: 'upload_interval', interval: parseInt(realDataValue.interval) * 60 83 | }) 84 | break 85 | case 9: 86 | decoded.messages.push({ 87 | type: 'model_info', 88 | detectionType: realDataValue.detectionType, 89 | modelId: realDataValue.modelId, 90 | modelVer: realDataValue.modelVer 91 | }) 92 | break 93 | case 0x120: 94 | // remove sensor 95 | decoded.messages.push({ 96 | type: 'report_remove_sensor', channel: 1 97 | }) 98 | break 99 | default: 100 | break 101 | } 102 | } else { 103 | decoded.messages.push({ 104 | type: 'unknown_message', dataID: dataID, dataValue: dataValue 105 | }) 106 | } 107 | 108 | } 109 | 110 | // if the complete id received, as "upload_sensor_id" 111 | if (sensorEuiHighBytes && sensorEuiLowBytes) { 112 | decoded.messages.unshift({ 113 | type: 'upload_sensor_id', channel: 1, sensorId: (sensorEuiHighBytes + sensorEuiLowBytes).toUpperCase() 114 | }) 115 | } 116 | // return 117 | return { data: decoded } 118 | } 119 | 120 | function crc16Check (data) { 121 | return true 122 | } 123 | 124 | // util 125 | function bytes2HexString (arrBytes) { 126 | var str = '' 127 | for (var i = 0; i < arrBytes.length; i++) { 128 | var tmp 129 | var num = arrBytes[i] 130 | if (num < 0) { 131 | tmp = (255 + num + 1).toString(16) 132 | } else { 133 | tmp = num.toString(16) 134 | } 135 | if (tmp.length === 1) { 136 | tmp = '0' + tmp 137 | } 138 | str += tmp 139 | } 140 | return str 141 | } 142 | 143 | // util 144 | function divideBy7Bytes (str) { 145 | var frameArray = [] 146 | for (var i = 0; i < str.length - 4; i += 14) { 147 | var data = str.substring(i, i + 14) 148 | frameArray.push(data) 149 | } 150 | return frameArray 151 | } 152 | 153 | // util 154 | function littleEndianTransform (data) { 155 | var dataArray = [] 156 | for (var i = 0; i < data.length; i += 2) { 157 | dataArray.push(data.substring(i, i + 2)) 158 | } 159 | dataArray.reverse() 160 | return dataArray 161 | } 162 | 163 | // util 164 | function strTo10SysNub (str) { 165 | var arr = littleEndianTransform(str) 166 | return parseInt(arr.toString() 167 | .replace(/,/g, ''), 16) 168 | } 169 | 170 | // util 171 | function checkDataIdIsMeasureUpload (dataId) { 172 | return parseInt(dataId) > 4096 173 | } 174 | 175 | // configurable. 176 | function isSpecialDataId (dataID) { 177 | switch (dataID) { 178 | case 0: 179 | case 1: 180 | case 2: 181 | case 3: 182 | case 4: 183 | case 7: 184 | case 9: 185 | case 0x120: 186 | return true 187 | default: 188 | return false 189 | } 190 | } 191 | 192 | // configurable 193 | function ttnDataSpecialFormat (dataId, str) { 194 | var strReverse = littleEndianTransform(str) 195 | if (dataId === 2 || dataId === 3) { 196 | return strReverse.join('') 197 | } 198 | 199 | // handle unsigned number 200 | var str2 = toBinary(strReverse) 201 | var dataArray = [] 202 | switch (dataId) { 203 | case 0: // DATA_BOARD_VERSION 204 | case 1: // DATA_SENSOR_VERSION 205 | // Using point segmentation 206 | for (var k = 0; k < str2.length; k += 16) { 207 | var tmp146 = str2.substring(k, k + 16) 208 | tmp146 = (parseInt(tmp146.substring(0, 8), 2) || 0) + '.' + (parseInt(tmp146.substring(8, 16), 2) || 0) 209 | dataArray.push(tmp146) 210 | } 211 | return dataArray.join(',') 212 | case 4: 213 | for (var i = 0; i < str2.length; i += 8) { 214 | var item = parseInt(str2.substring(i, i + 8), 2) 215 | if (item < 10) { 216 | item = '0' + item.toString() 217 | } else { 218 | item = item.toString() 219 | } 220 | dataArray.push(item) 221 | } 222 | return dataArray.join('') 223 | case 7: 224 | // battery && interval 225 | return { 226 | interval: parseInt(str2.substr(0, 16), 2), power: parseInt(str2.substr(-16, 16), 2) 227 | } 228 | case 9: 229 | let dataValue = { 230 | detectionType: parseInt(str2.substring(0, 8), 2), 231 | modelId: parseInt(str2.substring(8, 16), 2), 232 | modelVer: parseInt(str2.substring(16, 24), 2) 233 | } 234 | // 01010000 235 | return dataValue 236 | } 237 | } 238 | 239 | // util 240 | function ttnDataFormat (str) { 241 | var strReverse = littleEndianTransform(str) 242 | var str2 = toBinary(strReverse) 243 | if (str2.substring(0, 1) === '1') { 244 | var arr = str2.split('') 245 | var reverseArr = [] 246 | for (var forArr = 0; forArr < arr.length; forArr++) { 247 | var item = arr[forArr] 248 | if (parseInt(item) === 1) { 249 | reverseArr.push(0) 250 | } else { 251 | reverseArr.push(1) 252 | } 253 | } 254 | str2 = parseInt(reverseArr.join(''), 2) + 1 255 | return parseFloat('-' + str2 / 1000) 256 | } 257 | return parseInt(str2, 2) / 1000 258 | } 259 | 260 | // util 261 | function sensorAttrForVersion (dataValue) { 262 | var dataValueSplitArray = dataValue.split(',') 263 | return { 264 | ver_hardware: dataValueSplitArray[0], ver_software: dataValueSplitArray[1] 265 | } 266 | } 267 | 268 | // util 269 | function toBinary (arr) { 270 | var binaryData = [] 271 | for (var forArr = 0; forArr < arr.length; forArr++) { 272 | var item = arr[forArr] 273 | var data = parseInt(item, 16) 274 | .toString(2) 275 | var dataLength = data.length 276 | if (data.length !== 8) { 277 | for (var i = 0; i < 8 - dataLength; i++) { 278 | data = '0' + data 279 | } 280 | } 281 | binaryData.push(data) 282 | } 283 | return binaryData.toString() 284 | .replace(/,/g, '') 285 | } 286 | 287 | exports.handler = async (event) => { 288 | try { 289 | const lorawan_info = event["WirelessMetadata"]["LoRaWAN"]; 290 | const device_id = event["WirelessDeviceId"] 291 | const device_eui = lorawan_info["DevEui"] 292 | const lorawan_data = event["PayloadData"]; 293 | const resolved_data = decodeUplink(lorawan_data, lorawan_info["FPort"]); 294 | 295 | // publish all measurement data 296 | const input = { // PublishRequest 297 | topic: `${topic_prefix}${device_eui}`, 298 | qos: 0, 299 | retain: false, 300 | payload: JSON.stringify({ 301 | eui: device_eui, 302 | device_id: device_id, 303 | timestamp: lorawan_info["Timestamp"], 304 | data: resolved_data.data 305 | }) 306 | }; 307 | const command = new PublishCommand(input); 308 | const response = await client.send(command); 309 | console.log("response: " + JSON.stringify(response)); 310 | return { 311 | statusCode: 200, 312 | body: 'Message published successfully' + JSON.stringify(event) 313 | }; 314 | } catch (error) { 315 | console.error('Error publishing message:', error); 316 | 317 | return { 318 | statusCode: 500, 319 | body: 'Error publishing message' 320 | }; 321 | } 322 | }; 323 | 324 | -------------------------------------------------------------------------------- /S210X/ChirpStack/SenseCAP_S210x_ChirpStackV3_Decoder.js: -------------------------------------------------------------------------------- 1 | function Decode(fPort, bytes, variables) { 2 | var bytesString = bytes2HexString(bytes).toLocaleUpperCase(); 3 | var fport = parseInt(fPort); 4 | // var bytesString = input 5 | var decoded = { 6 | // valid 7 | valid: true, 8 | err: 0, 9 | // bytes 10 | payload: bytesString, 11 | // messages array 12 | messages: [] 13 | }; 14 | 15 | // CRC check 16 | if (!crc16Check(bytesString)) { 17 | decoded['valid'] = false; 18 | decoded['err'] = -1; // "crc check fail." 19 | return { 20 | data: decoded 21 | }; 22 | } 23 | 24 | // Length Check 25 | if ((bytesString.length / 2 - 2) % 7 !== 0) { 26 | decoded['valid'] = false; 27 | decoded['err'] = -2; // "length check fail." 28 | return { 29 | data: decoded 30 | }; 31 | } 32 | 33 | // Cache sensor id 34 | var sensorEuiLowBytes; 35 | var sensorEuiHighBytes; 36 | 37 | // Handle each frame 38 | var frameArray = divideBy7Bytes(bytesString); 39 | for (var forFrame = 0; forFrame < frameArray.length; forFrame++) { 40 | var frame = frameArray[forFrame]; 41 | // Extract key parameters 42 | var channel = strTo10SysNub(frame.substring(0, 2)); 43 | var dataID = strTo10SysNub(frame.substring(2, 6)); 44 | var dataValue = frame.substring(6, 14); 45 | var realDataValue = isSpecialDataId(dataID) ? ttnDataSpecialFormat(dataID, dataValue) : ttnDataFormat(dataValue); 46 | if (checkDataIdIsMeasureUpload(dataID)) { 47 | // if telemetry. 48 | decoded.messages.push({ 49 | type: 'report_telemetry', 50 | measurementId: dataID, 51 | measurementValue: realDataValue 52 | }); 53 | } else if (isSpecialDataId(dataID) || dataID === 5 || dataID === 6) { 54 | // if special order, except "report_sensor_id". 55 | switch (dataID) { 56 | case 0x00: 57 | // node version 58 | var versionData = sensorAttrForVersion(realDataValue); 59 | decoded.messages.push({ 60 | type: 'upload_version', 61 | hardwareVersion: versionData.ver_hardware, 62 | softwareVersion: versionData.ver_software 63 | }); 64 | break; 65 | case 1: 66 | // sensor version 67 | break; 68 | case 2: 69 | // sensor eui, low bytes 70 | sensorEuiLowBytes = realDataValue; 71 | break; 72 | case 3: 73 | // sensor eui, high bytes 74 | sensorEuiHighBytes = realDataValue; 75 | break; 76 | case 7: 77 | // battery power && interval 78 | decoded.messages.push({ 79 | type: 'upload_battery', 80 | battery: realDataValue.power 81 | }, { 82 | type: 'upload_interval', 83 | interval: parseInt(realDataValue.interval) * 60 84 | }); 85 | break; 86 | case 9: 87 | decoded.messages.push({ 88 | type: 'model_info', 89 | detectionType: realDataValue.detectionType, 90 | modelId: realDataValue.modelId, 91 | modelVer: realDataValue.modelVer 92 | }); 93 | break; 94 | case 0x120: 95 | // remove sensor 96 | decoded.messages.push({ 97 | type: 'report_remove_sensor', 98 | channel: 1 99 | }); 100 | break; 101 | default: 102 | break; 103 | } 104 | } else { 105 | decoded.messages.push({ 106 | type: 'unknown_message', 107 | dataID: dataID, 108 | dataValue: dataValue 109 | }); 110 | } 111 | } 112 | 113 | // if the complete id received, as "upload_sensor_id" 114 | if (sensorEuiHighBytes && sensorEuiLowBytes) { 115 | decoded.messages.unshift({ 116 | type: 'upload_sensor_id', 117 | channel: 1, 118 | sensorId: (sensorEuiHighBytes + sensorEuiLowBytes).toUpperCase() 119 | }); 120 | } 121 | // return 122 | return { 123 | data: decoded 124 | }; 125 | } 126 | function crc16Check(data) { 127 | return true; 128 | } 129 | 130 | // util 131 | function bytes2HexString(arrBytes) { 132 | var str = ''; 133 | for (var i = 0; i < arrBytes.length; i++) { 134 | var tmp; 135 | var num = arrBytes[i]; 136 | if (num < 0) { 137 | tmp = (255 + num + 1).toString(16); 138 | } else { 139 | tmp = num.toString(16); 140 | } 141 | if (tmp.length === 1) { 142 | tmp = '0' + tmp; 143 | } 144 | str += tmp; 145 | } 146 | return str; 147 | } 148 | 149 | // util 150 | function divideBy7Bytes(str) { 151 | var frameArray = []; 152 | for (var i = 0; i < str.length - 4; i += 14) { 153 | var data = str.substring(i, i + 14); 154 | frameArray.push(data); 155 | } 156 | return frameArray; 157 | } 158 | 159 | // util 160 | function littleEndianTransform(data) { 161 | var dataArray = []; 162 | for (var i = 0; i < data.length; i += 2) { 163 | dataArray.push(data.substring(i, i + 2)); 164 | } 165 | dataArray.reverse(); 166 | return dataArray; 167 | } 168 | 169 | // util 170 | function strTo10SysNub(str) { 171 | var arr = littleEndianTransform(str); 172 | return parseInt(arr.toString().replace(/,/g, ''), 16); 173 | } 174 | 175 | // util 176 | function checkDataIdIsMeasureUpload(dataId) { 177 | return parseInt(dataId) > 4096; 178 | } 179 | 180 | // configurable. 181 | function isSpecialDataId(dataID) { 182 | switch (dataID) { 183 | case 0: 184 | case 1: 185 | case 2: 186 | case 3: 187 | case 4: 188 | case 7: 189 | case 9: 190 | case 0x120: 191 | return true; 192 | default: 193 | return false; 194 | } 195 | } 196 | 197 | // configurable 198 | function ttnDataSpecialFormat(dataId, str) { 199 | var strReverse = littleEndianTransform(str); 200 | if (dataId === 2 || dataId === 3) { 201 | return strReverse.join(''); 202 | } 203 | 204 | // handle unsigned number 205 | var str2 = toBinary(strReverse); 206 | var dataArray = []; 207 | switch (dataId) { 208 | case 0: // DATA_BOARD_VERSION 209 | case 1: 210 | // DATA_SENSOR_VERSION 211 | // Using point segmentation 212 | for (var k = 0; k < str2.length; k += 16) { 213 | var tmp146 = str2.substring(k, k + 16); 214 | tmp146 = (parseInt(tmp146.substring(0, 8), 2) || 0) + '.' + (parseInt(tmp146.substring(8, 16), 2) || 0); 215 | dataArray.push(tmp146); 216 | } 217 | return dataArray.join(','); 218 | case 4: 219 | for (var i = 0; i < str2.length; i += 8) { 220 | var item = parseInt(str2.substring(i, i + 8), 2); 221 | if (item < 10) { 222 | item = '0' + item.toString(); 223 | } else { 224 | item = item.toString(); 225 | } 226 | dataArray.push(item); 227 | } 228 | return dataArray.join(''); 229 | case 7: 230 | // battery && interval 231 | return { 232 | interval: parseInt(str2.substr(0, 16), 2), 233 | power: parseInt(str2.substr(-16, 16), 2) 234 | }; 235 | case 9: 236 | var dataValue = { 237 | detectionType: parseInt(str2.substring(0, 8), 2), 238 | modelId: parseInt(str2.substring(8, 16), 2), 239 | modelVer: parseInt(str2.substring(16, 24), 2) 240 | }; 241 | // 01010000 242 | return dataValue; 243 | } 244 | } 245 | 246 | // util 247 | function ttnDataFormat(str) { 248 | var strReverse = littleEndianTransform(str); 249 | var str2 = toBinary(strReverse); 250 | if (str2.substring(0, 1) === '1') { 251 | var arr = str2.split(''); 252 | var reverseArr = []; 253 | for (var forArr = 0; forArr < arr.length; forArr++) { 254 | var item = arr[forArr]; 255 | if (parseInt(item) === 1) { 256 | reverseArr.push(0); 257 | } else { 258 | reverseArr.push(1); 259 | } 260 | } 261 | str2 = parseInt(reverseArr.join(''), 2) + 1; 262 | return parseFloat('-' + str2 / 1000); 263 | } 264 | return parseInt(str2, 2) / 1000; 265 | } 266 | 267 | // util 268 | function sensorAttrForVersion(dataValue) { 269 | var dataValueSplitArray = dataValue.split(','); 270 | return { 271 | ver_hardware: dataValueSplitArray[0], 272 | ver_software: dataValueSplitArray[1] 273 | }; 274 | } 275 | 276 | // util 277 | function toBinary(arr) { 278 | var binaryData = []; 279 | for (var forArr = 0; forArr < arr.length; forArr++) { 280 | var item = arr[forArr]; 281 | var data = parseInt(item, 16).toString(2); 282 | var dataLength = data.length; 283 | if (data.length !== 8) { 284 | for (var i = 0; i < 8 - dataLength; i++) { 285 | data = '0' + data; 286 | } 287 | } 288 | binaryData.push(data); 289 | } 290 | return binaryData.toString().replace(/,/g, ''); 291 | } -------------------------------------------------------------------------------- /S210X/TTN/SenseCAP_S210X_TTN_Decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SenseCAP & TTN (new v3) Converter 3 | * 4 | * @since 3.0 5 | * @return Object 6 | * @param Boolean valid Indicates whether the payload is a valid payload. 7 | * @param String err The reason for the payload to be invalid. 0 means valid, minus means invalid. 8 | * @param String payload Hexadecimal string, to show the payload. 9 | * @param Array messages One or more messages are parsed according to payload. 10 | * type // Enum: 11 | * // - "report_telemetry" 12 | * // - "upload_battery" 13 | * // - "upload_interval" 14 | * // - "upload_version" 15 | * // - "upload_sensor_id" 16 | * // - "report_remove_sensor" 17 | * // - "unknown_message" 18 | * 19 | * 20 | * 21 | * 22 | * @sample-1 23 | * var sample = Decoder(["00", "00", "00", "01", "01", "00", "01", "00", "07", "00", "64", "00", "3C", "00", "01", "20", "01", "00", "00", "00", "00", "28", "90"], null); 24 | * { 25 | * valid: true, 26 | * err: 0, 27 | * payload: '0000000101000100070064003C00012001000000002890', 28 | * messages: [ 29 | * { type: 'upload_version', 30 | * hardwareVersion: '1.0', 31 | * softwareVersion: '1.1' }, 32 | * { type: 'upload_battery', battery: 100 }, 33 | * { type: 'upload_interval', interval: 3600 }, 34 | * { type: 'report_remove_sensor', channel: 1 } 35 | * ] 36 | * } 37 | * @sample-2 38 | * var sample = Decoder(["01", "01", "10", "98", "53", "00", "00", "01", "02", "10", "A8", "7A", "00", "00", "AF", "51"], null); 39 | * { 40 | * valid: true, 41 | * err: 0, 42 | * payload: '01011098530000010210A87A0000AF51', 43 | * messages: [ 44 | * { type: 'report_telemetry', 45 | * measurementId: 4097, 46 | * measurementValue: 21.4 }, 47 | * { type: 'report_telemetry', 48 | * measurementId: 4098, 49 | * measurementValue: 31.4 } 50 | * ] 51 | * } 52 | * @sample-3 53 | * var sample = Decoder(["01", "01", "00", "01", "01", "00", "01", "01", "02", "00", "6A", "01", "00", "15", "01", "03", "00", "30", "F1", "F7", "2C", "01", "04", "00", "09", "0C", "13", "14", "01", "05", "00", "7F", "4D", "00", "00", "01", "06", "00", "00", "00", "00", "00", "4C", "BE"], null); 54 | * { 55 | * valid: true, 56 | * err: 0, 57 | * payload: '010100010100010102006A01001501030030F1F72C010400090C13140105007F4D0000010600000000004CBE', 58 | * messages: [ 59 | * { type: 'upload_sensor_id', sensorId: '30F1F72C6A010015', channel: 1 } 60 | * ] 61 | * } 62 | */ 63 | 64 | /** 65 | * Entry, decoder.js 66 | */ 67 | function decodeUplink (input) { 68 | var bytes = input['bytes']; 69 | // // init 70 | var bytesString = bytes2HexString(bytes) 71 | .toLocaleUpperCase(); 72 | // var bytesString = input 73 | var decoded = { 74 | // valid 75 | valid: true, err: 0, // bytes 76 | payload: bytesString, // messages array 77 | messages: [] 78 | } 79 | 80 | // CRC check 81 | if (!crc16Check(bytesString)) { 82 | decoded['valid'] = false 83 | decoded['err'] = -1 // "crc check fail." 84 | return { data: decoded } 85 | } 86 | 87 | // Length Check 88 | if ((((bytesString.length / 2) - 2) % 7) !== 0) { 89 | decoded['valid'] = false 90 | decoded['err'] = -2 // "length check fail." 91 | return { data: decoded } 92 | } 93 | 94 | // Cache sensor id 95 | var sensorEuiLowBytes 96 | var sensorEuiHighBytes 97 | 98 | // Handle each frame 99 | var frameArray = divideBy7Bytes(bytesString) 100 | for (var forFrame = 0; forFrame < frameArray.length; forFrame++) { 101 | var frame = frameArray[forFrame] 102 | // Extract key parameters 103 | var channel = strTo10SysNub(frame.substring(0, 2)) 104 | var dataID = strTo10SysNub(frame.substring(2, 6)) 105 | var dataValue = frame.substring(6, 14) 106 | var realDataValue = isSpecialDataId(dataID) ? ttnDataSpecialFormat(dataID, dataValue) : ttnDataFormat(dataValue) 107 | 108 | if (checkDataIdIsMeasureUpload(dataID)) { 109 | // if telemetry. 110 | decoded.messages.push({ 111 | type: 'report_telemetry', measurementId: dataID, measurementValue: realDataValue 112 | }) 113 | } else if (isSpecialDataId(dataID) || (dataID === 5) || (dataID === 6)) { 114 | // if special order, except "report_sensor_id". 115 | switch (dataID) { 116 | case 0x00: 117 | // node version 118 | var versionData = sensorAttrForVersion(realDataValue) 119 | decoded.messages.push({ 120 | type: 'upload_version', hardwareVersion: versionData.ver_hardware, softwareVersion: versionData.ver_software 121 | }) 122 | break 123 | case 1: 124 | // sensor version 125 | break 126 | case 2: 127 | // sensor eui, low bytes 128 | sensorEuiLowBytes = realDataValue 129 | break 130 | case 3: 131 | // sensor eui, high bytes 132 | sensorEuiHighBytes = realDataValue 133 | break 134 | case 7: 135 | // battery power && interval 136 | decoded.messages.push({ 137 | type: 'upload_battery', battery: realDataValue.power 138 | }, { 139 | type: 'upload_interval', interval: parseInt(realDataValue.interval) * 60 140 | }) 141 | break 142 | case 9: 143 | decoded.messages.push({ 144 | type: 'model_info', 145 | detectionType: realDataValue.detectionType, 146 | modelId: realDataValue.modelId, 147 | modelVer: realDataValue.modelVer 148 | }) 149 | break 150 | case 0x120: 151 | // remove sensor 152 | decoded.messages.push({ 153 | type: 'report_remove_sensor', channel: 1 154 | }) 155 | break 156 | default: 157 | break 158 | } 159 | } else { 160 | decoded.messages.push({ 161 | type: 'unknown_message', dataID: dataID, dataValue: dataValue 162 | }) 163 | } 164 | 165 | } 166 | 167 | // if the complete id received, as "upload_sensor_id" 168 | if (sensorEuiHighBytes && sensorEuiLowBytes) { 169 | decoded.messages.unshift({ 170 | type: 'upload_sensor_id', channel: 1, sensorId: (sensorEuiHighBytes + sensorEuiLowBytes).toUpperCase() 171 | }) 172 | } 173 | // return 174 | return { data: decoded } 175 | } 176 | 177 | function crc16Check (data) { 178 | return true 179 | } 180 | 181 | // util 182 | function bytes2HexString (arrBytes) { 183 | var str = '' 184 | for (var i = 0; i < arrBytes.length; i++) { 185 | var tmp 186 | var num = arrBytes[i] 187 | if (num < 0) { 188 | tmp = (255 + num + 1).toString(16) 189 | } else { 190 | tmp = num.toString(16) 191 | } 192 | if (tmp.length === 1) { 193 | tmp = '0' + tmp 194 | } 195 | str += tmp 196 | } 197 | return str 198 | } 199 | 200 | // util 201 | function divideBy7Bytes (str) { 202 | var frameArray = [] 203 | for (var i = 0; i < str.length - 4; i += 14) { 204 | var data = str.substring(i, i + 14) 205 | frameArray.push(data) 206 | } 207 | return frameArray 208 | } 209 | 210 | // util 211 | function littleEndianTransform (data) { 212 | var dataArray = [] 213 | for (var i = 0; i < data.length; i += 2) { 214 | dataArray.push(data.substring(i, i + 2)) 215 | } 216 | dataArray.reverse() 217 | return dataArray 218 | } 219 | 220 | // util 221 | function strTo10SysNub (str) { 222 | var arr = littleEndianTransform(str) 223 | return parseInt(arr.toString() 224 | .replace(/,/g, ''), 16) 225 | } 226 | 227 | // util 228 | function checkDataIdIsMeasureUpload (dataId) { 229 | return parseInt(dataId) > 4096 230 | } 231 | 232 | // configurable. 233 | function isSpecialDataId (dataID) { 234 | switch (dataID) { 235 | case 0: 236 | case 1: 237 | case 2: 238 | case 3: 239 | case 4: 240 | case 7: 241 | case 9: 242 | case 0x120: 243 | return true 244 | default: 245 | return false 246 | } 247 | } 248 | 249 | // configurable 250 | function ttnDataSpecialFormat (dataId, str) { 251 | var strReverse = littleEndianTransform(str) 252 | if (dataId === 2 || dataId === 3) { 253 | return strReverse.join('') 254 | } 255 | 256 | // handle unsigned number 257 | var str2 = toBinary(strReverse) 258 | var dataArray = [] 259 | switch (dataId) { 260 | case 0: // DATA_BOARD_VERSION 261 | case 1: // DATA_SENSOR_VERSION 262 | // Using point segmentation 263 | for (var k = 0; k < str2.length; k += 16) { 264 | var tmp146 = str2.substring(k, k + 16) 265 | tmp146 = (parseInt(tmp146.substring(0, 8), 2) || 0) + '.' + (parseInt(tmp146.substring(8, 16), 2) || 0) 266 | dataArray.push(tmp146) 267 | } 268 | return dataArray.join(',') 269 | case 4: 270 | for (var i = 0; i < str2.length; i += 8) { 271 | var item = parseInt(str2.substring(i, i + 8), 2) 272 | if (item < 10) { 273 | item = '0' + item.toString() 274 | } else { 275 | item = item.toString() 276 | } 277 | dataArray.push(item) 278 | } 279 | return dataArray.join('') 280 | case 7: 281 | // battery && interval 282 | return { 283 | interval: parseInt(str2.substr(0, 16), 2), power: parseInt(str2.substr(-16, 16), 2) 284 | } 285 | case 9: 286 | let dataValue = { 287 | detectionType: parseInt(str2.substring(0, 8), 2), 288 | modelId: parseInt(str2.substring(8, 16), 2), 289 | modelVer: parseInt(str2.substring(16, 24), 2) 290 | } 291 | // 01010000 292 | return dataValue 293 | } 294 | } 295 | 296 | // util 297 | function ttnDataFormat (str) { 298 | var strReverse = littleEndianTransform(str) 299 | var str2 = toBinary(strReverse) 300 | if (str2.substring(0, 1) === '1') { 301 | var arr = str2.split('') 302 | var reverseArr = [] 303 | for (var forArr = 0; forArr < arr.length; forArr++) { 304 | var item = arr[forArr] 305 | if (parseInt(item) === 1) { 306 | reverseArr.push(0) 307 | } else { 308 | reverseArr.push(1) 309 | } 310 | } 311 | str2 = parseInt(reverseArr.join(''), 2) + 1 312 | return parseFloat('-' + str2 / 1000) 313 | } 314 | return parseInt(str2, 2) / 1000 315 | } 316 | 317 | // util 318 | function sensorAttrForVersion (dataValue) { 319 | var dataValueSplitArray = dataValue.split(',') 320 | return { 321 | ver_hardware: dataValueSplitArray[0], ver_software: dataValueSplitArray[1] 322 | } 323 | } 324 | 325 | // util 326 | function toBinary (arr) { 327 | var binaryData = [] 328 | for (var forArr = 0; forArr < arr.length; forArr++) { 329 | var item = arr[forArr] 330 | var data = parseInt(item, 16) 331 | .toString(2) 332 | var dataLength = data.length 333 | if (data.length !== 8) { 334 | for (var i = 0; i < 8 - dataLength; i++) { 335 | data = '0' + data 336 | } 337 | } 338 | binaryData.push(data) 339 | } 340 | return binaryData.toString() 341 | .replace(/,/g, '') 342 | } 343 | 344 | // Samples 345 | // var sample = Decoder(["00", "00", "00", "01", "01", "00", "01", "00", "07", "00", "64", "00", "3C", "00", "01", "20", "01", "00", "00", "00", "00", "28", "90"], null); 346 | // var sample = Decoder(["01", "01", "10", "98", "53", "00", "00", "01", "02", "10", "A8", "7A", "00", "00", "AF", "51"], null); 347 | // var sample = Decoder(["01", "01", "00", "01", "01", "00", "01", "01", "02", "00", "6A", "01", "00", "15", "01", "03", "00", "30", "F1", "F7", "2C", "01", "04", "00", "09", "0C", "13", "14", "01", "05", "00", "7F", "4D", "00", "00", "01", "06", "00", "00", "00", "00", "00", "4C", "BE"], null); 348 | // console.log(sample); -------------------------------------------------------------------------------- /S2120/ChirpStack/SenseCAP_S2120_ChirpStackV3_Decoder.js: -------------------------------------------------------------------------------- 1 | function Decode(fPort, bytes, variables) { 2 | bytes = bytes2HexString(bytes).toLocaleUpperCase(); 3 | var result = { 4 | 'err': 0, 5 | 'payload': bytes, 6 | 'valid': true, 7 | messages: [] 8 | }; 9 | var splitArray = dataSplit(bytes); 10 | // data decoder 11 | var decoderArray = []; 12 | for (var i = 0; i < splitArray.length; i++) { 13 | var item = splitArray[i]; 14 | var dataId = item.dataId; 15 | var dataValue = item.dataValue; 16 | var messages = dataIdAndDataValueJudge(dataId, dataValue); 17 | decoderArray.push(messages); 18 | } 19 | result.messages = decoderArray; 20 | return { 21 | data: result 22 | }; 23 | } 24 | 25 | /** 26 | * data splits 27 | * @param bytes 28 | * @returns {*[]} 29 | */ 30 | function dataSplit(bytes) { 31 | var frameArray = []; 32 | for (var i = 0; i < bytes.length; i++) { 33 | var remainingValue = bytes; 34 | var dataId = remainingValue.substring(0, 2); 35 | dataId = dataId.toLowerCase(); 36 | var dataValue = void 0; 37 | var dataObj = {}; 38 | switch (dataId) { 39 | case '01': 40 | case '20': 41 | case '21': 42 | case '30': 43 | case '31': 44 | case '33': 45 | case '40': 46 | case '41': 47 | case '42': 48 | case '43': 49 | case '44': 50 | case '45': 51 | case '4a': 52 | dataValue = remainingValue.substring(2, 22); 53 | bytes = remainingValue.substring(22); 54 | dataObj = { 55 | 'dataId': dataId, 56 | 'dataValue': dataValue 57 | }; 58 | break; 59 | case '02': 60 | case '4b': 61 | dataValue = remainingValue.substring(2, 18); 62 | bytes = remainingValue.substring(18); 63 | dataObj = { 64 | 'dataId': dataId, 65 | 'dataValue': dataValue 66 | }; 67 | break; 68 | case '03': 69 | case '06': 70 | dataValue = remainingValue.substring(2, 4); 71 | bytes = remainingValue.substring(4); 72 | dataObj = { 73 | 'dataId': dataId, 74 | 'dataValue': dataValue 75 | }; 76 | break; 77 | case '05': 78 | case '34': 79 | dataValue = bytes.substring(2, 10); 80 | bytes = remainingValue.substring(10); 81 | dataObj = { 82 | 'dataId': dataId, 83 | 'dataValue': dataValue 84 | }; 85 | break; 86 | case '04': 87 | case '10': 88 | case '32': 89 | case '35': 90 | case '36': 91 | case '37': 92 | case '38': 93 | case '39': 94 | dataValue = bytes.substring(2, 20); 95 | bytes = remainingValue.substring(20); 96 | dataObj = { 97 | 'dataId': dataId, 98 | 'dataValue': dataValue 99 | }; 100 | break; 101 | case '4c': 102 | dataValue = bytes.substring(2, 14); 103 | bytes = remainingValue.substring(14); 104 | dataObj = { 105 | 'dataId': dataId, 106 | 'dataValue': dataValue 107 | }; 108 | break; 109 | default: 110 | dataValue = '9'; 111 | break; 112 | } 113 | if (dataValue.length < 2) { 114 | break; 115 | } 116 | frameArray.push(dataObj); 117 | } 118 | return frameArray; 119 | } 120 | function dataIdAndDataValueJudge(dataId, dataValue) { 121 | var messages = []; 122 | var temperature; 123 | var humidity; 124 | var illumination; 125 | var uv; 126 | var windSpeed; 127 | var windDirection; 128 | var rainfall; 129 | var airPressure; 130 | var peakWind; 131 | var rainAccumulation; 132 | switch (dataId) { 133 | case '01': 134 | temperature = dataValue.substring(0, 4); 135 | humidity = dataValue.substring(4, 6); 136 | illumination = dataValue.substring(6, 14); 137 | uv = dataValue.substring(14, 16); 138 | windSpeed = dataValue.substring(16, 20); 139 | messages = [{ 140 | measurementValue: loraWANV2DataFormat(temperature, 10), 141 | measurementId: '4097', 142 | type: 'Air Temperature' 143 | }, { 144 | measurementValue: loraWANV2DataFormat(humidity), 145 | measurementId: '4098', 146 | type: 'Air Humidity' 147 | }, { 148 | measurementValue: loraWANV2DataFormat(illumination), 149 | measurementId: '4099', 150 | type: 'Light Intensity' 151 | }, { 152 | measurementValue: loraWANV2DataFormat(uv, 10), 153 | measurementId: '4190', 154 | type: 'UV Index' 155 | }, { 156 | measurementValue: loraWANV2DataFormat(windSpeed, 10), 157 | measurementId: '4105', 158 | type: 'Wind Speed' 159 | }]; 160 | break; 161 | case '02': 162 | windDirection = dataValue.substring(0, 4); 163 | rainfall = dataValue.substring(4, 12); 164 | airPressure = dataValue.substring(12, 16); 165 | messages = [{ 166 | measurementValue: loraWANV2DataFormat(windDirection), 167 | measurementId: '4104', 168 | type: 'Wind Direction Sensor' 169 | }, { 170 | measurementValue: loraWANV2DataFormat(rainfall, 1000), 171 | measurementId: '4113', 172 | type: 'Rain Gauge' 173 | }, { 174 | measurementValue: loraWANV2DataFormat(airPressure, 0.1), 175 | measurementId: '4101', 176 | type: 'Barometric Pressure' 177 | }]; 178 | break; 179 | case '03': 180 | var Electricity = dataValue; 181 | messages = [{ 182 | 'Battery(%)': loraWANV2DataFormat(Electricity) 183 | }]; 184 | break; 185 | case '04': 186 | var electricityWhether = dataValue.substring(0, 2); 187 | var hwv = dataValue.substring(2, 6); 188 | var bdv = dataValue.substring(6, 10); 189 | var sensorAcquisitionInterval = dataValue.substring(10, 14); 190 | var gpsAcquisitionInterval = dataValue.substring(14, 18); 191 | messages = [{ 192 | 'Battery(%)': loraWANV2DataFormat(electricityWhether), 193 | 'Hardware Version': "".concat(loraWANV2DataFormat(hwv.substring(0, 2)), ".").concat(loraWANV2DataFormat(hwv.substring(2, 4))), 194 | 'Firmware Version': "".concat(loraWANV2DataFormat(bdv.substring(0, 2)), ".").concat(loraWANV2DataFormat(bdv.substring(2, 4))), 195 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionInterval)) * 60, 196 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionInterval)) * 60 197 | }]; 198 | break; 199 | case '05': 200 | var sensorAcquisitionIntervalFive = dataValue.substring(0, 4); 201 | var gpsAcquisitionIntervalFive = dataValue.substring(4, 8); 202 | messages = [{ 203 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalFive)) * 60, 204 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalFive)) * 60 205 | }]; 206 | break; 207 | case '06': 208 | var errorCode = dataValue; 209 | var descZh; 210 | switch (errorCode) { 211 | case '00': 212 | descZh = 'CCL_SENSOR_ERROR_NONE'; 213 | break; 214 | case '01': 215 | descZh = 'CCL_SENSOR_NOT_FOUND'; 216 | break; 217 | case '02': 218 | descZh = 'CCL_SENSOR_WAKEUP_ERROR'; 219 | break; 220 | case '03': 221 | descZh = 'CCL_SENSOR_NOT_RESPONSE'; 222 | break; 223 | case '04': 224 | descZh = 'CCL_SENSOR_DATA_EMPTY'; 225 | break; 226 | case '05': 227 | descZh = 'CCL_SENSOR_DATA_HEAD_ERROR'; 228 | break; 229 | case '06': 230 | descZh = 'CCL_SENSOR_DATA_CRC_ERROR'; 231 | break; 232 | case '07': 233 | descZh = 'CCL_SENSOR_DATA_B1_NO_VALID'; 234 | break; 235 | case '08': 236 | descZh = 'CCL_SENSOR_DATA_B2_NO_VALID'; 237 | break; 238 | case '09': 239 | descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH'; 240 | break; 241 | case '0A': 242 | descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED'; 243 | break; 244 | case '0B': 245 | descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED'; 246 | break; 247 | case '0C': 248 | descZh = 'CCL_SENSOR_DATA_VALUE_HI'; 249 | break; 250 | case '0D': 251 | descZh = 'CCL_SENSOR_DATA_VALUE_LOW'; 252 | break; 253 | case '0E': 254 | descZh = 'CCL_SENSOR_DATA_VALUE_MISSED'; 255 | break; 256 | case '0F': 257 | descZh = 'CCL_SENSOR_ARG_INVAILD'; 258 | break; 259 | case '10': 260 | descZh = 'CCL_SENSOR_RS485_MASTER_BUSY'; 261 | break; 262 | case '11': 263 | descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR'; 264 | break; 265 | case '12': 266 | descZh = 'CCL_SENSOR_RS485_REG_MISSED'; 267 | break; 268 | case '13': 269 | descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR'; 270 | break; 271 | case '14': 272 | descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR'; 273 | break; 274 | case '15': 275 | descZh = 'CCL_SENSOR_CONFIG_ERROR'; 276 | break; 277 | case 'FF': 278 | descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW'; 279 | break; 280 | default: 281 | descZh = 'CC_OTHER_FAILED'; 282 | break; 283 | } 284 | messages = [{ 285 | measurementId: '4101', 286 | type: 'sensor_error_event', 287 | errCode: errorCode, 288 | descZh: descZh 289 | }]; 290 | break; 291 | case '10': 292 | var statusValue = dataValue.substring(0, 2); 293 | var _loraWANV2BitDataForm = loraWANV2BitDataFormat(statusValue), 294 | status = _loraWANV2BitDataForm.status, 295 | type = _loraWANV2BitDataForm.type; 296 | var sensecapId = dataValue.substring(2); 297 | messages = [{ 298 | status: status, 299 | channelType: type, 300 | sensorEui: sensecapId 301 | }]; 302 | break; 303 | case '4a': 304 | temperature = dataValue.substring(0, 4); 305 | humidity = dataValue.substring(4, 6); 306 | illumination = dataValue.substring(6, 14); 307 | uv = dataValue.substring(14, 16); 308 | windSpeed = dataValue.substring(16, 20); 309 | messages = [{ 310 | measurementValue: loraWANV2DataFormat(temperature, 10), 311 | measurementId: '4097', 312 | type: 'Air Temperature' 313 | }, { 314 | measurementValue: loraWANV2DataFormat(humidity), 315 | measurementId: '4098', 316 | type: 'Air Humidity' 317 | }, { 318 | measurementValue: loraWANV2DataFormat(illumination), 319 | measurementId: '4099', 320 | type: 'Light Intensity' 321 | }, { 322 | measurementValue: loraWANV2DataFormat(uv, 10), 323 | measurementId: '4190', 324 | type: 'UV Index' 325 | }, { 326 | measurementValue: loraWANV2DataFormat(windSpeed, 10), 327 | measurementId: '4105', 328 | type: 'Wind Speed' 329 | }]; 330 | break; 331 | case '4b': 332 | windDirection = dataValue.substring(0, 4); 333 | rainfall = dataValue.substring(4, 12); 334 | airPressure = dataValue.substring(12, 16); 335 | messages = [{ 336 | measurementValue: loraWANV2DataFormat(windDirection), 337 | measurementId: '4104', 338 | type: 'Wind Direction Sensor' 339 | }, { 340 | measurementValue: loraWANV2DataFormat(rainfall, 1000), 341 | measurementId: '4113', 342 | type: 'Rain Gauge' 343 | }, { 344 | measurementValue: loraWANV2DataFormat(airPressure, 0.1), 345 | measurementId: '4101', 346 | type: 'Barometric Pressure' 347 | }]; 348 | break; 349 | case '4c': 350 | peakWind = dataValue.substring(0, 4); 351 | rainAccumulation = dataValue.substring(4, 12); 352 | messages = [{ 353 | measurementValue: loraWANV2DataFormat(peakWind, 10), 354 | measurementId: '4191', 355 | type: ' Peak Wind Gust' 356 | }, { 357 | measurementValue: loraWANV2DataFormat(rainAccumulation, 1000), 358 | measurementId: '4213', 359 | type: 'Rain Accumulation' 360 | }]; 361 | break; 362 | default: 363 | break; 364 | } 365 | return messages; 366 | } 367 | 368 | /** 369 | * 370 | * data formatting 371 | * @param str 372 | * @param divisor 373 | * @returns {string|number} 374 | */ 375 | function loraWANV2DataFormat(str) { 376 | var divisor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; 377 | var strReverse = bigEndianTransform(str); 378 | var str2 = toBinary(strReverse); 379 | if (str2.substring(0, 1) === '1') { 380 | var arr = str2.split(''); 381 | var reverseArr = arr.map(function (item) { 382 | if (parseInt(item) === 1) { 383 | return 0; 384 | } else { 385 | return 1; 386 | } 387 | }); 388 | str2 = parseInt(reverseArr.join(''), 2) + 1; 389 | return parseFloat('-' + str2 / divisor); 390 | } 391 | return parseInt(str2, 2) / divisor; 392 | } 393 | 394 | /** 395 | * Handling big-endian data formats 396 | * @param data 397 | * @returns {*[]} 398 | */ 399 | function bigEndianTransform(data) { 400 | var dataArray = []; 401 | for (var i = 0; i < data.length; i += 2) { 402 | dataArray.push(data.substring(i, i + 2)); 403 | } 404 | // array of hex 405 | return dataArray; 406 | } 407 | 408 | /** 409 | * Convert to an 8-digit binary number with 0s in front of the number 410 | * @param arr 411 | * @returns {string} 412 | */ 413 | function toBinary(arr) { 414 | var binaryData = arr.map(function (item) { 415 | var data = parseInt(item, 16).toString(2); 416 | var dataLength = data.length; 417 | if (data.length !== 8) { 418 | for (var i = 0; i < 8 - dataLength; i++) { 419 | data = "0" + data; 420 | } 421 | } 422 | return data; 423 | }); 424 | var ret = binaryData.toString().replace(/,/g, ''); 425 | return ret; 426 | } 427 | 428 | /** 429 | * sensor 430 | * @param str 431 | * @returns {{channel: number, type: number, status: number}} 432 | */ 433 | function loraWANV2BitDataFormat(str) { 434 | var strReverse = bigEndianTransform(str); 435 | var str2 = toBinary(strReverse); 436 | var channel = parseInt(str2.substring(0, 4), 2); 437 | var status = parseInt(str2.substring(4, 5), 2); 438 | var type = parseInt(str2.substring(5), 2); 439 | return { 440 | channel: channel, 441 | status: status, 442 | type: type 443 | }; 444 | } 445 | 446 | /** 447 | * channel info 448 | * @param str 449 | * @returns {{channelTwo: number, channelOne: number}} 450 | */ 451 | function loraWANV2ChannelBitFormat(str) { 452 | var strReverse = bigEndianTransform(str); 453 | var str2 = toBinary(strReverse); 454 | var one = parseInt(str2.substring(0, 4), 2); 455 | var two = parseInt(str2.substring(4, 8), 2); 456 | var resultInfo = { 457 | one: one, 458 | two: two 459 | }; 460 | return resultInfo; 461 | } 462 | 463 | /** 464 | * data log status bit 465 | * @param str 466 | * @returns {{total: number, level: number, isTH: number}} 467 | */ 468 | function loraWANV2DataLogBitFormat(str) { 469 | var strReverse = bigEndianTransform(str); 470 | var str2 = toBinary(strReverse); 471 | var isTH = parseInt(str2.substring(0, 1), 2); 472 | var total = parseInt(str2.substring(1, 5), 2); 473 | var left = parseInt(str2.substring(5), 2); 474 | var resultInfo = { 475 | isTH: isTH, 476 | total: total, 477 | left: left 478 | }; 479 | return resultInfo; 480 | } 481 | function bytes2HexString(arrBytes) { 482 | var str = ''; 483 | for (var i = 0; i < arrBytes.length; i++) { 484 | var tmp; 485 | var num = arrBytes[i]; 486 | if (num < 0) { 487 | tmp = (255 + num + 1).toString(16); 488 | } else { 489 | tmp = num.toString(16); 490 | } 491 | if (tmp.length === 1) { 492 | tmp = '0' + tmp; 493 | } 494 | str += tmp; 495 | } 496 | return str; 497 | } -------------------------------------------------------------------------------- /S2120/TTN/SenseCAP_S2120_TTN_Decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Entry, decoder.js 3 | */ 4 | function decodeUplink (input, port) { 5 | // data split 6 | 7 | var bytes = input['bytes'] 8 | // init 9 | bytes = bytes2HexString(bytes) 10 | .toLocaleUpperCase() 11 | 12 | let result = { 13 | 'err': 0, 'payload': bytes, 'valid': true, messages: [] 14 | } 15 | let splitArray = dataSplit(bytes) 16 | // data decoder 17 | let decoderArray = [] 18 | for (let i = 0; i < splitArray.length; i++) { 19 | let item = splitArray[i] 20 | let dataId = item.dataId 21 | let dataValue = item.dataValue 22 | let messages = dataIdAndDataValueJudge(dataId, dataValue) 23 | decoderArray.push(messages) 24 | } 25 | result.messages = decoderArray 26 | return { data: result } 27 | } 28 | 29 | /** 30 | * data splits 31 | * @param bytes 32 | * @returns {*[]} 33 | */ 34 | function dataSplit (bytes) { 35 | let frameArray = [] 36 | 37 | for (let i = 0; i < bytes.length; i++) { 38 | let remainingValue = bytes 39 | let dataId = remainingValue.substring(0, 2) 40 | dataId = dataId.toLowerCase() 41 | let dataValue 42 | let dataObj = {} 43 | switch (dataId) { 44 | case '01' : 45 | case '20' : 46 | case '21' : 47 | case '30' : 48 | case '31' : 49 | case '33' : 50 | case '40' : 51 | case '41' : 52 | case '42' : 53 | case '43' : 54 | case '44' : 55 | case '45' : 56 | case '4a' : 57 | dataValue = remainingValue.substring(2, 22) 58 | bytes = remainingValue.substring(22) 59 | dataObj = { 60 | 'dataId': dataId, 'dataValue': dataValue 61 | } 62 | break 63 | case '02': 64 | case '4b': 65 | dataValue = remainingValue.substring(2, 18) 66 | bytes = remainingValue.substring(18) 67 | dataObj = { 68 | 'dataId': dataId, 'dataValue': dataValue 69 | } 70 | break 71 | case '03' : 72 | case '06': 73 | dataValue = remainingValue.substring(2, 4) 74 | bytes = remainingValue.substring(4) 75 | dataObj = { 76 | 'dataId': dataId, 'dataValue': dataValue 77 | } 78 | break 79 | case '05' : 80 | case '34': 81 | dataValue = bytes.substring(2, 10) 82 | bytes = remainingValue.substring(10) 83 | dataObj = { 84 | 'dataId': dataId, 'dataValue': dataValue 85 | } 86 | break 87 | case '04': 88 | case '10': 89 | case '32': 90 | case '35': 91 | case '36': 92 | case '37': 93 | case '38': 94 | case '39': 95 | dataValue = bytes.substring(2, 20) 96 | bytes = remainingValue.substring(20) 97 | dataObj = { 98 | 'dataId': dataId, 'dataValue': dataValue 99 | } 100 | break 101 | case '4c': 102 | dataValue = bytes.substring(2, 14) 103 | bytes = remainingValue.substring(14) 104 | dataObj = { 105 | 'dataId': dataId, 'dataValue': dataValue 106 | } 107 | break 108 | default: 109 | dataValue = '9' 110 | break 111 | } 112 | if (dataValue.length < 2) { 113 | break 114 | } 115 | frameArray.push(dataObj) 116 | } 117 | return frameArray 118 | } 119 | 120 | function dataIdAndDataValueJudge (dataId, dataValue) { 121 | let messages = [] 122 | let temperature 123 | let humidity 124 | let illumination 125 | let uv 126 | let windSpeed 127 | let windDirection 128 | let rainfall 129 | let airPressure 130 | let peakWind 131 | let rainAccumulation 132 | switch (dataId) { 133 | case '01': 134 | temperature = dataValue.substring(0, 4) 135 | humidity = dataValue.substring(4, 6) 136 | illumination = dataValue.substring(6, 14) 137 | uv = dataValue.substring(14, 16) 138 | windSpeed = dataValue.substring(16, 20) 139 | messages = [{ 140 | measurementValue: loraWANV2DataFormat(temperature, 10), measurementId: '4097', type: 'Air Temperature' 141 | }, { 142 | measurementValue: loraWANV2DataFormat(humidity), measurementId: '4098', type: 'Air Humidity' 143 | }, { 144 | measurementValue: loraWANV2DataFormat(illumination), measurementId: '4099', type: 'Light Intensity' 145 | }, { 146 | measurementValue: loraWANV2DataFormat(uv, 10), measurementId: '4190', type: 'UV Index' 147 | }, { 148 | measurementValue: loraWANV2DataFormat(windSpeed, 10), measurementId: '4105', type: 'Wind Speed' 149 | }] 150 | break 151 | case '02': 152 | windDirection = dataValue.substring(0, 4) 153 | rainfall = dataValue.substring(4, 12) 154 | airPressure = dataValue.substring(12, 16) 155 | messages = [{ 156 | measurementValue: loraWANV2DataFormat(windDirection), measurementId: '4104', type: 'Wind Direction Sensor' 157 | }, { 158 | measurementValue: loraWANV2DataFormat(rainfall, 1000), measurementId: '4113', type: 'Rain Gauge' 159 | }, { 160 | 161 | measurementValue: loraWANV2DataFormat(airPressure, 0.1), measurementId: '4101', type: 'Barometric Pressure' 162 | }] 163 | break 164 | case '03': 165 | let Electricity = dataValue 166 | messages = [{ 167 | 'Battery(%)': loraWANV2DataFormat(Electricity) 168 | }] 169 | break 170 | case '04': 171 | let electricityWhether = dataValue.substring(0, 2) 172 | let hwv = dataValue.substring(2, 6) 173 | let bdv = dataValue.substring(6, 10) 174 | let sensorAcquisitionInterval = dataValue.substring(10, 14) 175 | let gpsAcquisitionInterval = dataValue.substring(14, 18) 176 | messages = [{ 177 | 'Battery(%)': loraWANV2DataFormat(electricityWhether), 178 | 'Hardware Version': `${loraWANV2DataFormat(hwv.substring(0, 2))}.${loraWANV2DataFormat(hwv.substring(2, 4))}`, 179 | 'Firmware Version': `${loraWANV2DataFormat(bdv.substring(0, 2))}.${loraWANV2DataFormat(bdv.substring(2, 4))}`, 180 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionInterval)) * 60, 181 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionInterval)) * 60 182 | }] 183 | break 184 | case '05': 185 | let sensorAcquisitionIntervalFive = dataValue.substring(0, 4) 186 | let gpsAcquisitionIntervalFive = dataValue.substring(4, 8) 187 | messages = [{ 188 | 'measureInterval': parseInt(loraWANV2DataFormat(sensorAcquisitionIntervalFive)) * 60, 189 | 'gpsInterval': parseInt(loraWANV2DataFormat(gpsAcquisitionIntervalFive)) * 60 190 | }] 191 | break 192 | case '06': 193 | let errorCode = dataValue 194 | let descZh 195 | switch (errorCode) { 196 | case '00': 197 | descZh = 'CCL_SENSOR_ERROR_NONE' 198 | break 199 | case '01': 200 | descZh = 'CCL_SENSOR_NOT_FOUND' 201 | break 202 | case '02': 203 | descZh = 'CCL_SENSOR_WAKEUP_ERROR' 204 | break 205 | case '03': 206 | descZh = 'CCL_SENSOR_NOT_RESPONSE' 207 | break 208 | case '04': 209 | descZh = 'CCL_SENSOR_DATA_EMPTY' 210 | break 211 | case '05': 212 | descZh = 'CCL_SENSOR_DATA_HEAD_ERROR' 213 | break 214 | case '06': 215 | descZh = 'CCL_SENSOR_DATA_CRC_ERROR' 216 | break 217 | case '07': 218 | descZh = 'CCL_SENSOR_DATA_B1_NO_VALID' 219 | break 220 | case '08': 221 | descZh = 'CCL_SENSOR_DATA_B2_NO_VALID' 222 | break 223 | case '09': 224 | descZh = 'CCL_SENSOR_RANDOM_NOT_MATCH' 225 | break 226 | case '0A': 227 | descZh = 'CCL_SENSOR_PUBKEY_SIGN_VERIFY_FAILED' 228 | break 229 | case '0B': 230 | descZh = 'CCL_SENSOR_DATA_SIGN_VERIFY_FAILED' 231 | break 232 | case '0C': 233 | descZh = 'CCL_SENSOR_DATA_VALUE_HI' 234 | break 235 | case '0D': 236 | descZh = 'CCL_SENSOR_DATA_VALUE_LOW' 237 | break 238 | case '0E': 239 | descZh = 'CCL_SENSOR_DATA_VALUE_MISSED' 240 | break 241 | case '0F': 242 | descZh = 'CCL_SENSOR_ARG_INVAILD' 243 | break 244 | case '10': 245 | descZh = 'CCL_SENSOR_RS485_MASTER_BUSY' 246 | break 247 | case '11': 248 | descZh = 'CCL_SENSOR_RS485_REV_DATA_ERROR' 249 | break 250 | case '12': 251 | descZh = 'CCL_SENSOR_RS485_REG_MISSED' 252 | break 253 | case '13': 254 | descZh = 'CCL_SENSOR_RS485_FUN_EXE_ERROR' 255 | break 256 | case '14': 257 | descZh = 'CCL_SENSOR_RS485_WRITE_STRATEGY_ERROR' 258 | break 259 | case '15': 260 | descZh = 'CCL_SENSOR_CONFIG_ERROR' 261 | break 262 | case 'FF': 263 | descZh = 'CCL_SENSOR_DATA_ERROR_UNKONW' 264 | break 265 | default: 266 | descZh = 'CC_OTHER_FAILED' 267 | break 268 | } 269 | messages = [{ 270 | measurementId: '4101', type: 'sensor_error_event', errCode: errorCode, descZh 271 | }] 272 | break 273 | case '10': 274 | let statusValue = dataValue.substring(0, 2) 275 | let { status, type } = loraWANV2BitDataFormat(statusValue) 276 | let sensecapId = dataValue.substring(2) 277 | messages = [{ 278 | status: status, channelType: type, sensorEui: sensecapId 279 | }] 280 | break 281 | case '4a': 282 | temperature = dataValue.substring(0, 4) 283 | humidity = dataValue.substring(4, 6) 284 | illumination = dataValue.substring(6, 14) 285 | uv = dataValue.substring(14, 16) 286 | windSpeed = dataValue.substring(16, 20) 287 | messages = [{ 288 | measurementValue: loraWANV2DataFormat(temperature, 10), measurementId: '4097', type: 'Air Temperature' 289 | }, { 290 | measurementValue: loraWANV2DataFormat(humidity), measurementId: '4098', type: 'Air Humidity' 291 | }, { 292 | measurementValue: loraWANV2DataFormat(illumination), measurementId: '4099', type: 'Light Intensity' 293 | }, { 294 | measurementValue: loraWANV2DataFormat(uv, 10), measurementId: '4190', type: 'UV Index' 295 | }, { 296 | measurementValue: loraWANV2DataFormat(windSpeed, 10), measurementId: '4105', type: 'Wind Speed' 297 | }] 298 | break 299 | case '4b': 300 | windDirection = dataValue.substring(0, 4) 301 | rainfall = dataValue.substring(4, 12) 302 | airPressure = dataValue.substring(12, 16) 303 | messages = [{ 304 | measurementValue: loraWANV2DataFormat(windDirection), measurementId: '4104', type: 'Wind Direction Sensor' 305 | }, { 306 | measurementValue: loraWANV2DataFormat(rainfall, 1000), measurementId: '4113', type: 'Rain Gauge' 307 | }, { 308 | 309 | measurementValue: loraWANV2DataFormat(airPressure, 0.1), measurementId: '4101', type: 'Barometric Pressure' 310 | }] 311 | break 312 | case '4c': 313 | peakWind = dataValue.substring(0, 4) 314 | rainAccumulation = dataValue.substring(4, 12) 315 | messages = [{ 316 | measurementValue: loraWANV2DataFormat(peakWind, 10), measurementId: '4191', type: ' Peak Wind Gust' 317 | }, { 318 | measurementValue: loraWANV2DataFormat(rainAccumulation, 1000), measurementId: '4213', type: 'Rain Accumulation' 319 | }] 320 | break 321 | default: 322 | break 323 | } 324 | return messages 325 | } 326 | 327 | /** 328 | * 329 | * data formatting 330 | * @param str 331 | * @param divisor 332 | * @returns {string|number} 333 | */ 334 | function loraWANV2DataFormat (str, divisor = 1) { 335 | let strReverse = bigEndianTransform(str) 336 | let str2 = toBinary(strReverse) 337 | if (str2.substring(0, 1) === '1') { 338 | let arr = str2.split('') 339 | let reverseArr = arr.map((item) => { 340 | if (parseInt(item) === 1) { 341 | return 0 342 | } else { 343 | return 1 344 | } 345 | }) 346 | str2 = parseInt(reverseArr.join(''), 2) + 1 347 | return parseFloat('-' + str2 / divisor) 348 | } 349 | return parseInt(str2, 2) / divisor 350 | } 351 | 352 | /** 353 | * Handling big-endian data formats 354 | * @param data 355 | * @returns {*[]} 356 | */ 357 | function bigEndianTransform (data) { 358 | let dataArray = [] 359 | for (let i = 0; i < data.length; i += 2) { 360 | dataArray.push(data.substring(i, i + 2)) 361 | } 362 | // array of hex 363 | return dataArray 364 | } 365 | 366 | /** 367 | * Convert to an 8-digit binary number with 0s in front of the number 368 | * @param arr 369 | * @returns {string} 370 | */ 371 | function toBinary (arr) { 372 | let binaryData = arr.map((item) => { 373 | let data = parseInt(item, 16) 374 | .toString(2) 375 | let dataLength = data.length 376 | if (data.length !== 8) { 377 | for (let i = 0; i < 8 - dataLength; i++) { 378 | data = `0` + data 379 | } 380 | } 381 | return data 382 | }) 383 | let ret = binaryData.toString() 384 | .replace(/,/g, '') 385 | return ret 386 | } 387 | 388 | /** 389 | * sensor 390 | * @param str 391 | * @returns {{channel: number, type: number, status: number}} 392 | */ 393 | function loraWANV2BitDataFormat (str) { 394 | let strReverse = bigEndianTransform(str) 395 | let str2 = toBinary(strReverse) 396 | let channel = parseInt(str2.substring(0, 4), 2) 397 | let status = parseInt(str2.substring(4, 5), 2) 398 | let type = parseInt(str2.substring(5), 2) 399 | return { channel, status, type } 400 | } 401 | 402 | /** 403 | * channel info 404 | * @param str 405 | * @returns {{channelTwo: number, channelOne: number}} 406 | */ 407 | function loraWANV2ChannelBitFormat (str) { 408 | let strReverse = bigEndianTransform(str) 409 | let str2 = toBinary(strReverse) 410 | let one = parseInt(str2.substring(0, 4), 2) 411 | let two = parseInt(str2.substring(4, 8), 2) 412 | let resultInfo = { 413 | one: one, two: two 414 | } 415 | return resultInfo 416 | } 417 | 418 | /** 419 | * data log status bit 420 | * @param str 421 | * @returns {{total: number, level: number, isTH: number}} 422 | */ 423 | function loraWANV2DataLogBitFormat (str) { 424 | let strReverse = bigEndianTransform(str) 425 | let str2 = toBinary(strReverse) 426 | let isTH = parseInt(str2.substring(0, 1), 2) 427 | let total = parseInt(str2.substring(1, 5), 2) 428 | let left = parseInt(str2.substring(5), 2) 429 | let resultInfo = { 430 | isTH: isTH, total: total, left: left 431 | } 432 | return resultInfo 433 | } 434 | 435 | function bytes2HexString (arrBytes) { 436 | var str = '' 437 | for (var i = 0; i < arrBytes.length; i++) { 438 | var tmp 439 | var num = arrBytes[i] 440 | if (num < 0) { 441 | tmp = (255 + num + 1).toString(16) 442 | } else { 443 | tmp = num.toString(16) 444 | } 445 | if (tmp.length === 1) { 446 | tmp = '0' + tmp 447 | } 448 | str += tmp 449 | } 450 | return str 451 | } 452 | -------------------------------------------------------------------------------- /SenseCAP Gen1 Sensor/SenseCAP_WirelessSensor_TTN_Decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SenseCAP & TTN (new v3) Converter 3 | * 4 | * @since 3.0 5 | * @return Object 6 | * @param Boolean valid Indicates whether the payload is a valid payload. 7 | * @param String err The reason for the payload to be invalid. 0 means valid, minus means invalid. 8 | * @param String payload Hexadecimal string, to show the payload. 9 | * @param Array messages One or more messages are parsed according to payload. 10 | * type // Enum: 11 | * // - "report_telemetry" 12 | * // - "upload_battery" 13 | * // - "upload_interval" 14 | * // - "upload_version" 15 | * // - "upload_sensor_id" 16 | * // - "report_remove_sensor" 17 | * // - "unknown_message" 18 | * 19 | * 20 | * 21 | * 22 | * @sample-1 23 | * var sample = Decoder(["00", "00", "00", "01", "01", "00", "01", "00", "07", "00", "64", "00", "3C", "00", "01", "20", "01", "00", "00", "00", "00", "28", "90"], null); 24 | * { 25 | * valid: true, 26 | * err: 0, 27 | * payload: '0000000101000100070064003C00012001000000002890', 28 | * messages: [ 29 | * { type: 'upload_version', 30 | * hardwareVersion: '1.0', 31 | * softwareVersion: '1.1' }, 32 | * { type: 'upload_battery', battery: 100 }, 33 | * { type: 'upload_interval', interval: 3600 }, 34 | * { type: 'report_remove_sensor', channel: 1 } 35 | * ] 36 | * } 37 | * @sample-2 38 | * var sample = Decoder(["01", "01", "10", "98", "53", "00", "00", "01", "02", "10", "A8", "7A", "00", "00", "AF", "51"], null); 39 | * { 40 | * valid: true, 41 | * err: 0, 42 | * payload: '01011098530000010210A87A0000AF51', 43 | * messages: [ 44 | * { type: 'report_telemetry', 45 | * measurementId: 4097, 46 | * measurementValue: 21.4 }, 47 | * { type: 'report_telemetry', 48 | * measurementId: 4098, 49 | * measurementValue: 31.4 } 50 | * ] 51 | * } 52 | * @sample-3 53 | * var sample = Decoder(["01", "01", "00", "01", "01", "00", "01", "01", "02", "00", "6A", "01", "00", "15", "01", "03", "00", "30", "F1", "F7", "2C", "01", "04", "00", "09", "0C", "13", "14", "01", "05", "00", "7F", "4D", "00", "00", "01", "06", "00", "00", "00", "00", "00", "4C", "BE"], null); 54 | * { 55 | * valid: true, 56 | * err: 0, 57 | * payload: '010100010100010102006A01001501030030F1F72C010400090C13140105007F4D0000010600000000004CBE', 58 | * messages: [ 59 | * { type: 'upload_sensor_id', sensorId: '30F1F72C6A010015', channel: 1 } 60 | * ] 61 | * } 62 | */ 63 | 64 | /** 65 | * Entry, decoder.js 66 | */ 67 | function decodeUplink (input) { 68 | var bytes = input['bytes']; 69 | // // init 70 | var bytesString = bytes2HexString(bytes) 71 | .toLocaleUpperCase(); 72 | // var bytesString = input 73 | var decoded = { 74 | // valid 75 | valid: true, err: 0, // bytes 76 | payload: bytesString, // messages array 77 | messages: [] 78 | } 79 | 80 | // CRC check 81 | if (!crc16Check(bytesString)) { 82 | decoded['valid'] = false 83 | decoded['err'] = -1 // "crc check fail." 84 | return { data: decoded } 85 | } 86 | 87 | // Length Check 88 | if ((((bytesString.length / 2) - 2) % 7) !== 0) { 89 | decoded['valid'] = false 90 | decoded['err'] = -2 // "length check fail." 91 | return { data: decoded } 92 | } 93 | 94 | // Cache sensor id 95 | var sensorEuiLowBytes 96 | var sensorEuiHighBytes 97 | 98 | // Handle each frame 99 | var frameArray = divideBy7Bytes(bytesString) 100 | for (var forFrame = 0; forFrame < frameArray.length; forFrame++) { 101 | var frame = frameArray[forFrame] 102 | // Extract key parameters 103 | var channel = strTo10SysNub(frame.substring(0, 2)) 104 | var dataID = strTo10SysNub(frame.substring(2, 6)) 105 | var dataValue = frame.substring(6, 14) 106 | var realDataValue = isSpecialDataId(dataID) ? ttnDataSpecialFormat(dataID, dataValue) : ttnDataFormat(dataValue) 107 | 108 | if (checkDataIdIsMeasureUpload(dataID)) { 109 | // if telemetry. 110 | decoded.messages.push({ 111 | type: 'report_telemetry', measurementId: dataID, measurementValue: realDataValue 112 | }) 113 | } else if (isSpecialDataId(dataID) || (dataID === 5) || (dataID === 6)) { 114 | // if special order, except "report_sensor_id". 115 | switch (dataID) { 116 | case 0x00: 117 | // node version 118 | var versionData = sensorAttrForVersion(realDataValue) 119 | decoded.messages.push({ 120 | type: 'upload_version', hardwareVersion: versionData.ver_hardware, softwareVersion: versionData.ver_software 121 | }) 122 | break 123 | case 1: 124 | // sensor version 125 | break 126 | case 2: 127 | // sensor eui, low bytes 128 | sensorEuiLowBytes = realDataValue 129 | break 130 | case 3: 131 | // sensor eui, high bytes 132 | sensorEuiHighBytes = realDataValue 133 | break 134 | case 7: 135 | // battery power && interval 136 | decoded.messages.push({ 137 | type: 'upload_battery', battery: realDataValue.power 138 | }, { 139 | type: 'upload_interval', interval: parseInt(realDataValue.interval) * 60 140 | }) 141 | break 142 | case 9: 143 | decoded.messages.push({ 144 | type: 'model_info', 145 | detectionType: realDataValue.detectionType, 146 | modelId: realDataValue.modelId, 147 | modelVer: realDataValue.modelVer 148 | }) 149 | break 150 | case 0x120: 151 | // remove sensor 152 | decoded.messages.push({ 153 | type: 'report_remove_sensor', channel: 1 154 | }) 155 | break 156 | default: 157 | break 158 | } 159 | } else { 160 | decoded.messages.push({ 161 | type: 'unknown_message', dataID: dataID, dataValue: dataValue 162 | }) 163 | } 164 | 165 | } 166 | 167 | // if the complete id received, as "upload_sensor_id" 168 | if (sensorEuiHighBytes && sensorEuiLowBytes) { 169 | decoded.messages.unshift({ 170 | type: 'upload_sensor_id', channel: 1, sensorId: (sensorEuiHighBytes + sensorEuiLowBytes).toUpperCase() 171 | }) 172 | } 173 | // return 174 | return { data: decoded } 175 | } 176 | 177 | function crc16Check (data) { 178 | return true 179 | } 180 | 181 | // util 182 | function bytes2HexString (arrBytes) { 183 | var str = '' 184 | for (var i = 0; i < arrBytes.length; i++) { 185 | var tmp 186 | var num = arrBytes[i] 187 | if (num < 0) { 188 | tmp = (255 + num + 1).toString(16) 189 | } else { 190 | tmp = num.toString(16) 191 | } 192 | if (tmp.length === 1) { 193 | tmp = '0' + tmp 194 | } 195 | str += tmp 196 | } 197 | return str 198 | } 199 | 200 | // util 201 | function divideBy7Bytes (str) { 202 | var frameArray = [] 203 | for (var i = 0; i < str.length - 4; i += 14) { 204 | var data = str.substring(i, i + 14) 205 | frameArray.push(data) 206 | } 207 | return frameArray 208 | } 209 | 210 | // util 211 | function littleEndianTransform (data) { 212 | var dataArray = [] 213 | for (var i = 0; i < data.length; i += 2) { 214 | dataArray.push(data.substring(i, i + 2)) 215 | } 216 | dataArray.reverse() 217 | return dataArray 218 | } 219 | 220 | // util 221 | function strTo10SysNub (str) { 222 | var arr = littleEndianTransform(str) 223 | return parseInt(arr.toString() 224 | .replace(/,/g, ''), 16) 225 | } 226 | 227 | // util 228 | function checkDataIdIsMeasureUpload (dataId) { 229 | return parseInt(dataId) > 4096 230 | } 231 | 232 | // configurable. 233 | function isSpecialDataId (dataID) { 234 | switch (dataID) { 235 | case 0: 236 | case 1: 237 | case 2: 238 | case 3: 239 | case 4: 240 | case 7: 241 | case 9: 242 | case 0x120: 243 | return true 244 | default: 245 | return false 246 | } 247 | } 248 | 249 | // configurable 250 | function ttnDataSpecialFormat (dataId, str) { 251 | var strReverse = littleEndianTransform(str) 252 | if (dataId === 2 || dataId === 3) { 253 | return strReverse.join('') 254 | } 255 | 256 | // handle unsigned number 257 | var str2 = toBinary(strReverse) 258 | var dataArray = [] 259 | switch (dataId) { 260 | case 0: // DATA_BOARD_VERSION 261 | case 1: // DATA_SENSOR_VERSION 262 | // Using point segmentation 263 | for (var k = 0; k < str2.length; k += 16) { 264 | var tmp146 = str2.substring(k, k + 16) 265 | tmp146 = (parseInt(tmp146.substring(0, 8), 2) || 0) + '.' + (parseInt(tmp146.substring(8, 16), 2) || 0) 266 | dataArray.push(tmp146) 267 | } 268 | return dataArray.join(',') 269 | case 4: 270 | for (var i = 0; i < str2.length; i += 8) { 271 | var item = parseInt(str2.substring(i, i + 8), 2) 272 | if (item < 10) { 273 | item = '0' + item.toString() 274 | } else { 275 | item = item.toString() 276 | } 277 | dataArray.push(item) 278 | } 279 | return dataArray.join('') 280 | case 7: 281 | // battery && interval 282 | return { 283 | interval: parseInt(str2.substr(0, 16), 2), power: parseInt(str2.substr(-16, 16), 2) 284 | } 285 | case 9: 286 | let dataValue = { 287 | detectionType: parseInt(str2.substring(0, 8), 2), 288 | modelId: parseInt(str2.substring(8, 16), 2), 289 | modelVer: parseInt(str2.substring(16, 24), 2) 290 | } 291 | // 01010000 292 | return dataValue 293 | } 294 | } 295 | 296 | // util 297 | function ttnDataFormat (str) { 298 | var strReverse = littleEndianTransform(str) 299 | var str2 = toBinary(strReverse) 300 | if (str2.substring(0, 1) === '1') { 301 | var arr = str2.split('') 302 | var reverseArr = [] 303 | for (var forArr = 0; forArr < arr.length; forArr++) { 304 | var item = arr[forArr] 305 | if (parseInt(item) === 1) { 306 | reverseArr.push(0) 307 | } else { 308 | reverseArr.push(1) 309 | } 310 | } 311 | str2 = parseInt(reverseArr.join(''), 2) + 1 312 | return parseFloat('-' + str2 / 1000) 313 | } 314 | return parseInt(str2, 2) / 1000 315 | } 316 | 317 | // util 318 | function sensorAttrForVersion (dataValue) { 319 | var dataValueSplitArray = dataValue.split(',') 320 | return { 321 | ver_hardware: dataValueSplitArray[0], ver_software: dataValueSplitArray[1] 322 | } 323 | } 324 | 325 | // util 326 | function toBinary (arr) { 327 | var binaryData = [] 328 | for (var forArr = 0; forArr < arr.length; forArr++) { 329 | var item = arr[forArr] 330 | var data = parseInt(item, 16) 331 | .toString(2) 332 | var dataLength = data.length 333 | if (data.length !== 8) { 334 | for (var i = 0; i < 8 - dataLength; i++) { 335 | data = '0' + data 336 | } 337 | } 338 | binaryData.push(data) 339 | } 340 | return binaryData.toString() 341 | .replace(/,/g, '') 342 | } 343 | 344 | // Samples 345 | // var sample = Decoder(["00", "00", "00", "01", "01", "00", "01", "00", "07", "00", "64", "00", "3C", "00", "01", "20", "01", "00", "00", "00", "00", "28", "90"], null); 346 | // var sample = Decoder(["01", "01", "10", "98", "53", "00", "00", "01", "02", "10", "A8", "7A", "00", "00", "AF", "51"], null); 347 | // var sample = Decoder(["01", "01", "00", "01", "01", "00", "01", "01", "02", "00", "6A", "01", "00", "15", "01", "03", "00", "30", "F1", "F7", "2C", "01", "04", "00", "09", "0C", "13", "14", "01", "05", "00", "7F", "4D", "00", "00", "01", "06", "00", "00", "00", "00", "00", "4C", "BE"], null); 348 | // console.log(sample); 349 | --------------------------------------------------------------------------------