Skip to main content

Niotix

Version: 2.0.0 (2025-06-23)

defmodule Parser do
# >>> Include platform-related modules here
# <<<
# >>> TEMPORARY MODULES
# import Logger
# <<<

def parse(payload, meta) do
%{lora: %{fport: port}} = meta
cond do
(port == 10) ->
parse10(payload)
(port == 30) ->
parse30(payload)
(port == 31) ->
parse31(payload)
(port == 99) ->
parse99(payload)
(true) ->
parse_fault(payload, port)
end
end

def parse10(<<__r1::5, ch_temp_en::1, ch_vsys_en::1, usb_pwr::1, # META
__r2::7, ct_plus_mod::1, # GLOBAL
__r3::3, in1_v_mod::1, in1_scale_mod::1, in1_freq_en::1, in1_dc_en::1, in1_ac_en::1, # AIN
__r4::3, in2_v_mod::1, in2_scale_mod::1, in2_freq_en::1, in2_dc_en::1, in2_ac_en::1, # AIN
__r5::3, in3_v_mod::1, in3_scale_mod::1, in3_freq_en::1, in3_dc_en::1, in3_ac_en::1, # AIN
__r6::3, in4_v_mod::1, in4_scale_mod::1, in4_freq_en::1, in4_dc_en::1, in4_ac_en::1, # AIN
__r7::32, bytes::binary>>) do
### FLAG MAP
flags_map = %{
"ch_temp_en" => ch_temp_en, "ch_vsys_en" => ch_vsys_en, "usb_pwr" => usb_pwr,
"in1_v_mod" => in1_v_mod, "in1_scale_mod" => in1_scale_mod, "in1_freq_en" => in1_freq_en, "in1_dc_en" => in1_dc_en, "in1_ac_en" => in1_ac_en,
"in2_v_mod" =>in2_v_mod, "in2_scale_mod" => in2_scale_mod, "in2_freq_en" => in2_freq_en, "in2_dc_en" => in2_dc_en, "in2_ac_en" => in2_ac_en,
"in3_v_mod" => in3_v_mod, "in3_scale_mod" => in3_scale_mod, "in3_freq_en" => in3_freq_en, "in3_dc_en" => in3_dc_en, "in3_ac_en" => in3_ac_en,
"in4_v_mod" => in4_v_mod, "in4_scale_mod" => in4_scale_mod, "in4_freq_en" => in4_freq_en, "in4_dc_en" => in4_dc_en, "in4_ac_en" => in4_ac_en
}


### DATA MAP
rmap = %{}
rmap = Map.put(rmap, "index", 0)

rmap = extract_system_vars(rmap, ch_vsys_en, ch_temp_en, bytes)

rmap = Enum.reduce [1, 2, 3, 4], rmap, fn ch, rmap_in ->
extract_results_in(flags_map, rmap_in, ch, bytes)
end

rmap = sum4IfDefined(flags_map, rmap, "_ac_A", "sum_in1234_ac_A")
rmap = sum4IfDefined(flags_map, rmap, "_dc_A", "sum_in1234_dc_A")

rmap = extract_ct_plus(rmap, ct_plus_mod, bytes)

######################################################
rmap = Map.delete(rmap, "index")
rmap
end

def parse30(<<__r1::6, in1_cosphi_en::1, in1_power_en::1, # IN1
__r2::6, in2_cosphi_en::1, in2_power_en::1, # IN2
__r3::6, in3_cosphi_en::1, in3_power_en::1, # IN3
in_ctr_count::little-unsigned-32, bytes::binary>>) do
### FLAG MAP
flags_map = %{
"in1_power_en" => in1_power_en, "in1_cosphi_en" => in1_cosphi_en,
"in2_power_en" => in2_power_en, "in2_cosphi_en" => in2_cosphi_en,
"in3_power_en" => in3_power_en, "in3_cosphi_en" => in3_cosphi_en}

