OpenHAB crashing every few days

did you install openhab using openhabian, or apt, or manually?

What caused this is a memory leak / keeps growing, and your java maximum heap memory limit is probably set too low.

The memory leak might be caused by the camera thing (ipcamera binding?)

Installed as Docker container

I’ve disabled the one camera channel that was throwing errors and nothing has crashed so far.

Can you post config details about this problematic channel?

Thing ipcamera:reolink:lane "Camera - Lane"
    [
    mjpegOptions= "-q:v 5 -r 2 -vf scale=640:-2 -update 1",
    ipAddress= "10.0.6.2",
    updateImageWhen= "0",
    gifPreroll= "0",
    onvifPort= "8000",
    ffmpegLocation= "/usr/bin/ffmpeg",
    ipWhitelist= "DISABLE",
    mp4OutOptions= "-c:v copy -c:a copy",
    pollTime= "1000",
    useToken= "false",
    password= "xxxxxx",
    port= "80",
    nvrChannel= "7",
    snapshotOptions= "-an -vsync vfr -q:v 2 -update 1",
    onvifMediaProfile= "1",
    gifOutOptions= "-r 2 -filter_complex
        scale=-2:360:flags=lanczos,setpts=0.5*PTS,split[o1][o2];[o1]palettegen[p];[o2]fifo[o3];[o3][p]paletteuse",
    hlsOutOptions= "-strict -2 -f lavfi -i aevalsrc=0 -acodec aac -vcodec copy
        -hls_flags delete_segments -hls_time 2 -hls_list_size 4",
    username= "admin"
    ]

It is nothing special. Pretty much all defaults (Reolink ipcamera). Just IP address and channel number are different between cameras.

Here is one that works, just a copy paste

Thing ipcamera:reolink:northcarpark "Camera - North Carpark"
    [
    mjpegOptions= "-q:v 5 -r 2 -vf scale=640:-2 -update 1",
    ipAddress= "10.0.6.2",
    updateImageWhen= "0",
    gifPreroll= "0",
    onvifPort= "8000",
    ffmpegLocation= "/usr/bin/ffmpeg",
    ipWhitelist= "DISABLE",
    mp4OutOptions= "-c:v copy -c:a copy",
    pollTime= "1000",
    useToken= "false",
    password= "xxxxxx",
    port= "80",
    nvrChannel= "6",
    snapshotOptions= "-an -vsync vfr -q:v 2 -update 1",
    onvifMediaProfile= "1",
    gifOutOptions= "-r 2 -filter_complex
        scale=-2:360:flags=lanczos,setpts=0.5*PTS,split[o1][o2];[o1]palettegen[p];[o2]fifo[o3];[o3][p]paletteuse",
    hlsOutOptions= "-strict -2 -f lavfi -i aevalsrc=0 -acodec aac -vcodec copy
        -hls_flags delete_segments -hls_time 2 -hls_list_size 4",
    username= "admin"
    ]

In your docker, add this environment variable (or if you already have it, add the argument to it)

    environment:
      EXTRA_JAVA_OPTS: "-Xmx2048m"

The dog_cat error is from a reolink camera, but it was fixed. Adding Reolink API to the IpCamera binding, beta testers needed - Add-ons / Bindings - openHAB Community Maybe you have something in the cache for that one camera. Maybe a comparison will help

Could it be from bouncing around between milestone, snapshot, and latest while trying to solve my other problem?

I will try this, but what does it do?

   -Xmx<size>        set maximum Java heap size

If you hadn’t set it, it would probably be at 1GB

If this was for me, I’m sorry, but do not know. I just recognized the cat_dog error from when I was testing the Reolink IP camera binding back last May and thought it might help somehow. Maybe you can spot a difference on the Device UI code tab between the working and non-working cameras.

Edit: Another thought might be to delete the non-working camera and then Scan to pick it up again (hopefully without the error)

And this makes it 2GB? Why not make it 3GB? OpenHAB and InfluxDB will be the only apps ever run on this Pi

