TTN 解码器 - Node-red
TTN decoder - Node-red
在 TTN 中,它们不再支持大型解码器。
我知道TTN中的解码器需要什么,它在我的DECODER函数中,但不知道如何在函数节点中执行它。
如果您使用 inject Payload [1,2,3] RAW,它会将 msg.payload.payload.uplink_message.frm_payload 的原始有效负载注入解码器。
DECODER 需要解码原始 payload 并在 msg.payload.uplink_message.decoded_payload
中输出
如果你使用 inject Payload [1,2,3] Decoded in the flow 你会看到最终结果需要是什么样子以及解码后的 msg.payload.uplink_message.decoded_payload
我还在学习JavaScript。
函数节点中的代码
function Decoder(bytes, port) {
// nbits: number of bits used to encode
// lo: min value that can be encoded
// hi: max value that can be encoded
// nresv: number of reserved values, not used for the encoding
function step_size(lo, hi, nbits, nresv) {
return 1.0 / ((((1<<nbits) - 1) - nresv) / (hi - lo));
}
function mt_value_decode(value, lo, hi, nbits, nresv) {
return (value - nresv / 2) * step_size(lo, hi, nbits, nresv) + lo;
}
// Gets the zero-based unsigned numeric value of the given bit(s)
function bits(value, lsb, msb) {
var len = msb - lsb + 1;
var mask = (1<<len) - 1;
return value>>lsb & mask;
}
// Gets the boolean value of the given bit
function bit(value, bit) {
return (value & (1<<bit)) > 0;
}
// Gets a hexadecimal representation ensuring a leading zero for each byte
function hex(bytes, separator) {
return bytes.map(function (b) {
return ("0" + b.toString(16)).substr(-2);
}).join(separator || "");
}
// Decodes 4 bytes into a signed integer, MSB
function int32(bytes) {
// JavaScript bitwise operators always work with 32 bits signed integers
return bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3];
}
// Decodes 4 bytes into an unsigned integer, MSB
function uint32(bytes) {
// Or, same result:
// return bytes[0] * 0x1000000 + bytes[1] * 0x10000 + bytes[2] * 0x100 + bytes[3];
// return bytes[0] * 0x1000000 + (bytes[1]<<16 | bytes[2]<<8 | bytes[3]);
// return bytes[0] * (1<<24) + (bytes[1]<<16 | bytes[2]<<8 | bytes[3]);
// return int32(bytes)>>>0;
// JavaScript bitwise operators always work with 32 bits signed integers;
// force conversion to unsigned 32 bits value using zero-fill right shift
return (bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3])>>>0;
}
// Decodes 1 to 4 MAC addresses and their RSSI
function mac_rssi(bytes) {
var items = [];
for (var offset = 0; offset < bytes.length; offset += 7) {
items.push({
mac_address: hex(bytes.slice(offset, offset + 6), ":"),
// Sign-extend to 32 bits to support negative values; dBm
rssi: bytes[offset + 6]<<24>>24,
});
}
return items;
}
function message(code, descriptions) {
return {
code: code,
description: code < 0 || code >= descriptions.length ? "UNKNOWN" : descriptions[code]
};
}
var decoded = {};
var i;
var type = bytes[0];
// All message types, except for Frame pending messages, share the same header
if (type !== 0x00) {
// Note: the Data Storage Integration stores nested objects as text
decoded.status = {
mode: message(bits(bytes[1], 5, 7), ["Standby", "Motion tracking", "Permanent tracking",
"Motion start/end tracking", "Activity tracking", "OFF"]),
sos: bit(bytes[1], 4),
tracking: bit(bytes[1], 3),
moving: bit(bytes[1], 2),
periodic: bit(bytes[1], 1),
on_demand: bit(bytes[1], 0)
};
// Trackers with a rechargeable battery:the percentage reflects the actual value
decoded.batteryPersentage = bytes[2];
decoded.temperature = Math.round(100 * mt_value_decode(bytes[3], -44, 85, 8, 0)) / 100;
decoded.ack = bits(bytes[4], 4, 7);
decoded.data = bits(bytes[4], 0, 3);
decoded.lastResetCause = "lastResetCause: " + bytes[5];
}
switch (type) {
case 0x00:
decoded.type = "FRAME PENDING";
decoded.token = bytes[1];
break;
case 0x03:
decoded.type = "POSITION";
switch (decoded.data) {
case 0:
decoded.position_type = "GPS fix";
decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
// Signed 32 bits integers; LSB is always zero
decoded.latitude = (bytes[6]<<24 | bytes[7]<<16 | bytes[8]<<8) / 1e7;
decoded.longitude = (bytes[9]<<24 | bytes[10]<<16 | bytes[11]<<8) / 1e7;
// Estimated Horizontal Position Error
decoded.ehpe = mt_value_decode(bytes[12], 0, 1000, 8, 0);
break;
case 1:
decoded.position_type = "GPS timeout";
decoded.timeout_cause = message(bytes[5], ["User timeout cause"]);
for (i = 0; i < 4; i++) {
// Carrier over noise (dBm) for the i-th satellite seen
decoded["cn" + i] = mt_value_decode(bytes[6 + i], 0, 2040, 8, 0);
}
break;
case 2:
// Documented as obsolete
decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
break;
case 3:
decoded.position_type = "WIFI timeout";
for (i = 0; i < 6; i++) {
decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
}
break;
case 4:
decoded.position_type = "WIFI failure";
for (i = 0; i < 6; i++) {
// Most of time a WIFI timeout occurs due to a low battery condition
decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
}
decoded.error = message(bytes[11], ["WIFI connection failure", "Scan failure",
"Antenna unavailable", "WIFI not supported on this device"]);
break;
case 5:
case 6:
decoded.position_type = "LP-GPS data";
// Encrypted; not described in the documentation
decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
break;
case 7:
decoded.position_type = "BLE beacon scan";
decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
// Remaining data: up to 4 beacons
decoded.beacons = mac_rssi(bytes.slice(6));
break;
case 8:
decoded.position_type = "BLE beacon failure";
decoded.error = message(bytes[5], ["BLE is not responding", "Internal error", "Shared antenna not available",
"Scan already on going", "No beacon detected", "Hardware incompatibility"]);
break;
// Test with: 0358D895090EC46E1FF44B9EB76466B3B87454AD500959CA1ED4AD525E67DA14A1AC
// or 032CD1890900C46E1FF44B9EC5C83A355A3898A6
case 9:
decoded.position_type = "WIFI BSSIDs";
decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
// Remaining data: up to 4 WiFi BSSIDs
decoded.stations = mac_rssi(bytes.slice(6));
break;
default:
decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
}
break;
case 0x04:
decoded.type = "ENERGY STATUS";
break;
case 0x05:
decoded.type = "HEARTBEAT";
decoded.mcuFirmware = bytes[6] + "." + bytes[7] + "." + bytes[8];
decoded.bleFirmware = bytes[9] + "." + bytes[10] + "." + bytes[11];
break;
case 0x07:
// Activity status message and configuration message share the same identifier
var tag = bytes[5];
switch (tag) {
case 1:
decoded.type = "ACTIVITY STATUS";
decoded.activity_counter = uint32(bytes.slice(6, 10));
break;
case 2:
decoded.type = "CONFIGURATION";
for (i = 0; i < 5; i++) {
var offset = 6 + 5 * i;
decoded["param" + i] = {
type: bytes[offset],
value: uint32(bytes.slice(offset + 1, offset + 5))
};
}
break;
case 3:
decoded.type = " SHOCK DETECTION";
decoded.axisX = "fwVersion: " + bytes[7] + "." + bytes[8];
decoded.axisY = "fwVersion: " + bytes[9] + "." + bytes[10];
decoded.axisX = "fwVersion: " + bytes[11] + "." + bytes[12];
//Byte 7-8 - X Axis
//Byte 9-10 - Y Axis
//Byte 11-12 - Z Axis
break;
default:
decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data + "/" + tag]);
}
break;
case 0x09:
decoded.type = "SHUTDOWN";
break;
case 0xFF:
decoded.type = "DEBUG";
break;
default:
decoded.error = message(0, ["UNSUPPORTED MESSAGE TYPE " + type]);
}
// Just some redundant debug info
decoded.debug = {
payload: hex(bytes),
length: bytes.length,
port: port,
server_time: new Date().toISOString()
};
return decoded;
}
问题还不是很清楚,但如果你想在函数节点中使用该代码,那么我建议如下:
将该代码放入功能节点的“On Start”选项卡中,但将第一行更改为以下内容:
context.Decoder = function(bytes, port) {
// nbits: number of bits used to encode
然后在“消息”选项卡中使用如下功能:
var decoded = contex.Decoder(msg.payload.payload.uplink_message.frm_payload, port)
msg.payload.uplink_message.decoded_payload = decoded
return msg
你没有说 port
的来源所以我会留给你来计算它应该有什么价值。
感谢您的帮助我需要补充:
bytes = new Buffer(data,'Base64');
var decoded = context.Decoder(bytes, msg.payload.uplink_message.f_port);
msg.payload.uplink_message.decoded_payload = decoded;
return msg;```
在 TTN 中,它们不再支持大型解码器。
我知道TTN中的解码器需要什么,它在我的DECODER函数中,但不知道如何在函数节点中执行它。
如果您使用 inject Payload [1,2,3] RAW,它会将 msg.payload.payload.uplink_message.frm_payload 的原始有效负载注入解码器。
DECODER 需要解码原始 payload 并在 msg.payload.uplink_message.decoded_payload
中输出如果你使用 inject Payload [1,2,3] Decoded in the flow 你会看到最终结果需要是什么样子以及解码后的 msg.payload.uplink_message.decoded_payload
我还在学习JavaScript。
函数节点中的代码
function Decoder(bytes, port) {
// nbits: number of bits used to encode
// lo: min value that can be encoded
// hi: max value that can be encoded
// nresv: number of reserved values, not used for the encoding
function step_size(lo, hi, nbits, nresv) {
return 1.0 / ((((1<<nbits) - 1) - nresv) / (hi - lo));
}
function mt_value_decode(value, lo, hi, nbits, nresv) {
return (value - nresv / 2) * step_size(lo, hi, nbits, nresv) + lo;
}
// Gets the zero-based unsigned numeric value of the given bit(s)
function bits(value, lsb, msb) {
var len = msb - lsb + 1;
var mask = (1<<len) - 1;
return value>>lsb & mask;
}
// Gets the boolean value of the given bit
function bit(value, bit) {
return (value & (1<<bit)) > 0;
}
// Gets a hexadecimal representation ensuring a leading zero for each byte
function hex(bytes, separator) {
return bytes.map(function (b) {
return ("0" + b.toString(16)).substr(-2);
}).join(separator || "");
}
// Decodes 4 bytes into a signed integer, MSB
function int32(bytes) {
// JavaScript bitwise operators always work with 32 bits signed integers
return bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3];
}
// Decodes 4 bytes into an unsigned integer, MSB
function uint32(bytes) {
// Or, same result:
// return bytes[0] * 0x1000000 + bytes[1] * 0x10000 + bytes[2] * 0x100 + bytes[3];
// return bytes[0] * 0x1000000 + (bytes[1]<<16 | bytes[2]<<8 | bytes[3]);
// return bytes[0] * (1<<24) + (bytes[1]<<16 | bytes[2]<<8 | bytes[3]);
// return int32(bytes)>>>0;
// JavaScript bitwise operators always work with 32 bits signed integers;
// force conversion to unsigned 32 bits value using zero-fill right shift
return (bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3])>>>0;
}
// Decodes 1 to 4 MAC addresses and their RSSI
function mac_rssi(bytes) {
var items = [];
for (var offset = 0; offset < bytes.length; offset += 7) {
items.push({
mac_address: hex(bytes.slice(offset, offset + 6), ":"),
// Sign-extend to 32 bits to support negative values; dBm
rssi: bytes[offset + 6]<<24>>24,
});
}
return items;
}
function message(code, descriptions) {
return {
code: code,
description: code < 0 || code >= descriptions.length ? "UNKNOWN" : descriptions[code]
};
}
var decoded = {};
var i;
var type = bytes[0];
// All message types, except for Frame pending messages, share the same header
if (type !== 0x00) {
// Note: the Data Storage Integration stores nested objects as text
decoded.status = {
mode: message(bits(bytes[1], 5, 7), ["Standby", "Motion tracking", "Permanent tracking",
"Motion start/end tracking", "Activity tracking", "OFF"]),
sos: bit(bytes[1], 4),
tracking: bit(bytes[1], 3),
moving: bit(bytes[1], 2),
periodic: bit(bytes[1], 1),
on_demand: bit(bytes[1], 0)
};
// Trackers with a rechargeable battery:the percentage reflects the actual value
decoded.batteryPersentage = bytes[2];
decoded.temperature = Math.round(100 * mt_value_decode(bytes[3], -44, 85, 8, 0)) / 100;
decoded.ack = bits(bytes[4], 4, 7);
decoded.data = bits(bytes[4], 0, 3);
decoded.lastResetCause = "lastResetCause: " + bytes[5];
}
switch (type) {
case 0x00:
decoded.type = "FRAME PENDING";
decoded.token = bytes[1];
break;
case 0x03:
decoded.type = "POSITION";
switch (decoded.data) {
case 0:
decoded.position_type = "GPS fix";
decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
// Signed 32 bits integers; LSB is always zero
decoded.latitude = (bytes[6]<<24 | bytes[7]<<16 | bytes[8]<<8) / 1e7;
decoded.longitude = (bytes[9]<<24 | bytes[10]<<16 | bytes[11]<<8) / 1e7;
// Estimated Horizontal Position Error
decoded.ehpe = mt_value_decode(bytes[12], 0, 1000, 8, 0);
break;
case 1:
decoded.position_type = "GPS timeout";
decoded.timeout_cause = message(bytes[5], ["User timeout cause"]);
for (i = 0; i < 4; i++) {
// Carrier over noise (dBm) for the i-th satellite seen
decoded["cn" + i] = mt_value_decode(bytes[6 + i], 0, 2040, 8, 0);
}
break;
case 2:
// Documented as obsolete
decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
break;
case 3:
decoded.position_type = "WIFI timeout";
for (i = 0; i < 6; i++) {
decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
}
break;
case 4:
decoded.position_type = "WIFI failure";
for (i = 0; i < 6; i++) {
// Most of time a WIFI timeout occurs due to a low battery condition
decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
}
decoded.error = message(bytes[11], ["WIFI connection failure", "Scan failure",
"Antenna unavailable", "WIFI not supported on this device"]);
break;
case 5:
case 6:
decoded.position_type = "LP-GPS data";
// Encrypted; not described in the documentation
decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
break;
case 7:
decoded.position_type = "BLE beacon scan";
decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
// Remaining data: up to 4 beacons
decoded.beacons = mac_rssi(bytes.slice(6));
break;
case 8:
decoded.position_type = "BLE beacon failure";
decoded.error = message(bytes[5], ["BLE is not responding", "Internal error", "Shared antenna not available",
"Scan already on going", "No beacon detected", "Hardware incompatibility"]);
break;
// Test with: 0358D895090EC46E1FF44B9EB76466B3B87454AD500959CA1ED4AD525E67DA14A1AC
// or 032CD1890900C46E1FF44B9EC5C83A355A3898A6
case 9:
decoded.position_type = "WIFI BSSIDs";
decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
// Remaining data: up to 4 WiFi BSSIDs
decoded.stations = mac_rssi(bytes.slice(6));
break;
default:
decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
}
break;
case 0x04:
decoded.type = "ENERGY STATUS";
break;
case 0x05:
decoded.type = "HEARTBEAT";
decoded.mcuFirmware = bytes[6] + "." + bytes[7] + "." + bytes[8];
decoded.bleFirmware = bytes[9] + "." + bytes[10] + "." + bytes[11];
break;
case 0x07:
// Activity status message and configuration message share the same identifier
var tag = bytes[5];
switch (tag) {
case 1:
decoded.type = "ACTIVITY STATUS";
decoded.activity_counter = uint32(bytes.slice(6, 10));
break;
case 2:
decoded.type = "CONFIGURATION";
for (i = 0; i < 5; i++) {
var offset = 6 + 5 * i;
decoded["param" + i] = {
type: bytes[offset],
value: uint32(bytes.slice(offset + 1, offset + 5))
};
}
break;
case 3:
decoded.type = " SHOCK DETECTION";
decoded.axisX = "fwVersion: " + bytes[7] + "." + bytes[8];
decoded.axisY = "fwVersion: " + bytes[9] + "." + bytes[10];
decoded.axisX = "fwVersion: " + bytes[11] + "." + bytes[12];
//Byte 7-8 - X Axis
//Byte 9-10 - Y Axis
//Byte 11-12 - Z Axis
break;
default:
decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data + "/" + tag]);
}
break;
case 0x09:
decoded.type = "SHUTDOWN";
break;
case 0xFF:
decoded.type = "DEBUG";
break;
default:
decoded.error = message(0, ["UNSUPPORTED MESSAGE TYPE " + type]);
}
// Just some redundant debug info
decoded.debug = {
payload: hex(bytes),
length: bytes.length,
port: port,
server_time: new Date().toISOString()
};
return decoded;
}
问题还不是很清楚,但如果你想在函数节点中使用该代码,那么我建议如下:
将该代码放入功能节点的“On Start”选项卡中,但将第一行更改为以下内容:
context.Decoder = function(bytes, port) {
// nbits: number of bits used to encode
然后在“消息”选项卡中使用如下功能:
var decoded = contex.Decoder(msg.payload.payload.uplink_message.frm_payload, port)
msg.payload.uplink_message.decoded_payload = decoded
return msg
你没有说 port
的来源所以我会留给你来计算它应该有什么价值。
感谢您的帮助我需要补充:
bytes = new Buffer(data,'Base64');
var decoded = context.Decoder(bytes, msg.payload.uplink_message.f_port);
msg.payload.uplink_message.decoded_payload = decoded;
return msg;```