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