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

Recommended decoder (JavaScript ES6)

1
let deviceData = {};
2
function Decoder (hexData) {
3
const toBool = value => value == '1';
4
let decbin = (number) => {
5
if (number < 0) {
6
number = 0xFFFFFFFF + number + 1
7
}
8
return parseInt(number, 10).toString(2)
9
}
10
11
const handleKeepAliveData = (byteArray) => {
12
let tmp = ("0" + byteArray[6].toString(16)).substr(-2);
13
let motorRange1 = tmp[1];
14
let motorRange2 = ("0" + byteArray[5].toString(16)).substr(-2);
15
let motorRange = parseInt(`0x${motorRange1}${motorRange2}`, 16);
16
17
let motorPos2 = ("0" + byteArray[4].toString(16)).substr(-2);
18
let motorPos1 = tmp[0];
19
let motorPosition = parseInt(`0x${motorPos1}${motorPos2}`, 16);
20
21
let batteryTmp = ("0" + byteArray[7].toString(16)).substr(-2)[0];
22
let batteryVoltageCalculated = 2 + parseInt(`0x${batteryTmp}`, 16) * 0.1;
23
24
let byteBin = decbin(byteArray[7]);
25
let openWindow = byteBin.substr(4, 1);
26
let childLockBin = decbin(byteArray[8]);
27
let childLock = childLockBin.charAt(0);
28
let highMotorConsumption = byteBin.substr(-2, 1);
29
let lowMotorConsumption = byteBin.substr(-3, 1);
30
let brokenSensor = byteBin.substr(-4, 1);
31
32
let sensorTemp;
33
if(byteArray[0] == 1){
34
sensorTemp = (byteArray[2] * 165) / 256 - 40;
35
}
36
if(byteArray[0] == 129){
37
sensorTemp = (byteArray[2] - 28.33333) / 5.66666;
38
}
39
40
let keepaliveData = {
41
reason: byteArray[0],
42
targetTemperature: byteArray[1],
43
sensorTemperature: sensorTemp,
44
relativeHumidity: (byteArray[3] * 100) / 256,
45
motorRange: motorRange,
46
motorPosition: motorPosition,
47
batteryVoltage: batteryVoltageCalculated,
48
openWindow: toBool(openWindow),
49
childLock: toBool(childLock),
50
highMotorConsumption: toBool(highMotorConsumption),
51
lowMotorConsumption: toBool(lowMotorConsumption),
52
brokenSensor: toBool(brokenSensor)
53
}
54
55
Object.assign(deviceData, { ...deviceData }, { ...keepaliveData })
56
}
57
58
if (hexData) {
59
let byteArray = hexData.match(/.{1,2}/g).map(byte => { return parseInt(byte, 16) })
60
if (byteArray[0] == 1 || byteArray[0] == 129) {
61
// its a keeapalive
62
handleKeepAliveData(byteArray);
63
} else {
64
let resultToPass = {};
65
let data = hexData.slice(0, -18);
66
let commands = data.match(/.{1,2}/g);
67
let command_len = 0;
68
69
commands.map((command, i) => {
70
switch (command) {
71
case '04':
72
{
73
command_len = 2;
74
let data = { deviceVersions: { hardware: Number(commands[i + 1]), software: Number(commands[i + 2]) } };
75
Object.assign(resultToPass, { ...resultToPass }, { ...data });
76
}
77
break;
78
case '12':
79
{
80
command_len = 1;
81
let data = { keepAliveTime: parseInt(commands[i + 1], 16) };
82
Object.assign(resultToPass, { ...resultToPass }, { ...data });
83
}
84
break;
85
case '13':
86
{
87
command_len = 4;
88
let enabled = toBool(parseInt(commands[i + 1], 16));
89
let duration = parseInt(commands[i + 2], 16) * 5;
90
let tmp = ("0" + commands[i + 4].toString(16)).substr(-2);
91
let motorPos2 = ("0" + commands[i + 3].toString(16)).substr(-2);
92
let motorPos1 = tmp[0];
93
let motorPosition = parseInt(`0x${motorPos1}${motorPos2}`, 16);
94
let delta = Number(tmp[1]);
95
96
let data = { openWindowParams: { enabled: enabled, duration: duration, motorPosition: motorPosition, delta: delta } };
97
Object.assign(resultToPass, { ...resultToPass }, { ...data });
98
99
}
100
break;
101
case '14':
102
{
103
command_len = 1;
104
let data = { childLock: toBool(parseInt(commands[i + 1], 16)) };
105
Object.assign(resultToPass, { ...resultToPass }, { ...data });
106
}
107
break;
108
case '15':
109
{
110
command_len = 2;
111
let data = { temperatureRangeSettings: { min: parseInt(commands[i + 1], 16), max: parseInt(commands[i + 2], 16) } };
112
Object.assign(resultToPass, { ...resultToPass }, { ...data });
113
}
114
break;
115
case '16':
116
{
117
command_len = 3;
118
let data = { internalAlgoParams: {period: parseInt(commands[i + 1], 16), pFirstLast: parseInt(commands[i + 2], 16), pNext: parseInt(commands[i + 3], 16)} };
119
Object.assign(resultToPass, { ...resultToPass }, { ...data });
120
}
121
break;
122
case '17':
123
{
124
command_len = 2;
125
let data = { internalAlgoTdiffParams: {warm: parseInt(commands[i + 1], 16), cold: parseInt(commands[i + 2], 16)} };
126
Object.assign(resultToPass, { ...resultToPass }, { ...data });
127
}
128
break;
129
case '18':
130
{
131
command_len = 1;
132
let data = { operationalMode: (commands[i + 1]).toString() };
133
Object.assign(resultToPass, { ...resultToPass }, { ...data });
134
}
135
break;
136
case '19':
137
{
138
command_len = 1;
139
let commandResponse = parseInt(commands[i + 1], 16);
140
let periodInMinutes = (commandResponse * 5) / 60;
141
let data = { joinRetryPeriod: periodInMinutes };
142
Object.assign(resultToPass, { ...resultToPass }, { ...data });
143
}
144
break;
145
case '1b':
146
{
147
command_len = 1;
148
let data = { uplinkType: commands[i + 1] };
149
Object.assign(resultToPass, { ...resultToPass }, { ...data });
150
}
151
break;
152
case '1d':
153
{
154
command_len = 2;
155
let deviceKeepAlive = deviceData.keepAliveTime ? deviceData.keepAliveTime : 5;
156
let wdpC = commands[i + 1] == '00' ? false : (commands[i + 1] * deviceKeepAlive) + 7;
157
let wdpUc = commands[i + 2] == '00' ? false : parseInt(commands[i + 2], 16);
158
let data = { watchDogParams: { wdpC, wdpUc } };
159
Object.assign(resultToPass, { ...resultToPass }, { ...data });
160
}
161
break;
162
case '1f':
163
{
164
command_len = 1;
165
let data = { primaryOperationalMode: commands[i + 1] };
166
Object.assign(resultToPass, { ...resultToPass }, { ...data });
167
}
168
break;
169
case '21':
170
{
171
command_len = 6;
172
let data = {batteryRangesBoundaries:{
173
Boundary1: parseInt(`${commands[i + 1]}${commands[i + 2]}`, 16),
174
Boundary2: parseInt(`${commands[i + 3]}${commands[i + 4]}`, 16),
175
Boundary3: parseInt(`${commands[i + 5]}${commands[i + 6]}`, 16),
176
177
}};
178
Object.assign(resultToPass, { ...resultToPass }, { ...data });
179
}
180
break;
181
case '23':
182
{
183
command_len = 4;
184
let data = {batteryRangesOverVoltage:{
185
Range1: parseInt(commands[i + 2], 16),
186
Range2: parseInt(commands[i + 3], 16),
187
Range3: parseInt(commands[i + 4], 16),
188
}};
189
Object.assign(resultToPass, { ...resultToPass }, { ...data });
190
}
191
break;
192
case '27':
193
{
194
command_len = 1;
195
let data = {OVAC: parseInt(commands[i + 1], 16)};
196
Object.assign(resultToPass, { ...resultToPass }, { ...data });
197
}
198
break;
199
case '28':
200
{
201
command_len = 1;
202
let data = { manualTargetTemperatureUpdate: parseInt(commands[i + 1], 16) };
203
Object.assign(resultToPass, { ...resultToPass }, { ...data });
204
}
205
break;
206
207
}
208
commands.splice(i,command_len);
209
})
210
211
Object.assign(deviceData, { ...deviceData }, { ...resultToPass });
212
213
// get only keepalive from device response
214
let keepaliveData = hexData.slice(-18);
215
let dataToPass = keepaliveData.match(/.{1,2}/g).map(byte => { return parseInt(byte, 16) });
216
217
handleKeepAliveData(dataToPass);
218
}
219
220
return deviceData;
221
}
222
223
}
Copied!

