Skip to content

Commit 0591e21

Browse files
authored
v.1.1.7
This version Changes naming for DC Chargers and adds some bugfixes on DC Chargers. It also changes how `Sigen Plant Daily Grid Import Energy` and `Sigen Plant Daily Grid Export Energy` read energy so they are calculated from the total and not incremented. * Enhance DC Charger naming logic, and add debug logging in coordinator * Add section for community add-ons and tools in README.md * Update DC Charger naming from "DC Charger" to "DC Charging" * Remove 'keep_existing' option and related logic from config flow and strings * Handle None value in safe_decimal function by returning Decimal(0.0) * Change daily energy sensors to get diffrence from total. As proposed in #170 * Add support for third-party inverter energy in daily sensor calculations * Refactor daily energy calculation to streamline lifetime data retrieval and enhance parameter handling * Add dynamic source entity ID construction and improve daily energy calculation logic * Enhance source entity ID construction to support explicit device context and improve fallback logic for plant-level sensors * Revert version number to 1.1.7 in manifest.json for release
1 parent 16a6df7 commit 0591e21

File tree

11 files changed

+670
-208
lines changed

11 files changed

+670
-208
lines changed

custom_components/sigen/calculated_sensor.py

Lines changed: 627 additions & 125 deletions
Large diffs are not rendered by default.

