SenseCAP S2120 Firmware version 2.0 decoding errors

Hi there, Well almost. :wink:

// Mapping measurement IDs to human-readable field names
var measurementIdToFieldName = {
‘4097’: ‘AIR_TEMPERATURE’,
‘4098’: ‘AIR_HUMIDITY’,
‘4099’: ‘LIGHT_INTENSITY’,
‘4190’: ‘UV_INDEX’,
‘4105’: ‘WIND_SPEED’,
‘4104’: ‘WIND_DIRECTION’,
‘4113’: ‘RAIN_GAUGE’,
‘4101’: ‘BAROMETRIC_PRESSURE’,
‘4191’: ‘PEAK_WIND_GUST’,
‘4213’: ‘RAIN_ACCUMULATION’,
‘4120’: ‘BATTERY’,
};

// Function to decode the SenseCAP payload and extract the data
function decodeSensecapPayload(data) {
if (!data || data.length === 0) {
console.log(“Input data is empty or undefined.”);
return { err: 1, messages: [], valid: false };
}

// Convert byte array to hex string
data = data2HexString(data).toUpperCase();
console.log("Payload in hex format: " + data);

var result = {
err: 0,
payload: data,
valid: true,
messages: [],
};

var splitArray = dataSplit(data);
console.log("Split data array: " + JSON.stringify(splitArray));

var decoderArray = [];

for (var i = 0; i < splitArray.length; i++) {
var item = splitArray[i];
var dataId = item.dataId;
var dataValue = item.dataValue;
console.log("Processing dataId: " + dataId + " with dataValue: " + dataValue);

var messages = dataIdAndDataValueJudge(dataId, dataValue);
console.log("Decoded messages: " + JSON.stringify(messages));

if (messages.length > 0) {
  decoderArray.push(messages);
}
}

result.messages = decoderArray;
return result;
}

// Function to split the incoming data into manageable pieces
function dataSplit(data) {
var frameArray = [];

console.log("Splitting data: " + data);
if (!data || data.length === 0) {
console.log(“No data to split.”);
return frameArray;
}

while (data.length > 2) {
var dataId = data.substring(0, 2);
var dataValue = ‘’;
var dataObj = {};

console.log("Processing dataId: " + dataId);

switch (dataId) {
  case '01':
  case '4A':
    dataValue = data.substring(2, 22);
    data = data.substring(22);
    break;
  case '02':
  case '4B':
    dataValue = data.substring(2, 18);
    data = data.substring(18);
    break;
  case '03':
    dataValue = data.substring(2, 4);
    data = data.substring(4);
    break;
  case '4C':
    dataValue = data.substring(2, 14);
    data = data.substring(14);
    break;
  default:
    console.log("Unknown dataId: " + dataId);
    dataValue = '';
    data = '';
    break;
}

if (dataValue) {
  dataObj = {
    dataId: dataId,
    dataValue: dataValue,
  };
  frameArray.push(dataObj);
}
}

console.log("Frame array after splitting: " + JSON.stringify(frameArray));
return frameArray;
}

