mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Pylontech integration (solar battery bank) (#4688)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -240,6 +240,7 @@ esphome/components/preferences/* @esphome/core | |||||||
| esphome/components/psram/* @esphome/core | esphome/components/psram/* @esphome/core | ||||||
| esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter | esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter | ||||||
| esphome/components/pvvx_mithermometer/* @pasiz | esphome/components/pvvx_mithermometer/* @pasiz | ||||||
|  | esphome/components/pylontech/* @functionpointer | ||||||
| esphome/components/qmp6988/* @andrewpc | esphome/components/qmp6988/* @andrewpc | ||||||
| esphome/components/qr_code/* @wjtje | esphome/components/qr_code/* @wjtje | ||||||
| esphome/components/qwiic_pir/* @kahrendt | esphome/components/qwiic_pir/* @kahrendt | ||||||
|   | |||||||
							
								
								
									
										46
									
								
								esphome/components/pylontech/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								esphome/components/pylontech/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | import logging | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import uart | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
|  | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@functionpointer"] | ||||||
|  | DEPENDENCIES = ["uart"] | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
|  | CONF_PYLONTECH_ID = "pylontech_id" | ||||||
|  | CONF_BATTERY = "battery" | ||||||
|  |  | ||||||
|  | pylontech_ns = cg.esphome_ns.namespace("pylontech") | ||||||
|  | PylontechComponent = pylontech_ns.class_( | ||||||
|  |     "PylontechComponent", cg.PollingComponent, uart.UARTDevice | ||||||
|  | ) | ||||||
|  | PylontechBattery = pylontech_ns.class_("PylontechBattery") | ||||||
|  |  | ||||||
|  | CV_NUM_BATTERIES = cv.int_range(1, 6) | ||||||
|  |  | ||||||
|  | PYLONTECH_COMPONENT_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_PYLONTECH_ID): cv.use_id(PylontechComponent), | ||||||
|  |         cv.Required(CONF_BATTERY): CV_NUM_BATTERIES, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(PylontechComponent), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(uart.UART_DEVICE_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await uart.register_uart_device(var, config) | ||||||
							
								
								
									
										91
									
								
								esphome/components/pylontech/pylontech.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								esphome/components/pylontech/pylontech.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | #include "pylontech.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pylontech { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "pylontech"; | ||||||
|  | static const int MAX_DATA_LENGTH_BYTES = 256; | ||||||
|  | static const uint8_t ASCII_LF = 0x0A; | ||||||
|  |  | ||||||
|  | PylontechComponent::PylontechComponent() {} | ||||||
|  |  | ||||||
|  | void PylontechComponent::dump_config() { | ||||||
|  |   this->check_uart_settings(115200, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); | ||||||
|  |   ESP_LOGCONFIG(TAG, "pylontech:"); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Connection with pylontech failed!"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (PylontechListener *listener : this->listeners_) { | ||||||
|  |     listener->dump_config(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PylontechComponent::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up pylontech..."); | ||||||
|  |   while (this->available() != 0) { | ||||||
|  |     this->read(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PylontechComponent::update() { this->write_str("pwr\n"); } | ||||||
|  |  | ||||||
|  | void PylontechComponent::loop() { | ||||||
|  |   uint8_t data; | ||||||
|  |  | ||||||
|  |   // pylontech sends a lot of data very suddenly | ||||||
|  |   // we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow | ||||||
|  |   while (this->available() > 0) { | ||||||
|  |     if (this->read_byte(&data)) { | ||||||
|  |       buffer_[buffer_index_write_] += (char) data; | ||||||
|  |       if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) || | ||||||
|  |           buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) { | ||||||
|  |         // complete line received | ||||||
|  |         buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // only process one line per call of loop() to not block esphome for too long | ||||||
|  |   if (buffer_index_read_ != buffer_index_write_) { | ||||||
|  |     this->process_line_(buffer_[buffer_index_read_]); | ||||||
|  |     buffer_[buffer_index_read_].clear(); | ||||||
|  |     buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PylontechComponent::process_line_(std::string &buffer) { | ||||||
|  |   ESP_LOGV(TAG, "Read from serial: %s", buffer.substr(0, buffer.size() - 2).c_str()); | ||||||
|  |   // clang-format off | ||||||
|  |   // example line to parse: | ||||||
|  |   // Power Volt  Curr Tempr Tlow  Thigh  Vlow Vhigh Base.St Volt.St Curr.St Temp.St Coulomb Time                B.V.St B.T.St MosTempr M.T.St | ||||||
|  |   // 1    50548  8910 25000 24200 25000  3368 3371  Charge  Normal  Normal  Normal  97%     2021-06-30 20:49:45 Normal Normal 22700    Normal | ||||||
|  |   // clang-format on | ||||||
|  |  | ||||||
|  |   PylontechListener::LineContents l{}; | ||||||
|  |   const int parsed = sscanf(                                                                                  // NOLINT | ||||||
|  |       buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %d %*s",  // NOLINT | ||||||
|  |       &l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st,     // NOLINT | ||||||
|  |       l.curr_st, l.temp_st, &l.coulomb, &l.mostempr);                                                         // NOLINT | ||||||
|  |  | ||||||
|  |   if (l.bat_num <= 0) { | ||||||
|  |     ESP_LOGD(TAG, "invalid bat_num in line %s", buffer.substr(0, buffer.size() - 2).c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (parsed != 14) { | ||||||
|  |     ESP_LOGW(TAG, "invalid line: found only %d items in %s", parsed, buffer.substr(0, buffer.size() - 2).c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (PylontechListener *listener : this->listeners_) { | ||||||
|  |     listener->on_line_read(&l); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float PylontechComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | }  // namespace pylontech | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										53
									
								
								esphome/components/pylontech/pylontech.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								esphome/components/pylontech/pylontech.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pylontech { | ||||||
|  |  | ||||||
|  | static const uint8_t NUM_BUFFERS = 20; | ||||||
|  | static const uint8_t TEXT_SENSOR_MAX_LEN = 8; | ||||||
|  |  | ||||||
|  | class PylontechListener { | ||||||
|  |  public: | ||||||
|  |   struct LineContents { | ||||||
|  |     int bat_num = 0, volt, curr, tempr, tlow, thigh, vlow, vhigh, coulomb, mostempr; | ||||||
|  |     char base_st[TEXT_SENSOR_MAX_LEN], volt_st[TEXT_SENSOR_MAX_LEN], curr_st[TEXT_SENSOR_MAX_LEN], | ||||||
|  |         temp_st[TEXT_SENSOR_MAX_LEN]; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   virtual void on_line_read(LineContents *line); | ||||||
|  |   virtual void dump_config(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class PylontechComponent : public PollingComponent, public uart::UARTDevice { | ||||||
|  |  public: | ||||||
|  |   PylontechComponent(); | ||||||
|  |  | ||||||
|  |   /// Schedule data readings. | ||||||
|  |   void update() override; | ||||||
|  |   /// Read data once available | ||||||
|  |   void loop() override; | ||||||
|  |   /// Setup the sensor and test for a connection. | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |   void register_listener(PylontechListener *listener) { this->listeners_.push_back(listener); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void process_line_(std::string &buffer); | ||||||
|  |  | ||||||
|  |   // ring buffer | ||||||
|  |   std::string buffer_[NUM_BUFFERS]; | ||||||
|  |   int buffer_index_write_ = 0; | ||||||
|  |   int buffer_index_read_ = 0; | ||||||
|  |  | ||||||
|  |   std::vector<PylontechListener *> listeners_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace pylontech | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										97
									
								
								esphome/components/pylontech/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								esphome/components/pylontech/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_VOLTAGE, | ||||||
|  |     CONF_CURRENT, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_BATTERY, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  |     CONF_ID, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from .. import ( | ||||||
|  |     CONF_PYLONTECH_ID, | ||||||
|  |     PYLONTECH_COMPONENT_SCHEMA, | ||||||
|  |     CONF_BATTERY, | ||||||
|  |     pylontech_ns, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | PylontechSensor = pylontech_ns.class_("PylontechSensor", cg.Component) | ||||||
|  |  | ||||||
|  | CONF_COULOMB = "coulomb" | ||||||
|  | CONF_TEMPERATURE_LOW = "temperature_low" | ||||||
|  | CONF_TEMPERATURE_HIGH = "temperature_high" | ||||||
|  | CONF_VOLTAGE_LOW = "voltage_low" | ||||||
|  | CONF_VOLTAGE_HIGH = "voltage_high" | ||||||
|  | CONF_MOS_TEMPERATURE = "mos_temperature" | ||||||
|  |  | ||||||
|  | TYPES: dict[str, cv.Schema] = { | ||||||
|  |     CONF_VOLTAGE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_VOLT, | ||||||
|  |         accuracy_decimals=3, | ||||||
|  |         device_class=DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ), | ||||||
|  |     CONF_CURRENT: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_AMPERE, | ||||||
|  |         accuracy_decimals=3, | ||||||
|  |         device_class=DEVICE_CLASS_CURRENT, | ||||||
|  |     ), | ||||||
|  |     CONF_TEMPERATURE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         accuracy_decimals=1, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ), | ||||||
|  |     CONF_TEMPERATURE_LOW: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         accuracy_decimals=1, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ), | ||||||
|  |     CONF_TEMPERATURE_HIGH: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         accuracy_decimals=1, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ), | ||||||
|  |     CONF_VOLTAGE_LOW: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         accuracy_decimals=1, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ), | ||||||
|  |     CONF_VOLTAGE_HIGH: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         accuracy_decimals=1, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ), | ||||||
|  |     CONF_COULOMB: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_PERCENT, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_BATTERY, | ||||||
|  |     ), | ||||||
|  |     CONF_MOS_TEMPERATURE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         accuracy_decimals=1, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = PYLONTECH_COMPONENT_SCHEMA.extend( | ||||||
|  |     {cv.GenerateID(): cv.declare_id(PylontechSensor)} | ||||||
|  | ).extend({cv.Optional(marker): schema for marker, schema in TYPES.items()}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     paren = await cg.get_variable(config[CONF_PYLONTECH_ID]) | ||||||
|  |     bat = cg.new_Pvariable(config[CONF_ID], config[CONF_BATTERY]) | ||||||
|  |  | ||||||
|  |     for marker in TYPES: | ||||||
|  |         if marker_config := config.get(marker): | ||||||
|  |             sens = await sensor.new_sensor(marker_config) | ||||||
|  |             cg.add(getattr(bat, f"set_{marker}_sensor")(sens)) | ||||||
|  |  | ||||||
|  |     cg.add(paren.register_listener(bat)) | ||||||
							
								
								
									
										60
									
								
								esphome/components/pylontech/sensor/pylontech_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								esphome/components/pylontech/sensor/pylontech_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | #include "pylontech_sensor.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pylontech { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "pylontech.sensor"; | ||||||
|  |  | ||||||
|  | PylontechSensor::PylontechSensor(int8_t bat_num) { this->bat_num_ = bat_num; } | ||||||
|  |  | ||||||
|  | void PylontechSensor::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Pylontech Sensor:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, " Battery %d", this->bat_num_); | ||||||
|  |   LOG_SENSOR("  ", "Voltage", this->voltage_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Current", this->current_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Temperature low", this->temperature_low_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Temperature high", this->temperature_high_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Voltage low", this->voltage_low_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Voltage high", this->voltage_high_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Coulomb", this->coulomb_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "MOS Temperature", this->mos_temperature_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PylontechSensor::on_line_read(PylontechListener::LineContents *line) { | ||||||
|  |   if (this->bat_num_ != line->bat_num) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->voltage_sensor_ != nullptr) { | ||||||
|  |     this->voltage_sensor_->publish_state(((float) line->volt) / 1000.0f); | ||||||
|  |   } | ||||||
|  |   if (this->current_sensor_ != nullptr) { | ||||||
|  |     this->current_sensor_->publish_state(((float) line->curr) / 1000.0f); | ||||||
|  |   } | ||||||
|  |   if (this->temperature_sensor_ != nullptr) { | ||||||
|  |     this->temperature_sensor_->publish_state(((float) line->tempr) / 1000.0f); | ||||||
|  |   } | ||||||
|  |   if (this->temperature_low_sensor_ != nullptr) { | ||||||
|  |     this->temperature_low_sensor_->publish_state(((float) line->tlow) / 1000.0f); | ||||||
|  |   } | ||||||
|  |   if (this->temperature_high_sensor_ != nullptr) { | ||||||
|  |     this->temperature_high_sensor_->publish_state(((float) line->thigh) / 1000.0f); | ||||||
|  |   } | ||||||
|  |   if (this->voltage_low_sensor_ != nullptr) { | ||||||
|  |     this->voltage_low_sensor_->publish_state(((float) line->vlow) / 1000.0f); | ||||||
|  |   } | ||||||
|  |   if (this->voltage_high_sensor_ != nullptr) { | ||||||
|  |     this->voltage_high_sensor_->publish_state(((float) line->vhigh) / 1000.0f); | ||||||
|  |   } | ||||||
|  |   if (this->coulomb_sensor_ != nullptr) { | ||||||
|  |     this->coulomb_sensor_->publish_state(line->coulomb); | ||||||
|  |   } | ||||||
|  |   if (this->mos_temperature_sensor_ != nullptr) { | ||||||
|  |     this->mos_temperature_sensor_->publish_state(((float) line->mostempr) / 1000.0f); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace pylontech | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										32
									
								
								esphome/components/pylontech/sensor/pylontech_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								esphome/components/pylontech/sensor/pylontech_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "../pylontech.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pylontech { | ||||||
|  |  | ||||||
|  | class PylontechSensor : public PylontechListener, public Component { | ||||||
|  |  public: | ||||||
|  |   PylontechSensor(int8_t bat_num); | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   SUB_SENSOR(voltage) | ||||||
|  |   SUB_SENSOR(current) | ||||||
|  |   SUB_SENSOR(temperature) | ||||||
|  |   SUB_SENSOR(temperature_low) | ||||||
|  |   SUB_SENSOR(temperature_high) | ||||||
|  |   SUB_SENSOR(voltage_low) | ||||||
|  |   SUB_SENSOR(voltage_high) | ||||||
|  |  | ||||||
|  |   SUB_SENSOR(coulomb) | ||||||
|  |   SUB_SENSOR(mos_temperature) | ||||||
|  |  | ||||||
|  |   void on_line_read(LineContents *line) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   int8_t bat_num_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace pylontech | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										41
									
								
								esphome/components/pylontech/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/pylontech/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import text_sensor | ||||||
|  | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
|  | from .. import ( | ||||||
|  |     CONF_PYLONTECH_ID, | ||||||
|  |     PYLONTECH_COMPONENT_SCHEMA, | ||||||
|  |     CONF_BATTERY, | ||||||
|  |     pylontech_ns, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | PylontechTextSensor = pylontech_ns.class_("PylontechTextSensor", cg.Component) | ||||||
|  |  | ||||||
|  | CONF_BASE_STATE = "base_state" | ||||||
|  | CONF_VOLTAGE_STATE = "voltage_state" | ||||||
|  | CONF_CURRENT_STATE = "current_state" | ||||||
|  | CONF_TEMPERATURE_STATE = "temperature_state" | ||||||
|  |  | ||||||
|  | MARKERS: list[str] = [ | ||||||
|  |     CONF_BASE_STATE, | ||||||
|  |     CONF_VOLTAGE_STATE, | ||||||
|  |     CONF_CURRENT_STATE, | ||||||
|  |     CONF_TEMPERATURE_STATE, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = PYLONTECH_COMPONENT_SCHEMA.extend( | ||||||
|  |     {cv.GenerateID(): cv.declare_id(PylontechTextSensor)} | ||||||
|  | ).extend({cv.Optional(marker): text_sensor.text_sensor_schema() for marker in MARKERS}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     paren = await cg.get_variable(config[CONF_PYLONTECH_ID]) | ||||||
|  |     bat = cg.new_Pvariable(config[CONF_ID], config[CONF_BATTERY]) | ||||||
|  |  | ||||||
|  |     for marker in MARKERS: | ||||||
|  |         if marker_config := config.get(marker): | ||||||
|  |             var = await text_sensor.new_text_sensor(marker_config) | ||||||
|  |             cg.add(getattr(bat, f"set_{marker}_text_sensor")(var)) | ||||||
|  |  | ||||||
|  |     cg.add(paren.register_listener(bat)) | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | #include "pylontech_text_sensor.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pylontech { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "pylontech.textsensor"; | ||||||
|  |  | ||||||
|  | PylontechTextSensor::PylontechTextSensor(int8_t bat_num) { this->bat_num_ = bat_num; } | ||||||
|  |  | ||||||
|  | void PylontechTextSensor::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Pylontech Text Sensor:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, " Battery %d", this->bat_num_); | ||||||
|  |   LOG_TEXT_SENSOR("  ", "Base state", this->base_state_text_sensor_); | ||||||
|  |   LOG_TEXT_SENSOR("  ", "Voltage state", this->voltage_state_text_sensor_); | ||||||
|  |   LOG_TEXT_SENSOR("  ", "Current state", this->current_state_text_sensor_); | ||||||
|  |   LOG_TEXT_SENSOR("  ", "Temperature state", this->temperature_state_text_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PylontechTextSensor::on_line_read(PylontechListener::LineContents *line) { | ||||||
|  |   if (this->bat_num_ != line->bat_num) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->base_state_text_sensor_ != nullptr) { | ||||||
|  |     this->base_state_text_sensor_->publish_state(std::string(line->base_st)); | ||||||
|  |   } | ||||||
|  |   if (this->voltage_state_text_sensor_ != nullptr) { | ||||||
|  |     this->voltage_state_text_sensor_->publish_state(std::string(line->volt_st)); | ||||||
|  |   } | ||||||
|  |   if (this->current_state_text_sensor_ != nullptr) { | ||||||
|  |     this->current_state_text_sensor_->publish_state(std::string(line->curr_st)); | ||||||
|  |   } | ||||||
|  |   if (this->temperature_state_text_sensor_ != nullptr) { | ||||||
|  |     this->temperature_state_text_sensor_->publish_state(std::string(line->temp_st)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace pylontech | ||||||
|  | }  // namespace esphome | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "../pylontech.h" | ||||||
|  | #include "esphome/components/text_sensor/text_sensor.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pylontech { | ||||||
|  |  | ||||||
|  | class PylontechTextSensor : public PylontechListener, public Component { | ||||||
|  |  public: | ||||||
|  |   PylontechTextSensor(int8_t bat_num); | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   SUB_TEXT_SENSOR(base_state) | ||||||
|  |   SUB_TEXT_SENSOR(voltage_state) | ||||||
|  |   SUB_TEXT_SENSOR(current_state) | ||||||
|  |   SUB_TEXT_SENSOR(temperature_state) | ||||||
|  |  | ||||||
|  |   void on_line_read(LineContents *line) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   int8_t bat_num_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace pylontech | ||||||
|  | }  // namespace esphome | ||||||
| @@ -99,6 +99,12 @@ pipsolar: | |||||||
|   id: inverter0 |   id: inverter0 | ||||||
|   uart_id: uart115200 |   uart_id: uart115200 | ||||||
|  |  | ||||||
|  | pylontech: | ||||||
|  |   - id: pylontech0 | ||||||
|  |     uart_id: uart115200 | ||||||
|  |   - id: pylontech1 | ||||||
|  |     uart_id: uart115200 | ||||||
|  |  | ||||||
| sx1509: | sx1509: | ||||||
|   - id: sx1509_hub |   - id: sx1509_hub | ||||||
|     address: 0x3E |     address: 0x3E | ||||||
| @@ -113,6 +119,30 @@ dac7678: | |||||||
|   internal_reference: true |   internal_reference: true | ||||||
|  |  | ||||||
| sensor: | sensor: | ||||||
|  |   - platform: pylontech | ||||||
|  |     pylontech_id: pylontech0 | ||||||
|  |     battery: 1 | ||||||
|  |     voltage: | ||||||
|  |       id: pyl01_voltage | ||||||
|  |     current: | ||||||
|  |       id: pyl01_current | ||||||
|  |     coulomb: | ||||||
|  |       id: pyl01_soc | ||||||
|  |     mos_temperature: | ||||||
|  |       id: pyl01_mos_temperature | ||||||
|  |   - platform: pylontech | ||||||
|  |     pylontech_id: pylontech1 | ||||||
|  |     battery: 1 | ||||||
|  |     voltage: | ||||||
|  |       id: pyl13_voltage | ||||||
|  |     temperature_low: | ||||||
|  |       id: pyl13_temperature_low | ||||||
|  |     temperature_high: | ||||||
|  |       id: pyl13_temperature_high | ||||||
|  |     voltage_low: | ||||||
|  |       id: pyl13_voltage_low | ||||||
|  |     voltage_high: | ||||||
|  |       id: pyl13_voltage_high | ||||||
|   - platform: homeassistant |   - platform: homeassistant | ||||||
|     entity_id: sensor.hello_world |     entity_id: sensor.hello_world | ||||||
|     id: ha_hello_world |     id: ha_hello_world | ||||||
| @@ -589,6 +619,17 @@ number: | |||||||
|     name: Tuya Number Copy |     name: Tuya Number Copy | ||||||
|  |  | ||||||
| text_sensor: | text_sensor: | ||||||
|  |   - platform: pylontech | ||||||
|  |     pylontech_id: pylontech0 | ||||||
|  |     battery: 1 | ||||||
|  |     base_state: | ||||||
|  |       id: pyl0_base_state | ||||||
|  |     voltage_state: | ||||||
|  |       id: pyl0_voltage_state | ||||||
|  |     current_state: | ||||||
|  |       id: pyl0_current_state | ||||||
|  |     temperature_state: | ||||||
|  |       id: pyl0_temperature_state | ||||||
|   - platform: pipsolar |   - platform: pipsolar | ||||||
|     pipsolar_id: inverter0 |     pipsolar_id: inverter0 | ||||||
|     device_mode: |     device_mode: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user