[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'] = 'Heating';
    break;
  case 'Cooling':  
    altherma['mode'] = 'Cooling';
    break;
  default:
    altherma['mode'] = data['Operation Mode'];
}

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

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:

4 Likes

I have tried to upload ESPAltherma_JSON to my M5StickC PLUS2. I looks like it does not boot. I don’t get the ESPAltherma welcome screen. Any ideas?

Hi, m5 stick plus2 need merge some commit, tomorrow i try to add support :+1:

1 Like

Hi, i added m5stick plus2, try if it work!

Thanks! m5stick plus2 is working now. Now let’s populate the virtual device with a flow

Hi!
Awesome!
I think i have it running, now going to connect it to my Altherma physically… what will happen hehe

But, the TEF is not complete right? I mean, in Platformio its possible to select certain LabelDefs (the info you want to read). I dont think they are all in the TEF right?

How can i add that certain information that i want to view? I dont know how this works

Hoi Bas,
Do you have some examples? Im going to connect it now to my Daikin.
Did you add some other capabilities? Any idea how?

Its like this


I dont get them all to work.

DHW? How do i get that running?
And Mode?
And Status?

Hi, yes the tef is partial, i used only some basical infos.
You need to customize the homeyscript for mapping other params to others capabilities!

In the homeyscrit uncomment log(data) and check the params that you need

Thanks for your response.
Im no programmer. Looking at the script but i dont see what i can uncomment?
I uncommented a lot in Platformio. But i think it is more that it will not show it in homey.

can you help me and show me what to do ?

I want to see all the uncomment data (from uncommenting in platformio. )

At latest line of homescript replace

//log(data)
With

log(data)

Save and click to play, in the result the system write all your data

Now for each value that you need to add in virtual device create a tag

await tag(‘esp_altherma_A’, data[‘B’]);

Where you replace A with the mnemonic name of you want and B with the key of data that you want.
Now in flow get this new tag and put it value in a new capability!

I wrote on the fly now I can’t be more precise :sweat_smile:

I got it working, thank you!

Now im curious how i can control it.

What i would like:
When my solarpanels are generating → I want to heat up my watertank. But therfore i need to raise the dhw_setpoint to 55 (thats the maximum the outdoor fan unit can generate)
Then it will run and heat up, after that i want to lower the dhw_setpoint again to 49-ish.

Any idea?

Otheroption i have is to control the heater with a certain resistance to switch the mode. But i prefer the above option…

you can use the Altherma smart grid function. If you enable this option, you can change the mode with a dedicated dry contact.
But i’ve never done it

Hey!
I already have this dry contact installed and it works too.
But it can do 2 things:

  1. Turn on the outside unit and heat up to 55 degrees Celsius
  2. Turn on the BUH Electrical heater and pump it to hell and back.

But I don’t have a BUH Electrical heater, it’s very expensive.

So I want to set the tank temp to 55 temporarily, heat it up with outside unit, and then put it back at 49.

I’ll check the github of the original one too, maybe it’s written somewhere…

Edit, i make a mistake.

Altherma should have 2 programmation for DHW (automatic 1 and automatic 2).
You can change the mode from automatic 1 and automatic 2 and change the DHW temperature

I will check it. So it should be possible to change the mode via this ESPAltherma?

No, it’s not possibile. EspAltherma can only (optional) control heat pump on/off and smart grid on/off, but i haven’t implement it by rest call (is needed external relay).
For changing the mode you need to use the input J8 with a dry contact and appropriate resistance for changing default mode. See manual for more infos

aah yeah that was my idea to do indeed with the resistances. But i hoped it would be possible to do via espaltherma.

Aside of that, any idea why the values update on different times? I mean: the mode is updated 5 hours ago, but the heating return + supply is 20 days ago. The script runs every 30 seconds so i find it strange… does the Heatpump not update it?

I ran the script and outcome is:

———————————————————
:x: Script Error
:warning: Error: Invalid Type. Got: string. Expected: Number
at FlowToken.setValue (/node_modules/@athombv/homey-apps-sdk-v3/lib/FlowToken.js:60:13)
at HomeyScriptApp.setToken (/app/app.js:247:38)
at tag (/app/app.js:327:38)
at esphome.js:116:7
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async HomeyScriptApp.runScript (/app/app.js:364:22)
at async Object.runScript (/app/api.js:30:22)

What does it say in English :stuck_out_tongue: