How to get meaningful error messages from functions?

I have got several functions, which rely on aavailabel json outputs from fronius inverters.

now already the second time, after a firmware upgrade, some fields in the json output changed. :frowning:

the big question: how do I get meaningful output of the functions?

[error] [function:Gen24 + merge] TypeError: Cannot read properties of undefined (reading 'channels')

does not help - the function below has 24 'channels' fields

help is much appreciated. :slight_smile:

var e_total_string1 = parseInt(msg.payload.Body.Data["393216"].channels.PV_ENERGYACTIVE_ACTIVE_SUM_01_U64) || 0;
var e_total_string2 = parseInt(msg.payload.Body.Data["393216"].channels.PV_ENERGYACTIVE_ACTIVE_SUM_02_U64) || 0;
if (!isNaN(parseInt(msg.payload.Body.Data["16252931"].channels.SMARTMETER_ENERGYACTIVE_ABSOLUT_PLUS_F64))){
    flow.set('OpenDTU.total.YieldTotal.v', parseInt(msg.payload.Body.Data["16252931"].channels.SMARTMETER_ENERGYACTIVE_ABSOLUT_PLUS_F64)/1000);
    flow.set('OpenDTU.total.YieldTotal.d', 6);
    flow.set('OpenDTU.total.Power.v', parseInt(msg.payload.Body.Data["16252931"].channels.SMARTMETER_POWERACTIVE_MEAN_SUM_F64));
    flow.set('OpenDTU.total.Power.d', 2);
}
var e_total_opendtu = ((msg.opendtu == -1) ? (parseInt(msg.payload.Body.Data["16252931"].channels.SMARTMETER_ENERGYACTIVE_ABSOLUT_PLUS_F64) || 0) : msg.opendtu);
var e_total = parseInt((e_total_string1 + e_total_string2) / 3600) + msg.symo12_string1 + msg.symo12_string2 + e_total_opendtu + msg.symo5_string1;
var bat_activecharge = parseInt(msg.payload.Body.Data["393216"].channels.BAT_ENERGYACTIVE_ACTIVECHARGE_SUM_01_U64) || 0;
var bat_activedischarge = parseInt(msg.payload.Body.Data["393216"].channels.BAT_ENERGYACTIVE_ACTIVEDISCHARGE_SUM_01_U64) || 0;
var e_importtotal = parseInt(msg.payload.Body.Data["16252928"].channels.SMARTMETER_ENERGYACTIVE_ABSOLUT_PLUS_F64) || 0;
var e_exporttotal = parseInt(msg.payload.Body.Data["16252928"].channels.SMARTMETER_ENERGYACTIVE_ABSOLUT_MINUS_F64) || 0;
var e_consumed = e_total + e_importtotal - e_exporttotal + parseInt((bat_activedischarge - bat_activecharge) / 3600);
global.set('Fronius_Live.E_Total_Gen24_string1', parseFloat(e_total_string1 / 3600));
global.set('Fronius_Live.E_Total_Gen24_string2', parseFloat(e_total_string2 / 3600));
global.set('Fronius_Live.E_Total_Gen24_battchg', parseFloat(bat_activecharge / 3600));
global.set('Fronius_Live.E_Total_Gen24_battdischg', parseFloat(bat_activedischarge / 3600));
var battserial = ''
var battdata = null
if (!isNaN(parseInt(msg.payload.Body.Data["16580608"].channels.COMPONENTS_MODE_ENABLE_U16)) && parseInt(msg.payload.Body.Data["16580608"].channels.COMPONENTS_MODE_ENABLE_U16) == 1) battserial = '16580608';
if (!isNaN(parseInt(msg.payload.Body.Data["16580609"].channels.COMPONENTS_MODE_ENABLE_U16)) && parseInt(msg.payload.Body.Data["16580609"].channels.COMPONENTS_MODE_ENABLE_U16) == 1) battserial = '16580609';
if (battserial != '') battdata = msg.payload.Body.Data[battserial];
return [{ topic: msg.topic, timestamp: msg.timestamp,
    e_importtotal: e_importtotal,
    e_exporttotal: e_exporttotal,
    e_consumed: e_consumed,
    bat_activecharge: bat_activecharge,
    bat_activedischarge: bat_activedischarge,
    e_total_string1: e_total_string1,
    e_total_string2: e_total_string2,
    e_total_string3: msg.symo12_string1,
    e_total_string4: msg.symo12_string2,
    e_total_opendtu: e_total_opendtu,
    e_total_garage: msg.symo5_string1,
    e_total: e_total,
    e_total_gen24_ac: parseInt(global.get('Fronius_Live.E_Total_Gen24_AC')), 
    //bat_SOC_min: msg.payload.Body.Data["16318464"].channels.BAT_PERCENT_POWERRESTRICTION_SOC_MIN_F64 || 0,
    bat_serial: battdata.attributes.serial || 0,
    bat_sw_version: battdata.attributes.sw_version || 0,
    bat_rawbyddata: JSON.parse(battdata.attributes.nameplate) || 0,
    bat_SOC_min: JSON.parse(battdata.attributes.nameplate).min_soc || 5,
    bat_capacity_wh: JSON.parse(battdata.attributes.nameplate).capacity_wh || 0,
    bat_SOC: battdata.channels.BAT_VALUE_STATE_OF_CHARGE_RELATIVE_U16 || 0,
    bat_SOH: battdata.channels.BAT_VALUE_STATE_OF_HEALTH_RELATIVE_U16 || 0,
    bat_temp_cell: battdata.channels.BAT_TEMPERATURE_CELL_F64 || 0,
    bat_temp_cell_max: battdata.channels.BAT_TEMPERATURE_CELL_MAX_F64 || 0,
    bat_temp_cell_min: battdata.channels.BAT_TEMPERATURE_CELL_MIN_F64 || 0,
    bat_livetimechg: battdata.channels.BAT_ENERGYACTIVE_LIFETIME_CHARGED_F64 || 0,
    bat_livetimedischg: battdata.channels.BAT_ENERGYACTIVE_LIFETIME_DISCHARGED_F64 || 0,
    bat_voltage: battdata.channels.BAT_VOLTAGE_DC_INTERNAL_F64 || 0,
    bat_temp_ambiente: battdata.channels.DEVICE_TEMPERATURE_AMBIENTEMEAN_F32 || 0,
}]

