Hi all,
I am a (happy ?) owner of a mechanical ventilation Nather Optimea which has an undocumented modbus RTC interface. I am sure you get my endgoal: I want to integrate it into HA through ESPhome. Sadly my HA box is not close to this MV and to be honest I do want to develop my esphome skills and this excuse is as good as any.
So through try and errors, I eventually managed to discover all the nather optimea sensors modbus registers and I am now capable to read them with a straightforward python script.
The second step plan was to base my custom “nather_optimea” component from the official pzemdc. Indeed, the pzemdc component is a modbus sensor component, how hard can it be to adapt it to the MV and integrate it with the new ESPHome “external component” feature. My optimist past self though “easy peasy”… And here I am begging for help!
What I did so far:
- I copy paste the pzemdc folder component (.py, .cpp .h files) to my esphome config folder (containing the YAML files) as said in the external component doc
- Rename everything (files and variables) from “pzemdc” to “nather_optimea”, and only leave one sensor to begin. (see source code below);
- Build a new esphome config file and try to compile it through the esphome GUI (last docker based version).
- I get the following error :
I don’t understand where I mess this up… And the most surprising is to get my component name duplicated?! Is anyone have a clue?
Thank you for your time and have a good evening!
Xavier
The test1.yaml file :
esphome:
name: test1
platform: ESP32
board: nodemcu-32s
external_components:
- source:
type: local
path: my_components
wifi:
ssid: "XXXXXXX"
password: "XXXXXXXX"
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "XXXXXXXXXXXX"
password: "XXXXXXXXXXX"
captive_portal:
# Enable logging
logger:
# Enable Home Assistant API
api:
ota:
uart:
tx_pin: TX
rx_pin: RX
baud_rate: 9600
sensor:
- platform: "nather_optimea"
#supply_flow_rate:
voltage:
name: "Supply flow rate"
The sensor.py file:
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, modbus
from esphome.const import (
CONF_SENSOR,
CONF_VOLTAGE,
CONF_ID,
DEVICE_CLASS_EMPTY,
ICON_EMPTY,
UNIT_EMPTY,
)
AUTO_LOAD = ["modbus"]
nather_optimea_ns = cg.esphome_ns.namespace("nather_optimea")
NATHER_OPTIMEA = nather_optimea_ns.class_("NATHER_OPTIMEA", cg.PollingComponent, modbus.ModbusDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(NATHER_OPTIMEA),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY
)
}
)
.extend(cv.polling_component_schema("60s"))
.extend(modbus.modbus_device_schema(0x01))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await modbus.register_modbus_device(var, config)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_supply_flow_rate_sensor(sens))
The nather_optimea.h file:
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/modbus/modbus.h"
namespace esphome {
namespace nather_optimea {
class NATHER_OPTIMEA : public PollingComponent, public modbus::ModbusDevice {
public:
void set_supply_flow_rate_sensor(sensor::Sensor *supply_flow_rate_sensor) { supply_flow_rate_sensor_ = supply_flow_rate_sensor; }
void update() override;
void on_modbus_data(const std::vector<uint8_t> &data) override;
void dump_config() override;
protected:
sensor::Sensor *supply_flow_rate_sensor_;
};
} // namespace nather_optimea
} // namespace esphome
and the nather_optimea.cpp file
#include "nather_optimea.h"
#include "esphome/core/log.h"
namespace esphome {
namespace nather_optimea {
static const char *TAG = "nather_optimea";
static const uint8_t NATHER_OPTIMEA_CMD_READ_IN_REGISTERS = 0x04;
static const uint16_t NATHER_OPTIMEA_REGISTER_COUNT = 1; // 1x 16-bit registers
static const uint16_t NATHER_OPTIMEA_REGISTER_SUPPLY_FLOW_RATE_ADDR = 4032
void NATHER_OPTIMEA::on_modbus_data(const std::vector<uint8_t> &data) {
if (data.size() < 20) {
ESP_LOGW(TAG, "Invalid size for PZEM AC!");
return;
}
// Nathea optimea reg examples:
// 4031: reg("supply_flow_rate","p1.2","Débit d'amenée",None,None,False),
// 4032: reg("extract_flow_rate","p1.3","Débit d'extration",None,None,False),
auto nather_optimea_get_16bit = [&](size_t i) -> uint16_t {
return (uint16_t(data[i + 0]) << 8) | (uint16_t(data[i + 1]) << 0);
};
auto nather_optimea_get_32bit = [&](size_t i) -> uint32_t {
return (uint32_t(nather_optimea_get_16bit(i + 2)) << 16) | (uint32_t(nather_optimea_get_16bit(i + 0)) << 0);
};
float supply_flow_rate = static_cast<float>(nather_optimea_get_16bit(0));
ESP_LOGD(TAG, "NATHER_OPTIMEA: supply_flow_rate=%.1f m3/h", supply_flow_rate);
if (this->supply_flow_rate_sensor_ != nullptr)
this->supply_flow_rate_sensor_->publish_state(supply_flow_rate);
}
void NATHER_OPTIMEA::update() { this->send(NATHER_OPTIMEA_CMD_READ_IN_REGISTERS,
NATHER_OPTIMEA_REGISTER_SUPPLY_FLOW_RATE_ADDR,
NATHER_OPTIMEA_REGISTER_COUNT); }
void NATHER_OPTIMEA::dump_config() {
ESP_LOGCONFIG(TAG, "NATHER_OPTIMEA:");
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
LOG_SENSOR("", "Supply Flow Rate", this->supply_flow_rate_sensor_);
}
} // namespace nather_optimea
} // namespace esphome