Thanks.

I wasn’t able to pick up my cameras from scanning. I had to create a thing and put the IP and NVR channel info in manually

you could try 3GB too and see which setting works.

I don’t know if I’d go to 3Gbs, 2 should work. I’d want to leave some for the host running docker. Also, if you have a memory leak, enlarging the max heap size will not cure the memory leak, it will just take longer before it crashes.

There are downsides to making your heap too large, as when it comes time to clean up the heap or resize it, everything running in java must halt until the cleanup is done. The larger the heap, the longer it takes to do the cleanup.

I recommend you check your system by using this binding. It will give you advice if something needs changing via WARN messages in the logs. It will also allow you to graph the CLEANED HEAP size and will also report if a possible leak is detected before your system has an issue.

Thanks for this.
I have installed it and will keep an eye on it. It tells me that my RAM is 91% full and ‘you can improve that by decreasing your Java -Xmx size as your heap appears to be larger than needed.’ in the log messages right now, and Cleaned Heap Percent is 43%.

I installed the Doctor binding, and it does look as though the memory is always increasing.
Is it possible to view logs from a previous session? When I restart OpenHAB, my log file starts fresh so I can’t see the last messages before the crash.

Not sure where to start looking for the culprit.

Someone suggested removing a shared cache variable I was using for a long string, but doing that made no difference.

I don’t have much going on in this system yet.
The busiest rules and transformation scripts are for my solar pv systems and heat pumps (x2 of each) polling Modbus every 5ish seconds, then calculating and updating a few items and state descriptions.

I tried disabling the cameras for a few days, but didn’t seem to impact the upwards trend of memory usage.

bindings:
ChatGPT, deCONZ, Exec, ipcamera, Konnected, Modbus, OpenWeatherMap, UniFi, Shelly Beta, The Doctor.

databases:
influx, MapDB, RRD4j

Currently running OH 4.2.0.M2

Try without “Shelly Beta” or at least replace “Shelly Beta” with the official Shelly binding packaged with 4.2M2.

I think I have finally found the problem, but no idea how to fix it. It’s taken a long time to track it down as it takes a long time to test each thing because it takes so long to see if the heap is increasing, staying flat, or decreasing. And I don’t have time to do it every day.

The problem appears to be a rule I run to update energy meters (kWh) for my SolarEdge solar PV systems. When this rule is enabled, heap increases until crash.

I am connecting to two SolarEdge 6KW inverters (Two houses side by side. Two independent systems) via Modbus and the SunSpec extension. I only get Lifetime Import, Export, and Production, so I need to calculate Lifetime Consumption, Self Consumption, and Self Consumption Coverage. As well as Day, Week, Month, and Year statistics for all 6 readings (Import, Export, Production, Consumption, Self Consumption, and Self Consumption Coverage).

It is triggered by any one of the 3 lifetime readings from either inverter (6 triggers total) so can run quite frequently. I admit I haven’t tested reducing the poll time significantly to see if it improves, as I suspect it will just take longer to see the ‘Cleaned Heap Percent’ increase.

I have a similar rule that updates live Power readings (kW) that triggers at a similar if not identical frequency but does not cause the ‘Cleaned Heap Percent’ to increase. I will include all the rules in my solaredge.js rule file just for maximum info or if someone else using SolarEdge wants to use my error code rule etc.

Everything seems like fairly basic coding. I’m not an professional. It’s really just a couple of functions, a few additions and subtraction of numbers, and a few calls to database (the database bit is my best guess at the moment).

Anyone have any ideas?

console.loggerName = 'org.openhab.SolarEdge';

let storedH1Errors = items.H1_SolarEdge_LastErrors.state.split(',')
let storedH2Errors = items.H2_SolarEdge_LastErrors.state.split(',')
let H1Errors = cache.private.put('h1errors', storedH1Errors)
let H2Errors = cache.private.put('h2errors', storedH2Errors)

function roundTo3(input) {
  return Math.round(Number.parseFloat(input) * 1000) / 1000;
}

function calculateAggregatePeriod(type, period, houseNo) {
  let midnight = time.toZDT('00:00');
  let dayOfWeek = time.toZDT().dayOfWeek().value();
  let startOfWeek = midnight.minusDays(dayOfWeek - 1);
  let startOfMonth = midnight.withDayOfMonth(1);
  let startOfYear = midnight.withDayOfYear(1);

  let aggregatePrefix = houseNo + "_SolarEdge_";

  // Lifetime Consumption
  let totalImport = roundTo3(items.getItem(aggregatePrefix + "AggregateLifetime_Import").numericState);
  let totalExport = roundTo3(items.getItem(aggregatePrefix + "AggregateLifetime_Export").numericState);
  let totalProduction = roundTo3(items.getItem(aggregatePrefix + "AggregateLifetime_Production").numericState);

  let deltaSince;
  let periodStart;

  switch (period) {
    case "Day":
      deltaSince = items.getItem(aggregatePrefix + "AggregateLifetime_" + type).history.deltaSince(midnight);
      periodStart = midnight;
      break;
    case "Week":
      deltaSince = items.getItem(aggregatePrefix + "AggregateLifetime_" + type).history.deltaSince(startOfWeek);
      periodStart = startOfWeek;
      break;
    case "Month":
      deltaSince = items.getItem(aggregatePrefix + "AggregateLifetime_" + type).history.deltaSince(startOfMonth);
      periodStart = startOfMonth;
      break;
    case "Year":
      deltaSince = items.getItem(aggregatePrefix + "AggregateLifetime_" + type).history.deltaSince(startOfYear);
      periodStart = startOfYear;
      break;
    default:
      return null;
  }

  if (deltaSince === null) {
    let currentValue = roundTo3(items.getItem(houseNo + "_SolarEdge_AggregateLifetime_" + type).numericState)
    let minimum = items.getItem(aggregatePrefix + "AggregateLifetime_" + type).history.minimumSince(periodStart).numericState;
    return roundTo3(currentValue - minimum);
  } else {
    return roundTo3(deltaSince);
  }
}

function updateAggregatePeriod(totalImport, totalExport, totalProduction, period, houseNo) {
  
  items.getItem(houseNo + "_SolarEdge_Aggregate" + period + "_Import").postUpdate(totalImport);
  items.getItem(houseNo + "_SolarEdge_Aggregate" + period + "_Export").postUpdate(totalExport);
  items.getItem(houseNo + "_SolarEdge_Aggregate" + period + "_Production").postUpdate(totalProduction);
  
  let totalConsumption = roundTo3(totalImport + totalProduction - totalExport);
  items.getItem(houseNo + "_SolarEdge_Aggregate" + period + "_Consumption").postUpdate(totalConsumption);

  let totalSelfConsumption = roundTo3(totalProduction - totalExport);
  items.getItem(houseNo + "_SolarEdge_Aggregate" + period + "_SelfConsumption").postUpdate(totalSelfConsumption);

  let totalCoverage = Math.round(totalSelfConsumption / totalConsumption * 100);
  items.getItem(houseNo + "_SolarEdge_Aggregate" + period + "_SelfConsumptionCoverage").postUpdate(totalCoverage);
}


// *************************  THIS IS THE RULE THAT SEEMS TO BE THE PROBLEM ***********************
rules.JSRule({
  name: "SolarEdge - Energy Meters",
  description: "Update Aggregate Energy items",
  triggers: [
    triggers.ItemStateChangeTrigger('H1_SolarEdge_AggregateLifetime_Import', undefined, undefined),
    triggers.ItemStateChangeTrigger('H1_SolarEdge_AggregateLifetime_Export', undefined, undefined),
    triggers.ItemStateChangeTrigger('H1_SolarEdge_AggregateLifetime_Production', undefined, undefined),

    triggers.ItemStateChangeTrigger('H2_SolarEdge_AggregateLifetime_Import', undefined, undefined),
    triggers.ItemStateChangeTrigger('H2_SolarEdge_AggregateLifetime_Export', undefined, undefined),
    triggers.ItemStateChangeTrigger('H2_SolarEdge_AggregateLifetime_Production', undefined, undefined)
  ],
  execute: (event) => {

    let itemName = event.itemName;
    let houseNo = itemName.split("_")[0];
    
    // Lifetime
    let totalImport = roundTo3(items.getItem(houseNo + "_SolarEdge_AggregateLifetime_Import").numericState);
    let totalExport = roundTo3(items.getItem(houseNo + "_SolarEdge_AggregateLifetime_Export").numericState);
    let totalProduction = roundTo3(items.getItem(houseNo + "_SolarEdge_AggregateLifetime_Production").numericState);
    
    if (totalImport !== null && totalExport !== null && totalProduction !== null) {
      updateAggregatePeriod(totalImport, totalExport, totalProduction, "Lifetime", houseNo);
    }
    
    // Day
    let totalImportDay = calculateAggregatePeriod("Import", "Day", houseNo);
    let totalExportDay = calculateAggregatePeriod("Export", "Day", houseNo);
    let totalProductionDay = calculateAggregatePeriod("Production", "Day", houseNo);
    updateAggregatePeriod(totalImportDay, totalExportDay, totalProductionDay, "Day", houseNo);
    
    // Week
    let totalImportWeek = calculateAggregatePeriod("Import", "Week", houseNo);
    let totalExportWeek = calculateAggregatePeriod("Export", "Week", houseNo);
    let totalProductionWeek = calculateAggregatePeriod("Production", "Week", houseNo);
    updateAggregatePeriod(totalImportWeek, totalExportWeek, totalProductionWeek, "Week", houseNo);
    
    // Month
    let totalImportMonth = calculateAggregatePeriod("Import", "Month", houseNo);
    let totalExportMonth = calculateAggregatePeriod("Export", "Month", houseNo);
    let totalProductionMonth = calculateAggregatePeriod("Production", "Month", houseNo);
    updateAggregatePeriod(totalImportMonth, totalExportMonth, totalProductionMonth, "Month", houseNo);
    
    // Year
    let totalImportYear = calculateAggregatePeriod("Import", "Year", houseNo);
    let totalExportYear = calculateAggregatePeriod("Export", "Year", houseNo);
    let totalProductionYear = calculateAggregatePeriod("Production", "Year", houseNo);
    updateAggregatePeriod(totalImportYear, totalExportYear, totalProductionYear, "Year", houseNo);
  }
});

rules.JSRule({
  name: "SolarEdge - Power Meters",
  description: "Update Live Power items",
  triggers: [
    triggers.ItemStateChangeTrigger('H1_SolarEdge_TotalRealPower', undefined, undefined),
    triggers.ItemStateChangeTrigger('H2_SolarEdge_TotalRealPower', undefined, undefined)
  ],
  execute: (event) => {
    let houseNo = event.itemName.split('_')[0].replace('H', '')
    let totalPower = Number.parseInt(items.getItem("H" + houseNo + "_SolarEdge_TotalRealPower").state)
    var production = Number.parseInt(items.getItem("H" + houseNo + "_SolarEdge_Live_Production").state)
    var liveExport = Math.max(0, totalPower)
    var liveImport = Math.min(0, totalPower) * -1;
    var consumption = Math.max(0, liveImport + production - liveExport)
    var selfConsumption = consumption - liveImport

    items.getItem("H" + houseNo + "_SolarEdge_Live_Import").postUpdate(liveImport + " W")
    items.getItem("H" + houseNo + "_SolarEdge_Live_Export").postUpdate(liveExport + " W")

    items.getItem("H" + houseNo + "_SolarEdge_Live_Consumption").postUpdate(consumption + " W")
    items.getItem("H" + houseNo + "_SolarEdge_Live_SelfConsumption").postUpdate(selfConsumption + " W")
        
  }
});

