[HOW TO] Advanced Virtual Device for ESPAltherma

Hi, i created a method for interfacing an ESP Altherma bridge with Homey .
You need an esp32 with a firmware can bridge data to json data
You can use it for reading value from your altherma bridge and populate a virtual device created with Device Capabilities app.
Actually it only read some info.

Prerequisites:

Step 1: Create a homeyscript for get the ESP Altherma data (example esp_altherma_bridge.js)

const b_ip = 'YOUR_ESP_ALTHERMA_IP';
const b_baseurl = 'http://'+ b_ip + '/getData';

function toFixed(num) {
  return Math.trunc((num * 100) / 100);    
}

data = {};

try {
  const res = await fetch(b_baseurl);
  if (res.ok) data = await res.json();
} catch (err) {
  log(err);
}

let ops = [];

altherma = {};
switch(data['Operation Mode']) {
  case 'Fan Only':
    altherma['mode'] = '-';
    break;
  case 'Heating': 
    altherma['mode'] = 'Riscaldamento';
    break;
  case 'Cooling':  
    altherma['mode'] = 'Raffrescamento';
    break;
  default:
    altherma['mode'] = data['Operation Mode'];
}

switch(data['I/U operation mode']) {
  case 'DHW':
    altherma['status'] = 'ACCESO';
    altherma['mode'] = 'ACS';  
    break;
  case 'Stop':
    altherma['status'] = '-';
    break;
  default:
    altherma['status'] = 'ACCESO';
}
//if (altherma['status'] == 'Stop') altherma['status'] = 'SPENTO';

let voltage = Math.min(230, data['Voltage (N-phase) (V)']);

altherma['heat_pump_power'] = (data['INV primary current (A)'] * voltage);
altherma['heat_pump_power_2'] = (data['INV secondary current (A)'] * voltage);
altherma['valve_2way'] = (data['2way valve(On:Heat_Off:Cool)'] == 'ON' ? 'Heat' : 'Cool') + ' ('+ data['2way valve(On:Heat_Off:Cool)'] +')';
altherma['valve_3way'] = (data['3way valve(On:DHW_Off:Space)'] == 'OFF' ? 'Ambient' : 'DHW') + ' ('+ data['3way valve(On:DHW_Off:Space)'] +')';
altherma['thermostat'] = data['Thermostat ON/OFF'];
if (data['Defrost Operation'] == 'ON') ops.push('defrost');
if (data['Oil Return Operation'] == 'ON') ops.push('oil_return');
if (data['Brine Flow Switch'] == 'ON') ops.push('brine');
if (ops.length > 0) altherma['operations'] = ops.join(', ');
altherma['t_outdoor'] = data['R1T-Outdoor air temp.'];
altherma['t_acs'] = data['2nd Domestic hot water temperature'];
altherma['t_mandata'] = data['[HPSU] Tv inflow Temp  (R1T)'];
altherma['t_mandataBuh'] = data['[HPSU] Tvbh inflow Temp after Buffer/BUH (R2T)'];
altherma['t_ritorno'] = data['[HPSU] Tr return Temp (R4T)'];
altherma['t_serbatoio_dhw'] = data['DHW tank temp. (R5T)'];
altherma['t_setpoint'] = data['LW setpoint (main)'];
altherma['dhw_setpoint'] = data['DHW setpoint'];  
altherma['t_heatingTarget'] = data['Boiler Heating Target Temp.'];
altherma['flowSensor'] = (data['Flow sensor (l/min)'] > 0 ? (data['Flow sensor (l/min)'] * 60).toFixed(0) : 0);
altherma['waterPump'] = data['Water pump operation'];
altherma['heat_delivered'] = (data['Flow sensor (l/min)'] * 0.06 * 1.16 * (altherma['t_mandataBuh'] - altherma['t_ritorno']));
altherma['heat_delivered_alt'] = (data['Flow sensor (l/min)'] * 0.06 * 1.16 * (altherma['t_mandata'] - altherma['t_ritorno']));

altherma['backupHeater'] = 'OFF';
if (data['BUH Step1'] == 'ON' || data['BUH Step2'] == 'ON') altherma['backupHeater'] = 'ON';
altherma['dt_h'] = data['Target delta T heating'];
altherma['dt_c'] = data['Target delta T cooling'];
altherma['cop'] = 0;
altherma['cop_alt'] = 0;
if (data['Operation Mode'] == 'Heating' && data['Freeze Protection'] == 'OFF') {
  let c = (altherma['heat_delivered'] / (altherma['heat_pump_power'] / 1000));
  if (c > 0) altherma['cop'] = parseFloat(c.toFixed(1));
  let c_alt = (altherma['heat_delivered_alt'] / (altherma['heat_pump_power'] / 1000));
  if (c_alt > 0) altherma['cop_alt'] = parseFloat(c_alt.toFixed(1));
}
altherma['heat_pump_power'] = parseFloat(altherma['heat_pump_power'].toFixed(1));
altherma['heat_pump_power_2'] = parseFloat(altherma['heat_pump_power_2'].toFixed(1));
if (altherma['mode'] == 'Cooling') {
  altherma['heat_delivered'] = 0;  
  altherma['heat_delivered_alt'] = 0;  
} 
altherma['heat_delivered'] = parseFloat(altherma['heat_delivered'].toFixed(1));
altherma['heat_delivered_alt'] = parseFloat(altherma['heat_delivered_alt'].toFixed(1));
let operation = '-';
if (altherma['defrost'] == 'ON') operation = 'defrost';
if (altherma['oil_return'] == 'ON') operation = 'oil_return';
if (altherma['brine'] == 'ON') operation = 'brine';

await tag('esp_altherma_operation', operation);  
await tag('esp_altherma_mode', altherma['mode']);  
await tag('esp_altherma_status', altherma['status']);  
await tag('esp_altherma_cop', altherma['cop']);  
await tag('esp_altherma_acs', altherma['t_acs']);  
await tag('esp_altherma_consum', altherma['heat_pump_power']);  
await tag('esp_altherma_mandata', altherma['t_mandata']);  
await tag('esp_altherma_ritorno', altherma['t_ritorno']);  

altherma['heat_pump_power'] += ' Wh';
altherma['heat_pump_power_2'] += ' Wh';
altherma['heat_delivered'] += ' Wh';
altherma['heat_delivered_alt'] += ' Wh';
altherma['t_outdoor'] += '°';
altherma['t_acs'] += '°';
altherma['t_mandata'] += '°';
altherma['t_mandataBuh'] += '°';
altherma['t_ritorno'] += '°';
altherma['t_serbatoio_dhw'] += '°';
altherma['t_setpoint'] += '°';
altherma['dhw_setpoint'] += '°';  
altherma['t_heatingTarget'] += '°';
altherma['flowSensor'] += ' l/h';

await tag('esp_altherma', JSON.stringify(altherma));  

//log(data);
//log(altherma);

return true;

Step 2: Create a Virtual Device for your Altherma device

  • With app or my.homey.com create a new “Device capabilities” device.

Step 3: Configuring Device Settings

  • Define the capabilities your virtual device will have, you could also import this sample [TEF] String
[tef:AVD:"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":/tef]

Step 4: Populate the virtual device with a flow

This is a implementation sample, you can suggest optimization or fix :slight_smile:

2 Likes