ESPHome 2025.10.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 if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_READ) {
116 ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
118 return;
119 }
120
121 std::vector<uint16_t> sixteen_bit_response;
122 for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
123 bool found = false;
124 for (auto *server_register : this->server_registers_) {
125 if (server_register->address == current_address) {
126 if (!server_register->read_lambda) {
127 break;
128 }
129 int64_t value = server_register->read_lambda();
130 ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
131 server_register->address, static_cast<size_t>(server_register->value_type),
132 server_register->register_count, server_register->format_value(value).c_str());
133
134 std::vector<uint16_t> payload;
135 payload.reserve(server_register->register_count * 2);
136 number_to_payload(payload, value, server_register->value_type);
137 sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
138 current_address += server_register->register_count;
139 found = true;
140 break;
141 }
142 }
143
144 if (!found) {
146 (current_address <= this->server_courtesy_response_.register_last_address)) {
147 ESP_LOGD(TAG,
148 "Could not match any register to address 0x%02X, but default allowed. "
149 "Returning default value: %d.",
150 current_address, this->server_courtesy_response_.register_value);
151 sixteen_bit_response.push_back(this->server_courtesy_response_.register_value);
152 current_address += 1; // Just increment by 1, as the default response is a single register
153 } else {
154 ESP_LOGW(TAG,
155 "Could not match any register to address 0x%02X and default not allowed. Sending exception response.",
156 current_address);
158 return;
159 }
160 }
161 }
162
163 std::vector<uint8_t> response;
164 for (auto v : sixteen_bit_response) {
165 auto decoded_value = decode_value(v);
166 response.push_back(decoded_value[0]);
167 response.push_back(decoded_value[1]);
168 }
169
170 this->send(function_code, start_address, number_of_registers, response.size(), response.data());
171}
172
173void ModbusController::on_modbus_write_registers(uint8_t function_code, const std::vector<uint8_t> &data) {
174 uint16_t number_of_registers;
175 uint16_t payload_offset;
176
178 number_of_registers = uint16_t(data[3]) | (uint16_t(data[2]) << 8);
179 if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_WRITE) {
180 ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
182 return;
183 }
184 uint16_t payload_size = data[4];
185 if (payload_size != number_of_registers * 2) {
186 ESP_LOGW(TAG, "Payload size of %d bytes is not 2 times the number of registers (%d). Sending exception response.",
187 payload_size, number_of_registers);
189 return;
190 }
191 payload_offset = 5;
192 } else if (function_code == ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
193 number_of_registers = 1;
194 payload_offset = 2;
195 } else {
196 ESP_LOGW(TAG, "Invalid function code 0x%X. Sending exception response.", function_code);
198 return;
199 }
200
201 uint16_t start_address = uint16_t(data[1]) | (uint16_t(data[0]) << 8);
202 ESP_LOGD(TAG,
203 "Received write holding registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
204 "0x%X.",
205 this->address_, function_code, start_address, number_of_registers);
206
207 auto for_each_register = [this, start_address, number_of_registers, payload_offset](
208 const std::function<bool(ServerRegister *, uint16_t offset)> &callback) -> bool {
209 uint16_t offset = payload_offset;
210 for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
211 bool ok = false;
212 for (auto *server_register : this->server_registers_) {
213 if (server_register->address == current_address) {
214 ok = callback(server_register, offset);
215 current_address += server_register->register_count;
216 offset += server_register->register_count * sizeof(uint16_t);
217 break;
218 }
219 }
220
221 if (!ok) {
222 return false;
223 }
224 }
225 return true;
226 };
227
228 // check all registers are writable before writing to any of them:
229 if (!for_each_register([](ServerRegister *server_register, uint16_t offset) -> bool {
230 return server_register->write_lambda != nullptr;
231 })) {
233 return;
234 }
235
236 // Actually write to the registers:
237 if (!for_each_register([&data](ServerRegister *server_register, uint16_t offset) {
238 int64_t number = payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF);
239 return server_register->write_lambda(number);
240 })) {
242 return;
243 }
244
245 std::vector<uint8_t> response;
246 response.reserve(6);
247 response.push_back(this->address_);
248 response.push_back(function_code);
249 response.insert(response.end(), data.begin(), data.begin() + 4);
250 this->send_raw(response);
251}
252
253SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
254 auto reg_it = std::find_if(
255 std::begin(this->register_ranges_), std::end(this->register_ranges_),
256 [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); });
257
258 if (reg_it == this->register_ranges_.end()) {
259 ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
260 } else {
261 return reg_it->sensors;
262 }
263
264 // not found
265 return {};
266}
267void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
268 const std::vector<uint8_t> &data) {
269 ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
270
271 // loop through all sensors with the same start address
272 auto sensors = find_sensors_(register_type, start_address);
273 for (auto *sensor : sensors) {
274 sensor->parse_and_publish(data);
275 }
276}
277
279 if (!this->allow_duplicate_commands_) {
280 // check if this command is already qeued.
281 // not very effective but the queue is never really large
282 for (auto &item : this->command_queue_) {
283 if (item->is_equal(command)) {
284 ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
285 static_cast<uint8_t>(command.register_type), command.register_address, command.register_count);
286 // update the payload of the queued command
287 // replaces a previous command
288 item->payload = command.payload;
289 return;
290 }
291 }
292 }
293 this->command_queue_.push_back(make_unique<ModbusCommandItem>(command));
294}
295
297 ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type,
299 if (r.skip_updates_counter == 0) {
300 // if a custom command is used the user supplied custom_data is only available in the SensorItem.
302 auto sensors = this->find_sensors_(r.register_type, r.start_address);
303 if (!sensors.empty()) {
304 auto sensor = sensors.cbegin();
306 this, (*sensor)->custom_data,
307 [this](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
308 this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
309 });
310 command_item.register_address = (*sensor)->start_address;
311 command_item.register_count = (*sensor)->register_count;
312 command_item.function_code = ModbusFunctionCode::CUSTOM;
313 queue_command(command_item);
314 }
315 } else {
317 }
318 r.skip_updates_counter = r.skip_updates; // reset counter to config value
319 } else {
321 }
322}
323//
324// Queue the modbus requests to be send.
325// Once we get a response to the command it is removed from the queue and the next command is send
326//
328 if (!this->command_queue_.empty()) {
329 ESP_LOGV(TAG, "%zu modbus commands already in queue", this->command_queue_.size());
330 } else {
331 ESP_LOGV(TAG, "Updating modbus component");
332 }
333
334 for (auto &r : this->register_ranges_) {
335 ESP_LOGVV(TAG, "Updating range 0x%X", r.start_address);
336 update_range_(r);
337 }
338}
339
340// walk through the sensors and determine the register ranges to read
342 this->register_ranges_.clear();
343 if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) {
344 ESP_LOGW(TAG, "No sensors registered");
345 return 0;
346 }
347
348 // iterator is sorted see SensorItemsComparator for details
349 auto ix = this->sensorset_.begin();
350 RegisterRange r = {};
351 uint8_t buffer_offset = 0;
352 SensorItem *prev = nullptr;
353 while (ix != this->sensorset_.end()) {
354 SensorItem *curr = *ix;
355
356 ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count,
357 curr->offset, curr->get_register_size(), curr->offset, curr->skip_updates, curr);
358
359 if (r.register_count == 0) {
360 // this is the first register in range
364 r.sensors.insert(curr);
365 r.skip_updates = curr->skip_updates;
367 buffer_offset = curr->get_register_size();
368
369 ESP_LOGV(TAG, "Started new range");
370 } else {
371 // this is not the first register in range so it might be possible
372 // to reuse the last register or extend the current range
373 if (!curr->force_new_range && r.register_type == curr->register_type &&
375 if (curr->start_address == (r.start_address + r.register_count - prev->register_count) &&
376 curr->register_count == prev->register_count && curr->get_register_size() == prev->get_register_size()) {
377 // this register can re-use the data from the previous register
378
379 // remove this sensore because start_address is changed (sort-order)
380 ix = this->sensorset_.erase(ix);
381
383 curr->offset += prev->offset;
384
385 this->sensorset_.insert(curr);
386 // move iterator backwards because it will be incremented later
387 ix--;
388
389 ESP_LOGV(TAG, "Re-use previous register - change to register: 0x%X %d offset=%u", curr->start_address,
390 curr->register_count, curr->offset);
391 } else if (curr->start_address == (r.start_address + r.register_count)) {
392 // this register can extend the current range
393
394 // remove this sensore because start_address is changed (sort-order)
395 ix = this->sensorset_.erase(ix);
396
398 curr->offset += buffer_offset;
399 buffer_offset += curr->get_register_size();
401
402 this->sensorset_.insert(curr);
403 // move iterator backwards because it will be incremented later
404 ix--;
405
406 ESP_LOGV(TAG, "Extend range - change to register: 0x%X %d offset=%u", curr->start_address,
407 curr->register_count, curr->offset);
408 }
409 }
410 }
411
412 if (curr->start_address == r.start_address && curr->register_type == r.register_type) {
413 // use the lowest non zero value for the whole range
414 // Because zero is the default value for skip_updates it is excluded from getting the min value.
415 if (curr->skip_updates != 0) {
416 if (r.skip_updates != 0) {
417 r.skip_updates = std::min(r.skip_updates, curr->skip_updates);
418 } else {
419 r.skip_updates = curr->skip_updates;
420 }
421 }
422
423 // add sensor to this range
424 r.sensors.insert(curr);
425
426 ix++;
427 } else {
428 ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
429 this->register_ranges_.push_back(r);
430 r = {};
431 buffer_offset = 0;
432 // do not increment the iterator here because the current sensor has to be re-evaluated
433 }
434
435 prev = curr;
436 }
437
438 if (r.register_count > 0) {
439 // Add the last range
440 ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
441 this->register_ranges_.push_back(r);
442 }
443
444 return this->register_ranges_.size();
445}
446
448 ESP_LOGCONFIG(TAG,
449 "ModbusController:\n"
450 " Address: 0x%02X\n"
451 " Max Command Retries: %d\n"
452 " Offline Skip Updates: %d\n"
453 " Server Courtesy Response:\n"
454 " Enabled: %s\n"
455 " Register Last Address: 0x%02X\n"
456 " Register Value: %d",
458 this->server_courtesy_response_.enabled ? "true" : "false",
459 this->server_courtesy_response_.register_last_address, this->server_courtesy_response_.register_value);
460
461#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
462 ESP_LOGCONFIG(TAG, "sensormap");
463 for (auto &it : this->sensorset_) {
464 ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d",
465 static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
466 it->get_register_size());
467 }
468 ESP_LOGCONFIG(TAG, "ranges");
469 for (auto &it : this->register_ranges_) {
470 ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
471 it.start_address, it.register_count, it.skip_updates);
472 }
473 ESP_LOGCONFIG(TAG, "server registers");
474 for (auto &r : this->server_registers_) {
475 ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address,
476 static_cast<uint8_t>(r->value_type), r->register_count);
477 }
478#endif
479}
480
482 // Incoming data to process?
483 if (!this->incoming_queue_.empty()) {
484 auto &message = this->incoming_queue_.front();
485 if (message != nullptr)
486 this->process_modbus_data_(message.get());
487 this->incoming_queue_.pop();
488
489 } else {
490 // all messages processed send pending commands
491 this->send_next_command_();
492 }
493}
494
495void ModbusController::on_write_register_response(ModbusRegisterType register_type, uint16_t start_address,
496 const std::vector<uint8_t> &data) {
497 ESP_LOGV(TAG, "Command ACK 0x%X %d ", get_data<uint16_t>(data, 0), get_data<int16_t>(data, 1));
498}
499
501 ESP_LOGV(TAG, "sensors");
502 for (auto &it : this->sensorset_) {
503 ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count,
504 it->get_register_size(), it->offset);
505 }
506}
507
509 ModbusController *modbusdevice, ModbusRegisterType register_type, uint16_t start_address, uint16_t register_count,
510 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
511 &&handler) {
516 cmd.register_address = start_address;
518 cmd.on_data_func = std::move(handler);
519 return cmd;
520}
521
523 ModbusRegisterType register_type, uint16_t start_address,
524 uint16_t register_count) {
529 cmd.register_address = start_address;
531 cmd.on_data_func = [modbusdevice](ModbusRegisterType register_type, uint16_t start_address,
532 const std::vector<uint8_t> &data) {
533 modbusdevice->on_register_data(register_type, start_address, data);
534 };
535 return cmd;
536}
537
539 uint16_t start_address, uint16_t register_count,
540 const std::vector<uint16_t> &values) {
545 cmd.register_address = start_address;
547 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
548 const std::vector<uint8_t> &data) {
549 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
550 };
551 for (auto v : values) {
552 auto decoded_value = decode_value(v);
553 cmd.payload.push_back(decoded_value[0]);
554 cmd.payload.push_back(decoded_value[1]);
555 }
556 return cmd;
557}
558
560 bool value) {
566 cmd.register_count = 1;
567 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
568 const std::vector<uint8_t> &data) {
569 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
570 };
571 cmd.payload.push_back(value ? 0xFF : 0);
572 cmd.payload.push_back(0);
573 return cmd;
574}
575
577 const std::vector<bool> &values) {
582 cmd.register_address = start_address;
583 cmd.register_count = values.size();
584 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
585 const std::vector<uint8_t> &data) {
586 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
587 };
588
589 uint8_t bitmask = 0;
590 int bitcounter = 0;
591 for (auto coil : values) {
592 if (coil) {
593 bitmask |= (1 << bitcounter);
594 }
595 bitcounter++;
596 if (bitcounter % 8 == 0) {
597 cmd.payload.push_back(bitmask);
598 bitmask = 0;
599 }
600 }
601 // add remaining bits
602 if (bitcounter % 8) {
603 cmd.payload.push_back(bitmask);
604 }
605 return cmd;
606}
607
609 uint16_t value) {
614 cmd.register_address = start_address;
615 cmd.register_count = 1; // not used here anyways
616 cmd.on_data_func = [modbusdevice, cmd](ModbusRegisterType register_type, uint16_t start_address,
617 const std::vector<uint8_t> &data) {
618 modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
619 };
620
621 auto decoded_value = decode_value(value);
622 cmd.payload.push_back(decoded_value[0]);
623 cmd.payload.push_back(decoded_value[1]);
624 return cmd;
625}
626
628 ModbusController *modbusdevice, const std::vector<uint8_t> &values,
629 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
630 &&handler) {
634 if (handler == nullptr) {
635 cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
636 ESP_LOGI(TAG, "Custom Command sent");
637 };
638 } else {
639 cmd.on_data_func = handler;
640 }
641 cmd.payload = values;
642
643 return cmd;
644}
645
647 ModbusController *modbusdevice, const std::vector<uint16_t> &values,
648 std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
649 &&handler) {
650 ModbusCommandItem cmd = {};
653 if (handler == nullptr) {
654 cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
655 ESP_LOGI(TAG, "Custom Command sent");
656 };
657 } else {
658 cmd.on_data_func = handler;
659 }
660 for (auto v : values) {
661 cmd.payload.push_back((v >> 8) & 0xFF);
662 cmd.payload.push_back(v & 0xFF);
663 }
664
665 return cmd;
666}
667
670 modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(),
671 this->payload.empty() ? nullptr : &this->payload[0]);
672 } else {
674 }
675 this->send_count_++;
676 ESP_LOGV(TAG, "Command sent %d 0x%X %d send_count: %d", uint8_t(this->function_code), this->register_address,
677 this->register_count, this->send_count_);
678 return true;
679}
680
682 // for custom commands we have to check for identical payloads, since
683 // address/count/type fields will be set to zero
685 ? this->payload == other.payload
686 : other.register_address == this->register_address && other.register_count == this->register_count &&
687 other.register_type == this->register_type && other.function_code == this->function_code;
688}
689
690void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
691 switch (value_type) {
694 data.push_back(value & 0xFFFF);
695 break;
699 data.push_back((value & 0xFFFF0000) >> 16);
700 data.push_back(value & 0xFFFF);
701 break;
705 data.push_back(value & 0xFFFF);
706 data.push_back((value & 0xFFFF0000) >> 16);
707 break;
710 data.push_back((value & 0xFFFF000000000000) >> 48);
711 data.push_back((value & 0xFFFF00000000) >> 32);
712 data.push_back((value & 0xFFFF0000) >> 16);
713 data.push_back(value & 0xFFFF);
714 break;
717 data.push_back(value & 0xFFFF);
718 data.push_back((value & 0xFFFF0000) >> 16);
719 data.push_back((value & 0xFFFF00000000) >> 32);
720 data.push_back((value & 0xFFFF000000000000) >> 48);
721 break;
722 default:
723 ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversation: %d",
724 static_cast<uint16_t>(value_type));
725 break;
726 }
727}
728
729int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
730 uint32_t bitmask) {
731 int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
732
733 size_t size = data.size() - offset;
734 bool error = false;
735 switch (sensor_value_type) {
737 if (size >= 2) {
738 value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ;
739 } else {
740 error = true;
741 }
742 break;
745 if (size >= 4) {
746 value = get_data<uint32_t>(data, offset);
747 value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
748 } else {
749 error = true;
750 }
751 break;
754 if (size >= 4) {
755 value = get_data<uint32_t>(data, offset);
756 value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
757 value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
758 } else {
759 error = true;
760 }
761 break;
763 if (size >= 2) {
764 value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
765 bitmask); // default is 0xFFFF ;
766 } else {
767 error = true;
768 }
769 break;
771 if (size >= 4) {
772 value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
773 } else {
774 error = true;
775 }
776 break;
778 if (size >= 4) {
779 value = get_data<uint32_t>(data, offset);
780 // Currently the high word is at the low position
781 // the sign bit is therefore at low before the switch
782 uint32_t sign_bit = (value & 0x8000) << 16;
784 static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
785 } else {
786 error = true;
787 }
788 } break;
791 // Ignore bitmask for QWORD
792 if (size >= 8) {
793 value = get_data<uint64_t>(data, offset);
794 } else {
795 error = true;
796 }
797 break;
800 // Ignore bitmask for QWORD
801 if (size >= 8) {
802 uint64_t tmp = get_data<uint64_t>(data, offset);
803 value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
804 } else {
805 error = true;
806 }
807 } break;
809 default:
810 break;
811 }
812 if (error)
813 ESP_LOGE(TAG, "not enough data for value");
814 return value;
815}
816
817void ModbusController::add_on_command_sent_callback(std::function<void(int, int)> &&callback) {
818 this->command_sent_callback_.add(std::move(callback));
819}
820
821void ModbusController::add_on_online_callback(std::function<void(int, int)> &&callback) {
822 this->online_callback_.add(std::move(callback));
823}
824
825void ModbusController::add_on_offline_callback(std::function<void(int, int)> &&callback) {
826 this->offline_callback_.add(std::move(callback));
827}
828
829} // namespace modbus_controller
830} // namespace esphome
uint8_t address
Definition bl0906.h:4
void send_raw(const std::vector< uint8_t > &payload)
Definition modbus.h:69
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:65
void send_error(uint8_t function_code, ModbusExceptionCode exception_code)
Definition modbus.h:70
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
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
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 add_on_command_sent_callback(std::function< void(int, int)> &&callback)
Set callback for commands.
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:38
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)
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...
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:227
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:29
uint32_t payload_size()