6namespace modbus_controller {
8static const char *
const TAG =
"modbus_controller";
25 if (!command->should_retry(this->max_cmd_retries_)) {
27 ESP_LOGW(TAG,
"Modbus device=%d set offline", this->
address_);
37 this->
offline_callback_.call((
int) command->function_code, command->register_address);
39 ESP_LOGD(TAG,
"Modbus command to device=%d register=0x%02X no response received - removed from send queue",
40 this->
address_, command->register_address);
43 ESP_LOGV(TAG,
"Sending next modbus command to device %d register 0x%02X count %d", this->
address_,
44 command->register_address, command->register_count);
52 if (!command->on_data_func) {
63 ESP_LOGW(TAG,
"Received modbus data but command queue is empty");
67 if (current_command !=
nullptr) {
69 ESP_LOGW(TAG,
"Modbus device=%d back online", this->
address_);
74 r.skip_updates_counter = 0;
79 this->
online_callback_.call((
int) current_command->function_code, current_command->register_address);
83 current_command->payload =
data;
85 ESP_LOGV(TAG,
"Modbus response queued");
92 ESP_LOGV(TAG,
"Process modbus response for address 0x%X size: %zu", response->
register_address,
98 ESP_LOGE(TAG,
"Modbus error function code: 0x%X exception: %d ", function_code, exception_code);
104 if (current_command !=
nullptr) {
106 "Modbus error - last command: function code=0x%X register address = 0x%X "
107 "registers count=%d "
109 function_code, current_command->register_address, current_command->register_count,
110 current_command->payload.size());
116 uint16_t number_of_registers) {
118 "Received read holding/input registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
120 this->
address_, function_code, start_address, number_of_registers);
123 ESP_LOGW(TAG,
"Invalid number of registers %d. Sending exception response.", number_of_registers);
128 std::vector<uint16_t> sixteen_bit_response;
129 for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
132 if (server_register->address == current_address) {
133 if (!server_register->read_lambda) {
136 int64_t value = server_register->read_lambda();
137 ESP_LOGD(TAG,
"Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
138 server_register->address,
static_cast<size_t>(server_register->value_type),
139 server_register->register_count, server_register->format_value(value).c_str());
141 std::vector<uint16_t> payload;
142 payload.reserve(server_register->register_count * 2);
144 sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
145 current_address += server_register->register_count;
155 "Could not match any register to address 0x%02X, but default allowed. "
156 "Returning default value: %d.",
159 current_address += 1;
162 "Could not match any register to address 0x%02X and default not allowed. Sending exception response.",
170 std::vector<uint8_t> response;
171 for (
auto v : sixteen_bit_response) {
173 response.push_back(decoded_value[0]);
174 response.push_back(decoded_value[1]);
177 this->
send(function_code, start_address, number_of_registers, response.size(), response.data());
181 uint16_t number_of_registers;
182 uint16_t payload_offset;
185 if (
data.size() < 5) {
186 ESP_LOGW(TAG,
"Write multiple registers data too short (%zu bytes)",
data.size());
190 number_of_registers = uint16_t(
data[3]) | (uint16_t(
data[2]) << 8);
192 ESP_LOGW(TAG,
"Invalid number of registers %d. Sending exception response.", number_of_registers);
198 ESP_LOGW(TAG,
"Payload size of %d bytes is not 2 times the number of registers (%d). Sending exception response.",
204 ESP_LOGW(TAG,
"Write multiple registers payload truncated (%zu bytes, expected %u)",
data.size(),
211 if (
data.size() < 4) {
212 ESP_LOGW(TAG,
"Write single register data too short (%zu bytes)",
data.size());
216 number_of_registers = 1;
219 ESP_LOGW(TAG,
"Invalid function code 0x%X. Sending exception response.", function_code);
224 uint16_t start_address = uint16_t(
data[1]) | (uint16_t(
data[0]) << 8);
226 "Received write holding registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
228 this->
address_, function_code, start_address, number_of_registers);
230 auto for_each_register = [
this, start_address, number_of_registers, payload_offset](
231 const std::function<bool(
ServerRegister *, uint16_t offset)> &callback) ->
bool {
232 uint16_t offset = payload_offset;
233 for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
236 if (server_register->address == current_address) {
237 ok = callback(server_register, offset);
238 current_address += server_register->register_count;
239 offset += server_register->register_count *
sizeof(uint16_t);
252 if (!for_each_register([](
ServerRegister *server_register, uint16_t offset) ->
bool {
268 std::vector<uint8_t> response;
271 response.push_back(function_code);
272 response.insert(response.end(),
data.begin(),
data.begin() + 4);
277 auto reg_it = std::find_if(
282 ESP_LOGE(TAG,
"No matching range for sensor found - start_address : 0x%X", start_address);
284 return reg_it->sensors;
291 const std::vector<uint8_t> &
data) {
292 ESP_LOGV(TAG,
"data for register address : 0x%X : ", start_address);
296 for (
auto *sensor : sensors) {
297 sensor->parse_and_publish(
data);
306 if (item->is_equal(command)) {
307 ESP_LOGW(TAG,
"Duplicate modbus command found: type=0x%x address=%u count=%u",
311 item->payload = command.
payload;
316 this->
command_queue_.push_back(make_unique<ModbusCommandItem>(command));
326 if (!sensors.empty()) {
327 auto sensor = sensors.cbegin();
329 this, (*sensor)->custom_data,
331 this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
333 command_item.register_address = (*sensor)->start_address;
334 command_item.register_count = (*sensor)->register_count;
352 ESP_LOGV(TAG,
"%zu modbus commands already in queue", this->
command_queue_.size());
354 ESP_LOGV(TAG,
"Updating modbus component");
358 ESP_LOGVV(TAG,
"Updating range 0x%X", r.start_address);
367 ESP_LOGW(TAG,
"No sensors registered");
374 uint8_t buffer_offset = 0;
392 ESP_LOGV(TAG,
"Started new range");
412 ESP_LOGV(TAG,
"Re-use previous register - change to register: 0x%X %d offset=%u", curr->
start_address,
421 curr->
offset += buffer_offset;
429 ESP_LOGV(TAG,
"Extend range - change to register: 0x%X %d offset=%u", curr->
start_address,
472 "ModbusController:\n"
474 " Max Command Retries: %d\n"
475 " Offline Skip Updates: %d\n"
476 " Server Courtesy Response:\n"
478 " Register Last Address: 0x%02X\n"
479 " Register Value: %d",
482 this->server_courtesy_response_.register_last_address, this->server_courtesy_response_.register_value);
484#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
485 ESP_LOGCONFIG(TAG,
"sensormap");
487 ESP_LOGCONFIG(TAG,
" Sensor type=%u start=0x%X offset=0x%X count=%d size=%zu",
488 static_cast<uint8_t
>(it->register_type), it->start_address, it->offset, it->register_count,
489 it->get_register_size());
491 ESP_LOGCONFIG(TAG,
"ranges");
493 ESP_LOGCONFIG(TAG,
" Range type=%u start=0x%X count=%d skip_updates=%d",
static_cast<uint8_t
>(it.register_type),
494 it.start_address, it.register_count, it.skip_updates);
496 ESP_LOGCONFIG(TAG,
"server registers");
498 ESP_LOGCONFIG(TAG,
" Address=0x%02X value_type=%u register_count=%u", r->address,
499 static_cast<uint8_t
>(r->value_type), r->register_count);
519 const std::vector<uint8_t> &
data) {
525 ESP_LOGV(TAG,
"sensors");
527 ESP_LOGV(TAG,
" Sensor start=0x%X count=%d size=%zu offset=%d", it->start_address, it->register_count,
528 it->get_register_size(), it->offset);
534 std::function<
void(
ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &
data)>
548 uint16_t register_count) {
556 const std::vector<uint8_t> &
data) {
563 uint16_t start_address, uint16_t register_count,
564 const std::vector<uint16_t> &values) {
572 const std::vector<uint8_t> &
data) {
575 for (
auto v : values) {
577 cmd.
payload.push_back(decoded_value[0]);
578 cmd.
payload.push_back(decoded_value[1]);
592 const std::vector<uint8_t> &
data) {
595 cmd.
payload.push_back(value ? 0xFF : 0);
601 const std::vector<bool> &values) {
609 const std::vector<uint8_t> &
data) {
615 for (
auto coil : values) {
617 bitmask |= (1 << bitcounter);
620 if (bitcounter % 8 == 0) {
621 cmd.
payload.push_back(bitmask);
626 if (bitcounter % 8) {
627 cmd.
payload.push_back(bitmask);
641 const std::vector<uint8_t> &
data) {
646 cmd.
payload.push_back(decoded_value[0]);
647 cmd.
payload.push_back(decoded_value[1]);
653 std::function<
void(
ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &
data)>
658 if (handler ==
nullptr) {
660 ESP_LOGI(TAG,
"Custom Command sent");
672 std::function<
void(
ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &
data)>
677 if (handler ==
nullptr) {
679 ESP_LOGI(TAG,
"Custom Command sent");
684 for (
auto v : values) {
685 cmd.
payload.push_back((v >> 8) & 0xFF);
686 cmd.
payload.push_back(v & 0xFF);
695 this->payload.empty() ?
nullptr : &this->payload[0]);
void send_raw(const std::vector< uint8_t > &payload)
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len=0, const uint8_t *payload=nullptr)
bool ready_for_immediate_send()
void send_error(uint8_t function_code, ModbusExceptionCode exception_code)
static ModbusCommandItem create_custom_command(ModbusController *modbusdevice, const std::vector< uint8_t > &values, std::function< void(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)> &&handler=nullptr)
Create custom modbus command.
bool is_equal(const ModbusCommandItem &other)
static ModbusCommandItem create_write_multiple_coils(ModbusController *modbusdevice, uint16_t start_address, const std::vector< bool > &values)
Create modbus write multiple registers command Function 15 (0Fhex) Write Multiple Coils.
static ModbusCommandItem create_write_single_coil(ModbusController *modbusdevice, uint16_t address, bool value)
Create modbus write single registers command Function 05 (05hex) Write Single Coil.
uint8_t send_count_
How many times this command has been sent.
static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, uint16_t value)
Create modbus write multiple registers command Function 16 (10hex) Write Multiple Registers.
static ModbusCommandItem create_read_command(ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count, std::function< void(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)> &&handler)
factory methods
ModbusRegisterType register_type
static ModbusCommandItem create_write_multiple_command(ModbusController *modbusdevice, uint16_t start_address, uint16_t register_count, const std::vector< uint16_t > &values)
Create modbus read command Function code 02-04.
uint16_t register_address
ModbusController * modbusdevice
ModbusFunctionCode function_code
std::function< void(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)> on_data_func
std::vector< uint8_t > payload
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)
default delegate called by process_modbus_data when a response has retrieved from the incoming queue
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final
called when a modbus request (function code 0x03 or 0x04) was parsed without errors
std::queue< std::unique_ptr< ModbusCommandItem > > incoming_queue_
modbus response data waiting to get processed
uint16_t command_throttle_
min time in ms between sending modbus commands
void on_write_register_response(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)
default delegate called by process_modbus_data when a response for a write response has retrieved fro...
bool allow_duplicate_commands_
if duplicate commands can be sent
CallbackManager< void(int, int)> command_sent_callback_
Command sent callback.
std::vector< RegisterRange > register_ranges_
Continuous range of modbus registers.
uint32_t last_command_timestamp_
when was the last send operation
CallbackManager< void(int, int)> offline_callback_
Server offline callback.
void dump_sensors_()
dump the parsed sensormap for diagnostics
std::vector< ServerRegister * > server_registers_
Collection of all server registers for this component.
SensorSet sensorset_
Collection of all sensors for this component.
uint8_t max_cmd_retries_
How many times we will retry a command if we get no response.
bool send_next_command_()
send the next modbus command from the send queue
std::list< std::unique_ptr< ModbusCommandItem > > command_queue_
Hold the pending requests to be sent.
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override
called when a modbus error response was received
void process_modbus_data_(const ModbusCommandItem *response)
parse incoming modbus data
void update_range_(RegisterRange &r)
submit the read command for the address range to the send queue
ServerCourtesyResponse server_courtesy_response_
Server courtesy response.
bool module_offline_
if module didn't respond the last command
size_t create_register_ranges_()
parse sensormap_ and create range of sequential addresses
uint16_t offline_skip_updates_
how many updates to skip if module is offline
void on_modbus_write_registers(uint8_t function_code, const std::vector< uint8_t > &data) final
called when a modbus request (function code 0x06 or 0x10) was parsed without errors
void dump_config() override
void on_modbus_data(const std::vector< uint8_t > &data) override
called when a modbus response was parsed without errors
void queue_command(const ModbusCommandItem &command)
queues a modbus command in the send queue
SensorSet find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const
CallbackManager< void(int, int)> online_callback_
Server online callback.
ModbusRegisterType register_type
virtual size_t get_register_size() const
SensorValueType value_type
ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type)
T get_data(const std::vector< uint8_t > &data, size_t buffer_offset)
Extract data from modbus response buffer.
int64_t payload_to_number(const std::vector< uint8_t > &data, SensorValueType sensor_value_type, uint8_t offset, uint32_t bitmask)
Convert vector<uint8_t> response payload to number.
void number_to_payload(std::vector< uint16_t > &data, int64_t value, SensorValueType value_type)
Convert float value to vector<uint16_t> suitable for sending.
std::set< SensorItem *, SensorItemsComparator > SensorSet
const std::vector< uint8_t > & data
@ WRITE_MULTIPLE_REGISTERS
const uint8_t MAX_NUM_OF_REGISTERS_TO_READ
const uint8_t MAX_NUM_OF_REGISTERS_TO_WRITE
Providing packet encoding functions for exchanging data with a remote host.
constexpr std::array< uint8_t, sizeof(T)> decode_value(T val)
Decode a value into its constituent bytes (from most to least significant).
uint32_t IRAM_ATTR HOT millis()
ModbusRegisterType register_type
uint16_t skip_updates_counter
uint16_t register_last_address