### DATA MAP
rmap = %{}
rmap = Map.put(rmap, "in_ctr_count", in_ctr_count)

rmap = Map.put(rmap, "index", 0) # Added in_ctr_count (idx + 4)
rmap = Enum.reduce [1, 2, 3], rmap, fn ch, rmap_in ->
parse30_extract_data(flags_map, rmap_in, ch, bytes)
end

rmap = sum3IfDefined(flags_map, rmap, '_kWh', "sum_in123_kWh")

######################################################
rmap = Map.delete(rmap, "index")
rmap
end

def parse31(<<__r1::7, in1_amp_h_acc_en::1, # IN1
__r2::7, in2_amp_h_acc_en::1, # IN2
__r3::7, in3_amp_h_acc_en::1, # IN3
__r4::7, in4_amp_h_acc_en::1, # IN4
in_ctr_count::little-unsigned-32, bytes::binary>>) do

### FLAG MAP
flags_map = %{
"in1_amp_h_acc_en" => in1_amp_h_acc_en, "in2_amp_h_acc_en" => in2_amp_h_acc_en, "in3_amp_h_acc_en" => in3_amp_h_acc_en, "in4_amp_h_acc_en" => in4_amp_h_acc_en
}

### DATA MAP
rmap = %{}
rmap = Map.put(rmap, "in_ctr_count", in_ctr_count)

rmap = Map.put(rmap, "index", 0) # Added in_ctr_count (idx + 4)
rmap = Enum.reduce [1, 2, 3, 4], rmap, fn ch, rmap_in ->
parse31_extract_data(flags_map, rmap_in, ch, bytes)
end

rmap = sum4IfDefined(flags_map, rmap, '_Ah', "sum_in1234_Ah")
######################################################
rmap = Map.delete(rmap, "index")
rmap
end

def parse_fault(payload, port) do
%{fault: "faulty port detected", port: port}
end

def extract_system_vars(rmap, ch_vsys_en, ch_temp_en, data) do
rmap = if ch_vsys_en do
index = rmap["index"]

vsys_raw = get_value(data, index, 2)
vsys = (vsys_raw * (3.7125-1.8) / 255.0) + 1.8

rmap = Map.put(rmap, "vsys_V", vsys)
rmap = Map.delete(rmap, "index")
rmap = Map.put(rmap, "index", index+1)
else
rmap
end
rmap = if ch_temp_en do
index = rmap["index"]

temp_C_raw = get_value(data, index, 2)
temp_C = (temp_C_raw) * (80 - (-22)) / 255 - 22

rmap = Map.put(rmap, "temp_C", temp_C)
rmap = Map.delete(rmap, "index")
rmap = Map.put(rmap, "index", index+1)
else
rmap
end
rmap
end

def sum3IfDefined(flags_map, rmap, suffix, sumKey) do

key1 = "in1#{suffix}"
key2 = "in2#{suffix}"
key3 = "in3#{suffix}"

rmap = if ((rmap[key1]!=nil) || (rmap[key2]!=nil) || (rmap[key3]!=nil)) do
newSum = 0
## Key 1
newSum = if (rmap[key1] != nil) do
newSum = newSum + rmap[key1]
else
newSum
end
## Key 2
newSum = if (rmap[key2] != nil) do
newSum = newSum + rmap[key2]
else
newSum
end
## Key 3
newSum = if (rmap[key3] != nil) do
newSum = newSum + rmap[key3]
else
newSum
end
rmap = Map.put(rmap, sumKey, newSum)
else
rmap
end
rmap
end

def sum4IfDefined(flags_map, rmap, suffix, sumKey) do

key1 = "in1#{suffix}"
key2 = "in2#{suffix}"
key3 = "in3#{suffix}"
key4 = "in4#{suffix}"

