ESPHome 2025.6.3
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,
350 "ModbusController:\n"
351 " Address: 0x%02X\n"
352 " Max Command Retries: %d\n"
353 " Offline Skip Updates: %d",
355#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
356 ESP_LOGCONFIG(TAG, "sensormap");
357 for (auto &it : this->sensorset_) {
358 ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d",
359 static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
360 it->get_register_size());
361 }
362 ESP_LOGCONFIG(TAG, "ranges");
363 for (auto &it : this->register_ranges_) {
364 ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
365 it.start_address, it.register_count, it.skip_updates);
366 }
367 ESP_LOGCONFIG(TAG, "server registers");
368 for (auto &r : this->server_registers_) {
369 ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address,
370 static_cast<uint8_t>(r->value_type), r->register_count);
371 }
372#endif
373}
374
376 // Incoming data to process?
377 if (!this->incoming_queue_.empty()) {
378 auto &message = this->incoming_queue_.front();
379 if (message != nullptr)
380 this->process_modbus_data_(message.get());
381 this->incoming_queue_.pop();
382
383 } else {
384 // all messages processed send pending commands
385 this->send_next_command_();
386 }
387}
388
389void ModbusController::on_write_register_response(ModbusRegisterType register_type, uint16_t start_address,
390 const std::vector<uint8_t> &data) {
391 ESP_LOGV(TAG, "Command ACK 0x%X %d ", get_data<uint16_t>(data, 0), get_data<int16_t>(data, 1));
392}
393
395 ESP_LOGV(TAG, "sensors");
396 for (auto &it : this->sensorset_) {
397 ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count,
398 it->get_register_size(), it->offset);
399 }
400}
401
403 ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count,
404 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
405 &&handler) {
410 cmd.register_address = start_address;
412 cmd.on_data_func = std::move(handler);
413 return cmd;
414}
415
417 ModbusRegisterType register_type, uint16_t start_address,
418 uint16_t register_count) {
423 cmd.register_address = start_address;
425 cmd.on_data_func = [modbusdevice](ModbusRegisterType register_type, uint16_t start_address,
426 const std::vector<uint8_t> &data) {
427 modbusdevice->on_register_data(register_type, start_address, data);
428 };
429 return cmd;
430}
431
433 uint16_t start_address, uint16_t register_count,
434 const std::vector<uint16_t> &values) {
439 cmd.register_address = start_address;
441 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
442 const std::vector<uint8_t> &data) {
443 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
444 };
445 for (auto v : values) {
446 auto decoded_value = decode_value(v);
447 cmd.payload.push_back(decoded_value[0]);
448 cmd.payload.push_back(decoded_value[1]);
449 }
450 return cmd;
451}
452
454 bool value) {
460 cmd.register_count = 1;
461 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
462 const std::vector<uint8_t> &data) {
463 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
464 };
465 cmd.payload.push_back(value ? 0xFF : 0);
466 cmd.payload.push_back(0);
467 return cmd;
468}
469
471 const std::vector<bool> &values) {
476 cmd.register_address = start_address;
477 cmd.register_count = values.size();
478 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
479 const std::vector<uint8_t> &data) {
480 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
481 };
482
483 uint8_t bitmask = 0;
484 int bitcounter = 0;
485 for (auto coil : values) {
486 if (coil) {
487 bitmask |= (1 << bitcounter);
488 }
489 bitcounter++;
490 if (bitcounter % 8 == 0) {
491 cmd.payload.push_back(bitmask);
492 bitmask = 0;
493 }
494 }
495 // add remaining bits
496 if (bitcounter % 8) {
497 cmd.payload.push_back(bitmask);
498 }
499 return cmd;
500}
501
503 uint16_t value) {
508 cmd.register_address = start_address;
509 cmd.register_count = 1; // not used here anyways
510 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
511 const std::vector<uint8_t> &data) {
512 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
513 };
514
515 auto decoded_value = decode_value(value);
516 cmd.payload.push_back(decoded_value[0]);
517 cmd.payload.push_back(decoded_value[1]);
518 return cmd;
519}
520
522 ModbusController *modbusdevice, const std::vector<uint8_t> &values,
523 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
524 &&handler) {
528 if (handler == nullptr) {
529 cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
530 ESP_LOGI(TAG, "Custom Command sent");
531 };
532 } else {
533 cmd.on_data_func = handler;
534 }
535 cmd.payload = values;
536
537 return cmd;
538}
539
541 ModbusController *modbusdevice, const std::vector<uint16_t> &values,
542 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
543 &&handler) {
544 ModbusCommandItem cmd = {};
547 if (handler == nullptr) {
548 cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
549 ESP_LOGI(TAG, "Custom Command sent");
550 };
551 } else {
552 cmd.on_data_func = handler;
553 }
554 for (auto v : values) {
555 cmd.payload.push_back((v >> 8) & 0xFF);
556 cmd.payload.push_back(v & 0xFF);
557 }
558
559 return cmd;
560}
561
564 modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(),
565 this->payload.empty() ? nullptr : &this->payload[0]);
566 } else {
568 }
569 this->send_count_++;
570 ESP_LOGV(TAG, "Command sent %d 0x%X %d send_count: %d", uint8_t(this->function_code), this->register_address,
571 this->register_count, this->send_count_);
572 return true;
573}
574
576 // for custom commands we have to check for identical payloads, since
577 // address/count/type fields will be set to zero
579 ? this->payload == other.payload
580 : other.register_address == this->register_address && other.register_count == this->register_count &&
581 other.register_type == this->register_type && other.function_code == this->function_code;
582}
583
584void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
585 switch (value_type) {
588 data.push_back(value & 0xFFFF);
589 break;
593 data.push_back((value & 0xFFFF0000) >> 16);
594 data.push_back(value & 0xFFFF);
595 break;
599 data.push_back(value & 0xFFFF);
600 data.push_back((value & 0xFFFF0000) >> 16);
601 break;
604 data.push_back((value & 0xFFFF000000000000) >> 48);
605 data.push_back((value & 0xFFFF00000000) >> 32);
606 data.push_back((value & 0xFFFF0000) >> 16);
607 data.push_back(value & 0xFFFF);
608 break;
611 data.push_back(value & 0xFFFF);
612 data.push_back((value & 0xFFFF0000) >> 16);
613 data.push_back((value & 0xFFFF00000000) >> 32);
614 data.push_back((value & 0xFFFF000000000000) >> 48);
615 break;
616 default:
617 ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversation: %d",
618 static_cast<uint16_t>(value_type));
619 break;
620 }
621}
622
623int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
624 uint32_t bitmask) {
625 int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
626
627 size_t size = data.size() - offset;
628 bool error = false;
629 switch (sensor_value_type) {
631 if (size >= 2) {
632 value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ;
633 } else {
634 error = true;
635 }
636 break;
639 if (size >= 4) {
640 value = get_data<uint32_t>(data, offset);
641 value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
642 } else {
643 error = true;
644 }
645 break;
648 if (size >= 4) {
649 value = get_data<uint32_t>(data, offset);
650 value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
651 value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
652 } else {
653 error = true;
654 }
655 break;
657 if (size >= 2) {
658 value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
659 bitmask); // default is 0xFFFF ;
660 } else {
661 error = true;
662 }
663 break;
665 if (size >= 4) {
666 value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
667 } else {
668 error = true;
669 }
670 break;
672 if (size >= 4) {
673 value = get_data<uint32_t>(data, offset);
674 // Currently the high word is at the low position
675 // the sign bit is therefore at low before the switch
676 uint32_t sign_bit = (value & 0x8000) << 16;
678 static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
679 } else {
680 error = true;
681 }
682 } break;
685 // Ignore bitmask for QWORD
686 if (size >= 8) {
687 value = get_data<uint64_t>(data, offset);
688 } else {
689 error = true;
690 }
691 break;
694 // Ignore bitmask for QWORD
695 if (size >= 8) {
696 uint64_t tmp = get_data<uint64_t>(data, offset);
697 value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
698 } else {
699 error = true;
700 }
701 } break;
703 default:
704 break;
705 }
706 if (error)
707 ESP_LOGE(TAG, "not enough data for value");
708 return value;
709}
710
711void ModbusController::add_on_command_sent_callback(std::function<void(int, int)> &&callback) {
712 this->command_sent_callback_.add(std::move(callback));
713}
714
715void ModbusController::add_on_online_callback(std::function<void(int, int)> &&callback) {
716 this->online_callback_.add(std::move(callback));
717}
718
719void ModbusController::add_on_offline_callback(std::function<void(int, int)> &&callback) {
720 this->offline_callback_.add(std::move(callback));
721}
722
723} // namespace modbus_controller
724} // 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:222
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28