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