TTN V3 Decoder (JavaScript ES5):

1
function decodeUplink(input) {
2
var bytes = input.bytes;
3
var data = {};
4
var resultToPass = {};
5
6
var rawHex = bytes.map(function(byte, i){
7
return ("0" + byte.toString(16)).substr(-2);
8
});
9
10
toBool = function (value) { return value == '1' };
11
12
function merge_obj(obj1, obj2) {
13
var obj3 = {};
14
for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
15
for (var attrname2 in obj2) { obj3[attrname2] = obj2[attrname2]; }
16
return obj3;
17
}
18
19
function handleKeepalive(bytes, data){
20
tmp = ("0" + bytes[6].toString(16)).substr(-2);
21
motorRange1 = tmp[1];
22
motorRange2 = ("0" + bytes[5].toString(16)).substr(-2);
23
motorRange = parseInt("0x" + motorRange1 + motorRange2, 16);
24
25
motorPos2 = ("0" + bytes[4].toString(16)).substr(-2);
26
motorPos1 = tmp[0];
27
motorPosition = parseInt("0x" + motorPos1 + motorPos2, 16);
28
29
batteryTmp = ("0" + bytes[7].toString(16)).substr(-2)[0];
30
batteryVoltageCalculated = 2 + parseInt("0x" + batteryTmp, 16) * 0.1;
31
32
decbin = function (number) {
33
if (number < 0) {
34
number = 0xFFFFFFFF + number + 1;
35
}
36
return parseInt(number, 10).toString(2);
37
};
38
39
byteBin = decbin(bytes[7].toString(16));
40
openWindow = byteBin.substr(4, 1);
41
childLockBin = decbin(bytes[8].toString(16));
42
childLock = childLockBin.charAt(0);
43
highMotorConsumption = byteBin.substr(-2, 1);
44
lowMotorConsumption = byteBin.substr(-3, 1);
45
brokenSensor = byteBin.substr(-4, 1);
46
47
var sensorTemp = 0;
48
if (Number(bytes[0].toString(16)) == 1) {
49
sensorTemp = (bytes[2] * 165) / 256 - 40;
50
}
51
52
if (Number(bytes[0].toString(16)) == 81) {
53
sensorTemp = (bytes[2] - 28.33333) / 5.66666;
54
}
55
56
57
data.reason = Number(bytes[0].toString(16));
58
data.targetTemperature = Number(bytes[1]);
59
data.sensorTemperature = Number(sensorTemp.toFixed(2));
60
data.relativeHumidity = Number(((bytes[3] * 100) / 256).toFixed(2));
61
data.motorRange = motorRange;
62
data.motorPosition = motorPosition;
63
data.batteryVoltage = Number(batteryVoltageCalculated.toFixed(2));
64
data.openWindow = toBool(openWindow);
65
data.childLock = toBool(childLock);
66
data.highMotorConsumption = toBool(highMotorConsumption);
67
data.lowMotorConsumption = toBool(lowMotorConsumption);
68
data.brokenSensor = toBool(brokenSensor);
69
70
return data;
71
}
72
73
function handleResponse(bytes, data){
74
var commands = bytes.map(function(byte, i){
75
return ("0" + byte.toString(16)).substr(-2);
76
});
77
commands = commands.slice(0,-9);
78
var command_len = 0;
79
80
commands.map(function (command, i) {
81
switch (command) {
82
case '04':
83
{
84
command_len = 2;
85
var hardwareVersion = commands[i + 1];
86
var softwareVersion = commands[i + 2];
87
var dataK = { deviceVersions: { hardware: Number(hardwareVersion), software: Number(softwareVersion) } };
88
resultToPass = merge_obj(resultToPass, dataK);
89
}
90
break;
91
case '12':
92
{
93
command_len = 1;
94
var dataC = { keepAliveTime: parseInt(commands[i + 1], 16) };
95
resultToPass = merge_obj(resultToPass, dataC);
96
}
97
break;
98
case '13':
99
{
100
command_len = 4;
101
var enabled = toBool(parseInt(commands[i + 1], 16));
102
var duration = parseInt(commands[i + 2], 16) * 5;
103
var tmp = ("0" + commands[i + 4].toString(16)).substr(-2);
104
var motorPos2 = ("0" + commands[i + 3].toString(16)).substr(-2);
105
var motorPos1 = tmp[0];
106
var motorPosition = parseInt('0x' + motorPos1 + motorPos2, 16);
107
var delta = Number(tmp[1]);
108
109
var dataD = { openWindowParams: { enabled: enabled, duration: duration, motorPosition: motorPosition, delta: delta } };
110
resultToPass = merge_obj(resultToPass, dataD);
111
}
112
break;
113
case '14':
114
{
115
command_len = 1;
116
var dataB = { childLock: toBool(parseInt(commands[i + 1], 16)) };
117
resultToPass = merge_obj(resultToPass, dataB);
118
}
119
break;
120
case '15':
121
{
122
command_len = 2;
123
var dataA = { temperatureRangeSettings: { min: parseInt(commands[i + 1], 16), max: parseInt(commands[i + 2], 16) } };
124
resultToPass = merge_obj(resultToPass, dataA);
125
}
126
break;
127
case '16':
128
{
129
command_len = 3;
130
var data = { internalAlgoParams: { period: parseInt(commands[i + 1], 16), pFirstLast: parseInt(commands[i + 2], 16), pNext: parseInt(commands[i + 3], 16) } };
131
resultToPass = merge_obj(resultToPass, data);
132
}
133
break;
134
case '17':
135
{
136
command_len = 2;
137
var dataF = { internalAlgoTdiffParams: { warm: parseInt(commands[i + 1], 16), cold: parseInt(commands[i + 2], 16) } };
138
resultToPass = merge_obj(resultToPass, dataF);
139
}
140
break;
141
case '18':
142
{
143
command_len = 1;
144
var dataE = { operationalMode: parseInt(commands[i + 1], 16) };
145
resultToPass = merge_obj(resultToPass, dataE);
146
}
147
break;
148
case '19':
149
{
150
command_len = 1;
151
var commandResponse = parseInt(commands[i + 1], 16);
152
var periodInMinutes = commandResponse * 5 / 60;
153
var dataH = { joinRetryPeriod: periodInMinutes };
154
resultToPass = merge_obj(resultToPass, dataH);
155
}
156
break;
157
case '1b':
158
{
159
command_len = 1;
160
var dataG = { uplinkType: parseInt(commands[i + 1], 16) };
161
resultToPass = merge_obj(resultToPass, dataG);
162
}
163
break;
164
case '1d':
165
{
166
// get default keepalive if it is not available in data
167
command_len = 2;
168
var deviceKeepAlive = 5;
169
var wdpC = commands[i + 1] == '00' ? false : commands[i + 1] * deviceKeepAlive + 7;
170
var wdpUc = commands[i + 2] == '00' ? false : parseInt(commands[i + 2], 16);
171
var dataJ = { watchDogParams: { wdpC: wdpC, wdpUc: wdpUc } };
172
resultToPass = merge_obj(resultToPass, dataJ);
173
}
174
break;
175
case '1f':
176
{
177
command_len = 1;
178
var data = { primaryOperationalMode: commands[i + 1] };
179
resultToPass = merge_obj(resultToPass, data);
180
}
181
break;
182
case '21':
183
{
184
command_len = 6;
185
var data = {batteryRangesBoundaries:{
186
Boundary1: parseInt(commands[i + 1] + commands[i + 2], 16),
187
Boundary2: parseInt(commands[i + 3] + commands[i + 4], 16),
188
Boundary3: parseInt(commands[i + 5] + commands[i + 6], 16),
189
}};
190
resultToPass = merge_obj(resultToPass, data);
191
}
192
break;
193
case '23':
194
{
195
command_len = 4;
196
var data = {batteryRangesOverVoltage:{
197
Range1: parseInt(commands[i + 2], 16),
198
Range2: parseInt(commands[i + 3], 16),
199
Range3: parseInt(commands[i + 4], 16),
200
}};
201
resultToPass = merge_obj(resultToPass, data);
202
}
203
break;
204
case '27':
205
{
206
command_len = 1;
207
var data = {OVAC: parseInt(commands[i + 1], 16)};
208
resultToPass = merge_obj(resultToPass, data);
209
}
210
break;
211
case '28':
212
{
213
command_len = 1;
214
var data = { manualTargetTemperatureUpdate: parseInt(commands[i + 1], 16) };
215
resultToPass = merge_obj(resultToPass, data);
216
217
}
218
break;
219
default:
220
break;
221
}
222
commands.splice(i,command_len);
223
});
224
return resultToPass;
225
}
226
227
if (bytes[0].toString(16) == 1 || bytes[0].toString(16) == 129) {
228
data = merge_obj(data, handleKeepalive(bytes, data));
229
}else{
230
data = merge_obj(data, handleResponse(bytes, data));
231
bytes = bytes.slice(-9);
232
data = merge_obj(data, handleKeepalive(bytes, data));
233
}
234
235
return {
236
data: {
237
decoded: data,
238
rawHex: rawHex,
239
bytes: bytes
240
}
241
};
242
}
Copied!