// Function to match dataId and dataValue to their corresponding measurements
function dataIdAndDataValueJudge(dataId, dataValue) {
var messages = [];

switch (dataId) {
case ‘01’:
case ‘4A’:
var temperature = loraWANV2DataFormat(dataValue.substring(0, 4), 10);
var humidity = loraWANV2DataFormat(dataValue.substring(4, 6));
var illumination = loraWANV2DataFormat(dataValue.substring(6, 14));
var uvIndex = loraWANV2DataFormat(dataValue.substring(14, 16), 10);
var windSpeed = loraWANV2DataFormat(dataValue.substring(16, 20), 10);

  messages.push({
    measurementValue: temperature,
    measurementId: '4097',
    type: 'Air Temperature',
  });
  messages.push({
    measurementValue: humidity,
    measurementId: '4098',
    type: 'Air Humidity',
  });
  messages.push({
    measurementValue: illumination,
    measurementId: '4099',
    type: 'Light Intensity',
  });
  messages.push({
    measurementValue: uvIndex,
    measurementId: '4190',
    type: 'UV Index',
  });
  messages.push({
    measurementValue: windSpeed,
    measurementId: '4105',
    type: 'Wind Speed',
  });
  break;

case '02':
case '4B':
  var windDirection = loraWANV2DataFormat(dataValue.substring(0, 4));
  var rainfall = loraWANV2DataFormat(dataValue.substring(4, 12), 1000);
  var airPressure = loraWANV2DataFormat(dataValue.substring(12, 16), 0.1);

  messages.push({
    measurementValue: windDirection,
    measurementId: '4104',
    type: 'Wind Direction Sensor',
  });
  messages.push({
    measurementValue: rainfall,
    measurementId: '4113',
    type: 'Rain Gauge',
  });
  messages.push({
    measurementValue: airPressure,
    measurementId: '4101',
    type: 'Barometric Pressure',
  });
  break;

case '4C':
  var peakWind = loraWANV2DataFormat(dataValue.substring(0, 4), 10);
  var rainAccumulation = loraWANV2DataFormat(dataValue.substring(4, 12), 1000);

  messages.push({
    measurementValue: peakWind,
    measurementId: '4191',
    type: 'Peak Wind Gust',
  });
  messages.push({
    measurementValue: rainAccumulation,
    measurementId: '4213',
    type: 'Rain Accumulation',
  });
  break;

case '03':
  var battery = loraWANV2DataFormat(dataValue);
  messages.push({
    measurementValue: battery,
    measurementId: '4120',
    type: 'Battery',
  });
  break;

default:
  console.log("Unrecognized dataId: " + dataId);
  break;
}

return messages;
}

// Helper functions for data formatting
function loraWANV2DataFormat(str, divisor) {
divisor = divisor || 1;
var strReverse = bigEndianTransform(str);
var str2 = toBinary(strReverse);

if (str2[0] === ‘1’) {
var arr = str2.split(’’).map(function (bit) {
return bit === ‘1’ ? ‘0’ : ‘1’;
});
str2 = (parseInt(arr.join(’’), 2) + 1).toString();
return parseFloat(’-’ + str2 / divisor);
}

return parseInt(str2, 2) / divisor;
}

function bigEndianTransform(data) {
var arr = [];
for (var i = 0; i < data.length; i += 2) {
arr.push(data.substring(i, i + 2));
}
return arr;
}

function toBinary(arr) {
return arr.map(function (byte) {
var binaryString = parseInt(byte, 16).toString(2);
while (binaryString.length < 8) {
binaryString = ‘0’ + binaryString;
}
return binaryString;
}).join(’’);
}

function data2HexString(arrBytes) {
if (!arrBytes || arrBytes.length === 0) {
console.log(“No data in arrBytes to convert.”);
return “”;
}

var str = ‘’;
for (var i = 0; i < arrBytes.length; i++) {
var tmp;
var num = arrBytes[i];
if (num < 0) {
tmp = (255 + num + 1).toString(16);
} else {
tmp = num.toString(16);
}
if (tmp.length === 1) {
tmp = ‘0’ + tmp;
}
str += tmp;
}

console.log("Hex string after conversion: " + str);
return str;
}

// Main Decoder function
function Decoder(bytes, port) {
console.log("Input bytes: " + bytes);

// Check if the input bytes are empty and use a test payload if they are
if (!bytes || bytes.length === 0) {
console.log(“Input bytes are empty. Using a test payload.”);

// Use a sample payload for testing
bytes = [0x4A, 0xFF, 0xE4, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x00, 0xD8, 0x00, 0x00, 0x00, 0x23, 0xDD, 0x4C, 0x00, 0x00, 0x00, 0x00];
console.log("Test payload: " + bytes);
}

// Proceed with decoding the payload
var decodedSensecapFields = decodeSensecapPayload(bytes).messages;
var datacakeFields = [];

if (decodedSensecapFields.length === 0) {
console.log(“No decoded fields available, returning empty array.”);
return datacakeFields;
}

for (var i = 0; i < decodedSensecapFields.length; i++) {
var arrayEntry = decodedSensecapFields[i];
arrayEntry.forEach(function (entry) {
// Map measurementId to a human-readable field name
var fieldName = measurementIdToFieldName[entry.measurementId] || ‘MEASUREMENT_ID_’ + entry.measurementId;

  datacakeFields.push({
    field: fieldName,
    value: entry.measurementValue,
  });
});
}

console.log("Decoded fields to return to Datacake: " + JSON.stringify(datacakeFields));
return datacakeFields;
}

WOW, That looks better :+1:

Cool,
GL :slight_smile: PJ :v:

1 Like