ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
modbus_controller.cpp
Go to the documentation of this file.
1#include "modbus_controller.h"
3#include "esphome/core/log.h"
4
5namespace esphome {
6namespace modbus_controller {
7
8static const char *const TAG = "modbus_controller";
9
11
12/*
13 To work with the existing modbus class and avoid polling for responses a command queue is used.
14 send_next_command will submit the command at the top of the queue and set the corresponding callback
15 to handle the response from the device.
16 Once the response has been processed it is removed from the queue and the next command is sent
17*/
19 uint32_t last_send = millis() - this->last_command_timestamp_;
20
21 if ((last_send > this->command_throttle_) && this->ready_for_immediate_send() && !this->command_queue_.empty()) {
22 auto &command = this->command_queue_.front();
23
24 // remove from queue if command was sent too often
25 if (!command->should_retry(this->max_cmd_retries_)) {
26 if (!this->module_offline_) {
27 ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_);
28
29 if (this->offline_skip_updates_ > 0) {
30 // Update skip_updates_counter to stop flooding channel with timeouts
31 for (auto &r : this->register_ranges_) {
32 r.skip_updates_counter = this->offline_skip_updates_;
33 }
34 }
35
36 this->module_offline_ = true;
37 this->offline_callback_.call((int) command->function_code, command->register_address);
38 }
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);
41 this->command_queue_.pop_front();
42 } else {
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);
45 command->send();
46
48
49 this->command_sent_callback_.call((int) command->function_code, command->register_address);
50
51 // remove from queue if no handler is defined
52 if (!command->on_data_func) {
53 this->command_queue_.pop_front();
54 }
55 }
56 }
57 return (!this->command_queue_.empty());
58}
59
60// Queue incoming response
61void ModbusController::on_modbus_data(const std::vector<uint8_t> &data) {
62 if (this->command_queue_.empty()) {
63 ESP_LOGW(TAG, "Received modbus data but command queue is empty");
64 return;
65 }
66 auto &current_command = this->command_queue_.front();
67 if (current_command != nullptr) {
68 if (this->module_offline_) {
69 ESP_LOGW(TAG, "Modbus device=%d back online", this->address_);
70
71 if (this->offline_skip_updates_ > 0) {
72 // Restore skip_updates_counter to restore commands updates
73 for (auto &r : this->register_ranges_) {
74 r.skip_updates_counter = 0;
75 }
76 }
77 // Restore module online state
78 this->module_offline_ = false;
79 this->online_callback_.call((int) current_command->function_code, current_command->register_address);
80 }
81
82 // Move the commandItem to the response queue
83 current_command->payload = data;
84 this->incoming_queue_.push(std::move(current_command));
85 ESP_LOGV(TAG, "Modbus response queued");
86 this->command_queue_.pop_front();
87 }
88}
89
90// Dispatch the response to the registered handler
92 ESP_LOGV(TAG, "Process modbus response for address 0x%X size: %zu", response->register_address,
93 response->payload.size());
94 response->on_data_func(response->register_type, response->register_address, response->payload);
95}
96
97void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_code) {
98 ESP_LOGE(TAG, "Modbus error function code: 0x%X exception: %d ", function_code, exception_code);
99 if (this->command_queue_.empty()) {
100 return;
101 }
102 // Remove pending command waiting for a response
103 auto &current_command = this->command_queue_.front();
104 if (current_command != nullptr) {
105 ESP_LOGE(TAG,
106 "Modbus error - last command: function code=0x%X register address = 0x%X "
107 "registers count=%d "
108 "payload size=%zu",
109 function_code, current_command->register_address, current_command->register_count,
110 current_command->payload.size());
111 this->command_queue_.pop_front();
112 }
113}
114
115void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t start_address,
116 uint16_t number_of_registers) {
117 ESP_LOGD(TAG,
118 "Received read holding/input registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
119 "0x%X.",
120 this->address_, function_code, start_address, number_of_registers);
121
122 if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_READ) {
123 ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
125 return;
126 }
127
128 std::vector<uint16_t> sixteen_bit_response;
129 for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
130 bool found = false;
131 for (auto *server_register : this->server_registers_) {
132 if (server_register->address == current_address) {
133 if (!server_register->read_lambda) {
134 break;
135 }
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());
140
141 std::vector<uint16_t> payload;
142 payload.reserve(server_register->register_count * 2);
143 modbus::helpers::number_to_payload(payload, value, server_register->value_type);
144 sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
145 current_address += server_register->register_count;
146 found = true;
147 break;
148 }
149 }
150
151 if (!found) {
153 (current_address <= this->server_courtesy_response_.register_last_address)) {
154 ESP_LOGD(TAG,
155 "Could not match any register to address 0x%02X, but default allowed. "
156 "Returning default value: %d.",
157 current_address, this->server_courtesy_response_.register_value);
158 sixteen_bit_response.push_back(this->server_courtesy_response_.register_value);
159 current_address += 1; // Just increment by 1, as the default response is a single register
160 } else {
161 ESP_LOGW(TAG,
162 "Could not match any register to address 0x%02X and default not allowed. Sending exception response.",
163 current_address);
165 return;
166 }
167 }
168 }
169
170 std::vector<uint8_t> response;
171 for (auto v : sixteen_bit_response) {
172 auto decoded_value = decode_value(v);
173 response.push_back(decoded_value[0]);
174 response.push_back(decoded_value[1]);
175 }
176
177 this->send(function_code, start_address, number_of_registers, response.size(), response.data());
178}
179
180void ModbusController::on_modbus_write_registers(uint8_t function_code, const std::vector<uint8_t> &data) {
181 uint16_t number_of_registers;
182 uint16_t payload_offset;
183
185 if (data.size() < 5) {
186 ESP_LOGW(TAG, "Write multiple registers data too short (%zu bytes)", data.size());
188 return;
189 }
190 number_of_registers = uint16_t(data[3]) | (uint16_t(data[2]) << 8);
191 if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_WRITE) {
192 ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
194 return;
195 }
196 uint16_t payload_size = data[4];
197 if (payload_size != number_of_registers * 2) {
198 ESP_LOGW(TAG, "Payload size of %d bytes is not 2 times the number of registers (%d). Sending exception response.",
199 payload_size, number_of_registers);
201 return;
202 }
203 if (data.size() < 5 + payload_size) {
204 ESP_LOGW(TAG, "Write multiple registers payload truncated (%zu bytes, expected %u)", data.size(),
205 5 + payload_size);
207 return;
208 }
209 payload_offset = 5;
210 } else if (function_code == ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
211 if (data.size() < 4) {
212 ESP_LOGW(TAG, "Write single register data too short (%zu bytes)", data.size());
214 return;
215 }
216 number_of_registers = 1;
217 payload_offset = 2;
218 } else {
219 ESP_LOGW(TAG, "Invalid function code 0x%X. Sending exception response.", function_code);
221 return;
222 }
223
224 uint16_t start_address = uint16_t(data[1]) | (uint16_t(data[0]) << 8);
225 ESP_LOGD(TAG,
226 "Received write holding registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
227 "0x%X.",
228 this->address_, function_code, start_address, number_of_registers);
229
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;) {
234 bool ok = false;
235 for (auto *server_register : this->server_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);
240 break;
241 }
242 }
243
244 if (!ok) {
245 return false;
246 }
247 }
248 return true;
249 };
250
251 // check all registers are writable before writing to any of them:
252 if (!for_each_register([](ServerRegister *server_register, uint16_t offset) -> bool {
253 return server_register->write_lambda != nullptr;
254 })) {
256 return;
257 }
258
259 // Actually write to the registers:
260 if (!for_each_register([&data](ServerRegister *server_register, uint16_t offset) {
261 int64_t number = modbus::helpers::payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF);
262 return server_register->write_lambda(number);
263 })) {
265 return;
266 }
267
268 std::vector<uint8_t> response;
269 response.reserve(6);
270 response.push_back(this->address_);
271 response.push_back(function_code);
272 response.insert(response.end(), data.begin(), data.begin() + 4);
273 this->send_raw(response);
274}
275
276SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
277 auto reg_it = std::find_if(
278 std::begin(this->register_ranges_), std::end(this->register_ranges_),
279 [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); });
280
281 if (reg_it == this->register_ranges_.end()) {
282 ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
283 } else {
284 return reg_it->sensors;
285 }
286
287 // not found
288 return {};
289}
290void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
291 const std::vector<uint8_t> &data) {
292 ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
293
294 // loop through all sensors with the same start address
295 auto sensors = find_sensors_(register_type, start_address);
296 for (auto *sensor : sensors) {
297 sensor->parse_and_publish(data);
298 }
299}
300
302 if (!this->allow_duplicate_commands_) {
303 // check if this command is already qeued.
304 // not very effective but the queue is never really large
305 for (auto &item : this->command_queue_) {
306 if (item->is_equal(command)) {
307 ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
308 static_cast<uint8_t>(command.register_type), command.register_address, command.register_count);
309 // update the payload of the queued command
310 // replaces a previous command
311 item->payload = command.payload;
312 return;
313 }
314 }
315 }
316 this->command_queue_.push_back(make_unique<ModbusCommandItem>(command));
317}
318
320 ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type,
322 if (r.skip_updates_counter == 0) {
323 // if a custom command is used the user supplied custom_data is only available in the SensorItem.
325 auto sensors = this->find_sensors_(r.register_type, r.start_address);
326 if (!sensors.empty()) {
327 auto sensor = sensors.cbegin();
329 this, (*sensor)->custom_data,
330 [this](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
331 this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
332 });
333 command_item.register_address = (*sensor)->start_address;
334 command_item.register_count = (*sensor)->register_count;
335 command_item.function_code = ModbusFunctionCode::CUSTOM;
336 queue_command(command_item);
337 }
338 } else {
340 }
341 r.skip_updates_counter = r.skip_updates; // reset counter to config value
342 } else {
344 }
345}
346//
347// Queue the modbus requests to be send.
348// Once we get a response to the command it is removed from the queue and the next command is send
349//
351 if (!this->command_queue_.empty()) {
352 ESP_LOGV(TAG, "%zu modbus commands already in queue", this->command_queue_.size());
353 } else {
354 ESP_LOGV(TAG, "Updating modbus component");
355 }
356
357 for (auto &r : this->register_ranges_) {
358 ESP_LOGVV(TAG, "Updating range 0x%X", r.start_address);
359 update_range_(r);
360 }
361}
362
363// walk through the sensors and determine the register ranges to read
365 this->register_ranges_.clear();
366 if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) {
367 ESP_LOGW(TAG, "No sensors registered");
368 return 0;
369 }
370
371 // iterator is sorted see SensorItemsComparator for details
372 auto ix = this->sensorset_.begin();
373 RegisterRange r = {};
374 uint8_t buffer_offset = 0;
375 SensorItem *prev = nullptr;
376 while (ix != this->sensorset_.end()) {
377 SensorItem *curr = *ix;
378
379 ESP_LOGV(TAG, "Register: 0x%X %d %d %zu offset=%u skip=%u addr=%p", curr->start_address, curr->register_count,
380 curr->offset, curr->get_register_size(), curr->offset, curr->skip_updates, curr);
381
382 if (r.register_count == 0) {
383 // this is the first register in range
387 r.sensors.insert(curr);
388 r.skip_updates = curr->skip_updates;
390 buffer_offset = curr->get_register_size();
391
392 ESP_LOGV(TAG, "Started new range");
393 } else {
394 // this is not the first register in range so it might be possible
395 // to reuse the last register or extend the current range
396 if (!curr->force_new_range && r.register_type == curr->register_type &&
398 if (curr->start_address == (r.start_address + r.register_count - prev->register_count) &&
399 curr->register_count == prev->register_count && curr->get_register_size() == prev->get_register_size()) {
400 // this register can re-use the data from the previous register
401
402 // remove this sensore because start_address is changed (sort-order)
403 ix = this->sensorset_.erase(ix);
404
406 curr->offset += prev->offset;
407
408 this->sensorset_.insert(curr);
409 // move iterator backwards because it will be incremented later
410 ix--;
411
412 ESP_LOGV(TAG, "Re-use previous register - change to register: 0x%X %d offset=%u", curr->start_address,
413 curr->register_count, curr->offset);
414 } else if (curr->start_address == (r.start_address + r.register_count)) {
415 // this register can extend the current range
416
417 // remove this sensore because start_address is changed (sort-order)
418 ix = this->sensorset_.erase(ix);
419
421 curr->offset += buffer_offset;
422 buffer_offset += curr->get_register_size();
424
425 this->sensorset_.insert(curr);
426 // move iterator backwards because it will be incremented later
427 ix--;
428
429 ESP_LOGV(TAG, "Extend range - change to register: 0x%X %d offset=%u", curr->start_address,
430 curr->register_count, curr->offset);
431 }
432 }
433 }
434
435 if (curr->start_address == r.start_address && curr->register_type == r.register_type) {
436 // use the lowest non zero value for the whole range
437 // Because zero is the default value for skip_updates it is excluded from getting the min value.
438 if (curr->skip_updates != 0) {
439 if (r.skip_updates != 0) {
440 r.skip_updates = std::min(r.skip_updates, curr->skip_updates);
441 } else {
442 r.skip_updates = curr->skip_updates;
443 }
444 }
445
446 // add sensor to this range
447 r.sensors.insert(curr);
448
449 ix++;
450 } else {
451 ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
452 this->register_ranges_.push_back(r);
453 r = {};
454 buffer_offset = 0;
455 // do not increment the iterator here because the current sensor has to be re-evaluated
456 }
457
458 prev = curr;
459 }
460
461 if (r.register_count > 0) {
462 // Add the last range
463 ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
464 this->register_ranges_.push_back(r);
465 }
466
467 return this->register_ranges_.size();
468}
469
471 ESP_LOGCONFIG(TAG,
472 "ModbusController:\n"
473 " Address: 0x%02X\n"
474 " Max Command Retries: %d\n"
475 " Offline Skip Updates: %d\n"
476 " Server Courtesy Response:\n"
477 " Enabled: %s\n"
478 " Register Last Address: 0x%02X\n"
479 " Register Value: %d",
481 this->server_courtesy_response_.enabled ? "true" : "false",
482 this->server_courtesy_response_.register_last_address, this->server_courtesy_response_.register_value);
483
484#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
485 ESP_LOGCONFIG(TAG, "sensormap");
486 for (auto &it : this->sensorset_) {
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());
490 }
491 ESP_LOGCONFIG(TAG, "ranges");
492 for (auto &it : this->register_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);
495 }
496 ESP_LOGCONFIG(TAG, "server registers");
497 for (auto &r : this->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);
500 }
501#endif
502}
503
505 // Incoming data to process?
506 if (!this->incoming_queue_.empty()) {
507 auto &message = this->incoming_queue_.front();
508 if (message != nullptr)
509 this->process_modbus_data_(message.get());
510 this->incoming_queue_.pop();
511
512 } else {
513 // all messages processed send pending commands
514 this->send_next_command_();
515 }
516}
517
518void ModbusController::on_write_register_response(ModbusRegisterType register_type, uint16_t start_address,
519 const std::vector<uint8_t> &data) {
520 ESP_LOGV(TAG, "Command ACK 0x%X %d ", modbus::helpers::get_data<uint16_t>(data, 0),
522}
523
525 ESP_LOGV(TAG, "sensors");
526 for (auto &it : this->sensorset_) {
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);
529 }
530}
531
533 ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count,
534 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
535 &&handler) {
540 cmd.register_address = start_address;
542 cmd.on_data_func = std::move(handler);
543 return cmd;
544}
545
547 ModbusRegisterType register_type, uint16_t start_address,
548 uint16_t register_count) {
553 cmd.register_address = start_address;
555 cmd.on_data_func = [modbusdevice](ModbusRegisterType register_type, uint16_t start_address,
556 const std::vector<uint8_t> &data) {
558 };
559 return cmd;
560}
561
563 uint16_t start_address, uint16_t register_count,
564 const std::vector<uint16_t> &values) {
569 cmd.register_address = start_address;
571 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
572 const std::vector<uint8_t> &data) {
574 };
575 for (auto v : values) {
576 auto decoded_value = decode_value(v);
577 cmd.payload.push_back(decoded_value[0]);
578 cmd.payload.push_back(decoded_value[1]);
579 }
580 return cmd;
581}
582
584 bool value) {
590 cmd.register_count = 1;
591 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
592 const std::vector<uint8_t> &data) {
594 };
595 cmd.payload.push_back(value ? 0xFF : 0);
596 cmd.payload.push_back(0);
597 return cmd;
598}
599
601 const std::vector<bool> &values) {
606 cmd.register_address = start_address;
607 cmd.register_count = values.size();
608 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
609 const std::vector<uint8_t> &data) {
611 };
612
613 uint8_t bitmask = 0;
614 int bitcounter = 0;
615 for (auto coil : values) {
616 if (coil) {
617 bitmask |= (1 << bitcounter);
618 }
619 bitcounter++;
620 if (bitcounter % 8 == 0) {
621 cmd.payload.push_back(bitmask);
622 bitmask = 0;
623 }
624 }
625 // add remaining bits
626 if (bitcounter % 8) {
627 cmd.payload.push_back(bitmask);
628 }
629 return cmd;
630}
631
633 uint16_t value) {
638 cmd.register_address = start_address;
639 cmd.register_count = 1; // not used here anyways
640 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
641 const std::vector<uint8_t> &data) {
643 };
644
645 auto decoded_value = decode_value(value);
646 cmd.payload.push_back(decoded_value[0]);
647 cmd.payload.push_back(decoded_value[1]);
648 return cmd;
649}
650
652 ModbusController *modbusdevice, const std::vector<uint8_t> &values,
653 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
654 &&handler) {
658 if (handler == nullptr) {
659 cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
660 ESP_LOGI(TAG, "Custom Command sent");
661 };
662 } else {
663 cmd.on_data_func = handler;
664 }
665 cmd.payload = values;
666
667 return cmd;
668}
669
671 ModbusController *modbusdevice, const std::vector<uint16_t> &values,
672 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
673 &&handler) {
674 ModbusCommandItem cmd = {};
677 if (handler == nullptr) {
678 cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
679 ESP_LOGI(TAG, "Custom Command sent");
680 };
681 } else {
682 cmd.on_data_func = handler;
683 }
684 for (auto v : values) {
685 cmd.payload.push_back((v >> 8) & 0xFF);
686 cmd.payload.push_back(v & 0xFF);
687 }
688
689 return cmd;
690}
691
694 modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(),
695 this->payload.empty() ? nullptr : &this->payload[0]);
696 } else {
698 }
699 this->send_count_++;
700 ESP_LOGV(TAG, "Command sent %d 0x%X %d send_count: %d", uint8_t(this->function_code), this->register_address,
701 this->register_count, this->send_count_);
702 return true;
703}
704
706 // for custom commands we have to check for identical payloads, since
707 // address/count/type fields will be set to zero
709 ? this->payload == other.payload
710 : other.register_address == this->register_address && other.register_count == this->register_count &&
711 other.register_type == this->register_type && other.function_code == this->function_code;
712}
713
714} // namespace modbus_controller
715} // namespace esphome
uint8_t address
Definition bl0906.h:4
void send_raw(const std::vector< uint8_t > &payload)
Definition modbus.h:104
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len=0, const uint8_t *payload=nullptr)
Definition modbus.h:100
void send_error(uint8_t function_code, ModbusExceptionCode exception_code)
Definition modbus.h:105
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
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.
std::function< void(ModbusRegisterType register_type, uint16_t start_address, const std::vector< uint8_t > &data)> on_data_func
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 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.
const char * message
Definition component.cpp:35
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
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.
Definition a01nyub.cpp:7
constexpr std::array< uint8_t, sizeof(T)> decode_value(T val)
Decode a value into its constituent bytes (from most to least significant).
Definition helpers.h:910
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
static void uint32_t
uint32_t payload_size()