devices.esphome.io
Genvex ECO 375, Nibe ERS 10-500, Alpha-Innotec LG 300 ECO B(E) ventilation
Genvex ECO 375, Nibe ERS 10-500, Alpha-Innotec LG 300 ECO B(E) ventilation
Device Type: miscElectrical Standard: globalBoard: esp32
Heat Recovery Ventilation system control via ModBUS protocol for Genvex ECO 375, Nibe ERS 10-500, Alpha-Innotec LG 300 ECO B(E). The ventilation unit must contain the Optima 260 (ES 960C) controller board, and have ModBUS enabled at 19600 baud.
Use an RS485 transceiver like MAX485 to the device via ModBUS. Some devices come pre-installed with a cloud controller already plugged in to the ModBUS port, this has to be be disconnected (ESPHome will go instead).
Basic configuration
The configuration below is based on an Olimex ESP32-EVB board for Ethernet connection to the network but any ESP32 module can be used (adjust GPIO pins accordingly).
substitutions: device_name: ventilation-system friendly_name: "Ventitaltion" device_ip: 192.168.1.10 device_description: "Heat Recovery Ventilation System Control" modbus_ctrl_id: genvex_modbus
esphome: name: ${device_name} comment: "${device_description}" platform: ESP32 board: esp32-evb
ethernet: type: LAN8720 mdc_pin: GPIO23 mdio_pin: GPIO18 clk_mode: GPIO0_IN phy_addr: 0 manual_ip: static_ip: ${device_ip} gateway: 192.168.1.1 subnet: 255.255.255.0 use_address: ${device_ip}
time:- platform: homeassistant id: homeassistant_time
uart: tx_pin: GPIO4 # DI rx_pin: GPIO36 # RO baud_rate: 19600 parity: EVEN id: mbus
modbus: flow_control_pin: GPIO14 # DE+RE id: modbus1 uart_id: mbus
modbus_controller:- id: ${modbus_ctrl_id} address: 1 modbus_id: modbus1 setup_priority: -10 command_throttle: 20ms update_interval: 5s
sensor:- platform: uptime name: ${friendly_name} controller uptime
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Inlet fan icon: "mdi:fan" id: genvex_inlet_fan register_type: read address: 9 unit_of_measurement: "%" value_type: U_WORD- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Extract fan icon: "mdi:fan" id: genvex_extract_fan register_type: read address: 10 unit_of_measurement: "%" value_type: U_WORD
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Supply air device_class: temperature register_type: read address: 0 unit_of_measurement: "°C" value_type: U_WORD accuracy_decimals: 1 filters: - filter_out: 0 - heartbeat: 120s - offset: -300 - multiply: 0.1- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Fresh air device_class: temperature register_type: read address: 2 unit_of_measurement: "°C" value_type: U_WORD accuracy_decimals: 1 filters: - filter_out: 0 - heartbeat: 120s - offset: -300 - multiply: 0.1- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Exhaust air device_class: temperature register_type: read address: 3 unit_of_measurement: "°C" value_type: U_WORD accuracy_decimals: 1 filters: - filter_out: 0 - heartbeat: 120s - offset: -300 - multiply: 0.1- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Extract air device_class: temperature id: genvex_temp_t7 register_type: read address: 6 unit_of_measurement: "°C" value_type: U_WORD accuracy_decimals: 1 filters: - filter_out: 0 - heartbeat: 120s - offset: -300 - multiply: 0.1
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Bypass icon: "mdi:debug-step-over" unit_of_measurement: "%" register_type: read address: 11 value_type: U_WORD
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Humidity device_class: humidity unit_of_measurement: "%" id: genvex_hum register_type: read address: 13 value_type: U_WORD filters: - filter_out: 0 - heartbeat: 120s
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Mode (modbus) icon: "mdi:speedometer-slow" id: genvex_mode register_type: holding entity_category: diagnostic address: 0 value_type: U_WORD disabled_by_default: true bitmask: 0x7fffffff
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Alarm code (DEC) icon: "mdi:alert-circle-outline" register_type: read entity_category: diagnostic address: 15 value_type: U_WORD disabled_by_default: true bitmask: 0x7fffffff
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Reheating icon: "mdi:water-boiler" register_type: read unit_of_measurement: "%" address: 5 value_type: U_WORD bitmask: 0x7fffffff
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Reheating control icon: "mdi:water-boiler" register_type: read unit_of_measurement: "%" address: 12 value_type: U_WORD bitmask: 0x7fffffff
- platform: template name: ${friendly_name} Humidity absolute lambda: |- const float mw = 18.01534; // molar mass of water g/mol const float r = 8.31447215; // Universal gas constant J/mol/K return (6.112 * powf(2.718281828, (17.67 * id(genvex_temp_t7).state) / (id(genvex_temp_t7).state + 243.5)) * id(genvex_hum).state * mw) / ((273.15 + id(genvex_temp_t7).state) * r); // in grams/m^3 accuracy_decimals: 2 update_interval: 120s icon: 'mdi:water' unit_of_measurement: 'g/m³'
- platform: template name: ${friendly_name} Dew point lambda: |- return (243.5*(log(id(genvex_hum).state/100)+((17.67*id(genvex_temp_t7).state)/ (243.5+id(genvex_temp_t7).state)))/(17.67-log(id(genvex_hum).state/100)- ((17.67*id(genvex_temp_t7).state)/(243.5+id(genvex_temp_t7).state)))); unit_of_measurement: °C update_interval: 120s icon: 'mdi:thermometer-alert'
binary_sensor:- platform: status name: ${friendly_name} controller network- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Alarm General Error device_class: problem register_type: read entity_category: diagnostic address: 15 bitmask: 0x1- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Bypass1 device_class: opening register_type: read entity_category: diagnostic address: 15 bitmask: 0x2- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Relay R9 register_type: read entity_category: diagnostic address: 15 bitmask: 0x4- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Alarm Filter device_class: problem register_type: read entity_category: diagnostic address: 15 bitmask: 0x8- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Alarm Frost device_class: problem register_type: read entity_category: diagnostic address: 15 bitmask: 0x10- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Alarm Hygrostat device_class: problem register_type: read entity_category: diagnostic address: 15 bitmask: 0x40- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Alarm Device Stopped device_class: problem register_type: read entity_category: diagnostic address: 15 bitmask: 0x80- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Bypass2 device_class: opening register_type: read entity_category: diagnostic address: 15 bitmask: 0x400- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Relay R8 register_type: read entity_category: diagnostic address: 15 bitmask: 0x1000 disabled_by_default: true- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Relay R2 Electric Reheating register_type: read entity_category: diagnostic address: 15 bitmask: 0x2000 disabled_by_default: true- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: Relay R3 Preheating register_type: read entity_category: diagnostic address: 15 bitmask: 0x4000 disabled_by_default: true
- platform: template name: ${friendly_name} turning off device_class: running entity_category: diagnostic id: genvex_turningoff lambda: |- if ((id(genvex_inlet_fan).state != 0 or id(genvex_extract_fan).state != 0) and id(genvex_mode).state == 0) { ESP_LOGD("ModbusLambda", "Cooldown extrarun before turning off"); return true; } else { return false; } on_state: # When turning off the unit, it will still run for about 3 minutes at its current speed. This trick helps with that, # it switches first to the lowest speed and then turns it off, so that those 3 minutes will be done in the most silent way. then: - if: condition: and: - binary_sensor.is_on: genvex_turningoff - lambda: return (id(genvex_inlet_fan).state != id(genvex_speed_in_1).state or id(genvex_extract_fan).state != id(genvex_speed_out_1).state); then: - lambda: |- modbus_controller::ModbusController *controller = id(${modbus_ctrl_id}); modbus_controller::ModbusCommandItem set_mode_slowfirst_command = modbus_controller::ModbusCommandItem::create_write_single_command(controller, 0, 1); modbus_controller::ModbusCommandItem set_mode_command = modbus_controller::ModbusCommandItem::create_write_single_command(controller, 0, 0); set_mode_slowfirst_command.on_data_func = [=](modbus_controller::ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) { ESP_LOGD("ModbusLambda", "Slow down mode 1 succes - Queuing set mode 0 command"); controller->queue_command(set_mode_command); }; ESP_LOGD("ModbusLambda", "Queuing set mode 1 slow down to lowest speed first command"); controller->queue_command(set_mode_slowfirst_command);number:- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Max duration for level 3-4 icon: "mdi:timer-sand" address: 14 unit_of_measurement: "h" value_type: U_WORD entity_category: config min_value: 1 max_value: 9 step: 1 mode: box
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Level 1 supply icon: "mdi:fan-speed-1" address: 7 unit_of_measurement: "%" id: genvex_speed_in_1 value_type: U_WORD entity_category: config min_value: 10 max_value: 45 step: 1 mode: box - platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Level 1 extract icon: "mdi:fan-speed-1" address: 10 unit_of_measurement: "%" id: genvex_speed_out_1 value_type: U_WORD entity_category: config min_value: 10 max_value: 45 step: 1 mode: box
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Level 2 supply icon: "mdi:fan-speed-2" address: 8 unit_of_measurement: "%" id: genvex_speed_in_2 value_type: U_WORD entity_category: config min_value: 40 max_value: 75 step: 1 mode: box
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Level 2 extract icon: "mdi:fan-speed-2" address: 11 unit_of_measurement: "%" id: genvex_speed_out_2 value_type: U_WORD entity_category: config min_value: 40 max_value: 75 step: 1 mode: box
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Level 3 supply icon: "mdi:fan-speed-3" address: 9 unit_of_measurement: "%" id: genvex_speed_in_3 value_type: U_WORD entity_category: config min_value: 60 max_value: 85 step: 1 mode: box
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Level 3 extract icon: "mdi:fan-speed-3" address: 12 unit_of_measurement: "%" id: genvex_speed_out_3 value_type: U_WORD entity_category: config min_value: 60 max_value: 85 step: 1 mode: box
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Temperature target unit_of_measurement: "°C" icon: "mdi:cog-outline" address: 2 lambda: "return (x + 100) / 10;" write_lambda: "return x * 10 - 100;" value_type: U_WORD entity_category: config min_value: 10 max_value: 30 step: 0.1 mode: box
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Bypass open internal delta unit_of_measurement: "°C" icon: "mdi:cog-outline" address: 18 lambda: "return x / 10;" write_lambda: "return x * 10;" value_type: U_WORD entity_category: config min_value: 1 max_value: 10 step: 0.1 mode: box
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Bypass close external delta unit_of_measurement: "°C" icon: "mdi:cog-outline" address: 26 value_type: U_WORD entity_category: config min_value: 0 max_value: 20 step: 1 mode: box
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Filter change period unit_of_measurement: "mo" icon: "mdi:cog-outline" address: 5 value_type: U_WORD entity_category: config min_value: 0 max_value: 12 step: 1 mode: box disabled_by_default: true
text_sensor:- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} unit time icon: "mdi:calendar-clock" address: 200 register_type: holding register_count: 6 skip_updates: 12 force_new_range: true lambda: return str_sprintf("20%02d-%02d-%02d %02d:%02d", data[11], data[9], data[7], data[1], data[3]);
select:- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Speed icon: "mdi:fan" id: genvex_sel address: 0 value_type: U_WORD optimistic: true optionsmap: "Low": 1 "Medium": 2 "High": 3 "Turbo": 4 "Off": 0
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Display model icon: "mdi:cog-outline" entity_category: config address: 41 value_type: U_WORD skip_updates: 24 disabled_by_default: true optionsmap: "None installed": 0 "OPT100": 1 "Boost button": 2
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Regulation method icon: "mdi:cog-outline" entity_category: config address: 16 value_type: U_WORD skip_updates: 24 disabled_by_default: true optionsmap: "Room": 0 "Supply": 1 "Extract": 2
switch:- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Reheating entity_category: config icon: "mdi:water-boiler" register_type: holding address: 1 bitmask: 1
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Max duration for level 3-4 enable entity_category: config icon: "mdi:toggle-switch" register_type: holding address: 4 bitmask: 1
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Humidity control entity_category: config icon: "mdi:air-humidifier" register_type: holding address: 6 bitmask: 1
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Filter change autostop entity_category: config icon: "mdi:toggle-switch" disabled_by_default: true register_type: holding address: 15 bitmask: 1
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Device stop enable entity_category: config icon: "mdi:toggle-switch" disabled_by_default: true register_type: holding address: 25 bitmask: 1
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Calendar mode entity_category: config icon: "mdi:toggle-switch" disabled_by_default: true register_type: holding address: 45 bitmask: 1
- platform: modbus_controller modbus_controller_id: ${modbus_ctrl_id} name: ${friendly_name} Filter reset entity_category: config icon: "mdi:air-filter" register_type: holding address: 47 bitmask: 1
button:- platform: restart name: ${friendly_name} controller restart entity_category: diagnostic- platform: safe_mode name: ${friendly_name} controller safe mode boot entity_category: diagnostic- platform: template name: ${friendly_name} modbus manual update entity_category: config icon: "mdi:eye-refresh-outline" disabled_by_default: true on_press: then: - lambda: 'return id(${modbus_ctrl_id})->update();'- platform: template name: ${friendly_name} Sync date and time entity_category: diagnostic icon: "mdi:clock-edit-outline" disabled_by_default: true on_press: if: condition: time.has_time: then: - lambda: |- // get local time and sync to device time_t now = ::time(nullptr); struct tm *time_info = ::localtime(&now); uint16_t minute = time_info->tm_min; uint16_t hour = time_info->tm_hour; uint16_t day = time_info->tm_mday; uint16_t month = time_info->tm_mon + 1; uint16_t year = time_info->tm_year % 100; modbus_controller::ModbusController *controller = id(${modbus_ctrl_id}); modbus_controller::ModbusCommandItem set_hour = modbus_controller::ModbusCommandItem::create_write_single_command(controller, 200, hour); modbus_controller::ModbusCommandItem set_minute = modbus_controller::ModbusCommandItem::create_write_single_command(controller, 201, minute); modbus_controller::ModbusCommandItem set_day = modbus_controller::ModbusCommandItem::create_write_single_command(controller, 203, day); modbus_controller::ModbusCommandItem set_month = modbus_controller::ModbusCommandItem::create_write_single_command(controller, 204, month); modbus_controller::ModbusCommandItem set_year = modbus_controller::ModbusCommandItem::create_write_single_command(controller, 205, year); // commands have to be run in reversed order nested in on_data_func's because the queue_command normally exits the procedure set_month.on_data_func = [=](modbus_controller::ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) { ESP_LOGI("ModbusLambda", "Setting year to %02d", year); controller->queue_command(set_year); }; set_day.on_data_func = [=](modbus_controller::ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) { ESP_LOGI("ModbusLambda", "Setting month to %02d", month); controller->queue_command(set_month); }; set_minute.on_data_func = [=](modbus_controller::ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) { ESP_LOGI("ModbusLambda", "Setting day to %02d", day); controller->queue_command(set_day); }; set_hour.on_data_func = [=](modbus_controller::ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) { ESP_LOGI("ModbusLambda", "Setting minute to %02d", minute); controller->queue_command(set_minute); }; ESP_LOGI("ModbusLambda", "Setting hour to %02d", hour); controller->queue_command(set_hour);
Notes
If you get CRC errors use shorter cables and double-check for the 120 Ohm termination resistors as per ModBUS standard.
The factory pre-installed cloud controller (usually a separate box or board) has to be disconnected because it acts a ModBUS Master, just like the ESPHome node configured as above. You can't have two ModBUS RTU Masters on the same bus.
These units were shipped with various controller boards inside, this configuration only matches Optima 260 (ES 960C).
Edit this page on GitHub