Ebusd communication with Javascript rule

Since I use the ebus binding in combination with ebusd to read values from my heating system (heating + thermostat) I have some problems:

  1. A lot of errors in the openhab log:
    ◦ ‘ERR: wrong symbol received’,
    ◦ ‘ERR: SYN received’,
    ◦ ‘ERR: invalid position in decode’, …
    The errors are not caused by the ebusd binding, but are somehow hardware related as they are visible in the ebusd log. I don’t like to see a lot of red lines in Frontail.
    Since about a week I upgraded the ebus adapter firmware to 20240330, and now the errors are mostly gone.
  2. The polling of the values does not happen at the same time. So in the charts, the Flame value does not change at the same time as the Energy Manager state. And if there are errors, sometimes a read event is not seen.
  3. Since I upgraded to OH 4.1, the bridge silently stops working once or two times a week. The bridge apparently stays online, but nothing happens. Disabling and restarting the bridge helps.

If ebusd is used, there is a command ebusctl, which can be used from the command line to read or write values.

I use this command in a rule with two Javascript scripts via executeCommandLine. It solves some of my problems.

  • The error messages can be configured in the code (console.info, console.warn, console.error).
  • All the read values are polled one after the other every minute (cron expression: 5 * * * * ? *). This takes 2 seconds.
  • There is no need to write configuration files for the binding; instead edit the code to include the values read from ebusd and the corresponding items.

The built-in polling mechanism and cache of ebusd is not used (pollinterval=0), all read commands are executed from the script.

Configuration

• Raspberry pi 3B
• OH 4.1.2
• Javascript scripting installed (Settings -  Add-ons - Automation) 
• Ebus adapter shield v5 with firmware 20240330 connected via ethernet
• Ebusd version 23.3.23.3
• Ebusd configuration string in /etc/default/ebusd:
EBUSD_OPTS="--device=ens:192.168.1.100:9999 --latency=30 --pollinterval=0 --sendretries=5"

Read script

// ebusdRead
// Reads ebusd values with 'ebusctl read'

  // Floating point values
  var readFloat = new Map([ 
    ['392 DisplayedRoomTemp',           'vwTempLiving'],
    ['392 ActualRoomTempDesiredHc1',    'vwTempLivingGewenst'],
    ['392 Hc1ManualOPRoomTempDesired',  'vwTempLivingManueel'],
    ['bai Flowtemp',                    'vwTempHeen'],
    ['bai ReturnTemp',                  'vwTempRetour'],
    ['bai FlowSetPotmeter',             'vwTempIngesteld'],
    ['bai StorageTemp',                 'wwTemp'],
    ['bai HwcSetPotmeter',              'wwTempIngesteld'],
    ['bai WaterPressure',               'wwDruk']
  ]);
  readFloat.forEach((ohItem, ebusName) => {
    if (result = readEbus(ebusName)){
      //console.info(ebusName + ' = ' + parseFloat(result));
      items.getItem(ohItem).postUpdate(parseFloat(result));
    }
  })

  //  Energy Manager State (0=off, 1=heating, 2=hot water heating, 3=hot water pump lag)
  if (result = readEbus('392 EnerManState')){
    //console.info('392 EnerManState = ' + parseInt(result));
    items.getItem('vwMode').postUpdate(parseInt(result));
	}
    
  // Flame (on/off, transformed to 1/0)
  if (result = readEbus('bai Flame')){
    //console.info('bai Flame = ' + result);
    items.getItem('vwVlam').postUpdate(result==='on'?1:0);
  }

  // Heating circuit operation mode ('auto', 'manual', 'off')
  if (result = readEbus('392 Hc1OPMode')){
    //console.info('392 Hc1OPMode = ' + result);
    items.getItem('vwOpMode').postUpdate(result);
  }

function readEbus (ebusName){
  let command = 'ebusctl read -f -c '+ ebusName;
  result = actions.Exec.executeCommandLine(time.Duration.ofSeconds(5),'/bin/sh', '-c',command);
  if (result == null){
    console.info('Failed reading ' + ebusName + ': null');
  }
  else if (result.substr(0,4) == 'ERR:'){
    console.info('Failed reading ' + ebusName + ': ' + result.trim());
  }
  else {
    return result.trim();
  }
}

Some coding tricks explained

  • The ebusd floating point values and items are defined as a map, so they can be edited easily.
  • The map is iterated by using forEach. The function is coded as an arrow function, see here. The order of the parameters is inversed from what is defined in the map.
  • The condition ‘(result = readEbus(ebusName)’ is false if result == null or undefined or empty string or NaN or 0.

Write script

Writing is a bit more complicated.

  • It is good to do some checks first.
  • After writing, the item has to be read again, to check if it is written correctly

Unfortunately most of the values of my devices are read only. I provided a possibility to switch the room thermostat to manual mode and adjust the manual temperature setting.
The triggers are defined as ‘received command’.

// ebusdWrite
// Write values to ebusd with ebusctl

var itemName = event.itemName.toString();
var targetValue = event.itemCommand.toString();
//console.info ('ebusdWrite '+ itemName + ' ' + targetValue);

switch(itemName){
  
  case 'vwOpMode':   
    // if illegal value, do nothing
    var validModes = ['auto', 'manual', 'off'];
    if(validModes.indexOf(targetValue) == -1){ 
      console.error('Error writing ' + itemName + ': targetValue not valid');
    }
    else{
      result = write('392 Hc1OPMode', itemName, targetValue);      
    }
    break;

  case 'vwTempLivingManueel':
    if (targetValue < 18) targetValue = 18;
    if (targetValue > 25) targetValue = 25;
    result = write('392 Hc1ManualOPRoomTempDesired', itemName, targetValue);
    break;
}


function write(ebusName, itemName, targetValue){
  command = 'ebusctl write -c '+ ebusName + ' ' + targetValue;    
  result = actions.Exec.executeCommandLine(time.Duration.ofSeconds(5),'/bin/sh', '-c',command).trim();  
  if (result !== 'done'){
    console.error('Error writing ' + itemName + ': ' + result);
  }
  else{
    // read value as a check if it is written correctly
    command = 'ebusctl read -f -c '+ ebusName;
    result = actions.Exec.executeCommandLine(time.Duration.ofSeconds(5),'/bin/sh', '-c',command).trim();
    items.getItem(itemName).postUpdate(result);   
    console.info(itemName + ' write result: ' + result);
  }
 
  return result;
}

One detail to keep in mind: the first time you execute a Javascript script in OH, it will run very slowly as explained here. Afterwards it runs fast.