rmap = if ((rmap[key1]!=nil) || (rmap[key2]!=nil) || (rmap[key3]!=nil) || (rmap[key4]!=nil)) do
newSum = 0
## Key 1
newSum = if (rmap[key1] != nil) do
newSum = newSum + rmap[key1]
else
newSum
end
## Key 2
newSum = if (rmap[key2] != nil) do
newSum = newSum + rmap[key2]
else
newSum
end
## Key 3
newSum = if (rmap[key3] != nil) do
newSum = newSum + rmap[key3]
else
newSum
end
## Key 4
newSum = if (rmap[key4] != nil) do
newSum = newSum + rmap[key4]
else
newSum
end
rmap = Map.put(rmap, sumKey, newSum)
else
rmap
end
rmap
end
def parse31_extract_data(flags_map, rmap, ch, data) do
################################################################
##### AMPH EN
rmap = if (flags_map["in#{ch}_amp_h_acc_en"]==1) do
index = rmap["index"]
in_Ah = get_value(data, index, 4) / 100.0
rmap = Map.put(rmap, "in#{ch}_Ah", in_Ah)
rmap = Map.delete(rmap, "index")
rmap = Map.put(rmap, "index", index+4)
else
rmap
end
rmap
end

def parse30_extract_data(flags_map, rmap, ch, data) do
################################################################
##### POW EN
rmap = if (flags_map["in#{ch}_power_en"]==1) do
index = rmap["index"]
in_kWh = get_value(data, index, 4) / 100.0
rmap = Map.put(rmap, "in#{ch}_kWh", in_kWh)
rmap = Map.delete(rmap, "index")
rmap = Map.put(rmap, "index", index+4)
else
rmap
end

##### COSPHI EN
rmap = if (flags_map["in#{ch}_cosphi_en"]==1) do
index = rmap["index"]
in_cosphi = get_value(data, index, 0)
rmap = Map.put(rmap, "in#{ch}_cosphi", in_cosphi)
rmap = Map.delete(rmap, "index")
rmap = Map.put(rmap, "index", index+2)
else
rmap
end
rmap
end

# def extract_results_in(flags_map, rmap, ch, data) do

def extract_ct_plus(rmap, ct_plus_mod, data) do
error = 0

rmap = if (ct_plus_mod == 1) do
#### TOTAL
rmap = Enum.reduce [1, 2, 3], rmap, fn ch, rmap_pwr_coeff ->
in_pow_factor = get_value(data, rmap["index"]+(ch-1)*2, 0)
rmap = Map.put(rmap_pwr_coeff, "in#{ch}_pow_factor", in_pow_factor)
end

#### Calculate Power VA ####
rmap = if (rmap["in4_voltage_VAC"] != nil) do
rmap = if (rmap["in1_ac_A"] != nil) do
rmap = Map.put(rmap, "in1_pow_app_VA", rmap["in1_ac_A"] * rmap["in4_voltage_VAC"])
rmap = Map.put(rmap, "in1_pow_act_W", rmap["in1_pow_app_VA"] * rmap["in1_pow_factor"])
rmap = Map.put(rmap, "in1_pow_react_VAR", :math.sqrt((rmap["in1_pow_app_VA"] * rmap["in1_pow_app_VA"]) - (rmap["in1_pow_act_W"] * rmap["in1_pow_act_W"])))
else
rmap = Map.put(rmap, "in1_pow_app_VA", error)
rmap = Map.put(rmap, "in1_pow_act_W", error)
rmap = Map.put(rmap, "in1_pow_react_VAR", error)
end

rmap = if (rmap["in2_ac_A"] != nil) do
rmap = Map.put(rmap, "in2_pow_app_VA", rmap["in2_ac_A"] * rmap["in4_voltage_VAC"])
rmap = Map.put(rmap, "in2_pow_act_W", rmap["in2_pow_app_VA"] * rmap["in2_pow_factor"])
rmap = Map.put(rmap, "in2_pow_react_VAR", :math.sqrt((rmap["in2_pow_app_VA"] * rmap["in2_pow_app_VA"]) - (rmap["in2_pow_act_W"] * rmap["in2_pow_act_W"])))
else
rmap = Map.put(rmap, "in2_pow_app_VA", error)
rmap = Map.put(rmap, "in2_pow_act_W", error)
rmap = Map.put(rmap, "in2_pow_react_VAR", error)
end