rules.JSRule({
  name: "SolarEdge - Add Errors to SolarEdge Log",
  description: "Store inverter errors in the 'Last Errors' item",
  triggers: [
    triggers.ItemStateChangeTrigger('H1_SolarEdge_ErrorCode', 0, undefined),
    triggers.ItemStateChangeTrigger('H2_SolarEdge_ErrorCode', 0, undefined)
  ],
  execute: (event) => {
    let houseNumber = event.itemName.split('_')[0].replace('H', '')
    console.info('Inverter ' + houseNumber +  ' detected an error')

    var errorList = [
      { code: '0', description: 'No error' },
      { code: '25,134', description: 'Isolation Fault' },
      { code: '10,37,38', description: 'Ground Current - RCD' },
      { code: '14', description: 'AC Voltage Too High' },
      { code: '15', description: 'DC Voltage Too High (surge)' },
      { code: '16,149,153,181,166,167,168,170', description: 'Hardware Error' },
      { code: '17,117', description: 'Temperature Too High' },
      { code: '24', description: 'Faulty Temp. Sensor' },
      { code: '26', description: 'Faulty AC Relay' },
      { code: '28', description: 'RCD Sensor Error' },
      { code: '29,30', description: 'Phase Balance Error' },
      { code: '31,33', description: 'AC Voltage Too High' },
      { code: '32,41', description: 'AC Voltage Too Low' },
      { code: '34,64,65,66', description: 'AC Freq Too High' },
      { code: '35,67,68,69', description: 'AC Freq Too Low' },
      { code: '36', description: 'DC Injection' },
      { code: '40', description: 'Islanding' },
      { code: '44', description: 'No Country Selected' },
      { code: '46', description: 'Phase Unbalance' },
      { code: '144', description: 'Islanding Passive' },
      { code: '145', description: 'UDC Max' },
      { code: '146', description: 'UDC Min' },
      { code: '147,150,151,12', description: 'Arc Fault Detected' },
      { code: '152', description: 'Arc detector self-test failed' },
      { code: '178', description: 'Internal RGM Error' },
      { code: '185', description: 'Energy Meter Comm. Error' },
      { code: '13', description: 'ARC_PWR_ DETECT' },
      { code: '55', description: 'V-Line Max' },
      { code: '56', description: 'V-Line Min' },
      { code: '57,59,60', description: 'I-ACDC L1/L2/L3. AC overcurrent' },
      { code: '61', description: 'I-RCD STEP. Ground Current RCD' },
      { code: '100,101,102', description: 'TZ L1/L2/L3 AC overcurrent' },
      { code: '158', description: 'Controller 3/12/34 Err' },
      { code: '199', description: 'RSD Error' },
      { code: '127', description: 'IRCDMax' },
      { code: '169', description: 'RCD Error' },
      { code: '133', description: 'Temp Sensor fault' },
      { code: '123', description: 'MainError' },
      { code: '96,98', description: 'Islanding Trip 1/2' },
      { code: '62', description: 'I-RCD MAX' },
      { code: '171', description: 'Over voltage Vin' },
      { code: '163', description: 'Tz Over current 1/2/3' },
      { code: '166,167,168', description: 'Tz Over voltage cap 1/2/3' },
      { code: '169', description: 'Tz Over current Rcd' },
      { code: '178,179,180', description: 'Vf1/2/3 surge' },
      { code: '137', description: 'RCD Test' },
      { code: '96,98', description: 'Islanding Trip1/2' }
    ];
  
    function getErrorDescription(errorCode) {
      for (var i = 0; i < errorList.length; i++) {
          var codes = expandCodes(errorList[i].code);
          var description = errorList[i].description;

          if (codes.includes(errorCode)) {
              return description;
          }
      }
      return 'Error code not found';
    }

    function expandCodes(codes) {
        return codes.split(',').map(code => code.trim());
    }

    var errorCodeToSearch = event.newState;
    var errorDescription = getErrorDescription(errorCodeToSearch);

    let h = event.itemName.split('_')[0]
    let houseNo = h.replace('H', '')
    let timestamp = time.toZDT().toString().replace('Z[SYSTEM]', '').replace('T', ' ').split('.')[0]
    let logMessage = [timestamp + '>' + errorCodeToSearch + '>' + errorDescription]
    let logFileMessage = timestamp + " > Inverter " + houseNo + " > Error Code: " + errorCodeToSearch + " > " + errorDescription

    let logFile = items.getItem("H" + houseNo + "_SolarEdge_ErrorLog_Write")

    if (h == 'H1') {

      H1Errors = cache.private.get('h1errors')

      H1Errors = logMessage.concat(H1Errors)
      if (H1Errors.length > 10) {
        H1Errors.length = 10
      }
      cache.private.put('h1errors', H1Errors)
      
      items.H1_SolarEdge_LastErrors.postUpdate(H1Errors)
      logFile.sendCommand(logFileMessage)

    } else if (h == 'H2') {

      H2Errors = cache.private.get('h2errors')

      H2Errors = logMessage.concat(H2Errors)  
      if (H2Errors.length > 10) {
        H2Errors.length = 10
      }
      cache.private.put('h2errors', H2Errors)

      items.H2_SolarEdge_LastErrors.postUpdate(H2Errors)
      logFile.sendCommand(logFileMessage)
    }
  }
})

