function to_signed(val, bits) {
if ((val & (1 << (bits - 1))) > 0) {
var mask = Math.pow(2, bits) - 1;
val = (~val & mask) + 1;
val = val * -1;
}
return val;
}
function dec_u8(bytes, idx) {
return bytes[idx];
}
function dec_u16(bytes, idx) {
return (bytes[idx + 1] << 8) | bytes[idx];
}
function dec_u32(bytes, idx) {
return (bytes[idx + 3] << 24) | (bytes[idx + 2] << 16) | (bytes[idx + 1] << 8) | bytes[idx];
}
function dec_i8(bytes, idx) {
return to_signed(bytes[idx], 8);
}
function dec_i16(bytes, idx) {
return to_signed((bytes[idx + 1] << 8) | bytes[idx], 16);
}
function dec_i32(bytes, idx) {
return to_signed(dec_u32(bytes, idx), 32);
}
function dec_f16(bytes, idx) {
var h = (bytes[idx + 1] << 8) | bytes[idx];
var s = (h & 0x8000) >> 15;
var e = (h & 0x7c00) >> 10;
var f = h & 0x03ff;
if (e === 0) {
return (s ? -1 : 1) * 0.00006103515625 * (f / 1024.0);
}
if (e === 0x1f) {
return f ? NaN : (s ? -1 : 1) * Infinity;
}
return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + f / 1024.0);
}
function dec_f32(bytes, idx) {
var buf = new Uint8Array([bytes[idx], bytes[idx + 1], bytes[idx + 2], bytes[idx + 3]]);
return new DataView(buf.buffer).getFloat32(0, true);
}
function dec_f64(bytes, idx) {
var buf = new Uint8Array([
bytes[idx],
bytes[idx + 1],
bytes[idx + 2],
bytes[idx + 3],
bytes[idx + 4],
bytes[idx + 5],
bytes[idx + 6],
bytes[idx + 7]
]);
return new DataView(buf.buffer).getFloat64(0, true);
}
var TYPE_U8 = { size: 1, decode: function(b, o) { return dec_u8(b, o); } };
var TYPE_U16 = { size: 2, decode: function(b, o) { return dec_u16(b, o); } };
var TYPE_U32 = { size: 4, decode: function(b, o) { return dec_u32(b, o); } };
var TYPE_I8 = { size: 1, decode: function(b, o) { return dec_i8(b, o); } };
var TYPE_I16 = { size: 2, decode: function(b, o) { return dec_i16(b, o); } };
var TYPE_I32 = { size: 4, decode: function(b, o) { return dec_i32(b, o); } };
var TYPE_F16 = { size: 2, decode: function(b, o) { return dec_f16(b, o); } };
var TYPE_F32 = { size: 4, decode: function(b, o) { return dec_f32(b, o); } };
var TYPE_F64 = { size: 8, decode: function(b, o) { return dec_f64(b, o); } };
function num_to_fixed(x, digits) {
return Number(x.toFixed(digits));
}
function merge(target, source) {
for (var k in source) {
if (source.hasOwnProperty(k)) {
target[k] = source[k];
}
}
return target;
}
function applyFixedPrecision(res, channels) {
for (var i = 0; i < channels.length; i++) {
var name = channels[i][0];
var fixed = channels[i][5];
if (res[name] !== undefined) {
res[name] = num_to_fixed(res[name], fixed);
}
}
return res;
}
function sumIfDefined(channels, keys, sumKey) {
var sum = 0;
var hasAny = false;
for (var i = 0; i < keys.length; i++) {
if (channels[keys[i]] !== undefined) {
sum += channels[keys[i]];
hasAny = true;
}
}
if (hasAny) {
channels[sumKey] = sum;
}
}
function formatCounterElapsed(seconds) {
var days = Math.floor(seconds / (24 * 3600));
var hours = Math.floor((seconds % (24 * 3600)) / 3600);
var minutes = Math.floor((seconds % 3600) / 60);
var secs = seconds % 60;
return "[D:" + days + "|H:" + hours + "|M:" + minutes + "|S:" + secs + "]";
}
function parseBitFlags(bytes, offset, count, bitNames) {
var flags = {};
for (var i = 0; i < count; i++) {
for (var n = 0; n < 8; n++) {
var idx = i * 8 + n;
if (bitNames[idx]) {
flags[bitNames[idx]] = (bytes[offset + i] & (1 << n)) !== 0;
}
}
}
return flags;
}
function parseChannels(bytes, offset, flags, channels) {
var result = {};
var idx = offset;
for (var i = 0; i < channels.length; i++) {
var ch = channels[i];
if (flags[ch[1]]) {
result[ch[0]] = ch[3] * ch[2].decode(bytes, idx) + ch[4];
idx += ch[2].size;
}
}
return result;
}
function makeBits_p10(prefix) {
return [prefix + "_ac_en", prefix + "_dc_en", prefix + "_freq_en", prefix + "_scaled_mode", prefix + "_voltage_mode", null, null, null];
}
function makeChannels_p10(prefix) {
return [
[prefix + "_ac_raw_A", prefix + "_ac_en", TYPE_F16, 1.0, 0, 6],
[prefix + "_dc_raw_A", prefix + "_dc_en", TYPE_F16, 1.0, 0, 6],
[prefix + "_freq", prefix + "_freq_en", TYPE_U16, 0.01, 0, 2],
[prefix + "_coeff", prefix + "_scaled_mode", TYPE_F16, 1.0, 0, 3]
];
}
var BITSET_P10 = [].concat(
["usb_powered", "ch_vsys_en", "ch_temp_en", null, null, null, null, null],
["ct_plus_mode", "rogowski_mode", null, null, null, null, null, null],
makeBits_p10("in1"),
makeBits_p10("in2"),
makeBits_p10("in3"),
makeBits_p10("in4")
);
var CHAN_SYSTEM_P10 = [
["vsys_V", "ch_vsys_en", TYPE_U8, 0.0075, 1.8, 3],
["temp_C", "ch_temp_en", TYPE_U8, 0.4, -22.0, 1]
];
var CHAN_PLUS_P10 = [
["in1_pow_factor", "ct_plus_mode", TYPE_F16, 1.0, 0, 4],
["in2_pow_factor", "ct_plus_mode", TYPE_F16, 1.0, 0, 4],
["in3_pow_factor", "ct_plus_mode", TYPE_F16, 1.0, 0, 4]
];
var CHANNELS_P10 = [].concat(
CHAN_SYSTEM_P10,
makeChannels_p10("in1"),
makeChannels_p10("in2"),
makeChannels_p10("in3"),
makeChannels_p10("in4"),
CHAN_PLUS_P10
);
var CHAN_POST_P10 = [
["in1_ac_A", null, null, 0, 0, 3],
["in2_ac_A", null, null, 0, 0, 3],
["in3_ac_A", null, null, 0, 0, 3],
["in4_ac_A", null, null, 0, 0, 3],
["in1_voltage_VAC", null, null, 0, 0, 2],
["in2_voltage_VAC", null, null, 0, 0, 2],
["in3_voltage_VAC", null, null, 0, 0, 2],
["in4_voltage_VAC", null, null, 0, 0, 2],
["sum_in1234_ac_A", null, null, 0, 0, 3],
["sum_in1234_dc_A", null, null, 0, 0, 3]
];
var CHAN_POST2_P10 = [
["in1_pow_app_VA", null, null, 0, 0, 2],
["in2_pow_app_VA", null, null, 0, 0, 2],
["in3_pow_app_VA", null, null, 0, 0, 2],
["in1_pow_act_W", null, null, 0, 0, 2],
["in2_pow_act_W", null, null, 0, 0, 2],
["in3_pow_act_W", null, null, 0, 0, 2],
["in1_pow_react_VAR", null, null, 0, 0, 2],
["in2_pow_react_VAR", null, null, 0, 0, 2],
["in3_pow_react_VAR", null, null, 0, 0, 2],
["sum_in123_pow_app_VA", null, null, 0, 0, 2],
["sum_in123_pow_act_W", null, null, 0, 0, 2],
["sum_in123_pow_react_VAR", null, null, 0, 0, 2]
];
var CHAN_ALL_P10 = [].concat(CHANNELS_P10, CHAN_POST_P10, CHAN_POST2_P10);
function decodePort10(bytes) {
if (bytes.length < 10) {
throw new Error("Port 10 payload too short: expected at least 10 bytes, got " + bytes.length);
}
var flags = parseBitFlags(bytes, 0, 6, BITSET_P10);
var channels = parseChannels(bytes, 10, flags, CHANNELS_P10);
var ct_plus_mode = flags.ct_plus_mode;
var rogowski_mode = flags.rogowski_mode;
var max_ac_scaled_A = rogowski_mode ? 10000 : 1000;
for (var ix = 1; ix <= 4; ix++) {
if (!flags["in" + ix + "_scaled_mode"]) continue;
var voltage_mode = flags["in" + ix + "_voltage_mode"];
var coeff = (channels["in" + ix + "_coeff"] || 1.0) * (voltage_mode ? 1000.0 : 1.0);
var ac_raw_A = channels["in" + ix + "_ac_raw_A"];
if (ac_raw_A !== undefined) {
var ac_scaled = ac_raw_A * coeff;
if (!voltage_mode && Math.abs(ac_scaled) > max_ac_scaled_A) {
throw new Error(
"Port 10 current limit exceeded: max " + max_ac_scaled_A + "A in " + (rogowski_mode ? "rogowski" : "normal") + " mode"
);
}
channels[voltage_mode ? "in" + ix + "_voltage_VAC" : "in" + ix + "_ac_A"] = ac_scaled;
}
var dc_raw_A = channels["in" + ix + "_dc_raw_A"];
if (dc_raw_A !== undefined) {
channels[voltage_mode ? "in" + ix + "_voltage_VDC" : "in" + ix + "_dc_A"] = dc_raw_A * coeff;
}
}
sumIfDefined(channels, ["in1_ac_A", "in2_ac_A", "in3_ac_A", "in4_ac_A"], "sum_in1234_ac_A");
sumIfDefined(channels, ["in1_dc_A", "in2_dc_A", "in3_dc_A", "in4_dc_A"], "sum_in1234_dc_A");
if (ct_plus_mode) {
var in4_voltage_VAC = channels.in4_voltage_VAC;
for (var j = 1; j <= 3; j++) {
channels["in" + j + "_pow_app_VA"] = in4_voltage_VAC * channels["in" + j + "_ac_A"];
}
for (var k = 1; k <= 3; k++) {
var app = channels["in" + k + "_pow_app_VA"];
var factor = Math.max(-1, Math.min(1, channels["in" + k + "_pow_factor"]));
var act = app * factor;
channels["in" + k + "_pow_act_W"] = act;
channels["in" + k + "_pow_react_VAR"] = Math.sqrt(app * app - act * act);
}
channels["sum_in123_pow_app_VA"] = channels.in1_pow_app_VA + channels.in2_pow_app_VA + channels.in3_pow_app_VA;
channels["sum_in123_pow_act_W"] = channels.in1_pow_act_W + channels.in2_pow_act_W + channels.in3_pow_act_W;
channels["sum_in123_pow_react_VAR"] = channels.in1_pow_react_VAR + channels.in2_pow_react_VAR + channels.in3_pow_react_VAR;
}
applyFixedPrecision(channels, CHAN_ALL_P10);
return merge({ flags: flags }, channels);
}
function makeChannels_p30(prefix) {
return [
[prefix + "_kWh", prefix + "_power_en", TYPE_I32, 0.01, 0, 2],
[prefix + "_cosphi", prefix + "_cosphi_en", TYPE_F16, 1.0, 0, 3]
];
}
var BITSET_P30 = [].concat(
["in1_power_en", "in1_cosphi_en", null, null, null, null, null, null],
["in2_power_en", "in2_cosphi_en", null, null, null, null, null, null],
["in3_power_en", "in3_cosphi_en", null, null, null, null, null, null]
);
var CHANNELS_P30 = [].concat(
[["in_ctr_count", "dummy", TYPE_U32, 1.0, 0, 0]],
makeChannels_p30("in1"),
makeChannels_p30("in2"),
makeChannels_p30("in3")
);
var CHAN_POST_P30 = [["sum_in123_kWh", null, null, 0, 0, 2]];
var CHAN_ALL_P30 = [].concat(CHANNELS_P30, CHAN_POST_P30);
function decodePort30(bytes) {
if (bytes.length < 7) {
throw new Error("Port 30 payload too short: expected at least 7 bytes, got " + bytes.length);
}
var flags = parseBitFlags(bytes, 0, 3, BITSET_P30);
flags.dummy = true;
var channels = parseChannels(bytes, 3, flags, CHANNELS_P30);
sumIfDefined(channels, ["in1_kWh", "in2_kWh", "in3_kWh", "in4_kWh"], "sum_in123_kWh");
channels.in_ctr_time_elapse = formatCounterElapsed(channels.in_ctr_count);
applyFixedPrecision(channels, CHAN_ALL_P30);
return merge({ flags: flags }, channels);
}
function makeChannels_p31(prefix) {
return [[prefix + "_Ah", prefix + "_amp_h_en", TYPE_I32, 0.01, 0, 2]];
}
var BITSET_P31 = [].concat(
["in1_amp_h_en", null, null, null, null, null, null, null],
["in2_amp_h_en", null, null, null, null, null, null, null],
["in3_amp_h_en", null, null, null, null, null, null, null],
["in4_amp_h_en", null, null, null, null, null, null, null]
);
var CHANNELS_P31 = [].concat(
[["in_ctr_count", "dummy", TYPE_U32, 1.0, 0, 0]],
makeChannels_p31("in1"),
makeChannels_p31("in2"),
makeChannels_p31("in3"),
makeChannels_p31("in4")
);
var CHAN_POST_P31 = [["sum_in1234_Ah", null, null, 0, 0, 2]];
var CHAN_ALL_P31 = [].concat(CHANNELS_P31, CHAN_POST_P31);
function decodePort31(bytes) {
if (bytes.length < 8) {
throw new Error("Port 31 payload too short: expected at least 8 bytes, got " + bytes.length);
}
var flags = parseBitFlags(bytes, 0, 4, BITSET_P31);
flags.dummy = true;
var channels = parseChannels(bytes, 4, flags, CHANNELS_P31);
sumIfDefined(channels, ["in1_Ah", "in2_Ah", "in3_Ah", "in4_Ah"], "sum_in1234_Ah");
channels.in_ctr_time_elapse = formatCounterElapsed(channels.in_ctr_count);
applyFixedPrecision(channels, CHAN_ALL_P31);
return merge({ flags: flags }, channels);
}
function decodePort42(bytes) {
if (bytes.length !== 1) {
throw new Error("Port 42 payload size: expected 1 byte, got " + bytes.length);
}
return {
button_press_count: bytes[0]
};
}
function decodePort99(bytes) {
if (bytes.length < 7) {
throw new Error("Port 99 payload too short: expected at least 7 bytes, got " + bytes.length);
}
var result = {
reboot_counter: dec_u32(bytes, 0),
app_version_major: bytes[4],
app_version_minor: bytes[5],
app_version_patch: bytes[6],
app_version: bytes[4] + "." + bytes[5] + "." + bytes[6]
};
if (bytes.length >= 10) {
result.app_version_tweak = bytes[7];
result.app_version = bytes[4] + "." + bytes[5] + "." + bytes[6] + "." + bytes[7];
var hwVersionByte = bytes[8];
result.hw_version_patch = hwVersionByte & 0x07;
result.hw_version_minor = (hwVersionByte >> 3) & 0x07;
result.hw_version_major = (hwVersionByte >> 6) & 0x03;
result.hw_version = result.hw_version_major + "." + result.hw_version_minor + "." + result.hw_version_patch;
result.hw_variant = bytes[9];
var hex = bytes[9].toString(16).toUpperCase();
result.hw_variant_hex = "0x" + (hex.length < 2 ? "0" + hex : hex);
}
return result;
}
function decodePortX(bytes, port) {
switch (port) {
case 10:
return decodePort10(bytes);
case 30:
return decodePort30(bytes);
case 31:
return decodePort31(bytes);
case 42:
return decodePort42(bytes);
case 99:
return decodePort99(bytes);
}
throw new Error("No decoder for port: " + port);
}
function decodeUplink(input) {
return {
data: decodePortX(input.bytes, input.fPort),
warnings: [],
errors: []
};
}
function Decode(fPort, bytes, variables) {
return decodePortX(bytes, fPort);
}
if (typeof module !== "undefined" && module.exports) {
module.exports = {
decodeUplink: decodeUplink,
Decode: Decode,
decodePortX: decodePortX,
decodePort10: decodePort10,
decodePort30: decodePort30,
decodePort31: decodePort31,
decodePort42: decodePort42,
decodePort99: decodePort99,
dec_u8: dec_u8,
dec_u16: dec_u16,
dec_u32: dec_u32,
dec_i8: dec_i8,
dec_i16: dec_i16,
dec_i32: dec_i32,
dec_f16: dec_f16,
to_signed: to_signed,
parseBitFlags: parseBitFlags,
parseChannels: parseChannels,
sumIfDefined: sumIfDefined,
formatCounterElapsed: formatCounterElapsed,
num_to_fixed: num_to_fixed
};
}