Tektelic Decoder (JavaScript ES5):

1
arr = [];
2
for (var i = 0; i < bytes.length; ++i)
3
arr.push(bytes[i]);
4
function toHexString(arr) {
5
var s = '';
6
arr.forEach(function(byte) {
7
s += ('0' + (byte & 0xFF).toString(16)).slice(-2);
8
});
9
return s;
10
}
11
12
var hexData = toHexString(arr);
13
var bytes = hexData.match(/.{1,2}/g).map(function (byte){
14
return parseInt(byte,16);
15
}
16
);
17
18
19
var deviceData;
20
function merge_obj(obj1,obj2){
21
var obj3 = {};
22
for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
23
for (attrname in obj2) { obj3[attrname] = obj2[attrname]; }
24
return obj3;
25
}
26
function toBool(value) { return value == '1'}
27
function handleKeepAliveData(bytes){
28
tmp = ("0" + bytes[6].toString(16)).substr(-2);
29
motorRange1 = tmp[1];
30
motorRange2 = ("0" + bytes[5].toString(16)).substr(-2);
31
motorRange = parseInt("0x"+ motorRange1 + motorRange2, 16);
32
33
motorPos2 = ("0" + bytes[4].toString(16)).substr(-2);
34
motorPos1 = tmp[0];
35
motorPosition = parseInt("0x"+ motorPos1 + motorPos2, 16);
36
37
batteryTmp = ("0" + bytes[7].toString(16)).substr(-2)[0];
38
batteryVoltageCalculated = 2 + parseInt("0x"+ batteryTmp , 16) * 0.1;
39
function decbin(number) {
40
if (number < 0) {
41
number = 0xFFFFFFFF + number + 1;
42
}
43
return parseInt(number, 10).toString(2);
44
}
45
46
byteBin = decbin(bytes[7]);
47
openWindow = byteBin.substr(4, 1);
48
childLockBin = decbin(bytes[8]);
49
childLock = childLockBin.charAt(0);
50
highMotorConsumption = byteBin.substr(-2, 1);
51
lowMotorConsumption = byteBin.substr(-3, 1);
52
brokenSensor = byteBin.substr(-4, 1);
53
54
var sensorTemp;
55
56
if(bytes[0] == 1){
57
sensorTemp = (bytes[2] * 165) / 256 - 40;
58
}
59
60
if(bytes[0] == 81){
61
sensorTemp = (bytes[2] - 28.33333) / 5.66666;
62
}
63
64
return {
65
hex: hexData,
66
reason: bytes[0],
67
targetTemperature: bytes[1],
68
sensorTemperature: sensorTemp,
69
relativeHumidity: bytes[3]/ 256 ,
70
motorRange: motorRange,
71
motorPosition: motorPosition,
72
batteryVoltage: batteryVoltageCalculated,
73
openWindow: toBool(openWindow),
74
childLock: toBool(childLock),
75
highMotorConsumption: toBool(highMotorConsumption),
76
lowMotorConsumption: toBool(lowMotorConsumption),
77
brokenSensor: toBool(brokenSensor),
78
port: port,
79
"payload length": bytes.length
80
};
81
}
82
if (bytes[0] == 1 || bytes[0] == 81) {
83
deviceData = merge_obj(deviceData, handleKeepAliveData(bytes));
84
return deviceData;
85
} else {
86
87
if(bytes.length<3)
88
return { "hex": toHexString(arr), "port": port , "payload length": bytes.length};
89
// parse command answers
90
var data = commandsReadingHelper(deviceData, String(hexData).split(",").join(""), 18);
91
deviceData = merge_obj(deviceData,data);
92
93
// get only keepalive from device response
94
var keepaliveData = String(hexData).split(",").join("").slice(-18);
95
var dataToPass = keepaliveData.match(/.{1,2}/g).map(function (byte) { return parseInt(byte, 16) });
96
97
deviceData = merge_obj(deviceData, handleKeepAliveData(dataToPass));
98
return deviceData;
99
}
100
101
function commandsReadingHelper(deviceData, hexData, payloadLength) {
102
var resultToPass = {};
103
var data = hexData.slice(0, -payloadLength);
104
var commands = data.match(/.{1,2}/g);
105
var command_len = 0;
106
107
commands.map(function (command, i) {
108
switch (command) {
109
case '04':
110
{
111
try {
112
command_len = 2;
113
var hardwareVersion = commands[i + 1];
114
var softwareVersion = commands[i + 2];
115
data = { deviceVersions: { hardware: Number(hardwareVersion), software: Number(softwareVersion) } };
116
117
resultToPass = merge_obj(resultToPass,data);
118
119
} catch (e) {
120
// console.log(e)
121
}
122
}
123
break;
124
case '12':
125
{
126
try {
127
command_len = 1;
128
data = { keepAliveTime: parseInt(commands[i + 1], 16) };
129
resultToPass = merge_obj(resultToPass,data);
130
} catch (e) {
131
// console.log(e)
132
}
133
}
134
break;
135
case '13':
136
{
137
try {
138
command_len = 4;
139
var enabled = toBool(parseInt(commands[i + 1], 16));
140
var duration = parseInt(commands[i + 2], 16) * 5;
141
var tmp = ("0" + commands[i + 4].toString(16)).substr(-2);
142
var motorPos2 = ("0" + commands[i + 3].toString(16)).substr(-2);
143
var motorPos1 = tmp[0];
144
var motorPosition = parseInt('0x' + motorPos1 + motorPos2, 16);
145
var delta = Number(tmp[1]);
146
147
data = { openWindowParams: { enabled: enabled, duration: duration, motorPosition: motorPosition, delta: delta } };
148
resultToPass = merge_obj(resultToPass,data);
149
} catch (e) {
150
// console.log(e)
151
}
152
}
153
break;
154
case '14':
155
{
156
try {
157
command_len = 1;
158
data = { childLock: toBool(parseInt(commands[i + 1], 16)) };
159
resultToPass = merge_obj(resultToPass,data);
160
} catch (e) {
161
// console.log(e)
162
}
163
}
164
break;
165
case '15':
166
{
167
try {
168
command_len = 2;
169
data = { temperatureRangeSettings: { min: parseInt(commands[i + 1], 16), max: parseInt(commands[i + 2], 16) } };
170
resultToPass = merge_obj(resultToPass,data);
171
} catch (e) {
172
// console.log(e)
173
}
174
}
175
break;
176
case '16':
177
{
178
try {
179
command_len = 3;
180
data = { internalAlgoParams: { period: parseInt(commands[i + 1], 16), pFirstLast: parseInt(commands[i + 2], 16), pNext: parseInt(commands[i + 3], 16) } };
181
resultToPass = merge_obj(resultToPass,data);
182
} catch (e) {
183
// console.log(e)
184
}
185
}
186
break;
187
case '17':
188
{
189
try {
190
command_len = 2;
191
data = { internalAlgoTdiffParams: { warm: parseInt(commands[i + 1], 16), cold: parseInt(commands[i + 2], 16) } };
192
resultToPass = merge_obj(resultToPass,data);
193
} catch (e) {
194
// console.log(e)
195
}
196
}
197
break;
198
case '18':
199
{
200
try {
201
command_len = 1;
202
data = { operationalMode: commands[i + 1].toString() };
203
resultToPass = merge_obj(resultToPass,data);
204
} catch (e) {
205
// console.log(e)
206
}
207
}
208
break;
209
case '19':
210
{
211
try {
212
command_len = 1;
213
var commandResponse = parseInt(commands[i + 1], 16);
214
var periodInMinutes = commandResponse * 5 / 60;
215
data = { joinRetryPeriod: periodInMinutes };
216
resultToPass = merge_obj(resultToPass,data);
217
} catch (e) {
218
// console.log(e)
219
}
220
}
221
break;
222
case '1b':
223
{
224
try {
225
command_len = 2;
226
data = { uplinkType: commands[i + 1] };
227
resultToPass = merge_obj(resultToPass,data);
228
} catch (e) {
229
// console.log(e)
230
}
231
}
232
break;
233
case '1d':
234
{
235
try {
236
// get default keepalive if it is not available in data
237
command_len = 2;
238
var deviceKeepAlive = deviceData.keepAliveTime ? deviceData.keepAliveTime : 5;
239
var wdpC = commands[i + 1] == '00' ? false : commands[i + 1] * deviceKeepAlive + 7;
240
var wdpUc = commands[i + 2] == '00' ? false : parseInt(commands[i + 2], 16);
241
data = { watchDogParams: { wdpC: wdpC, wdpUc: wdpUc } };
242
resultToPass = merge_obj(resultToPass,data);
243
} catch (e) {
244
// console.log(e)
245
}
246
}
247
break;
248
case '1f':
249
{
250
try {
251
command_len = 1;
252
data = { primaryOperationalMode: commands[i + 1] };
253
resultToPass = merge_obj(resultToPass, data);
254
} catch (e) {
255
// console.log(e)
256
}
257
}
258
break;
259
case '21':
260
{
261
try {
262
command_len = 6;
263
data = {batteryRangesBoundaries:{
264
Boundary1: parseInt(commands[i + 1] + commands[i + 2], 16),
265
Boundary2: parseInt(commands[i + 3] + commands[i + 4], 16),
266
Boundary3: parseInt(commands[i + 5] + commands[i + 6], 16),
267
}};
268
resultToPass = merge_obj(resultToPass, data);
269
} catch (e) {
270
// console.log(e)
271
}
272
}
273
break;
274
case '23':
275
{
276
try {
277
command_len = 4;
278
data = {batteryRangesOverVoltage:{
279
Range1: parseInt(commands[i + 2], 16),
280
Range2: parseInt(commands[i + 3], 16),
281
Range3: parseInt(commands[i + 4], 16),
282
}};
283
resultToPass = merge_obj(resultToPass, data);
284
} catch (e) {
285
// console.log(e)
286
}
287
}
288
break;
289
case '27':
290
{
291
try {
292
command_len = 1;
293
data = {OVAC: parseInt(commands[i + 1], 16)};
294
resultToPass = merge_obj(resultToPass, data);
295
} catch (e) {
296
// console.log(e)
297
}
298
}
299
break;
300
case '28':
301
{
302
try {
303
command_len = 1;
304
data = { manualTargetTemperatureUpdate: parseInt(commands[i + 1], 16) };
305
resultToPass = merge_obj(resultToPass, data);
306
} catch (e) {
307
// console.log(e)
308
}
309
}
310
break;
311
312
default:
313
break;
314
}
315
commands.splice(i,command_len);
316
});
317
318
return resultToPass;
319
};
Copied!