rmap = if (rmap["in3_ac_A"] != nil) do
rmap = Map.put(rmap, "in3_pow_app_VA", rmap["in3_ac_A"] * rmap["in4_voltage_VAC"])
rmap = Map.put(rmap, "in3_pow_act_W", rmap["in3_pow_app_VA"] * rmap["in3_pow_factor"])
rmap = Map.put(rmap, "in3_pow_react_VAR", :math.sqrt((rmap["in3_pow_app_VA"] * rmap["in3_pow_app_VA"]) - (rmap["in3_pow_act_W"] * rmap["in3_pow_act_W"])))
else
rmap = Map.put(rmap, "in3_pow_app_VA", error)
rmap = Map.put(rmap, "in3_pow_act_W", error)
rmap = Map.put(rmap, "in3_pow_react_VAR", error)
end

### SUMS
rmap = if ((rmap["in1_ac_A"] != nil) && (rmap["in2_ac_A"] != nil) && (rmap["in3_ac_A"] != nil)) do
rmap = Map.put(rmap, "sum_in123_pow_app_VA", rmap["in1_pow_app_VA"]+rmap["in2_pow_app_VA"]+rmap["in3_pow_app_VA"])
rmap = Map.put(rmap, "sum_in123_pow_act_W", rmap["in1_pow_act_W"]+rmap["in2_pow_act_W"]+rmap["in3_pow_act_W"])
rmap = Map.put(rmap, "sum_in123_pow_react_VAR",rmap["in1_pow_react_VAR"]+rmap["in2_pow_react_VAR"]+rmap["in3_pow_react_VAR"])
else
rmap = Map.put(rmap, "sum_in123_pow_app_VA", error)
rmap = Map.put(rmap, "sum_in123_pow_act_W", error)
rmap = Map.put(rmap, "sum_in123_pow_react_VAR", error)
end

else
rmap = Map.put(rmap, "in1_pow_app_VA", error)
rmap = Map.put(rmap, "in2_pow_app_VA", error)
rmap = Map.put(rmap, "in3_pow_app_VA", error)

#### Calculate Power W ####
rmap = Map.put(rmap, "in1_pow_act_W", error)
rmap = Map.put(rmap, "in2_pow_act_W", error)
rmap = Map.put(rmap, "in3_pow_act_W", error)

#### Calculate VAR power
rmap = Map.put(rmap, "in1_pow_react_VAR", error)
rmap = Map.put(rmap, "in2_pow_react_VAR", error)
rmap = Map.put(rmap, "in3_pow_react_VAR", error)

### SUMS
rmap = Map.put(rmap, "sum_in123_pow_app_VA", error)
rmap = Map.put(rmap, "sum_in123_pow_act_W", error)
rmap = Map.put(rmap, "sum_in123_pow_react_VAR", error)
end
else
rmap
end
end

def extract_results_in(flags_map, rmap, ch, data) do
################################################################
##### AC EN
rmap = if (flags_map["in#{ch}_ac_en"]==1) do
index = rmap["index"]
in_ac_raw_A = get_value(data, index, 0)
rmap = Map.put(rmap, "in#{ch}_ac_raw_A", in_ac_raw_A)
rmap = Map.delete(rmap, "index")
rmap = Map.put(rmap, "index", index+2)
else
rmap
end
##### DC EN
rmap = if (flags_map["in#{ch}_dc_en"]==1) do
index = rmap["index"]
in_dc_raw_A = get_value(data, index, 0)
rmap = Map.put(rmap, "in#{ch}_dc_raw_A", in_dc_raw_A)
rmap = Map.delete(rmap, "index")
rmap = Map.put(rmap, "index", index+2)
else
rmap
end
##### FREQ EN
rmap = if (flags_map["in#{ch}_freq_en"]==1) do
index = rmap["index"]
in_freq = get_value(data, index, 1)/100
rmap = Map.put(rmap, "in#{ch}_freq", in_freq)
rmap = Map.delete(rmap, "index")
rmap = Map.put(rmap, "index", index+2)
else
rmap
end
##### COEFF EN
rmap = if (flags_map["in#{ch}_scale_mod"]==1) do
index = rmap["index"]