custom_components/sigen/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ def generate_sigen_entity(
7979
else:
8080
sensor_id = f"{device_name} DC Charger"
8181
sensor_name = f"{sensor_id} {description.name}"
82-
# Keep device_type as DEVICE_TYPE_DC_CHARGER, don't change it to DEVICE_TYPE_INVERTER
8382
else:
8483
sensor_name = f"{device_name} {description.name}"
8584
sensor_id = sensor_name
@@ -266,6 +265,8 @@ def safe_float(value: Any, precision: int = 6) -> Optional[float]:
266265
def safe_decimal(value: Any) -> Optional[Decimal]:
267266
"""Convert to Decimal only if possible, else None."""
268267
try:
268+
if value is None:
269+
return Decimal(0.0)
269270
return Decimal(str(value))
270271
except (InvalidOperation, TypeError, ValueError):
271272
_LOGGER.warning("Could not convert value %s (type %s) to Decimal", value, type(value).__name__)

custom_components/sigen/config_flow.py

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
CONF_PARENT_PLANT_ID,
4848
CONF_INVERTER_HAS_DCCHARGER,
4949
CONF_READ_ONLY,
50-
CONF_KEEP_EXISTING,
5150
CONF_REMOVE_DEVICE,
5251
CONF_SCAN_INTERVAL,
5352
DEVICE_TYPE_NEW_PLANT,
@@ -56,7 +55,6 @@
5655
DEVICE_TYPE_DC_CHARGER,
5756
DEVICE_TYPE_PLANT,
5857
DEVICE_TYPE_UNKNOWN,
59-
LEGACY_SENSOR_MIGRATION_MAP,
6058
STEP_DHCP_PLANT_CONFIG,
6159
STEP_DEVICE_TYPE,
6260
STEP_PLANT_CONFIG,
@@ -108,7 +106,6 @@ def generate_plant_schema(
108106
user_input,
109107
discovered_ip = "",
110108
use_inverter_device_id = False,
111-
keep_existing_alternative = False,
112109
display_update_frq = False
113110
) -> vol.Schema:
114111
"""Dynamically create schema using the determined prefill_host
@@ -117,7 +114,6 @@ def generate_plant_schema(
117114
user_input (_type_, optional): _description_. Defaults to None.
118115
discovered_ip (str, optional): _description_. Defaults to "".
119116
use_inverter_device_id (bool, optional): _description_. Defaults to False.
120-
keep_existing_alternative (bool, optional): _description_. Defaults to False.
121117
display_update_frq (bool, optional): _description_. Defaults to False.
122118
123119
Returns:
@@ -135,9 +131,6 @@ def generate_plant_schema(
135131
validation_schema[vol.Required(CONF_READ_ONLY,
136132
default=user_input.get(CONF_READ_ONLY, DEFAULT_READ_ONLY))] = bool
137133

138-
if keep_existing_alternative:
139-
validation_schema[vol.Required(CONF_KEEP_EXISTING,
140-
default=user_input.get(CONF_KEEP_EXISTING, False))] = bool
141134
if display_update_frq:
142135
validation_schema[vol.Required(CONF_SCAN_INTERVAL,
143136
default=user_input.get(CONF_SCAN_INTERVAL,DEFAULT_SCAN_INTERVAL))] = vol.All(vol.Coerce(int))
@@ -285,20 +278,7 @@ async def async_step_dhcp_plant_config(
285278
errors = {}
286279

287280
# Check if the accumulated energy consumption sensor exists
288-
keep_existing = self.hass.states.get(next(iter(LEGACY_SENSOR_MIGRATION_MAP.values())))
289-
if keep_existing:
290-
_LOGGER.debug(
291-
"Found old yaml integration."
292-
)
293-
# Give an error that it is recommended to remove the old integration.
294-
else:
295-
_LOGGER.debug("Old yaml integration does not exist.")
296-
297-
validation_schema = {vol.Required(CONF_READ_ONLY, default=DEFAULT_READ_ONLY): bool}
298-
if keep_existing:
299-
validation_schema[vol.Required(CONF_KEEP_EXISTING, default=False)] = bool
300-
301-
schema = vol.Schema(validation_schema)
281+
schema = vol.Schema({vol.Required(CONF_READ_ONLY, default=DEFAULT_READ_ONLY): bool})
302282

303283
if user_input is None:
304284
return self.async_show_form(
@@ -307,10 +287,6 @@ async def async_step_dhcp_plant_config(
307287
description_placeholders={"ip_address": self._discovered_ip},
308288
)
309289

310-
# Check if keep_existing is set to True, if so and the user has not approved to continue, throw an error.
311-
if keep_existing and not user_input.get(CONF_KEEP_EXISTING, False):
312-
errors[CONF_KEEP_EXISTING] = "old_config_found"
313-
314290
if errors:
315291
return self.async_show_form(
316292
step_id=STEP_DHCP_PLANT_CONFIG,
@@ -457,21 +433,9 @@ async def async_step_plant_config(
457433
errors = {}
458434
has_dc_charger = False
459435

460-
# Check if the accumulated energy consumption sensor exists
461-
legacy_yaml_present = self.hass.states.get(next(iter(LEGACY_SENSOR_MIGRATION_MAP.values())))
462-
if legacy_yaml_present:
463-
_LOGGER.debug(
464-
"Found old yaml integration."
465-
)
466-
# Store or use accumulatedEnergyState.state if needed for setup logic
467-
else:
468-
_LOGGER.debug("Old yaml integration does not exist.")
469-
470-
471436
if user_input is None:
472437
schema = generate_plant_schema(DEFAULT_PLANT_CONNECTION,
473438
discovered_ip=self._discovered_ip or "",
474-
keep_existing_alternative=bool(legacy_yaml_present),
475439
use_inverter_device_id=True,
476440
display_update_frq=True
477441
)
@@ -481,10 +445,6 @@ async def async_step_plant_config(
481445
data_schema=schema,
482446
)
483447

484-
# Check if keep_existing is set to True, if so and the user has not approved to continue, throw an error.
485-
if legacy_yaml_present and not user_input.get(CONF_KEEP_EXISTING, False):
486-
errors[CONF_KEEP_EXISTING] = "old_config_found"
487-
488448
# Process and validate inverter ID
489449
try:
490450
inverter_id = int(user_input[CONF_INVERTER_SLAVE_ID])
@@ -511,7 +471,6 @@ async def async_step_plant_config(
511471
step_id=STEP_PLANT_CONFIG,
512472
data_schema=generate_plant_schema(user_input,
513473
discovered_ip=self._discovered_ip or "",
514-
keep_existing_alternative=bool(legacy_yaml_present),
515474
use_inverter_device_id=True,
516475
display_update_frq=True
517476
),
@@ -522,6 +481,7 @@ async def async_step_plant_config(
522481
new_plant_connection = DEFAULT_PLANT_CONNECTION.copy()
523482
new_plant_connection[CONF_HOST] = user_input[CONF_HOST]
524483
new_plant_connection[CONF_PORT] = user_input[CONF_PORT]
484+
new_plant_connection[CONF_SLAVE_ID] = DEFAULT_PLANT_SLAVE_ID
525485
new_plant_connection[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL]
526486
self._data[CONF_PLANT_CONNECTION] = new_plant_connection
527487

custom_components/sigen/const.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
CONF_DC_CHARGER_CONNECTIONS = "dc_charger_connections"
2424
CONF_DEVICE_TYPE = "device_type"
2525
CONF_PARENT_DEVICE_ID = "parent_device_id"
26-
CONF_KEEP_EXISTING = "keep_existing"
2726
CONF_VALUES_TO_INIT = "values_to_initialize"
2827
STEP_ACCUMULATED_ENERGY_CONFIG = "accumulated_energy_config"
2928

custom_components/sigen/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "Sigenergy ESS",
44
"codeowners": ["@TypQxQ"],
55
"config_flow": true,
6-
"dependencies": ["modbus", "integration"],
6+
"dependencies": ["modbus", "integration", "recorder"],
77
"dhcp": [
88
{
99
"macaddress": "0C47A99*",
@@ -21,5 +21,5 @@
2121
"loggers": ["custom_components.sigen"],
2222
"quality_scale": "custom",
2323
"requirements": ["pymodbus>=3.8.3"],
24-
"version": "1.1.6"
24+
"version": "1.1.7"
2525
}

custom_components/sigen/modbus.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,7 @@ async def async_read_plant_data(self) -> Dict[str, Any]:
897897
**{name: reg for name, reg in PLANT_PARAMETER_REGISTERS.items()
898898
if reg.register_type != RegisterType.WRITE_ONLY}
899899
}
900-
_LOGGER.debug("Attempting to probe plant registers...")
900+
_LOGGER.debug("Attempting to probe plant registers on %s...", plant_info)
901901
self.plant_register_intervals = await self.async_probe_registers(
902902
plant_info, all_plant_registers
903903
)
@@ -951,7 +951,7 @@ async def async_read_inverter_data(self, inverter_name: str) -> Dict[str, Any]:
951951
**{name: reg for name, reg in INVERTER_PARAMETER_REGISTERS.items()
952952
if reg.register_type != RegisterType.WRITE_ONLY}
953953
}
954-
_LOGGER.debug("Attempting to probe inverter '%s' registers...", inverter_name)
954+
_LOGGER.debug("Attempting to probe inverter '%s' registers on %s...", inverter_name, inverter_info)
955955
self.inverter_register_intervals[inverter_name] = await self.async_probe_registers(
956956
inverter_info, all_inverter_registers
957957
)
@@ -1001,7 +1001,7 @@ async def async_read_dc_charger_data(self, inverter_name: str) -> Dict[str, Any]
10011001
**{name: reg for name, reg in DC_CHARGER_PARAMETER_REGISTERS.items()
10021002
if reg.register_type != RegisterType.WRITE_ONLY}
10031003
}
1004-
_LOGGER.debug("Attempting to probe DC charger '%s' registers...", inverter_name)
1004+
_LOGGER.debug("Attempting to probe DC charger '%s' registers on %s...", inverter_name, inverter_info)
10051005
self.dc_charger_register_intervals[inverter_name] = await self.async_probe_registers(
10061006
inverter_info, all_dc_charger_registers
10071007
)

custom_components/sigen/sensor.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
SensorDeviceClass,
1010
SensorEntity,
1111
SensorEntityDescription,
12-
SensorStateClass,
1312
)
1413
from homeassistant.config_entries import ( # pylint: disable=syntax-error
1514
ConfigEntry,
@@ -31,11 +30,11 @@
3130
SigenergyCalculations as SC,
3231
SigenergyCalculatedSensors as SCS,
3332
SigenergyIntegrationSensor,
33+
SigenergyLifetimeDailySensor,
3434
)
3535
from .static_sensor import StaticSensors as SS
3636
from .static_sensor import COORDINATOR_DIAGNOSTIC_SENSORS # Import the new descriptions
3737
from .common import generate_sigen_entity, generate_device_id, SigenergySensorEntityDescription, SensorEntityDescription
38-
import inspect
3938
from .const import (
4039
DOMAIN,
4140
DEVICE_TYPE_PLANT,
@@ -63,7 +62,8 @@ async def async_setup_entry(
6362
entities_to_add = []
6463

6564
# Helper to add entities to the list
66-
def add_entities_for_device(device_name, device_conn, entity_descriptions, entity_class, device_type, **kwargs):
65+
def add_entities_for_device(device_name, device_conn,
66+
entity_descriptions, entity_class, device_type, **kwargs):
6767
entities_to_add.extend(
6868
generate_sigen_entity(
6969
plant_name,
@@ -79,7 +79,13 @@ def add_entities_for_device(device_name, device_conn, entity_descriptions, entit
7979

8080
# Plant Sensors
8181
add_entities_for_device(None, None, SS.PLANT_SENSORS, SigenergySensor, DEVICE_TYPE_PLANT)
82-
add_entities_for_device(None, None, SCS.PLANT_SENSORS, SigenergySensor, DEVICE_TYPE_PLANT)
82+
83+
# Add calculated plant sensors (all use regular SigenergySensor class)
84+
add_entities_for_device(None, None, SCS.PLANT_SENSORS, SigenergySensor, DEVICE_TYPE_PLANT, hass=hass)
85+
86+
# Add lifetime-based daily sensors with the special sensor class
87+
add_entities_for_device(None, None, SCS.PLANT_LIFETIME_DAILY_SENSORS, SigenergyLifetimeDailySensor, DEVICE_TYPE_PLANT, hass=hass)
88+
8389
add_entities_for_device(None, None, SCS.PLANT_INTEGRATION_SENSORS, SigenergyIntegrationSensor, DEVICE_TYPE_PLANT, hass=hass)
8490
add_entities_for_device(None, None, list(COORDINATOR_DIAGNOSTIC_SENSORS), CoordinatorDiagnosticSensor, DEVICE_TYPE_PLANT)
8591

@@ -113,7 +119,10 @@ def add_entities_for_device(device_name, device_conn, entity_descriptions, entit
113119

114120
# DC Charger
115121
if device_conn.get(CONF_INVERTER_HAS_DCCHARGER, False):
116-
dc_name = f"{device_name} DC Charger"
122+
if "dc charger" not in device_name.lower():
123+
dc_name = f"{device_name} DC Charger"
124+
else:
125+
dc_name = device_name
117126
parent_inverter_id = f"{coordinator.hub.config_entry.entry_id}_{generate_device_id(device_name)}"
118127
dc_id = f"{parent_inverter_id}_dc_charger"
119128
dc_device_info = DeviceInfo(

custom_components/sigen/static_sensor.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,47 +1668,47 @@ class StaticSensors:
16681668
DC_CHARGER_SENSORS = [
16691669
SigenergySensorEntityDescription(
16701670
key="dc_charger_vehicle_battery_voltage",
1671-
name="DC Charger Vehicle Battery Voltage",
1671+
name="Vehicle Battery Voltage",
16721672
device_class=SensorDeviceClass.VOLTAGE,
16731673
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
16741674
state_class=SensorStateClass.MEASUREMENT,
16751675
icon="mdi:car-battery",
16761676
),
16771677
SigenergySensorEntityDescription(
16781678
key="dc_charger_charging_current",
1679-
name="DC Charger Charging Current",
1679+
name="Charging Current",
16801680
device_class=SensorDeviceClass.CURRENT,
16811681
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
16821682
state_class=SensorStateClass.MEASUREMENT,
16831683
icon="mdi:car-electric",
16841684
),
16851685
SigenergySensorEntityDescription(
16861686
key="dc_charger_output_power",
1687-
name="DC Charger Output Power",
1687+
name="Output Power",
16881688
device_class=SensorDeviceClass.POWER,
16891689
native_unit_of_measurement=UnitOfPower.KILO_WATT,
16901690
state_class=SensorStateClass.MEASUREMENT,
16911691
icon="mdi:ev-plug-ccs2",
16921692
),
16931693
SigenergySensorEntityDescription(
16941694
key="dc_charger_vehicle_soc",
1695-
name="DC Charger Vehicle SOC",
1695+
name="Vehicle SOC",
16961696
device_class=SensorDeviceClass.BATTERY,
16971697
native_unit_of_measurement=PERCENTAGE,
16981698
state_class=SensorStateClass.MEASUREMENT,
16991699
icon="mdi:battery",
17001700
),
17011701
SigenergySensorEntityDescription(
17021702
key="dc_charger_current_charging_capacity",
1703-
name="DC Charger Current Charging Capacity (Single Time)",
1703+
name="Current Charging Capacity (Single Time)",
17041704
device_class=SensorDeviceClass.ENERGY,
17051705
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
17061706
state_class=SensorStateClass.TOTAL,
17071707
icon="mdi:car-electric",
17081708
),
17091709
SigenergySensorEntityDescription(
17101710
key="dc_charger_current_charging_duration",
1711-
name="DC Charger Current Charging Duration (Single Time)",
1711+
name="Current Charging Duration (Single Time)",
17121712
icon="mdi:timer",
17131713
state_class=SensorStateClass.MEASUREMENT,
17141714
),

custom_components/sigen/strings.json

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@
99
"title": "Configure Sigenergy Plant",
1010
"description": "A new Sigenergy device was discovered at {ip_address}. Select your configuration options:",
1111
"data": {
12-
"read_only": "Read-Only Mode",
13-
"keep_existing": "Keep Existing Sensors (and potentially rename new ones)"
12+
"read_only": "Read-Only Mode"
1413
},
1514
"data_description": {
16-
"read_only": "(Prevents Home Assistant from making changes to the Sigenergy system. Recommended for initial setup. Can be changed later in device settings.)",
17-
"keep_existing": "If you have previous configurations for this device, adding it again might create duplicate or numbered sensors (e.g., `sensor_name_2`).\n\nRECOMMENDED PATH: Leave this unchecked and manually delete any old Sigenergy YAML configuration and related sensor entities in Home Assistant *before* submitting. This ensures new sensors use standard names.\n\nALTERNATIVE PATH: Check this box to keep existing sensors. No cleanup is needed beforehand, but new sensors with conflicting names will be automatically numbered. You can manage/rename sensors later in device settings."
15+
"read_only": "(Prevents Home Assistant from making changes to the Sigenergy system. Recommended for initial setup. Can be changed later in device settings.)"
1816
}
1917
},
2018
"device_type": {
@@ -36,12 +34,10 @@
3634
"scan_interval": "Scan Interval (seconds)",
3735
"migrate_yaml": "Migrate legacy YAML sensor values",
3836
"migrate_yaml_description": "If enabled, initial sensor values will be imported from the legacy YAML configuration (if present).",
39-
"read_only": "Read-Only Mode",
40-
"keep_existing": "Keep Existing Sensors (and potentially rename new ones)"
37+
"read_only": "Read-Only Mode"
4138
},
4239
"data_description": {
43-
"read_only": "(Prevents Home Assistant from making changes to the Sigenergy system. Recommended for initial setup. Can be changed later in device settings.)",
44-
"keep_existing": "If you have previous configurations for this device, adding it again might create duplicate or numbered sensors (e.g., `sensor_name_2`).\n\nRECOMMENDED PATH: Leave this unchecked and manually delete any old Sigenergy YAML configuration and related sensor entities in Home Assistant *before* submitting. This ensures new sensors use standard names.\n\nALTERNATIVE PATH: Check this box to keep existing sensors. No cleanup is needed beforehand, but new sensors with conflicting names will be automatically numbered. You can manage/rename sensors later in device settings."
40+
"read_only": "(Prevents Home Assistant from making changes to the Sigenergy system. Recommended for initial setup. Can be changed later in device settings.)"
4541
}
4642
},
4743
"select_plant": {
@@ -105,8 +101,7 @@
105101
"cannot_be_lower_than_high": "Cannot be lower than the high interval.",
106102
"cannot_be_lower_than_medium": "Cannot be lower than the medium interval.",
107103
"invalid_host": "Invalid host address provided.",
108-
"invalid_port": "Invalid port number. Must be between 1 and 65535.",
109-
"old_config_found": "Please approve potential sensor renaming (check the box) or delete the old configuration before continuing."
104+
"invalid_port": "Invalid port number. Must be between 1 and 65535."
110105
},
111106
"abort": {
112107
"already_configured": "This Sigenergy system is already configured.",

custom_components/sigen/switch.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ class SigenergySwitchEntityDescription(SwitchEntityDescription):
110110

111111
DC_CHARGER_SWITCHES: list[SigenergySwitchEntityDescription] = [
112112
SigenergySwitchEntityDescription(
113-
key="dc_charger_start_stop",
114-
name="DC Charger",
113+
key="dc_charging",
114+
name="DC Charging",
115115
icon="mdi:ev-station",
116116
is_on_fn=lambda data, identifier: data.get("dc_chargers", {}).get(identifier, {}).get("dc_charger_output_power", 0) > 0,
117117
turn_on_fn=lambda coordinator, identifier: coordinator.async_write_parameter("dc_charger", identifier, "dc_charger_start_stop", 0),
@@ -131,7 +131,8 @@ async def async_setup_entry(
131131
entities_to_add = []
132132

133133
# Helper to add entities to the list
134-
def add_entities_for_device(device_name, device_conn, entity_descriptions, device_type, **kwargs):
134+
def add_entities_for_device(device_name, device_conn,
135+
entity_descriptions, device_type, **kwargs):
135136
entities_to_add.extend(
136137
generate_sigen_entity(
137138
plant_name,

0 commit comments

Comments
 (0)