Is every message expected to have all of these properties?

payload.Body.Data["16252928"]
payload.Body.Data["16252931"]
payload.Body.Data["16580608"]
payload.Body.Data["16580609"]
payload.Body.Data["393216"]

I suspect a message arrives missing one or more.
You could hunt it down by a switch node or nodes before the function, testing if payload has those keys.

Use guards to check it is "something" before attempting to access sub properties...

if(!msg.payload?.Body?.Data?.["393216"]?.channels) {
  throw new Error('Body Data 393216 channels is not valid')
}
// Body Data 393216 channels is something, let's get it's values...
//

Another tool to use when trying to figure out where a function node is going wrong is node.warn("message"). This sends the supplied message to the debug tab. You can add these calls to your code and then find out how far you are getting into the function before things go wrong.

Using guards is the correct answer, but it is also worth knowing about node.warn() for debugging in general.

I.E. add lines like this:

// block of code here
node.warn("first block of code completed");
// next block of code here
node.warn("2nd block of code completed");

Then by looking to see which of your messages you see before the error you can pin down where the error is happening. Then when you fix the problem comment out all the node.warn() calls.

I also like node.status(). It prints the supplied message below the node in the flow. This is great for keeping track of the most recent data sent into the node.

With both of these you can mix static text with variables:
node.status("inLength: " + inLength + " aveSize: " + aveSize);