in_coeff = get_value(data, index, 0)
rmap = Map.put(rmap, "in#{ch}_coeff", in_coeff)
rmap = Map.delete(rmap, "index")
rmap = Map.put(rmap, "index", index+2)

## Voltage mode
in_v_mod = flags_map["in#{ch}_v_mod"]
in_coeff = if (in_coeff==0) do 1 else in_coeff end
in_coeff = if (in_v_mod==1) do in_coeff * 1000 else in_coeff end

## AC
rmap = if (flags_map["in#{ch}_ac_en"] == 1) do
in_ac_raw_A = rmap["in#{ch}_ac_raw_A"]
key_add = if (in_v_mod==1) do
"in#{ch}_voltage_VAC"
else
"in#{ch}_ac_A"
end
Map.put(rmap, key_add, in_ac_raw_A * in_coeff)
end

## DC
rmap = if (flags_map["in#{ch}_dc_en"] == 1) do
in_dc_raw_A = rmap["in#{ch}_dc_raw_A"]
key_add = if (in_v_mod==1) do
"in#{ch}_voltage_VDC"
else
"in#{ch}_dc_A"
end
Map.put(rmap, key_add, in_dc_raw_A * in_coeff)
else
rmap
end
else
rmap
end
rmap
end

def get_value(binary_arr, index, type) do
cond do
(type == 0) ->
<<front::binary-size(index), bin_value::binary-size(2), rest::binary>> = binary_arr
get_fp16(bin_value)
(type == 1) ->
<<front::binary-size(index), bin_value::binary-size(2), rest::binary>> = binary_arr
<<return_val::little-unsigned-16>> = bin_value
return_val
(type == 2) ->
<<front::binary-size(index), bin_value::binary-size(1), rest::binary>> = binary_arr
<<return_val::little-unsigned-8>> = bin_value
return_val
(type == 3) ->
<<front::binary-size(index), bin_value::binary-size(4), rest::binary>> = binary_arr
<<return_val::little-unsigned-32>> = bin_value
return_val
(type == 4) ->
<<front::binary-size(index), bin_value::binary-size(4), rest::binary>> = binary_arr
<<return_val::little-signed-32>> = bin_value
return_val
end
end

def get_fp16(<<f2::8, s::1, e::5, f1::2>>) do
<<f::10>> = <<f1::2, f2::8>>
cond do
(e == 0) ->
cond do
(s==1) ->
-1 * 0.00006103515625 * (f / 1024.0)
(s==0) ->
1 * 0.00006103515625 * (f / 1024.0)
end
(e == 31) ->
cond do
(f > 0) ->
nil
(f == 0) ->
cond do
(s == 1) ->
:negative_infinity
(s == 0) ->
:positive_infinity
end
end
(true) ->
cond do
(s == 1) ->
s = -1 * (:math.pow(2.0, (e - 15))) * (1 + f / 1024.0) #(Float.pow(2.0, (e - 15))) * (1 + f / 1024.0)
(s == 0) ->
s = 1 * (:math.pow(2.0, (e - 15))) * (1 + f / 1024.0) #(Float.pow(2.0, (e - 15))) * (1 + f / 1024.0)
end
end
end

def parse99(<<bytes::binary>>) do
if (byte_size(bytes) < 4) do
nil
else
%{reboot_counter: get_value(bytes, 0, 3)}
end
end
end