⬆️Vicki Uplink Decoder
Use the following code to decode Vicki LoRaWAN's payload into human-friendly format.
You'll be able to retrieve:
Target Temperature
Measured temperature and humidity
motorPosition and motorRange
openWindow detection
ChildLock
High and Low Motor Consumption warning bytes
Temp and humidity sensor status byte - is it functional or broken
Battery voltage
Universal Decoder:
Supports: The Thinks Network, Milesight, DataCake, Chirpstack
// DataCake
function Decoder(bytes, port){
var decoded = decodeUplink({ bytes: bytes, fPort: port }).data;
return decoded;
}
// Milesight
function Decode(port, bytes){
var decoded = decodeUplink({ bytes: bytes, fPort: port }).data;
return decoded;
}
// The Things Industries / Main
function decodeUplink(input) {
var bytes = input.bytes;
var data = {};
var resultToPass = {};
var toBool = function (value) { return value == '1' };
function merge_obj(obj1, obj2) {
var obj3 = {};
for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
for (var attrname2 in obj2) { obj3[attrname2] = obj2[attrname2]; }
return obj3;
}
function handleKeepalive(bytes, data){
var tmp = ("0" + bytes[6].toString(16)).substr(-2);
var motorRange1 = tmp[1];
var motorRange2 = ("0" + bytes[5].toString(16)).substr(-2);
var motorRange = (parseInt(motorRange1, 16) << 8) | parseInt(motorRange2, 16);
var motorPos2 = ("0" + bytes[4].toString(16)).substr(-2);
var motorPos1 = tmp[0];
var motorPosition = (parseInt(motorPos1, 16) << 8) | parseInt(motorPos2, 16);
var batteryTmp = ("0" + bytes[7].toString(16)).substr(-2)[0];
var batteryVoltageCalculated = 2 + parseInt("0x" + batteryTmp, 16) * 0.1;
var decbin = function(number) {
if (number < 0) {
number = 0xFFFFFFFF + number + 1;
}
number = number.toString(2);
return "00000000".substr(number.length) + number;
}
var byte7Bin = decbin(bytes[7]);
var openWindow = byte7Bin[4];
var highMotorConsumption = byte7Bin[5];
var lowMotorConsumption = byte7Bin[6];
var brokenSensor = byte7Bin[7];
var byte8Bin = decbin(bytes[8]);
var childLock = byte8Bin[0];
var calibrationFailed = byte8Bin[1];
var attachedBackplate = byte8Bin[2];
var perceiveAsOnline = byte8Bin[3];
var antiFreezeProtection = byte8Bin[4];
var sensorTemp = 0;
if (Number(bytes[0].toString(16)) == 1) {
sensorTemp = (bytes[2] * 165) / 256 - 40;
}
if (Number(bytes[0].toString(16)) == 81) {
sensorTemp = (bytes[2] - 28.33333) / 5.66666;
}
data.reason = Number(bytes[0].toString(16));
data.targetTemperature = Number(bytes[1]);
data.sensorTemperature = Number(sensorTemp.toFixed(2));
data.relativeHumidity = Number(((bytes[3] * 100) / 256).toFixed(2));
data.motorRange = motorRange;
data.motorPosition = motorPosition;
data.batteryVoltage = Number(batteryVoltageCalculated.toFixed(2));
data.openWindow = toBool(openWindow);
data.highMotorConsumption = toBool(highMotorConsumption);
data.lowMotorConsumption = toBool(lowMotorConsumption);
data.brokenSensor = toBool(brokenSensor);
data.childLock = toBool(childLock);
data.calibrationFailed = toBool(calibrationFailed);
data.attachedBackplate = toBool(attachedBackplate);
data.perceiveAsOnline = toBool(perceiveAsOnline);
data.antiFreezeProtection = toBool(antiFreezeProtection);
data.valveOpenness = motorRange != 0 ? Math.round((1-(motorPosition/motorRange))*100) : 0;
if(!data.hasOwnProperty('targetTemperatureFloat')){
data.targetTemperatureFloat = parseFloat(bytes[1]);
}
return data;
}
function handleResponse(bytes, data){
var commands = bytes.map(function(byte, i){
return ("0" + byte.toString(16)).substr(-2);
});
// commands = commands.slice(0,-9);
var command_len = 0;
commands.map(function (command, i) {
switch (command) {
case '01':
{
command_len = 9;
var data = { decodeKeepalive: true };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '81':
{
command_len = 9;
var data = { decodeKeepalive: true };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '04':
{
command_len = 2;
var hardwareVersion = commands[i + 1];
var softwareVersion = commands[i + 2];
var dataK = { 'deviceVersions': { 'hardware': Number(hardwareVersion), 'software': Number(softwareVersion) } };
resultToPass = merge_obj(resultToPass, dataK);
}
break;
case '12':
{
command_len = 1;
var dataC = { 'keepAliveTime': parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, dataC);
}
break;
case '13':
{
command_len = 4;
var enabled = toBool(parseInt(commands[i + 1], 16));
var duration = parseInt(commands[i + 2], 16) * 5;
var tmp = ("0" + commands[i + 4].toString(16)).substr(-2);
var motorPos2 = ("0" + commands[i + 3].toString(16)).substr(-2);
var motorPos1 = tmp[0];
var motorPosition = (parseInt(motorPos1, 16) << 8) | parseInt(motorPos2, 16);
var delta = Number(tmp[1]);
var dataD = { 'openWindowParams': { 'enabled': enabled, 'duration': duration, 'motorPosition': motorPosition, 'delta': delta } };
resultToPass = merge_obj(resultToPass, dataD);
}
break;
case '14':
{
command_len = 1;
var dataB = { 'childLock': toBool(parseInt(commands[i + 1], 16)) };
resultToPass = merge_obj(resultToPass, dataB);
}
break;
case '15':
{
command_len = 2;
var dataA = { 'temperatureRangeSettings': { 'min': parseInt(commands[i + 1], 16), 'max': parseInt(commands[i + 2], 16) } };
resultToPass = merge_obj(resultToPass, dataA);
}
break;
case '16':
{
command_len = 2;
var data = { 'internalAlgoParams': { 'period': parseInt(commands[i + 1], 16), 'pFirstLast': parseInt(commands[i + 2], 16), 'pNext': parseInt(commands[i + 3], 16) } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '17':
{
command_len = 2;
var dataF = { 'internalAlgoTdiffParams': { 'warm': parseInt(commands[i + 1], 16), 'cold': parseInt(commands[i + 2], 16) } };
resultToPass = merge_obj(resultToPass, dataF);
}
break;
case '18':
{
command_len = 1;
var dataE = { 'operationalMode': parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, dataE);
}
break;
case '19':
{
command_len = 1;
var commandResponse = parseInt(commands[i + 1], 16);
var periodInMinutes = commandResponse * 5 / 60;
var dataH = { 'joinRetryPeriod': periodInMinutes };
resultToPass = merge_obj(resultToPass, dataH);
}
break;
case '1b':
{
command_len = 1;
var dataG = { 'uplinkType': parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, dataG);
}
break;
case '1d':
{
// get default keepalive if it is not available in data
command_len = 2;
var wdpC = commands[i + 1] == '00' ? false : parseInt(commands[i + 1], 16);
var wdpUc = commands[i + 2] == '00' ? false : parseInt(commands[i + 2], 16);
var dataJ = { 'watchDogParams': { 'wdpC': wdpC, 'wdpUc': wdpUc } };
resultToPass = merge_obj(resultToPass, dataJ);
}
break;
case '1f':
{
command_len = 1;
var data = { 'primaryOperationalMode': commands[i + 1] };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '21':
{
command_len = 6;
var data = {'batteryRangesBoundaries':{
'Boundary1': (parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16),
'Boundary2': (parseInt(commands[i + 3], 16) << 8) | parseInt(commands[i + 4], 16),
'Boundary3': (parseInt(commands[i + 5], 16) << 8) | parseInt(commands[i + 6], 16),
}};
resultToPass = merge_obj(resultToPass, data);
}
break;
case '23':
{
command_len = 4;
var data = {'batteryRangesOverVoltage':{
'Range1': parseInt(commands[i + 2], 16),
'Range2': parseInt(commands[i + 3], 16),
'Range3': parseInt(commands[i + 4], 16),
}};
resultToPass = merge_obj(resultToPass, data);
}
break;
case '27':
{
command_len = 1;
var data = {'OVAC': parseInt(commands[i + 1], 16)};
resultToPass = merge_obj(resultToPass, data);
}
break;
case '28':
{
command_len = 1;
var data = { 'manualTargetTemperatureUpdate': parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '29':
{
command_len = 2;
var data = { 'proportionalAlgoParams': { 'coefficient': parseInt(commands[i + 1], 16), 'period': parseInt(commands[i + 2], 16) } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '2b':
{
command_len = 1;
var data = { 'algoType': commands[i + 1] };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '36':
{
command_len = 3;
var kp = ((parseInt(commands[i + 1], 16) << 16) | (parseInt(commands[i + 2], 16) << 8) | parseInt(commands[i + 3], 16)) / 131072;
var data = { 'proportionalGain': Number(kp).toFixed(5) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '3d':
{
command_len = 3;
var ki = ((parseInt(commands[i + 1], 16) << 16) | (parseInt(commands[i + 2], 16) << 8) | parseInt(commands[i + 3], 16)) / 131072;
var data = { 'integralGain': Number(ki).toFixed(5) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '3f':
{
command_len = 2;
var data = { 'integralValue' : ((parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16))/10 };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '40':
{
command_len = 1;
var data = { 'piRunPeriod' : parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '42':
{
command_len = 1;
var data = { 'tempHysteresis' : parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '44':
{
command_len = 2;
var data = { 'extSensorTemperature' : ((parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16))/10 };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '46':
{
command_len = 3;
var enabled = toBool(parseInt(commands[i + 1], 16));
var duration = parseInt(commands[i + 2], 16) * 5;
var delta = parseInt(commands[i + 3], 16) /10;
var data = { 'openWindowParams': { 'enabled': enabled, 'duration': duration, 'delta': delta } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '48':
{
command_len = 1;
var data = { 'forceAttach' : parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '4a':
{
command_len = 3;
var activatedTemperature = parseInt(commands[i + 1], 16)/10;
var deactivatedTemperature = parseInt(commands[i + 2], 16)/10;
var targetTemperature = parseInt(commands[i + 3], 16);
var data = { 'antiFreezeParams': { 'activatedTemperature': activatedTemperature, 'deactivatedTemperature': deactivatedTemperature, 'targetTemperature': targetTemperature } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '4b':
{
command_len = 1;
var data = { 'patchVersion' : parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '4d':
{
command_len = 2;
var data = { 'piMaxIntegratedError' : ((parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16))/10 };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '50':
{
command_len = 2;
var data = { 'effectiveMotorRange': { 'minValveOpenness': 100 - parseInt(commands[i + 2], 16), 'maxValveOpenness': 100 - parseInt(commands[i + 1], 16) } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '52':
{
command_len = 2;
var data = { 'targetTemperatureFloat' : ((parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16))/10 };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '54':
{
command_len = 1;
var offset = (parseInt(commands[i + 1], 16) - 28) * 0.176;
var data = { 'temperatureOffset' : offset };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '58':
{
command_len = 1;
var notificationByte = parseInt(commands[i + 1], 16);
// Extract notification flags from bits
var temperatureRestoredAfterManualBoost = !!(notificationByte & 0x01); // Bit 0
var temperatureChangedByHeatingSchedule = !!(notificationByte & 0x02); // Bit 1
var data = {
notifications: {
temperatureRestoredAfterManualBoost: temperatureRestoredAfterManualBoost,
temperatureChangedByHeatingSchedule: temperatureChangedByHeatingSchedule
}
};
resultToPass = merge_obj(resultToPass, data);
}
break;
case '5a':
{
command_len = 41;
var eventsGroup = parseInt(commands[i + 1], 16); // 0 = events 0-7, 1 = events 8-15, 2 = events 16-19
var eventGroupIndex = {0: '0-7', 1: '8-15', 2: '16-19'};
var heatingEvents = [];
// Process up to 8 events for groups 0 and 1, but only 4 events for group 2
var eventsToProcess = (eventsGroup === 2) ? 4 : 8;
for (var eventIdx = 0; eventIdx < eventsToProcess; eventIdx++) {
// Each event takes 5 bytes (hour, minute, temp high, temp low, weekday bitmask)
var offset = i + 2 + (eventIdx * 5);
// Check if this event is configured
// Make sure we have valid values at this offset
if (offset >= commands.length || offset + 4 >= commands.length) {
continue;
}
var hour = parseInt(commands[offset], 16);
var minute = parseInt(commands[offset + 1], 16);
var tempHigh = parseInt(commands[offset + 2], 16);
var tempLow = parseInt(commands[offset + 3], 16);
var weekdayByte = parseInt(commands[offset + 4], 16);
// Skip events that are not configured (zeros or NaN values)
if (isNaN(hour) || isNaN(minute) || isNaN(tempHigh) || isNaN(tempLow) || isNaN(weekdayByte) ||
(hour === 0 && minute === 0 && tempHigh === 0 && tempLow === 0 && weekdayByte === 0)) {
continue;
}
// Calculate actual event index in the full range (0-23)
var globalEventIndex = (eventsGroup * 8) + eventIdx;
// Decode weekday bitmask (bit 0=Mon, bit 1=Tue, bit 2=Wed, bit 3=Thu, bit 4=Fri, bit 5=Sat, bit 6=Sun)
var weekdays = {
monday: !!(weekdayByte & 0x01), // bit 0
tuesday: !!(weekdayByte & 0x02), // bit 1
wednesday: !!(weekdayByte & 0x04), // bit 2
thursday: !!(weekdayByte & 0x08), // bit 3
friday: !!(weekdayByte & 0x10), // bit 4
saturday: !!(weekdayByte & 0x20), // bit 5
sunday: !!(weekdayByte & 0x40) // bit 6
};
// Create heating event object
var heatingEvent = {
index: globalEventIndex,
start: (hour < 10 ? '0' + hour : hour) + ':' + (minute < 10 ? '0' + minute : minute),
targetTemperature: ((tempHigh << 8) | tempLow) / 10,
weekdays: weekdays
};
heatingEvents.push(heatingEvent);
}
var data = {
heatingEventGroup: eventGroupIndex[eventsGroup],
heatingEvents: heatingEvents
};
resultToPass = merge_obj(resultToPass, data);
}
break;
case '5c': {
command_len = 4;
// Note: Months are 0-11 (January=0, December=11)
var startMonth = parseInt(commands[i + 1], 16);
var startDay = parseInt(commands[i + 2], 16);
var endMonth = parseInt(commands[i + 3], 16);
var endDay = parseInt(commands[i + 4], 16);
// Convert to human-readable month names
var monthNames = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
var data = {
heatingSchedule: {
start: startDay + ' ' + monthNames[startMonth],
end: endDay + ' ' + monthNames[endMonth],
}
};
resultToPass = merge_obj(resultToPass, data);
break;
}
case '5e': {
command_len = 4;
// Parse 32-bit UNIX timestamp (4 bytes)
var unixTimestamp = (parseInt(commands[i + 1], 16) << 24) |
(parseInt(commands[i + 2], 16) << 16) |
(parseInt(commands[i + 3], 16) << 8) |
parseInt(commands[i + 4], 16);
// Convert UNIX timestamp to JavaScript Date
var dateObj = new Date(unixTimestamp * 1000); // Convert seconds to milliseconds
var date = dateObj.getUTCDate() + '/' + (dateObj.getUTCMonth() + 1) + '/' + dateObj.getUTCFullYear()
var time = dateObj.getUTCHours() + ':' + (dateObj.getUTCMinutes() < 10 ? '0' : '') + dateObj.getUTCMinutes()
var data = {
deviceTime: "" + date + " " + time
};
resultToPass = merge_obj(resultToPass, data);
break;
}
case '60': {
command_len = 1;
var offsetByte = parseInt(commands[i + 1], 16);
var offsetHours = (offsetByte & 0x80) ? (offsetByte - 256) : offsetByte;
var data = { deviceTimeZone: offsetHours };
resultToPass = merge_obj(resultToPass, data);
break;
}
case '62': {
command_len = 1;
var timeValue = parseInt(commands[i + 1], 16);
var data = { autoSetpointRestoreStatus: timeValue === 0 ? 0 : timeValue * 10 };
resultToPass = merge_obj(resultToPass, data);
break;
}
case '64': {
command_len = 1;
var ledDurationValue = parseInt(commands[i + 1], 16);
var durationInSeconds = ledDurationValue / 2; // As per the docs, value is divided by 2 to get seconds
var data = {
ledIndicationDuration: durationInSeconds
};
resultToPass = merge_obj(resultToPass, data);
break;
}
case '66': {
command_len = 2;
var tempHigh = parseInt(commands[i + 1], 16);
var tempLow = parseInt(commands[i + 2], 16);
var targetTemp = (tempHigh << 8) | tempLow;
var data = {
offlineTargetTemperature: targetTemp === 0 ? 0 : targetTemp / 10
};
resultToPass = merge_obj(resultToPass, data);
break;
}
case '68': {
command_len = 1;
var data = { internalAlgoTempState: parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
break;
}
case '6a': {
command_len = 12;
var temperatureLevels = {};
// Process 6 scale levels (0-5), each with a 2-byte temperature value
for (var level = 0; level < 6; level++) {
var tempHighByte = parseInt(commands[i + 1 + (level * 2)], 16);
var tempLowByte = parseInt(commands[i + 2 + (level * 2)], 16);
var tempValue = (tempHighByte << 8) | tempLowByte;
// The temperature values are pre-multiplied by 10
temperatureLevels['level' + level] = tempValue / 10;
}
var data = {
temperatureLevels: temperatureLevels
};
resultToPass = merge_obj(resultToPass, data);
break;
}
case '6c': {
command_len = 4;
var eventsHighByte = parseInt(commands[i + 2], 16); // Events 19-16
var eventsMidByte = parseInt(commands[i + 3], 16); // Events 15-8
var eventsLowByte = parseInt(commands[i + 4], 16); // Events 7-0
var eventsByte = (eventsHighByte << 16) | (eventsMidByte << 8) | eventsLowByte;
// Create a more structured and readable format for heating events
var heatingEventStates = {};
// Process all 20 events (0-19) in a single loop
for (var eventIdx = 0; eventIdx < 20; eventIdx++) {
// Calculate which bit to check
var bitPosition;
if (eventIdx >= 16) {
// Events 16-19 in high byte
bitPosition = eventIdx - 16;
heatingEventStates[eventIdx] = !!(eventsHighByte & (1 << bitPosition));
} else if (eventIdx >= 8) {
// Events 8-15 in mid byte
bitPosition = eventIdx - 8;
heatingEventStates[eventIdx] = !!(eventsMidByte & (1 << bitPosition));
} else {
// Events 0-7 in low byte
bitPosition = eventIdx;
heatingEventStates[eventIdx] = !!(eventsLowByte & (1 << bitPosition));
}
}
var data = {
heatingEventStates: heatingEventStates
};
resultToPass = merge_obj(resultToPass, data);
break;
}
case '6e': {
command_len = 1;
var data = { timeRequestByMACcommand: parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
break;
}
case 'a0': {
command_len = 4;
var fuota_address = (parseInt(commands[i + 1], 16) << 24) |
(parseInt(commands[i + 2], 16) << 16) |
(parseInt(commands[i + 3], 16) << 8) |
parseInt(commands[i + 4], 16);
var fuota_address_raw = commands[i + 1] + commands[i + 2] +
commands[i + 3] + commands[i + 4];
resultToPass = merge_obj(resultToPass, { fuota: { fuota_address: fuota_address, fuota_address_raw: fuota_address_raw } });
break;
}
default:
break;
}
commands.splice(i,command_len);
});
return resultToPass;
}
if (bytes[0].toString(16) == 1 || bytes[0].toString(16) == 81) {
data = merge_obj(data, handleKeepalive(bytes, data));
} else {
data = merge_obj(data, handleResponse(bytes, data));
var shouldKeepAlive = data.hasOwnProperty('decodeKeepalive') ? true : false;
if ('decodeKeepalive' in data) {
delete data.decodeKeepalive;
}
if (shouldKeepAlive) {
bytes = bytes.slice(-9);
data = merge_obj(data, handleKeepalive(bytes, data));
}
}
return {
data: data
};
}Tektelic Decoder (JavaScript ES5):
arr = [];
for (var i = 0; i < bytes.length; ++i)
arr.push(bytes[i]);
function toHexString(arr) {
var s = '';
arr.forEach(function(byte) {
s += ('0' + (byte & 0xFF).toString(16)).slice(-2);
});
return s;
}
var hexData = toHexString(arr);
var bytes = hexData.match(/.{1,2}/g).map(byte =>
parseInt(byte,16)
)
var deviceData;
function merge_obj(obj1,obj2){
var obj3 = {};
for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
return obj3;
}
var toBool = function (value) { return value == '1'};
function handleKeepAliveData(bytes){
tmp = ("0" + bytes[6].toString(16)).substr(-2);
motorRange1 = tmp[1];
motorRange2 = ("0" + bytes[5].toString(16)).substr(-2);
motorRange = parseInt("0x"+ motorRange1 + motorRange2, 16);
motorPos2 = ("0" + bytes[4].toString(16)).substr(-2);
motorPos1 = tmp[0];
motorPosition = parseInt("0x"+ motorPos1 + motorPos2, 16);
batteryTmp = ("0" + bytes[7].toString(16)).substr(-2)[0];
batteryVoltageCalculated = 2 + parseInt("0x"+ batteryTmp , 16) * 0.1;
decbin = function (number) {
if (number < 0) {
number = 0xFFFFFFFF + number + 1;
}
return parseInt(number, 10).toString(2);
};
byte7Bin = decbin(bytes[7]);
openWindow = byte7Bin[4];
highMotorConsumption = byte7Bin[5];
lowMotorConsumption = byte7Bin[6];
brokenSensor = byte7Bin[7];
byte8Bin = decbin(bytes[8]);
childLock = byte8Bin[0];
calibrationFailed = byte8Bin[1];
attachedBackplate = byte8Bin[2];
perceiveAsOnline = byte8Bin[3];
antiFreezeProtection = byte8Bin[4];
var sensorTemp;
if(bytes[0] == 1){
sensorTemp = (bytes[2] * 165) / 256 - 40;
}
if(bytes[0] == 129){
sensorTemp = (bytes[2] - 28.33333) / 5.66666;
}
return {
hex: hexData,
reason: bytes[0],
targetTemperature: bytes[1],
sensorTemperature: sensorTemp,
relativeHumidity: bytes[3]/ 256 ,
motorRange: motorRange,
motorPosition: motorPosition,
batteryVoltage: batteryVoltageCalculated,
openWindow: toBool(openWindow),
childLock: toBool(childLock),
highMotorConsumption: toBool(highMotorConsumption),
lowMotorConsumption: toBool(lowMotorConsumption),
brokenSensor: toBool(brokenSensor),
calibrationFailed: toBool(calibrationFailed),
attachedBackplate: toBool(attachedBackplate),
perceiveAsOnline: toBool(perceiveAsOnline),
antiFreezeProtection: toBool(antiFreezeProtection),
motorOpenness: Math.round((1-(motorPosition/motorRange))*100),
port: port,
"payload length": bytes.length
};
}
if (bytes[0] == 1 || bytes[0] == 129) {
deviceData = merge_obj(deviceData, handleKeepAliveData(bytes));
return deviceData;
} else {
if(bytes.length<3)
return { "hex": toHexString(arr), "port": port , "payload length": bytes.length};
// parse command answers
var data = commandsReadingHelper(deviceData, String(hexData).split(",").join(""), 18);
deviceData = merge_obj(deviceData,data);
// get only keepalive from device response
var keepaliveData = String(hexData).split(",").join("").slice(-18);
var dataToPass = keepaliveData.match(/.{1,2}/g).map(function (byte) { return parseInt(byte, 16) });
deviceData = merge_obj(deviceData, handleKeepAliveData(dataToPass));
return deviceData;
}
function commandsReadingHelper(deviceData, hexData, payloadLength) {
var resultToPass = {};
var data = hexData.slice(0, -payloadLength);
var commands = data.match(/.{1,2}/g);
commands.map(function (command, i) {
switch (command) {
case '04':
{
command_len = 2;
var hardwareVersion = commands[i + 1];
var softwareVersion = commands[i + 2];
var dataK = { deviceVersions: { hardware: Number(hardwareVersion), software: Number(softwareVersion) } };
resultToPass = merge_obj(resultToPass, dataK);
}
break;
case '12':
{
command_len = 1;
var dataC = { keepAliveTime: parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, dataC);
}
break;
case '13':
{
command_len = 4;
var enabled = toBool(parseInt(commands[i + 1], 16));
var duration = parseInt(commands[i + 2], 16) * 5;
var tmp = ("0" + commands[i + 4].toString(16)).substr(-2);
var motorPos2 = ("0" + commands[i + 3].toString(16)).substr(-2);
var motorPos1 = tmp[0];
var motorPosition = parseInt('0x' + motorPos1 + motorPos2, 16);
var delta = Number(tmp[1]);
var dataD = { openWindowParams: { enabled: enabled, duration: duration, motorPosition: motorPosition, delta: delta } };
resultToPass = merge_obj(resultToPass, dataD);
}
break;
case '14':
{
command_len = 1;
var dataB = { childLock: toBool(parseInt(commands[i + 1], 16)) };
resultToPass = merge_obj(resultToPass, dataB);
}
break;
case '15':
{
command_len = 2;
var dataA = { temperatureRangeSettings: { min: parseInt(commands[i + 1], 16), max: parseInt(commands[i + 2], 16) } };
resultToPass = merge_obj(resultToPass, dataA);
}
break;
case '16':
{
command_len = 2;
var data = { internalAlgoParams: { period: parseInt(commands[i + 1], 16), pFirstLast: parseInt(commands[i + 2], 16), pNext: parseInt(commands[i + 3], 16) } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '17':
{
command_len = 2;
var dataF = { internalAlgoTdiffParams: { warm: parseInt(commands[i + 1], 16), cold: parseInt(commands[i + 2], 16) } };
resultToPass = merge_obj(resultToPass, dataF);
}
break;
case '18':
{
command_len = 1;
var dataE = { operationalMode: parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, dataE);
}
break;
case '19':
{
command_len = 1;
var commandResponse = parseInt(commands[i + 1], 16);
var periodInMinutes = commandResponse * 5 / 60;
var dataH = { joinRetryPeriod: periodInMinutes };
resultToPass = merge_obj(resultToPass, dataH);
}
break;
case '1b':
{
command_len = 1;
var dataG = { uplinkType: parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, dataG);
}
break;
case '1d':
{
// get default keepalive if it is not available in data
command_len = 2;
var wdpC = commands[i + 1] == '00' ? false : parseInt(commands[i + 1], 16);
var wdpUc = commands[i + 2] == '00' ? false : parseInt(commands[i + 2], 16);
var dataJ = { watchDogParams: { wdpC: wdpC, wdpUc: wdpUc } };
resultToPass = merge_obj(resultToPass, dataJ);
}
break;
case '1f':
{
command_len = 1;
var data = { primaryOperationalMode: commands[i + 1] };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '21':
{
command_len = 6;
var data = {batteryRangesBoundaries:{
Boundary1: parseInt(commands[i + 1] + commands[i + 2], 16),
Boundary2: parseInt(commands[i + 3] + commands[i + 4], 16),
Boundary3: parseInt(commands[i + 5] + commands[i + 6], 16),
}};
resultToPass = merge_obj(resultToPass, data);
}
break;
case '23':
{
command_len = 4;
var data = {batteryRangesOverVoltage:{
Range1: parseInt(commands[i + 2], 16),
Range2: parseInt(commands[i + 3], 16),
Range3: parseInt(commands[i + 4], 16),
}};
resultToPass = merge_obj(resultToPass, data);
}
break;
case '27':
{
command_len = 1;
var data = {OVAC: parseInt(commands[i + 1], 16)};
resultToPass = merge_obj(resultToPass, data);
}
break;
case '28':
{
command_len = 1;
var data = { manualTargetTemperatureUpdate: parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '29':
{
command_len = 2;
var data = { proportionalAlgoParams: { coefficient: parseInt(commands[i + 1], 16), period: parseInt(commands[i + 2], 16) } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '2b':
{
command_len = 1;
var data = { algoType: commands[i + 1] };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '36':
{
command_len = 3;
var kp = parseInt(`${commands[i + 1]}${commands[i + 2]}${commands[i + 3]}`, 16) / 131072;
var data = { proportionalGain: Number(kp).toFixed(5) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '3d':
{
command_len = 3;
var ki = parseInt(`${commands[i + 1]}${commands[i + 2]}${commands[i + 3]}`, 16) / 131072;
var data = { integralGain: Number(ki).toFixed(5) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '3f':
{
command_len = 2;
var data = { integralValue : (parseInt(`${commands[i + 1]}${commands[i + 2]}`, 16))/10 };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '40':
{
command_len = 1;
var data = { piRunPeriod : parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '42':
{
command_len = 1;
var data = { tempHysteresis : parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '44':
{
command_len = 2;
var data = { extSensorTemperature : (parseInt(`${commands[i + 1]}${commands[i + 2]}`, 16))/10 };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '46':
{
command_len = 3;
var enabled = toBool(parseInt(commands[i + 1], 16));
var duration = parseInt(commands[i + 2], 16) * 5;
var delta = parseInt(commands[i + 3], 16) /10;
var data = { openWindowParams: { enabled: enabled, duration: duration, delta: delta } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '48':
{
command_len = 1;
var data = { forceAttach : parseInt(commands[i + 1], 16) };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '4a':
{
command_len = 3;
var activatedTemperature = parseInt(commands[i + 1], 16)/10;
var deactivatedTemperature = parseInt(commands[i + 2], 16)/10;
var targetTemperature = parseInt(commands[i + 3], 16);
var data = { antiFreezeParams: { activatedTemperature, deactivatedTemperature, targetTemperature } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '4d':
{
command_len = 2;
var data = { piMaxIntegratedError : (parseInt(`${commands[i + 1]}${commands[i + 2]}`, 16))/10 };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '50':
{
command_len = 2;
var data = { effectiveMotorRange: { minMotorRange: parseInt(commands[i + 1], 16), maxMotorRange: parseInt(commands[i + 2], 16) } };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '52':
{
command_len = 2;
var data = { targetTemperatureFloat : (parseInt(`${commands[i + 1]}${commands[i + 2]}`, 16))/10 };
resultToPass = merge_obj(resultToPass, data);
}
break;
case '54':
{
command_len = 1;
var offset = (parseInt(commands[i + 1], 16) - 28) * 0.176
var data = { temperatureOffset : offset };
resultToPass = merge_obj(resultToPass, data);
}
break;
default:
break;
}
commands.splice(i,command_len);
});
return resultToPass;
};
Last updated
Was this helpful?