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