Chirpstack Decoder (JavaScript ES5):

1
function Decode(fPort, bytes, variables) {
2
var data = {};
3
var resultToPass = {};
4
5
var rawHex = bytes.map(function(byte, i){
6
return ("0" + byte.toString(16)).substr(-2);
7
});
8
9
toBool = function (value) { return value == '1' };
10
11
function merge_obj(obj1, obj2) {
12
var obj3 = {};
13
for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
14
for (var attrname2 in obj2) { obj3[attrname2] = obj2[attrname2]; }
15
return obj3;
16
}
17
18
function handleKeepalive(bytes, data){
19
tmp = ("0" + bytes[6].toString(16)).substr(-2);
20
motorRange1 = tmp[1];
21
motorRange2 = ("0" + bytes[5].toString(16)).substr(-2);
22
motorRange = parseInt("0x" + motorRange1 + motorRange2, 16);
23
24
motorPos2 = ("0" + bytes[4].toString(16)).substr(-2);
25
motorPos1 = tmp[0];
26
motorPosition = parseInt("0x" + motorPos1 + motorPos2, 16);
27
28
batteryTmp = ("0" + bytes[7].toString(16)).substr(-2)[0];
29
batteryVoltageCalculated = 2 + parseInt("0x" + batteryTmp, 16) * 0.1;
30
31
decbin = function (number) {
32
if (number < 0) {
33
number = 0xFFFFFFFF + number + 1;
34
}
35
return parseInt(number, 10).toString(2);
36
};
37
38
byteBin = decbin(bytes[7].toString(16));
39
openWindow = byteBin.substr(4, 1);
40
childLockBin = decbin(bytes[8].toString(16));
41
childLock = childLockBin.charAt(0);
42
highMotorConsumption = byteBin.substr(-2, 1);
43
lowMotorConsumption = byteBin.substr(-3, 1);
44
brokenSensor = byteBin.substr(-4, 1);
45
46
var sensorTemp = 0;
47
if (Number(bytes[0].toString(16)) == 1) {
48
sensorTemp = (bytes[2] * 165) / 256 - 40;
49
}
50
51
console.log(bytes[0].toString(16))
52
if (Number(bytes[0].toString(16)) == 81) {
53
sensorTemp = (bytes[2] - 28.33333) / 5.66666;
54
}
55
56
57
data.reason = Number(bytes[0].toString(16));
58
data.targetTemperature = Number(bytes[1]);
59
data.sensorTemperature = Number(sensorTemp.toFixed(2));
60
data.relativeHumidity = Number(((bytes[3] * 100) / 256).toFixed(2));
61
data.motorRange = motorRange;
62
data.motorPosition = motorPosition;
63
data.batteryVoltage = Number(batteryVoltageCalculated.toFixed(2));
64
data.openWindow = toBool(openWindow);
65
data.childLock = toBool(childLock);
66
data.highMotorConsumption = toBool(highMotorConsumption);
67
data.lowMotorConsumption = toBool(lowMotorConsumption);
68
data.brokenSensor = toBool(brokenSensor);
69
70
return data;
71
}
72
73
function handleResponse(bytes, data){
74
var commands = bytes.map(function(byte, i){
75
return ("0" + byte.toString(16)).substr(-2);
76
});
77
commands = commands.slice(0,-9);
78
var command_len = 0;
79
80
commands.map(function (command, i) {
81
switch (command) {
82
case '04':
83
{
84
command_len = 2;
85
var hardwareVersion = commands[i + 1];
86
var softwareVersion = commands[i + 2];
87
var dataK = { deviceVersions: { hardware: Number(hardwareVersion), software: Number(softwareVersion) } };
88
resultToPass = merge_obj(resultToPass, dataK);
89
}
90
break;
91
case '12':
92
{
93
command_len = 1;
94
var dataC = { keepAliveTime: parseInt(commands[i + 1], 16) };
95
resultToPass = merge_obj(resultToPass, dataC);
96
}
97
break;
98
case '13':
99
{
100
command_len = 4;
101
var enabled = toBool(parseInt(commands[i + 1], 16));
102
var duration = parseInt(commands[i + 2], 16) * 5;
103
var tmp = ("0" + commands[i + 4].toString(16)).substr(-2);
104
var motorPos2 = ("0" + commands[i + 3].toString(16)).substr(-2);
105
var motorPos1 = tmp[0];
106
var motorPosition = parseInt('0x' + motorPos1 + motorPos2, 16);
107
var delta = Number(tmp[1]);
108
109
var dataD = { openWindowParams: { enabled: enabled, duration: duration, motorPosition: motorPosition, delta: delta } };
110
resultToPass = merge_obj(resultToPass, dataD);
111
}
112
break;
113
case '14':
114
{
115
command_len = 1;
116
var dataB = { childLock: toBool(parseInt(commands[i + 1], 16)) };
117
resultToPass = merge_obj(resultToPass, dataB);
118
}
119
break;
120
case '15':
121
{
122
command_len = 2;
123
var dataA = { temperatureRangeSettings: { min: parseInt(commands[i + 1], 16), max: parseInt(commands[i + 2], 16) } };
124
resultToPass = merge_obj(resultToPass, dataA);
125
}
126
break;
127
case '16':
128
{
129
command_len = 3;
130
var data = { internalAlgoParams: { period: parseInt(commands[i + 1], 16), pFirstLast: parseInt(commands[i + 2], 16), pNext: parseInt(commands[i + 3], 16) } };
131
resultToPass = merge_obj(resultToPass, data);
132
}
133
break;
134
case '17':
135
{
136
command_len = 2;
137
var dataF = { internalAlgoTdiffParams: { warm: parseInt(commands[i + 1], 16), cold: parseInt(commands[i + 2], 16) } };
138
resultToPass = merge_obj(resultToPass, dataF);
139
}
140
break;
141
case '18':
142
{
143
command_len = 1;
144
var dataE = { operationalMode: parseInt(commands[i + 1], 16) };
145
resultToPass = merge_obj(resultToPass, dataE);
146
}
147
break;
148
case '19':
149
{
150
command_len = 1;
151
var commandResponse = parseInt(commands[i + 1], 16);
152
var periodInMinutes = commandResponse * 5 / 60;
153
var dataH = { joinRetryPeriod: periodInMinutes };
154
resultToPass = merge_obj(resultToPass, dataH);
155
}
156
break;
157
case '1b':
158
{
159
command_len = 1;
160
var dataG = { uplinkType: parseInt(commands[i + 1], 16) };
161
resultToPass = merge_obj(resultToPass, dataG);
162
}
163
break;
164
case '1d':
165
{
166
// get default keepalive if it is not available in data
167
command_len = 2;
168
var deviceKeepAlive = 5;
169
var wdpC = commands[i + 1] == '00' ? false : commands[i + 1] * deviceKeepAlive + 7;
170
var wdpUc = commands[i + 2] == '00' ? false : parseInt(commands[i + 2], 16);
171
var dataJ = { watchDogParams: { wdpC: wdpC, wdpUc: wdpUc } };
172
resultToPass = merge_obj(resultToPass, dataJ);
173
}
174
break;
175
case '1f':
176
{
177
command_len = 1;
178
var data = { primaryOperationalMode: commands[i + 1] };
179
resultToPass = merge_obj(resultToPass, data);
180
}
181
break;
182
case '21':
183
{
184
command_len = 6;
185
var data = {batteryRangesBoundaries:{
186
Boundary1: parseInt(commands[i + 1] + commands[i + 2], 16),
187
Boundary2: parseInt(commands[i + 3] + commands[i + 4], 16),
188
Boundary3: parseInt(commands[i + 5] + commands[i + 6], 16),
189
}};
190
resultToPass = merge_obj(resultToPass, data);
191
}
192
break;
193
case '23':
194
{
195
command_len = 4;
196
var data = {batteryRangesOverVoltage:{
197
Range1: parseInt(commands[i + 2], 16),
198
Range2: parseInt(commands[i + 3], 16),
199
Range3: parseInt(commands[i + 4], 16),
200
}};
201
resultToPass = merge_obj(resultToPass, data);
202
}
203
break;
204
case '27':
205
{
206
command_len = 1;
207
var data = {OVAC: parseInt(commands[i + 1], 16)};
208
resultToPass = merge_obj(resultToPass, data);
209
}
210
break;
211
case '28':
212
{
213
command_len = 1;
214
var data = { manualTargetTemperatureUpdate: parseInt(commands[i + 1], 16) };
215
resultToPass = merge_obj(resultToPass, data);
216
217
}
218
break;
219
default:
220
break;
221
}
222
commands.splice(i,command_len);
223
});
224
return resultToPass;
225
}
226
227
if (bytes[0].toString(16) == 1 || bytes[0].toString(16) == 129) {
228
data = merge_obj(data, handleKeepalive(bytes, data));
229
}else{
230
data = merge_obj(data, handleResponse(bytes, data));
231
bytes = bytes.slice(-9);
232
data = merge_obj(data, handleKeepalive(bytes, data));
233
}
234
235
return data
236
}
Copied!