ESPHome 2026.5.0b1
Loading...
Searching...
No Matches
pipsolar.cpp
Go to the documentation of this file.
1#include "pipsolar.h"
3#include "esphome/core/log.h"
4
5namespace esphome::pipsolar {
6
7static const char *const TAG = "pipsolar";
8
9void Pipsolar::setup() {
10 this->state_ = STATE_IDLE;
11 this->command_start_millis_ = 0;
12}
13
15 uint8_t buf[64];
16 size_t avail;
17 while ((avail = this->available()) > 0) {
18 if (!this->read_array(buf, std::min(avail, sizeof(buf)))) {
19 break;
20 }
21 }
22}
23
24void Pipsolar::loop() {
25 // Read message
26 if (this->state_ == STATE_IDLE) {
27 this->empty_uart_buffer_();
28
29 if (this->send_next_command_()) {
30 // command sent
31 return;
32 }
33
34 if (this->send_next_poll_()) {
35 // poll sent
36 return;
37 }
38
39 return;
40 }
41 if (this->state_ == STATE_COMMAND_COMPLETE) {
42 if (this->check_incoming_length_(4)) {
43 if (this->check_incoming_crc_()) {
44 // crc ok
45 if (this->read_buffer_[1] == 'A' && this->read_buffer_[2] == 'C' && this->read_buffer_[3] == 'K') {
46 ESP_LOGD(TAG, "command successful");
47 } else {
48 ESP_LOGD(TAG, "command not successful");
49 }
50 this->command_queue_[this->command_queue_position_] = std::string("");
52 this->state_ = STATE_IDLE;
53 } else {
54 // crc failed
55 // no log message necessary, check_incoming_crc_() logs
56 this->command_queue_[this->command_queue_position_] = std::string("");
58 this->state_ = STATE_IDLE;
59 }
60 } else {
61 ESP_LOGD(TAG, "command %s response length not OK: with length %zu",
62 this->command_queue_[this->command_queue_position_].c_str(), this->read_pos_);
63 this->command_queue_[this->command_queue_position_] = std::string("");
65 this->state_ = STATE_IDLE;
66 }
67 }
68
69 if (this->state_ == STATE_POLL_CHECKED) {
70 ESP_LOGD(TAG, "poll %s decode", this->enabled_polling_commands_[this->last_polling_command_].command);
72 (const char *) this->read_buffer_);
73 this->state_ = STATE_IDLE;
74 return;
75 }
76
77 if (this->state_ == STATE_POLL_COMPLETE) {
78 if (this->check_incoming_crc_()) {
79 if (this->read_buffer_[0] == '(' && this->read_buffer_[1] == 'N' && this->read_buffer_[2] == 'A' &&
80 this->read_buffer_[3] == 'K') {
81 ESP_LOGD(TAG, "poll %s NACK", this->enabled_polling_commands_[this->last_polling_command_].command);
83 this->state_ = STATE_IDLE;
84 return;
85 }
86 // crc ok
89 return;
90 } else {
91 // crc failed
92 // no log message necessary, check_incoming_crc_() logs
94 this->state_ = STATE_IDLE;
95 }
96 }
97
98 if (this->state_ == STATE_COMMAND || this->state_ == STATE_POLL) {
99 size_t avail = this->available();
100 while (avail > 0) {
101 uint8_t buf[64];
102 size_t to_read = std::min(avail, sizeof(buf));
103 if (!this->read_array(buf, to_read)) {
104 break;
105 }
106 avail -= to_read;
107 bool done = false;
108 for (size_t i = 0; i < to_read; i++) {
109 uint8_t byte = buf[i];
110
111 // make sure data and null terminator fit in buffer
112 if (this->read_pos_ >= PIPSOLAR_READ_BUFFER_LENGTH - 1) {
113 this->read_pos_ = 0;
114 this->empty_uart_buffer_();
115 ESP_LOGW(TAG, "response data too long, discarding.");
116 done = true;
117 break;
118 }
119 this->read_buffer_[this->read_pos_] = byte;
120 this->read_pos_++;
121
122 // end of answer
123 if (byte == 0x0D) {
124 this->read_buffer_[this->read_pos_] = 0;
125 this->empty_uart_buffer_();
126 if (this->state_ == STATE_POLL) {
128 }
129 if (this->state_ == STATE_COMMAND) {
131 }
132 done = true;
133 break;
134 }
135 }
136 if (done) {
137 break;
138 }
139 }
140 }
141 if (this->state_ == STATE_COMMAND) {
143 // command timeout
144 const char *command = this->command_queue_[this->command_queue_position_].c_str();
146 ESP_LOGD(TAG, "command %s timeout", command);
147 this->command_queue_[this->command_queue_position_] = std::string("");
149 this->state_ = STATE_IDLE;
150 return;
151 }
152 }
153 if (this->state_ == STATE_POLL) {
155 // command timeout
156 ESP_LOGD(TAG, "poll %s timeout", this->enabled_polling_commands_[this->last_polling_command_].command);
158 this->state_ = STATE_IDLE;
159 }
160 }
161}
162
164 if (this->read_pos_ >= 3 && this->read_pos_ - 3 == length) {
165 return 1;
166 }
167 return 0;
168}
169
171 if (this->read_pos_ < 3)
172 return 0;
173 uint16_t crc16;
175 if (((uint8_t) ((crc16) >> 8)) == read_buffer_[read_pos_ - 3] &&
176 ((uint8_t) ((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) {
177 ESP_LOGD(TAG, "CRC OK");
178 read_buffer_[read_pos_ - 1] = 0;
179 read_buffer_[read_pos_ - 2] = 0;
180 read_buffer_[read_pos_ - 3] = 0;
181 return 1;
182 }
183 ESP_LOGD(TAG, "CRC NOK expected: %X %X but got: %X %X", ((uint8_t) ((crc16) >> 8)), ((uint8_t) ((crc16) &0xff)),
185 return 0;
186}
187
188// send next command from queue
190 uint16_t crc16;
191 if (!this->command_queue_[this->command_queue_position_].empty()) {
192 const char *command = this->command_queue_[this->command_queue_position_].c_str();
193 uint8_t byte_command[16];
194 size_t length = this->command_queue_[this->command_queue_position_].length();
195 if (length > sizeof(byte_command)) {
196 ESP_LOGE(TAG, "Command too long: %zu", length);
197 this->command_queue_[this->command_queue_position_].clear();
198 return false;
199 }
200 for (size_t i = 0; i < length; i++) {
201 byte_command[i] = (uint8_t) this->command_queue_[this->command_queue_position_].at(i);
202 }
203 this->state_ = STATE_COMMAND;
205 this->empty_uart_buffer_();
206 this->read_pos_ = 0;
207 crc16 = this->pipsolar_crc_(byte_command, length);
208 this->write_str(command);
209 // checksum
210 this->write(((uint8_t) ((crc16) >> 8))); // highbyte
211 this->write(((uint8_t) ((crc16) &0xff))); // lowbyte
212 // end Byte
213 this->write(0x0D);
214 ESP_LOGD(TAG, "Sending command from queue: %s with length %d", command, length);
215 return true;
216 }
217 return false;
218}
219
221 uint16_t crc16;
222 for (uint8_t i = 0; i < POLLING_COMMANDS_MAX; i++) {
223 this->last_polling_command_ = (this->last_polling_command_ + 1) % POLLING_COMMANDS_MAX;
225 // not enabled
226 continue;
227 }
228 if (!this->enabled_polling_commands_[this->last_polling_command_].needs_update) {
229 // no update requested
230 continue;
231 }
232 this->state_ = STATE_POLL;
234 this->empty_uart_buffer_();
235 this->read_pos_ = 0;
236 crc16 = this->pipsolar_crc_(this->enabled_polling_commands_[this->last_polling_command_].command,
240 // checksum
241 this->write(((uint8_t) ((crc16) >> 8))); // highbyte
242 this->write(((uint8_t) ((crc16) &0xff))); // lowbyte
243 // end Byte
244 this->write(0x0D);
245 ESP_LOGD(TAG, "Sending polling command: %s with length %d",
248 return true;
249 }
250 return false;
251}
252
253void Pipsolar::queue_command(const std::string &command) {
254 uint8_t next_position = command_queue_position_;
255 for (uint8_t i = 0; i < COMMAND_QUEUE_LENGTH; i++) {
256 uint8_t testposition = (next_position + i) % COMMAND_QUEUE_LENGTH;
257 if (command_queue_[testposition].empty()) {
258 command_queue_[testposition] = command;
259 ESP_LOGD(TAG, "Command queued successfully: %s at position %d", command.c_str(), testposition);
260 return;
261 }
262 }
263 ESP_LOGD(TAG, "Command queue full dropping command: %s", command.c_str());
264}
265
267 switch (polling_command) {
268 case POLLING_QPIRI:
270 break;
271 case POLLING_QPIGS:
273 break;
274 case POLLING_QMOD:
276 break;
277 case POLLING_QFLAG:
279 break;
280 case POLLING_QPIWS:
282 break;
283 case POLLING_QT:
285 break;
286 case POLLING_QMN:
288 break;
289 default:
290 break;
291 }
292}
294 // handlers are designed in a way that an empty message sets all sensors to unknown
295 this->handle_poll_response_(polling_command, "");
296}
297
299 if (this->last_qpiri_) {
300 this->last_qpiri_->publish_state(message);
301 }
302
303 size_t pos = 0;
304 this->skip_start_(message, &pos);
305
306 this->read_float_sensor_(message, &pos, this->grid_rating_voltage_);
307 this->read_float_sensor_(message, &pos, this->grid_rating_current_);
308 this->read_float_sensor_(message, &pos, this->ac_output_rating_voltage_);
309 this->read_float_sensor_(message, &pos, this->ac_output_rating_frequency_);
310 this->read_float_sensor_(message, &pos, this->ac_output_rating_current_);
311
312 this->read_int_sensor_(message, &pos, this->ac_output_rating_apparent_power_);
313 this->read_int_sensor_(message, &pos, this->ac_output_rating_active_power_);
314
315 this->read_float_sensor_(message, &pos, this->battery_rating_voltage_);
316 this->read_float_sensor_(message, &pos, this->battery_recharge_voltage_);
317 this->read_float_sensor_(message, &pos, this->battery_under_voltage_);
318 this->read_float_sensor_(message, &pos, this->battery_bulk_voltage_);
319 this->read_float_sensor_(message, &pos, this->battery_float_voltage_);
320
321 this->read_int_sensor_(message, &pos, this->battery_type_);
322 this->read_int_sensor_(message, &pos, this->current_max_ac_charging_current_);
323 this->read_int_sensor_(message, &pos, this->current_max_charging_current_);
324
325 esphome::optional<int> input_voltage_range = parse_number<int32_t>(this->read_field_(message, &pos));
326 esphome::optional<int> output_source_priority = parse_number<int32_t>(this->read_field_(message, &pos));
327
328 this->read_int_sensor_(message, &pos, this->charger_source_priority_);
329 this->read_int_sensor_(message, &pos, this->parallel_max_num_);
330 this->read_int_sensor_(message, &pos, this->machine_type_);
331 this->read_int_sensor_(message, &pos, this->topology_);
332 this->read_int_sensor_(message, &pos, this->output_mode_);
333
334 this->read_float_sensor_(message, &pos, this->battery_redischarge_voltage_);
335
336 esphome::optional<int> pv_ok_condition_for_parallel = parse_number<int32_t>(this->read_field_(message, &pos));
337 esphome::optional<int> pv_power_balance = parse_number<int32_t>(this->read_field_(message, &pos));
338
339 if (this->input_voltage_range_) {
340 this->input_voltage_range_->publish_state(input_voltage_range.value_or(NAN));
341 }
342 // special for input voltage range switch
343 if (this->input_voltage_range_switch_ && input_voltage_range.has_value()) {
344 this->input_voltage_range_switch_->publish_state(input_voltage_range.value() == 1);
345 }
346
347 if (this->output_source_priority_) {
348 this->output_source_priority_->publish_state(output_source_priority.value_or(NAN));
349 }
350 // special for output source priority switches
351 if (this->output_source_priority_utility_switch_ && output_source_priority.has_value()) {
352 this->output_source_priority_utility_switch_->publish_state(output_source_priority.value() == 0);
353 }
354 if (this->output_source_priority_solar_switch_ && output_source_priority.has_value()) {
355 this->output_source_priority_solar_switch_->publish_state(output_source_priority.value() == 1);
356 }
357 if (this->output_source_priority_battery_switch_ && output_source_priority.has_value()) {
358 this->output_source_priority_battery_switch_->publish_state(output_source_priority.value() == 2);
359 }
360 if (this->output_source_priority_hybrid_switch_ && output_source_priority.has_value()) {
361 this->output_source_priority_hybrid_switch_->publish_state(output_source_priority.value() == 3);
362 }
363
364 if (this->pv_ok_condition_for_parallel_) {
365 this->pv_ok_condition_for_parallel_->publish_state(pv_ok_condition_for_parallel.value_or(NAN));
366 }
367 // special for pv ok condition switch
368 if (this->pv_ok_condition_for_parallel_switch_ && pv_ok_condition_for_parallel.has_value()) {
369 this->pv_ok_condition_for_parallel_switch_->publish_state(pv_ok_condition_for_parallel.value() == 1);
370 }
371
372 if (this->pv_power_balance_) {
373 this->pv_power_balance_->publish_state(pv_power_balance.value_or(NAN));
374 }
375 // special for power balance switch
376 if (this->pv_power_balance_switch_ && pv_power_balance.has_value()) {
377 this->pv_power_balance_switch_->publish_state(pv_power_balance.value() == 1);
378 }
379}
380
382 if (this->last_qpigs_) {
383 this->last_qpigs_->publish_state(message);
384 }
385
386 size_t pos = 0;
387 this->skip_start_(message, &pos);
388
389 this->read_float_sensor_(message, &pos, this->grid_voltage_);
390 this->read_float_sensor_(message, &pos, this->grid_frequency_);
391 this->read_float_sensor_(message, &pos, this->ac_output_voltage_);
392 this->read_float_sensor_(message, &pos, this->ac_output_frequency_);
393
394 this->read_int_sensor_(message, &pos, this->ac_output_apparent_power_);
395 this->read_int_sensor_(message, &pos, this->ac_output_active_power_);
396 this->read_int_sensor_(message, &pos, this->output_load_percent_);
397 this->read_int_sensor_(message, &pos, this->bus_voltage_);
398
399 this->read_float_sensor_(message, &pos, this->battery_voltage_);
400
401 this->read_int_sensor_(message, &pos, this->battery_charging_current_);
402 this->read_int_sensor_(message, &pos, this->battery_capacity_percent_);
403 this->read_int_sensor_(message, &pos, this->inverter_heat_sink_temperature_);
404
405 this->read_float_sensor_(message, &pos, this->pv_input_current_for_battery_);
406 this->read_float_sensor_(message, &pos, this->pv_input_voltage_);
407 this->read_float_sensor_(message, &pos, this->battery_voltage_scc_);
408
409 this->read_int_sensor_(message, &pos, this->battery_discharge_current_);
410
411 std::string device_status_1 = this->read_field_(message, &pos);
412 this->publish_binary_sensor_(this->get_bit_(device_status_1, 0), this->add_sbu_priority_version_);
413 this->publish_binary_sensor_(this->get_bit_(device_status_1, 1), this->configuration_status_);
414 this->publish_binary_sensor_(this->get_bit_(device_status_1, 2), this->scc_firmware_version_);
415 this->publish_binary_sensor_(this->get_bit_(device_status_1, 3), this->load_status_);
416 this->publish_binary_sensor_(this->get_bit_(device_status_1, 4), this->battery_voltage_to_steady_while_charging_);
417 this->publish_binary_sensor_(this->get_bit_(device_status_1, 5), this->charging_status_);
418 this->publish_binary_sensor_(this->get_bit_(device_status_1, 6), this->scc_charging_status_);
419 this->publish_binary_sensor_(this->get_bit_(device_status_1, 7), this->ac_charging_status_);
420
421 esphome::optional<int> battery_voltage_offset_for_fans_on = parse_number<int32_t>(this->read_field_(message, &pos));
422 if (this->battery_voltage_offset_for_fans_on_) {
423 this->battery_voltage_offset_for_fans_on_->publish_state(battery_voltage_offset_for_fans_on.value_or(NAN) / 10.0f);
424 }
425 this->read_int_sensor_(message, &pos, this->eeprom_version_);
426 this->read_int_sensor_(message, &pos, this->pv_charging_power_);
427
428 std::string device_status_2 = this->read_field_(message, &pos);
429 this->publish_binary_sensor_(this->get_bit_(device_status_2, 0), this->charging_to_floating_mode_);
430 this->publish_binary_sensor_(this->get_bit_(device_status_2, 1), this->switch_on_);
431 this->publish_binary_sensor_(this->get_bit_(device_status_2, 2), this->dustproof_installed_);
432}
433
435 if (this->last_qmod_) {
436 this->last_qmod_->publish_state(message);
437 }
438 // QMOD response is "(M" where M is the device-mode character. Bail out if the
439 // message is shorter than 2 chars (e.g. empty error response from
440 // handle_poll_error_) — reading message[1] would otherwise be out of bounds.
441 if (message[0] == '\0' || message[1] == '\0')
442 return;
443 if (this->device_mode_) {
444 std::string mode;
445 mode = char(message[1]);
446 this->device_mode_->publish_state(mode);
447 }
448}
449
451 // result like:"(EbkuvxzDajy"
452 // get through all char: ignore first "(" Enable flag on 'E', Disable on 'D') else set the corresponding value
453 if (this->last_qflag_) {
454 this->last_qflag_->publish_state(message);
455 }
456
457 QFLAGValues values = QFLAGValues();
458 bool enabled = true;
459 for (size_t i = 1; i < strlen(message); i++) {
460 switch (message[i]) {
461 case 'E':
462 enabled = true;
463 break;
464 case 'D':
465 enabled = false;
466 break;
467 case 'a':
468 values.silence_buzzer_open_buzzer = enabled;
469 break;
470 case 'b':
471 values.overload_bypass_function = enabled;
472 break;
473 case 'k':
474 values.lcd_escape_to_default = enabled;
475 break;
476 case 'u':
477 values.overload_restart_function = enabled;
478 break;
479 case 'v':
480 values.over_temperature_restart_function = enabled;
481 break;
482 case 'x':
483 values.backlight_on = enabled;
484 break;
485 case 'y':
487 break;
488 case 'z':
489 values.fault_code_record = enabled;
490 break;
491 case 'j':
492 values.power_saving = enabled;
493 break;
494 }
495 }
496
497 this->publish_binary_sensor_(values.silence_buzzer_open_buzzer, this->silence_buzzer_open_buzzer_);
498 this->publish_binary_sensor_(values.overload_bypass_function, this->overload_bypass_function_);
499 this->publish_binary_sensor_(values.lcd_escape_to_default, this->lcd_escape_to_default_);
500 this->publish_binary_sensor_(values.overload_restart_function, this->overload_restart_function_);
501 this->publish_binary_sensor_(values.over_temperature_restart_function, this->over_temperature_restart_function_);
502 this->publish_binary_sensor_(values.backlight_on, this->backlight_on_);
504 this->alarm_on_when_primary_source_interrupt_);
505 this->publish_binary_sensor_(values.fault_code_record, this->fault_code_record_);
506 this->publish_binary_sensor_(values.power_saving, this->power_saving_);
507}
508
510 // '(00000000000000000000000000000000'
511 // iterate over all available flag (as not all models have all flags, but at least in the same order)
512 if (this->last_qpiws_) {
513 this->last_qpiws_->publish_state(message);
514 }
515
516 size_t pos = 0;
517 this->skip_start_(message, &pos);
518 std::string flags = this->read_field_(message, &pos);
519
520 esphome::optional<bool> enabled;
521 bool value_warnings_present = false;
522 bool value_faults_present = false;
523
524 for (size_t i = 0; i < 36; i++) {
525 if (i == 31 || i == 32) {
526 // special case for fault code
527 continue;
528 }
529 enabled = this->get_bit_(flags, i);
530 switch (i) {
531 case 0:
532 this->publish_binary_sensor_(enabled, this->warning_power_loss_);
533 value_warnings_present |= enabled.value_or(false);
534 break;
535 case 1:
536 this->publish_binary_sensor_(enabled, this->fault_inverter_fault_);
537 value_faults_present |= enabled.value_or(false);
538 break;
539 case 2:
540 this->publish_binary_sensor_(enabled, this->fault_bus_over_);
541 value_faults_present |= enabled.value_or(false);
542 break;
543 case 3:
544 this->publish_binary_sensor_(enabled, this->fault_bus_under_);
545 value_faults_present |= enabled.value_or(false);
546 break;
547 case 4:
548 this->publish_binary_sensor_(enabled, this->fault_bus_soft_fail_);
549 value_faults_present |= enabled.value_or(false);
550 break;
551 case 5:
552 this->publish_binary_sensor_(enabled, this->warning_line_fail_);
553 value_warnings_present |= enabled.value_or(false);
554 break;
555 case 6:
556 this->publish_binary_sensor_(enabled, this->fault_opvshort_);
557 value_faults_present |= enabled.value_or(false);
558 break;
559 case 7:
560 this->publish_binary_sensor_(enabled, this->fault_inverter_voltage_too_low_);
561 value_faults_present |= enabled.value_or(false);
562 break;
563 case 8:
564 this->publish_binary_sensor_(enabled, this->fault_inverter_voltage_too_high_);
565 value_faults_present |= enabled.value_or(false);
566 break;
567 case 9:
568 this->publish_binary_sensor_(enabled, this->warning_over_temperature_);
569 value_warnings_present |= enabled.value_or(false);
570 break;
571 case 10:
572 this->publish_binary_sensor_(enabled, this->warning_fan_lock_);
573 value_warnings_present |= enabled.value_or(false);
574 break;
575 case 11:
576 this->publish_binary_sensor_(enabled, this->warning_battery_voltage_high_);
577 value_warnings_present |= enabled.value_or(false);
578 break;
579 case 12:
580 this->publish_binary_sensor_(enabled, this->warning_battery_low_alarm_);
581 value_warnings_present |= enabled.value_or(false);
582 break;
583 case 14:
584 this->publish_binary_sensor_(enabled, this->warning_battery_under_shutdown_);
585 value_warnings_present |= enabled.value_or(false);
586 break;
587 case 15:
588 this->publish_binary_sensor_(enabled, this->warning_battery_derating_);
589 value_warnings_present |= enabled.value_or(false);
590 break;
591 case 16:
592 this->publish_binary_sensor_(enabled, this->warning_over_load_);
593 value_warnings_present |= enabled.value_or(false);
594 break;
595 case 17:
596 this->publish_binary_sensor_(enabled, this->warning_eeprom_failed_);
597 value_warnings_present |= enabled.value_or(false);
598 break;
599 case 18:
600 this->publish_binary_sensor_(enabled, this->fault_inverter_over_current_);
601 value_faults_present |= enabled.value_or(false);
602 break;
603 case 19:
604 this->publish_binary_sensor_(enabled, this->fault_inverter_soft_failed_);
605 value_faults_present |= enabled.value_or(false);
606 break;
607 case 20:
608 this->publish_binary_sensor_(enabled, this->fault_self_test_failed_);
609 value_faults_present |= enabled.value_or(false);
610 break;
611 case 21:
612 this->publish_binary_sensor_(enabled, this->fault_op_dc_voltage_over_);
613 value_faults_present |= enabled.value_or(false);
614 break;
615 case 22:
616 this->publish_binary_sensor_(enabled, this->fault_battery_open_);
617 value_faults_present |= enabled.value_or(false);
618 break;
619 case 23:
620 this->publish_binary_sensor_(enabled, this->fault_current_sensor_failed_);
621 value_faults_present |= enabled.value_or(false);
622 break;
623 case 24:
624 this->publish_binary_sensor_(enabled, this->fault_battery_short_);
625 value_faults_present |= enabled.value_or(false);
626 break;
627 case 25:
628 this->publish_binary_sensor_(enabled, this->warning_power_limit_);
629 value_warnings_present |= enabled.value_or(false);
630 break;
631 case 26:
632 this->publish_binary_sensor_(enabled, this->warning_pv_voltage_high_);
633 value_warnings_present |= enabled.value_or(false);
634 break;
635 case 27:
636 this->publish_binary_sensor_(enabled, this->fault_mppt_overload_);
637 value_faults_present |= enabled.value_or(false);
638 break;
639 case 28:
640 this->publish_binary_sensor_(enabled, this->warning_mppt_overload_);
641 value_warnings_present |= enabled.value_or(false);
642 break;
643 case 29:
644 this->publish_binary_sensor_(enabled, this->warning_battery_too_low_to_charge_);
645 value_warnings_present |= enabled.value_or(false);
646 break;
647 case 30:
648 this->publish_binary_sensor_(enabled, this->fault_dc_dc_over_current_);
649 value_faults_present |= enabled.value_or(false);
650 break;
651 case 33:
652 this->publish_binary_sensor_(enabled, this->warning_low_pv_energy_);
653 value_warnings_present |= enabled.value_or(false);
654 break;
655 case 34:
656 this->publish_binary_sensor_(enabled, this->warning_high_ac_input_during_bus_soft_start_);
657 value_warnings_present |= enabled.value_or(false);
658 break;
659 case 35:
660 this->publish_binary_sensor_(enabled, this->warning_battery_equalization_);
661 value_warnings_present |= enabled.value_or(false);
662 break;
663 }
664 }
665
666 this->publish_binary_sensor_(value_warnings_present, this->warnings_present_);
667 this->publish_binary_sensor_(value_faults_present, this->faults_present_);
668
669 if (this->fault_code_) {
670 if (flags.length() < 33) {
671 this->fault_code_->publish_state(NAN);
672 } else {
673 std::string fc(flags, 31, 2);
674 this->fault_code_->publish_state(parse_number<int>(fc).value_or(NAN));
675 }
676 }
677}
678
679void Pipsolar::handle_qt_(const char *message) {
680 if (this->last_qt_) {
681 this->last_qt_->publish_state(message);
682 }
683}
684
686 if (this->last_qmn_) {
687 this->last_qmn_->publish_state(message);
688 }
689}
690
691void Pipsolar::skip_start_(const char *message, size_t *pos) {
692 if (message[*pos] == '(') {
693 (*pos)++;
694 }
695}
696void Pipsolar::skip_field_(const char *message, size_t *pos) {
697 // find delimiter or end of string
698 while (message[*pos] != '\0' && message[*pos] != ' ') {
699 (*pos)++;
700 }
701 if (message[*pos] != '\0') {
702 // skip delimiter after this field if there is one
703 (*pos)++;
704 }
705}
706std::string Pipsolar::read_field_(const char *message, size_t *pos) {
707 size_t begin = *pos;
708 // find delimiter or end of string
709 while (message[*pos] != '\0' && message[*pos] != ' ') {
710 (*pos)++;
711 }
712 if (*pos == begin) {
713 return "";
714 }
715
716 std::string field(message, begin, *pos - begin);
717
718 if (message[*pos] != '\0') {
719 // skip delimiter after this field if there is one
720 (*pos)++;
721 }
722
723 return field;
724}
725
726void Pipsolar::read_float_sensor_(const char *message, size_t *pos, sensor::Sensor *sensor) {
727 if (sensor != nullptr) {
728 std::string field = this->read_field_(message, pos);
729 sensor->publish_state(parse_number<float>(field).value_or(NAN));
730 } else {
731 this->skip_field_(message, pos);
732 }
733}
734void Pipsolar::read_int_sensor_(const char *message, size_t *pos, sensor::Sensor *sensor) {
735 if (sensor != nullptr) {
736 std::string field = this->read_field_(message, pos);
737 esphome::optional<int32_t> parsed = parse_number<int32_t>(field);
738 sensor->publish_state(parsed.has_value() ? parsed.value() : NAN);
739 } else {
740 this->skip_field_(message, pos);
741 }
742}
743
744void Pipsolar::publish_binary_sensor_(esphome::optional<bool> b, binary_sensor::BinarySensor *sensor) {
745 if (sensor) {
746 if (b.has_value()) {
747 sensor->publish_state(b.value());
748 } else {
749 sensor->invalidate_state();
750 }
751 }
752}
753
754esphome::optional<bool> Pipsolar::get_bit_(std::string bits, uint8_t bit_pos) {
755 if (bit_pos >= bits.length()) {
756 return {};
757 }
758 return bits[bit_pos] == '1';
759}
760
761void Pipsolar::dump_config() {
762 ESP_LOGCONFIG(TAG, "Pipsolar enabled polling commands:");
763 for (auto &enabled_polling_command : this->enabled_polling_commands_) {
764 if (enabled_polling_command.length != 0) {
765 ESP_LOGCONFIG(TAG, "%s", enabled_polling_command.command);
766 }
767 }
768}
769void Pipsolar::update() {
770 for (auto &enabled_polling_command : this->enabled_polling_commands_) {
771 if (enabled_polling_command.length != 0) {
772 enabled_polling_command.needs_update = true;
773 }
774 }
775}
776
777void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand polling_command) {
778 for (auto &enabled_polling_command : this->enabled_polling_commands_) {
779 if (enabled_polling_command.length == strlen(command)) {
780 uint8_t len = strlen(command);
781 if (memcmp(enabled_polling_command.command, command, len) == 0) {
782 return;
783 }
784 }
785 if (enabled_polling_command.length == 0) {
786 size_t length = strlen(command);
787
788 enabled_polling_command.command = new uint8_t[length + 1]; // NOLINT(cppcoreguidelines-owning-memory)
789 for (size_t i = 0; i < length + 1; i++) {
790 enabled_polling_command.command[i] = (uint8_t) command[i];
791 }
792 enabled_polling_command.errors = 0;
793 enabled_polling_command.identifier = polling_command;
794 enabled_polling_command.length = length;
795 enabled_polling_command.needs_update = true;
796 return;
797 }
798 }
799}
800
801uint16_t Pipsolar::pipsolar_crc_(uint8_t *msg, uint8_t len) {
802 uint16_t crc = crc16be(msg, len);
803 uint8_t crc_low = crc & 0xff;
804 uint8_t crc_high = crc >> 8;
805 if (crc_low == 0x28 || crc_low == 0x0d || crc_low == 0x0a)
806 crc_low++;
807 if (crc_high == 0x28 || crc_high == 0x0d || crc_high == 0x0a)
808 crc_high++;
809 crc = (crc_high << 8) | crc_low;
810 return crc;
811}
812
813} // namespace esphome::pipsolar
BedjetMode mode
BedJet operating mode.
void invalidate_state()
Clear the state — sets has_state() to false and fires callbacks with nullopt.
Base class for all binary_sensor-type classes.
void publish_state(bool new_state)
Publish a new state to the front-end.
static const size_t COMMAND_QUEUE_LENGTH
Definition pipsolar.h:195
void handle_qmod_(const char *message)
Definition pipsolar.cpp:434
uint8_t read_buffer_[PIPSOLAR_READ_BUFFER_LENGTH]
Definition pipsolar.h:230
uint16_t pipsolar_crc_(uint8_t *msg, uint8_t len)
Definition pipsolar.cpp:801
void handle_poll_error_(ENUMPollingCommand polling_command)
Definition pipsolar.cpp:293
void handle_qpiws_(const char *message)
Definition pipsolar.cpp:509
esphome::optional< bool > get_bit_(std::string bits, uint8_t bit_pos)
Definition pipsolar.cpp:754
void handle_qt_(const char *message)
Definition pipsolar.cpp:679
uint8_t check_incoming_length_(uint8_t length)
Definition pipsolar.cpp:163
void handle_qpigs_(const char *message)
Definition pipsolar.cpp:381
void handle_poll_response_(ENUMPollingCommand polling_command, const char *message)
Definition pipsolar.cpp:266
static const size_t PIPSOLAR_READ_BUFFER_LENGTH
Definition pipsolar.h:194
std::string read_field_(const char *message, size_t *pos)
Definition pipsolar.cpp:706
std::string command_queue_[COMMAND_QUEUE_LENGTH]
Definition pipsolar.h:228
void read_int_sensor_(const char *message, size_t *pos, sensor::Sensor *sensor)
Definition pipsolar.cpp:734
void read_float_sensor_(const char *message, size_t *pos, sensor::Sensor *sensor)
Definition pipsolar.cpp:726
void handle_qflag_(const char *message)
Definition pipsolar.cpp:450
void skip_field_(const char *message, size_t *pos)
Definition pipsolar.cpp:696
void handle_qpiri_(const char *message)
Definition pipsolar.cpp:298
PollingCommand enabled_polling_commands_[POLLING_COMMANDS_MAX]
Definition pipsolar.h:245
void publish_binary_sensor_(esphome::optional< bool > b, binary_sensor::BinarySensor *sensor)
Definition pipsolar.cpp:744
static const size_t POLLING_COMMANDS_MAX
Definition pipsolar.h:197
void skip_start_(const char *message, size_t *pos)
Definition pipsolar.cpp:691
static const size_t COMMAND_TIMEOUT
Definition pipsolar.h:196
void add_polling_command_(const char *command, ENUMPollingCommand polling_command)
Definition pipsolar.cpp:777
void handle_qmn_(const char *message)
Definition pipsolar.cpp:685
Base-class for all sensors.
Definition sensor.h:47
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:38
void write_str(const char *str)
Definition uart.h:32
void write_array(const uint8_t *data, size_t len)
Definition uart.h:26
size_t write(uint8_t data)
Definition uart.h:57
const char * message
Definition component.cpp:35
uint16_t flags
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t reverse_poly, bool refin, bool refout)
Calculate a CRC-16 checksum of data with size len.
Definition helpers.cpp:86
std::string size_t len
optional< T > parse_number(const char *str)
Parse an unsigned decimal number from a null-terminated string.
Definition helpers.h:1143
size_t size_t pos
Definition helpers.h:1038
uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t poly, bool refin, bool refout)
Definition helpers.cpp:126
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
esphome::optional< bool > overload_bypass_function
Definition pipsolar.h:33
esphome::optional< bool > alarm_on_when_primary_source_interrupt
Definition pipsolar.h:38
esphome::optional< bool > backlight_on
Definition pipsolar.h:37
esphome::optional< bool > lcd_escape_to_default
Definition pipsolar.h:34
esphome::optional< bool > fault_code_record
Definition pipsolar.h:39
esphome::optional< bool > over_temperature_restart_function
Definition pipsolar.h:36
esphome::optional< bool > silence_buzzer_open_buzzer
Definition pipsolar.h:32
esphome::optional< bool > overload_restart_function
Definition pipsolar.h:35
esphome::optional< bool > power_saving
Definition pipsolar.h:40
uint16_t length
Definition tt21100.cpp:0