ESPHome 2025.5.0
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_) && !waiting_for_response() && !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 auto &current_command = this->command_queue_.front();
63 if (current_command != nullptr) {
64 if (this->module_offline_) {
65 ESP_LOGW(TAG, "Modbus device=%d back online", this->address_);
66
67 if (this->offline_skip_updates_ > 0) {
68 // Restore skip_updates_counter to restore commands updates
69 for (auto &r : this->register_ranges_) {
70 r.skip_updates_counter = 0;
71 }
72 }
73 // Restore module online state
74 this->module_offline_ = false;
75 this->online_callback_.call((int) current_command->function_code, current_command->register_address);
76 }
77
78 // Move the commandItem to the response queue
79 current_command->payload = data;
80 this->incoming_queue_.push(std::move(current_command));
81 ESP_LOGV(TAG, "Modbus response queued");
82 this->command_queue_.pop_front();
83 }
84}
85
86// Dispatch the response to the registered handler
88 ESP_LOGV(TAG, "Process modbus response for address 0x%X size: %zu", response->register_address,
89 response->payload.size());
90 response->on_data_func(response->register_type, response->register_address, response->payload);
91}
92
93void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_code) {
94 ESP_LOGE(TAG, "Modbus error function code: 0x%X exception: %d ", function_code, exception_code);
95 // Remove pending command waiting for a response
96 auto &current_command = this->command_queue_.front();
97 if (current_command != nullptr) {
98 ESP_LOGE(TAG,
99 "Modbus error - last command: function code=0x%X register address = 0x%X "
100 "registers count=%d "
101 "payload size=%zu",
102 function_code, current_command->register_address, current_command->register_count,
103 current_command->payload.size());
104 this->command_queue_.pop_front();
105 }
106}
107
108void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t start_address,
109 uint16_t number_of_registers) {
110 ESP_LOGD(TAG,
111 "Received read holding/input registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
112 "0x%X.",
113 this->address_, function_code, start_address, number_of_registers);
114
115 std::vector<uint16_t> sixteen_bit_response;
116 for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
117 bool found = false;
118 for (auto *server_register : this->server_registers_) {
119 if (server_register->address == current_address) {
120 float value = server_register->read_lambda();
121
122 ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.",
123 server_register->address, static_cast<uint8_t>(server_register->value_type),
124 server_register->register_count, value);
125 std::vector<uint16_t> payload = float_to_payload(value, server_register->value_type);
126 sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
127 current_address += server_register->register_count;
128 found = true;
129 break;
130 }
131 }
132
133 if (!found) {
134 ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address);
135 std::vector<uint8_t> error_response;
136 error_response.push_back(this->address_);
137 error_response.push_back(0x81);
138 error_response.push_back(0x02);
139 this->send_raw(error_response);
140 return;
141 }
142 }
143
144 std::vector<uint8_t> response;
145 for (auto v : sixteen_bit_response) {
146 auto decoded_value = decode_value(v);
147 response.push_back(decoded_value[0]);
148 response.push_back(decoded_value[1]);
149 }
150
151 this->send(function_code, start_address, number_of_registers, response.size(), response.data());
152}
153
154SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
155 auto reg_it = std::find_if(
156 std::begin(this->register_ranges_), std::end(this->register_ranges_),
157 [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); });
158
159 if (reg_it == this->register_ranges_.end()) {
160 ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
161 } else {
162 return reg_it->sensors;
163 }
164
165 // not found
166 return {};
167}
168void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
169 const std::vector<uint8_t> &data) {
170 ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
171
172 // loop through all sensors with the same start address
173 auto sensors = find_sensors_(register_type, start_address);
174 for (auto *sensor : sensors) {
175 sensor->parse_and_publish(data);
176 }
177}
178
180 if (!this->allow_duplicate_commands_) {
181 // check if this command is already qeued.
182 // not very effective but the queue is never really large
183 for (auto &item : this->command_queue_) {
184 if (item->is_equal(command)) {
185 ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
186 static_cast<uint8_t>(command.register_type), command.register_address, command.register_count);
187 // update the payload of the queued command
188 // replaces a previous command
189 item->payload = command.payload;
190 return;
191 }
192 }
193 }
194 this->command_queue_.push_back(make_unique<ModbusCommandItem>(command));
195}
196
198 ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type,
200 if (r.skip_updates_counter == 0) {
201 // if a custom command is used the user supplied custom_data is only available in the SensorItem.
203 auto sensors = this->find_sensors_(r.register_type, r.start_address);
204 if (!sensors.empty()) {
205 auto sensor = sensors.cbegin();
207 this, (*sensor)->custom_data,
208 [this](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
209 this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
210 });
211 command_item.register_address = (*sensor)->start_address;
212 command_item.register_count = (*sensor)->register_count;
213 command_item.function_code = ModbusFunctionCode::CUSTOM;
214 queue_command(command_item);
215 }
216 } else {
218 }
219 r.skip_updates_counter = r.skip_updates; // reset counter to config value
220 } else {
222 }
223}
224//
225// Queue the modbus requests to be send.
226// Once we get a response to the command it is removed from the queue and the next command is send
227//
229 if (!this->command_queue_.empty()) {
230 ESP_LOGV(TAG, "%zu modbus commands already in queue", this->command_queue_.size());
231 } else {
232 ESP_LOGV(TAG, "Updating modbus component");
233 }
234
235 for (auto &r : this->register_ranges_) {
236 ESP_LOGVV(TAG, "Updating range 0x%X", r.start_address);
237 update_range_(r);
238 }
239}
240
241// walk through the sensors and determine the register ranges to read
243 this->register_ranges_.clear();
244 if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) {
245 ESP_LOGW(TAG, "No sensors registered");
246 return 0;
247 }
248
249 // iterator is sorted see SensorItemsComparator for details
250 auto ix = this->sensorset_.begin();
251 RegisterRange r = {};
252 uint8_t buffer_offset = 0;
253 SensorItem *prev = nullptr;
254 while (ix != this->sensorset_.end()) {
255 SensorItem *curr = *ix;
256
257 ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count,
258 curr->offset, curr->get_register_size(), curr->offset, curr->skip_updates, curr);
259
260 if (r.register_count == 0) {
261 // this is the first register in range
265 r.sensors.insert(curr);
266 r.skip_updates = curr->skip_updates;
268 buffer_offset = curr->get_register_size();
269
270 ESP_LOGV(TAG, "Started new range");
271 } else {
272 // this is not the first register in range so it might be possible
273 // to reuse the last register or extend the current range
274 if (!curr->force_new_range && r.register_type == curr->register_type &&
276 if (curr->start_address == (r.start_address + r.register_count - prev->register_count) &&
277 curr->register_count == prev->register_count && curr->get_register_size() == prev->get_register_size()) {
278 // this register can re-use the data from the previous register
279
280 // remove this sensore because start_address is changed (sort-order)
281 ix = this->sensorset_.erase(ix);
282
284 curr->offset += prev->offset;
285
286 this->sensorset_.insert(curr);
287 // move iterator backwards because it will be incremented later
288 ix--;
289
290 ESP_LOGV(TAG, "Re-use previous register - change to register: 0x%X %d offset=%u", curr->start_address,
291 curr->register_count, curr->offset);
292 } else if (curr->start_address == (r.start_address + r.register_count)) {
293 // this register can extend the current range
294
295 // remove this sensore because start_address is changed (sort-order)
296 ix = this->sensorset_.erase(ix);
297
299 curr->offset += buffer_offset;
300 buffer_offset += curr->get_register_size();
302
303 this->sensorset_.insert(curr);
304 // move iterator backwards because it will be incremented later
305 ix--;
306
307 ESP_LOGV(TAG, "Extend range - change to register: 0x%X %d offset=%u", curr->start_address,
308 curr->register_count, curr->offset);
309 }
310 }
311 }
312
313 if (curr->start_address == r.start_address && curr->register_type == r.register_type) {
314 // use the lowest non zero value for the whole range
315 // Because zero is the default value for skip_updates it is excluded from getting the min value.
316 if (curr->skip_updates != 0) {
317 if (r.skip_updates != 0) {
318 r.skip_updates = std::min(r.skip_updates, curr->skip_updates);
319 } else {
320 r.skip_updates = curr->skip_updates;
321 }
322 }
323
324 // add sensor to this range
325 r.sensors.insert(curr);
326
327 ix++;
328 } else {
329 ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
330 this->register_ranges_.push_back(r);
331 r = {};
332 buffer_offset = 0;
333 // do not increment the iterator here because the current sensor has to be re-evaluated
334 }
335
336 prev = curr;
337 }
338
339 if (r.register_count > 0) {
340 // Add the last range
341 ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
342 this->register_ranges_.push_back(r);
343 }
344
345 return this->register_ranges_.size();
346}
347
349 ESP_LOGCONFIG(TAG, "ModbusController:");
350 ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
351 ESP_LOGCONFIG(TAG, " Max Command Retries: %d", this->max_cmd_retries_);
352 ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_);
353#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
354 ESP_LOGCONFIG(TAG, "sensormap");
355 for (auto &it : this->sensorset_) {
356 ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d",
357 static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
358 it->get_register_size());
359 }
360 ESP_LOGCONFIG(TAG, "ranges");
361 for (auto &it : this->register_ranges_) {
362 ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
363 it.start_address, it.register_count, it.skip_updates);
364 }
365 ESP_LOGCONFIG(TAG, "server registers");
366 for (auto &r : this->server_registers_) {
367 ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address,
368 static_cast<uint8_t>(r->value_type), r->register_count);
369 }
370#endif
371}
372
374 // Incoming data to process?
375 if (!this->incoming_queue_.empty()) {
376 auto &message = this->incoming_queue_.front();
377 if (message != nullptr)
378 this->process_modbus_data_(message.get());
379 this->incoming_queue_.pop();
380
381 } else {
382 // all messages processed send pending commands
383 this->send_next_command_();
384 }
385}
386
387void ModbusController::on_write_register_response(ModbusRegisterType register_type, uint16_t start_address,
388 const std::vector<uint8_t> &data) {
389 ESP_LOGV(TAG, "Command ACK 0x%X %d ", get_data<uint16_t>(data, 0), get_data<int16_t>(data, 1));
390}
391
393 ESP_LOGV(TAG, "sensors");
394 for (auto &it : this->sensorset_) {
395 ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count,
396 it->get_register_size(), it->offset);
397 }
398}
399
401 ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count,
402 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
403 &&handler) {
408 cmd.register_address = start_address;
410 cmd.on_data_func = std::move(handler);
411 return cmd;
412}
413
415 ModbusRegisterType register_type, uint16_t start_address,
416 uint16_t register_count) {
421 cmd.register_address = start_address;
423 cmd.on_data_func = [modbusdevice](ModbusRegisterType register_type, uint16_t start_address,
424 const std::vector<uint8_t> &data) {
425 modbusdevice->on_register_data(register_type, start_address, data);
426 };
427 return cmd;
428}
429
431 uint16_t start_address, uint16_t register_count,
432 const std::vector<uint16_t> &values) {
437 cmd.register_address = start_address;
439 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
440 const std::vector<uint8_t> &data) {
441 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
442 };
443 for (auto v : values) {
444 auto decoded_value = decode_value(v);
445 cmd.payload.push_back(decoded_value[0]);
446 cmd.payload.push_back(decoded_value[1]);
447 }
448 return cmd;
449}
450
452 bool value) {
458 cmd.register_count = 1;
459 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
460 const std::vector<uint8_t> &data) {
461 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
462 };
463 cmd.payload.push_back(value ? 0xFF : 0);
464 cmd.payload.push_back(0);
465 return cmd;
466}
467
469 const std::vector<bool> &values) {
474 cmd.register_address = start_address;
475 cmd.register_count = values.size();
476 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
477 const std::vector<uint8_t> &data) {
478 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
479 };
480
481 uint8_t bitmask = 0;
482 int bitcounter = 0;
483 for (auto coil : values) {
484 if (coil) {
485 bitmask |= (1 << bitcounter);
486 }
487 bitcounter++;
488 if (bitcounter % 8 == 0) {
489 cmd.payload.push_back(bitmask);
490 bitmask = 0;
491 }
492 }
493 // add remaining bits
494 if (bitcounter % 8) {
495 cmd.payload.push_back(bitmask);
496 }
497 return cmd;
498}
499
501 uint16_t value) {
506 cmd.register_address = start_address;
507 cmd.register_count = 1; // not used here anyways
508 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
509 const std::vector<uint8_t> &data) {
510 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
511 };
512
513 auto decoded_value = decode_value(value);
514 cmd.payload.push_back(decoded_value[0]);
515 cmd.payload.push_back(decoded_value[1]);
516 return cmd;
517}
518
520 ModbusController *modbusdevice, const std::vector<uint8_t> &values,
521 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
522 &&handler) {
526 if (handler == nullptr) {
527 cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
528 ESP_LOGI(TAG, "Custom Command sent");
529 };
530 } else {
531 cmd.on_data_func = handler;
532 }
533 cmd.payload = values;
534
535 return cmd;
536}
537
539 ModbusController *modbusdevice, const std::vector<uint16_t> &values,
540 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
541 &&handler) {
542 ModbusCommandItem cmd = {};
545 if (handler == nullptr) {
546 cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
547 ESP_LOGI(TAG, "Custom Command sent");
548 };
549 } else {
550 cmd.on_data_func = handler;
551 }
552 for (auto v : values) {
553 cmd.payload.push_back((v >> 8) & 0xFF);
554 cmd.payload.push_back(v & 0xFF);
555 }
556
557 return cmd;
558}
559
562 modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(),
563 this->payload.empty() ? nullptr : &this->payload[0]);
564 } else {
566 }
567 this->send_count_++;
568 ESP_LOGV(TAG, "Command sent %d 0x%X %d send_count: %d", uint8_t(this->function_code), this->register_address,
569 this->register_count, this->send_count_);
570 return true;
571}
572
574 // for custom commands we have to check for identical payloads, since
575 // address/count/type fields will be set to zero
577 ? this->payload == other.payload
578 : other.register_address == this->register_address && other.register_count == this->register_count &&
579 other.register_type == this->register_type && other.function_code == this->function_code;
580}
581
582void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
583 switch (value_type) {
586 data.push_back(value & 0xFFFF);
587 break;
591 data.push_back((value & 0xFFFF0000) >> 16);
592 data.push_back(value & 0xFFFF);
593 break;
597 data.push_back(value & 0xFFFF);
598 data.push_back((value & 0xFFFF0000) >> 16);
599 break;
602 data.push_back((value & 0xFFFF000000000000) >> 48);
603 data.push_back((value & 0xFFFF00000000) >> 32);
604 data.push_back((value & 0xFFFF0000) >> 16);
605 data.push_back(value & 0xFFFF);
606 break;
609 data.push_back(value & 0xFFFF);
610 data.push_back((value & 0xFFFF0000) >> 16);
611 data.push_back((value & 0xFFFF00000000) >> 32);
612 data.push_back((value & 0xFFFF000000000000) >> 48);
613 break;
614 default:
615 ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversation: %d",
616 static_cast<uint16_t>(value_type));
617 break;
618 }
619}
620
621int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
622 uint32_t bitmask) {
623 int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
624
625 size_t size = data.size() - offset;
626 bool error = false;
627 switch (sensor_value_type) {
629 if (size >= 2) {
630 value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ;
631 } else {
632 error = true;
633 }
634 break;
637 if (size >= 4) {
638 value = get_data<uint32_t>(data, offset);
639 value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
640 } else {
641 error = true;
642 }
643 break;
646 if (size >= 4) {
647 value = get_data<uint32_t>(data, offset);
648 value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
649 value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
650 } else {
651 error = true;
652 }
653 break;
655 if (size >= 2) {
656 value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
657 bitmask); // default is 0xFFFF ;
658 } else {
659 error = true;
660 }
661 break;
663 if (size >= 4) {
664 value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
665 } else {
666 error = true;
667 }
668 break;
670 if (size >= 4) {
671 value = get_data<uint32_t>(data, offset);
672 // Currently the high word is at the low position
673 // the sign bit is therefore at low before the switch
674 uint32_t sign_bit = (value & 0x8000) << 16;
676 static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
677 } else {
678 error = true;
679 }
680 } break;
683 // Ignore bitmask for QWORD
684 if (size >= 8) {
685 value = get_data<uint64_t>(data, offset);
686 } else {
687 error = true;
688 }
689 break;
692 // Ignore bitmask for QWORD
693 if (size >= 8) {
694 uint64_t tmp = get_data<uint64_t>(data, offset);
695 value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
696 } else {
697 error = true;
698 }
699 } break;
701 default:
702 break;
703 }
704 if (error)
705 ESP_LOGE(TAG, "not enough data for value");
706 return value;
707}
708
709void ModbusController::add_on_command_sent_callback(std::function<void(int, int)> &&callback) {
710 this->command_sent_callback_.add(std::move(callback));
711}
712
713void ModbusController::add_on_online_callback(std::function<void(int, int)> &&callback) {
714 this->online_callback_.add(std::move(callback));
715}
716
717void ModbusController::add_on_offline_callback(std::function<void(int, int)> &&callback) {
718 this->offline_callback_.add(std::move(callback));
719}
720
721} // namespace modbus_controller
722} // namespace esphome
uint8_t address
Definition bl0906.h:4
void send_raw(const std::vector< uint8_t > &payload)
Definition modbus.h:66
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:62
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 3 or 4) was parsed without errors
std::queue< std::unique_ptr< ModbusCommandItem > > incoming_queue_
modbus response data waiting to get processed
void add_on_online_callback(std::function< void(int, int)> &&callback)
Set callback for online changes.
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
void add_on_offline_callback(std::function< void(int, int)> &&callback)
Set callback for offline changes.
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
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 add_on_command_sent_callback(std::function< void(int, int)> &&callback)
Set callback for commands.
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.
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
ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type)
std::vector< uint16_t > float_to_payload(float value, SensorValueType value_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.
N mask_and_shift_by_rightbit(N data, uint32_t mask)
Extract bits from value and shift right according to the bitmask if the bitmask is 0x00F0 we want the...
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
constexpr14 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:221
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27