Hello,
I have here a heating system for my house with a modbus master device (Windhager heating) and one slave (controller for heatexchanger pumps etc). I connected an ESP in parallel (I don’t want to change the existing installation) and my goal is to listen on the bus and catch the slaves values which are exchanged. In a second step I will also catch the data coming from the master.
I got inspired by following topic: Modbus "Man-in-the-middle" But it was not exactly fitting my needs.
Finally I ended up copying the ESPHome Modbus
component and rename it to ModbusSnifferComponent
. There I succesfully altered the method parse_modbus_byte_()
so that I’m able to decode the byte stream from the uart into request and response frames. My next step would be to call
device->on_modbus_data(data);
in order to save the data.
For this I copied the ESP home device implementation ModbusController
and renamed it to MyModbusController
. I had to do this, because the constructor of this class expects an object of type Modbus
which I don’t have. In my case it’s ModbusSniffer
.
And now my problem:
In __init___.py
of my_modbus_controller
I need to import ModbusSniffer
. But as far as I understand it is not possible to import an external component to an external component. I tried something like this, but without success:
from .my_components import ModbusSniffer
I’m stuck now. Any hints tipps ideas what to do?
Below some extracts of my code…
my folder structure:
esphome-web-8edf90.yaml
my_components/
modbus_sniffer/
__init__.py
modbus_sniffer.cpp
modbus_sniffer.h
my_modbus_controller/
__init__.py
my_modbus_controller.cpp
my_modbus_controller.h
const.py
(and other files)
esphome-web-8edf90.yaml extract:
external_components:
- source:
type: local
path: ./my_components
uart:
- id: mod_bus
#(more parameters)
modbus_sniffer: #works fine
id: modbus_sniff
uart_id: mod_bus
my_modbus_controller:
- id: modbus_device
address: 0x3C
modbus_id: modbus_sniff #this handover is the challenge
setup_priority: -10
my_components/modbus_sniffer/_init_.py
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import CONF_ID
DEPENDENCIES = ["uart"]
modbus_sniffer_component_ns = cg.esphome_ns.namespace("modbus_sniffer_component")
ModbusSnifferComponent = modbus_sniffer_component_ns.class_(
"ModbusSnifferComponent", cg.Component, uart.UARTDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ModbusSnifferComponent),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(uart.UART_DEVICE_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
my_components/modbus_sniffer/modbus_sniffer_component.h:
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace modbus_sniffer_component {
class ModbusDevice;
class ModbusSnifferComponent : public uart::UARTDevice, public Component {
public:
ModbusSnifferComponent() = default;
void setup() override;
void loop() override;
void dump_config() override;
void register_device(ModbusDevice *device) { this->devices_.push_back(device); }
void set_disable_crc(bool disable_crc) { disable_crc_ = disable_crc; }
protected:
bool parse_modbus_byte_(uint8_t byte);
bool disable_crc_;
std::vector<uint8_t> rx_buffer_;
uint32_t last_modbus_byte_{0};
std::vector<ModbusDevice *> devices_;
};
class ModbusSnifferDevice {
public:
explicit ModbusSnifferDevice(uint8_t address) : address_(address) {}
void set_parent(ModbusSnifferComponent *parent) { parent_ = parent; }
void set_address(uint8_t address) { address_ = address; }
virtual void on_modbus_data(const std::vector<uint8_t> &data) = 0;
virtual void on_modbus_error(uint8_t function_code, uint8_t exception_code) {}
virtual void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers){};
protected:
friend ModbusSnifferComponent;
ModbusSnifferComponent *parent_;
uint8_t address_;
};
} // namespace modbus_sniffer_component
} // namespace esphome
my_components/my_modbus_controller/_init_.py extract:
import binascii
from esphome import automation
import esphome.codegen as cg
from .my_components import ModbusSniffer //<------ here is currently my problem
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
CONF_ID,
CONF_LAMBDA,
CONF_NAME,
CONF_OFFSET,
CONF_TRIGGER_ID,
)
from esphome.cpp_helpers import logging
from .const import (
CONF_ALLOW_DUPLICATE_COMMANDS,
CONF_BITMASK,
CONF_BYTE_OFFSET,
CONF_COMMAND_THROTTLE,
CONF_CUSTOM_COMMAND,
CONF_FORCE_NEW_RANGE,
CONF_MAX_CMD_RETRIES,
CONF_MODBUS_CONTROLLER_ID,
CONF_OFFLINE_SKIP_UPDATES,
CONF_ON_COMMAND_SENT,
CONF_ON_OFFLINE,
CONF_ON_ONLINE,
CONF_REGISTER_COUNT,
CONF_REGISTER_TYPE,
CONF_RESPONSE_SIZE,
CONF_SKIP_UPDATES,
CONF_VALUE_TYPE,
)
CODEOWNERS = ["@martgras"]
AUTO_LOAD = ["modbus_sniffer_component"]
CONF_READ_LAMBDA = "read_lambda"
CONF_SERVER_REGISTERS = "server_registers"
MULTI_CONF = True
modbus_controller_ns = cg.esphome_ns.namespace("my_modbus_controller")
ModbusController = modbus_controller_ns.class_(
"MyModbusController", cg.PollingComponent, modbus_sniffer_component.ModbusDevice
)
....
my_modbus_controller/my_modbus_controller.h
#pragma once
#include "esphome/core/component.h"
#include "esphome/my_components/modbus_sniffer/modbus_sniffer_component.h"
#include "esphome/core/automation.h"
#include <list>
#include <queue>
#include <set>
#include <utility>
#include <vector>
namespace esphome {
namespace my_modbus_controller {
class MyModbusController;
(some code)
class MyModbusController : public PollingComponent, public modbus_sniffer_component::ModbusDevice { //<--- here I had to exchange "modbus" by "modbus_sniffer_component"
(more code)
}
} // namespace my_modbus_controller
} // namespace esphome
Regards
P.S.: I was not able to figure out what to do that the .h extracts are highlighted in color. I tried to mark it as C++, C, Python but no success.