rules.JSRule({
  name: "SolarEdge - Add Status to SolarEdge Log",
  description: "",
  triggers: [
    triggers.ItemStateChangeTrigger('H1_SolarEdge_InverterStatus', undefined, undefined),
    triggers.ItemStateChangeTrigger('H2_SolarEdge_InverterStatus', undefined, undefined)
  ],
  execute: (event) => {
    let newData = event.newState
    let oldData = event.oldState

    let inputName = event.itemName
    let inputItem = items.getItem(inputName)
    let houseNo = inputName.split("_")[0].replace("H", "")
    let logFile = items.getItem("H" + houseNo + "_SolarEdge_ErrorLog_Write")

    let aC = items.getItem(inputName.replace("InverterStatus", "ACVoltage")).state;
    let dC = items.getItem(inputName.replace("InverterStatus", "DCVoltage")).state;
    let liveImport = items.getItem(inputName.replace("InverterStatus", "Live_Import")).state;
    let liveExport = items.getItem(inputName.replace("InverterStatus", "Live_Export")).state;
    let liveProduction = items.getItem(inputName.replace("InverterStatus", "Live_Production")).state;

    // console.info("Adding Inverter Status to Error Log")
    
    let timestamp = time.toZDT().toString().replace('Z[SYSTEM]', '').replace('T', ' ').split('.')[0]
    let logMessage = [timestamp + '>Status change>'  + oldData + "  to " + newData]
    let logFileMessage = (timestamp + " - Status change: " + oldData + " to " + newData + " - AC: " + aC + " - DC: " + dC + " - Import: " + liveImport + " - Export: " + liveExport + " - Production: " + liveProduction).toString()
    
    if (houseNo == '1') {

      H1Errors = cache.private.get('h1errors')

      H1Errors = logMessage.concat(H1Errors);
      if (H1Errors.length > 10) {
        H1Errors.length = 10
      }
      cache.private.put('h1errors', H1Errors)

      items.H1_SolarEdge_LastErrors.postUpdate(H1Errors);
      logFile.sendCommand(logFileMessage);

    } else if (houseNo == '2') {

      H2Errors = cache.private.get('h2errors')

      H2Errors = logMessage.concat(H2Errors)  ;
      if (H2Errors.length > 10) {
        H2Errors.length = 10
      }
      cache.private.put('h2errors', H2Errors)

      items.H2_SolarEdge_LastErrors.postUpdate(H2Errors);
      logFile.sendCommand(logFileMessage);
    }
  }
});


rules.JSRule({
  name: "SolarEdge - Clear SolarEdge Log",
  description: "Clear Error Log",
  triggers: [
    triggers.ItemStateUpdateTrigger('Config', 'Clear_SolarEdge1_ErrorLog'),
    triggers.ItemStateUpdateTrigger('Config', 'Clear_SolarEdge2_ErrorLog')
  ],
  execute: (event) => {
    let houseNo = event.receivedState.toString().split("_")[1].replace("SolarEdge", "")

    console.info("Clearing Error Log of SolarEdge " + houseNo)
    
    if (houseNo == "1") {
      H1Errors = []
      cache.private.put('h1errors', H1Errors)
      

    } else if (houseNo = "2") {
      H2Errors = []
      cache.private.put('h2errors', H2Errors)
    }

    items.getItem("H" + houseNo + "_SolarEdge_LastErrors").sendCommand('')

    items.Config.sendCommand('waiting...')
  }
});

I also have a few transformation scripts applied to these items and things. They all seem to run fine without increasing the heap.

autoStateDescription.js
Applied to all energy and power items

(function(data) {

  function round(input, points) {
    return Math.round(input * Math.pow(10, points)) / Math.pow(10, points);
  }

  if (data == 'NULL') {
    return;
  }

  let newData = Quantity(data)
  let dimension = newData.dimension

  let dimensionType;
  if (dimension == "[L]²·[M]/[T]²") {
      dimensionType = "energy"
      newData = newData.toUnit("Wh")
  } else if (dimension == "[L]²·[M]/[T]³") {
      dimensionType = "power"
      newData = newData.toUnit("W")
  }

  let dataNumber = newData.float
  let unit = newData.symbol;

  let xUnit =  Math.round(dataNumber) + " " + unit
  let kxUnit = round(dataNumber / Math.pow(10, 3), 1) + " k" + unit
  let MxUnit = round(dataNumber / Math.pow(10, 6), 2) + " M" + unit
  let GxUnit = round(dataNumber / Math.pow(10, 9), 2) + " G" + unit
  let TxUnit = round(dataNumber / Math.pow(10, 12), 2) + " T" + unit

  let output;
  
  if (dataNumber >= Math.pow(10, 3) && dataNumber < Math.pow(10, 6)) {
    output = kxUnit
  } else if (dataNumber >= Math.pow(10, 6) && dataNumber < Math.pow(10, 9)) {
    output = MxUnit
  } else if (dataNumber >= Math.pow(10, 9) && dataNumber < Math.pow(10, 12)) {
    output = GxUnit
  } else if (dataNumber >= Math.pow(10, 12)) {
    output = TxUnit
  } else {
    output = xUnit
  }

  return output.toString()

})(input)

solaredge_LiveImport.js and solaredge_LiveExport.js
For turning my single reading of a negative and positive total power, to independent Import and Export Items

(function(i) {
    var clamp = Math.min(0, Number.parseFloat(i)) * -1; 
    return clamp.toString() + " W"
})(input)
(function(i) {
    var clamp = Math.max(0, Number.parseFloat(i)); 
    return clamp.toString() + " W"
})(input)

Error Catch
6 transformation scripts (3 things; import, export, production x 2 PV systems) to ignore some spikes in the data I sometimes get.

(function(data) {
    // console.info('Starting H1_SolarEdge_AggregateLifetime_Export Transformation')

    if (data == "NULL" || data == "UNDEF") {
        data = Quantity("0 W")
      }
      
    let returnValue;
    let itemState = items.getItem('H1_SolarEdge_AggregateLifetime_Export').quantityState
    
    if (Quantity(data).lessThan(itemState)) {
      console.error('Error in House 1 Lifetime Export Modbus data, use old reading of ' + itemState)
      returnValue = itemState
    } else {
      // console.info('Use new reading ' + data )
      returnValue = data
    }
    
    return returnValue
  })(input)

screenshot of my widget for reading all this data -

With my zwave energy reporting devices (may not be applicable) I have to be very careful about reporting frequency in setting parameters. What I have done is to create an item to count reports. Maybe this will help just to see how often the rule is running.
Just a thought.
reports 2024-05-01 164403