Releases: AndrewTapp/solaredgeoptimizers
v2.4.14
v2.4.14 Summary of Changes (SolarEdge Optimizers Integration)
This document summarizes the main changes made to the integration, including new features, lifetime energy logic, reliability improvements, code quality, and documentation updates.
1. New site-level sensors (SolarEdge One only)
-
Installation date
- Entity:
sensor.[prefix]installation_date_[site](orsensor.[prefix]installation_datewhen site ID is not in entity IDs). - Source: Portal
layout/information/site/{siteId}→installationDate. - Cached for 2 hours (
SITE_INFO_CACHE_TTL), same as layout.
- Entity:
-
Peak power
- Entity:
sensor.[prefix]peak_power_[site](orsensor.[prefix]peak_powerwhen site ID is not in entity IDs). - Source: Portal
layout/information/site/{siteId}→peakPower(kW). - Same cache as above.
- Entity:
Both values are fetched via get_site_info_cached() in the SolarEdge One API and stored on the site aggregated data; the sensor platform exposes them only when using the One API.
2. Lifetime energy: portal overrides and removal of threshold
Previous behaviour
- Site lifetime could use a “portal total” when aggregated data was considered unreliable (below
RELIABLE_THRESHOLD_KWH).
Current behaviour
-
Site lifetime
- Uses the portal’s dashboard production (Wh) from
dashboard/energy/sites/{siteId}?start-date={installation_date}&end-date=...
when that value is greater than the aggregated site total. - Start date for the request is the site installation date from site info.
- Uses the portal’s dashboard production (Wh) from
-
Inverter and string lifetime
- Use the portal’s layout/energy by-inverter data (Wh) from
layout/energy/site/{siteId}/by-inverter?start-date={installation_date}&...
when the portal value is greater than the aggregated inverter or string total.
- Use the portal’s layout/energy by-inverter data (Wh) from
-
Removed
- The constant
RELIABLE_THRESHOLD_KWHand all logic based on it. - Override rule is now simply: if portal value > aggregated value, use portal value (with start date = installation date).
- The constant
Implementation details
-
const.py
- Added
SITE_INFO_CACHE_TTL(2 h). - Added
SENSOR_TYPE_INSTALLATION_DATE,SENSOR_TYPE_PEAK_POWERand included them inSENSOR_TYPE_AGGREGATED_SITE. - Removed
RELIABLE_THRESHOLD_KWH.
- Added
-
solaredge_one_api.py
get_site_info_cached(): layout/information/site →installationDate,peakPower(kW).get_dashboard_site_production_cached(installation_date): dashboard/energy/sites →summary.production(Wh).get_layout_energy_by_inverter_cached(installation_date): layout/energy by-inverter → per-inverter and per-string energy (Wh).- Caches keyed/used by installation date and date range where relevant.
-
api_dual.py
- Wraps the above three methods; when not using SolarEdge One, they return empty/None.
-
coordinator.py
- Fetches site info and installation date; uses it for dashboard production and by-inverter calls.
- Passes
site_infoandportal_by_inverterinto aggregation. - Applies portal overrides for site, inverter, and string lifetime when portal value > aggregated (values from portal are in Wh; converted to kWh for entity state).
-
sensor.py
- New site-level sensor types for Installation date and Peak power (attributes, units, device classes, translation keys).
3. Reliability and resource handling
-
Unload / file descriptors
- On integration unload, the coordinator is always retrieved, its API client is closed (
my_api.close()), and the coordinator is removed fromhass.data, even ifasync_unload_platforms()fails. - Ensures sessions and connections are released and avoids file descriptor leaks (especially for the legacy API’s
requests.Sessionpool).
- On integration unload, the coordinator is always retrieved, its API client is closed (
-
SolarEdge One API
close()- Made explicitly idempotent: if already closed, returns immediately.
-
Dual API
close()- Closes both One and legacy clients; tracks and logs if any client fails to close while still attempting to close the other.
4. Debug logging
- Coordinator: site info fetch result (installation_date, peak_power), dashboard production usage (Wh→kWh), by-inverter fetch and inverter count, and when portal overrides are applied for site, inverter, and string. When creating inverter aggregated data, debug log includes
max_active_power(kW). - SolarEdge One API: cache miss logs for site info, dashboard production, and by-inverter energy; building inverter node logs
maxActivePower(kW) when present. - All debug output remains guarded with
isEnabledFor(logging.DEBUG)so there is no extra cost at info level.
5. Code quality (CodeFactor / Pylint / pycodestyle)
-
Project root
.pylintrc: design limits relaxed (e.g.max-args=10,max-module-lines=2000) so coordinator/sensor and HA callback signatures are not flagged.setup.cfg:[pycodestyle]max-line-length = 159to align with Pylint.
-
Inline disables
# pylint: disable=too-many-argumentsadded where the design requires more than five parameters (e.g._calculate_aggregated_data,_register_inverter_and_string_devices,_process_single_string, sensor builder functions,api_dual.__init__, config_flowasync_show_form).
6. Translations and i18n
- All translation files (e.g.
translations/*.json) include keys for the new site sensors:
entity.sensor.installation_date.name,entity.sensor.peak_power.name. - Documentation (including internationalization.md) updated to list Installation date and Peak power in the entity sensor names and translation structure.
7. Documentation updates
-
README.md
- New site-level Installation date and Peak power; lifetime energy described as portal override (dashboard production / by-inverter; start date = installation date); code quality section updated to reference
.pylintrcandsetup.cfg.
- New site-level Installation date and Peak power; lifetime energy described as portal override (dashboard production / by-inverter; start date = installation date); code quality section updated to reference
-
info.md
- Same site sensors and lifetime behaviour; RELIABLE_THRESHOLD removed.
-
docs/Wiki-Home.md
- Overview, device hierarchy, sensor reference, and API section updated for Installation date and Peak power; caching table includes site info (2 h) and layout (2 h for both One and legacy); lifetime energy and portal overrides described; RELIABLE_THRESHOLD removed and replaced with
SITE_INFO_CACHE_TTLin constants; file structure no longer references CODEQUALITY.md/pyproject.toml in the integration folder.
- Overview, device hierarchy, sensor reference, and API section updated for Installation date and Peak power; caching table includes site info (2 h) and layout (2 h for both One and legacy); lifetime energy and portal overrides described; RELIABLE_THRESHOLD removed and replaced with
-
docs/SolarEdge-One-API-Summary.md
- Site info, dashboard production, and by-inverter energy; site-level Installation date and Peak power sensors; portal lifetime overrides and installation date as start date.
-
docs/internationalization.md
- Installation date and Peak power added to the list of sensor entity names and translation keys.
8. New inverter-level sensor: Max active power (SolarEdge One only)
-
Max active power
- Entity:
sensor.[prefix]max_active_power_[site]_[inverter](orsensor.[prefix]max_active_power_[inverter]when site ID is not in entity IDs). - Source: Portal layout logical v2
.../layout/logical/generic/v2/site/{siteId}?include-optimizers=true→ per-inverterproperties.maxActivePower(watts). Displayed as kW (same as site Peak power). - Cached with the layout (2 h); no separate API call.
- One API only; legacy layout does not provide this field (sensor still created, value unknown until available).
- Entity:
-
Implementation
- const.py:
SENSOR_TYPE_MAX_ACTIVE_POWER, added toSENSOR_TYPE_AGGREGATED_INVERTER. - solaredge_one_api.py:
_v2_build_inverter_logical_nodereadsmaxActivePower(W) from inverter properties, converts to kW, adds to inverterdatadict. - solaredgeoptimizers.py:
SolarEdgeInverterhasmaxActivePower(fromdata.get("maxActivePower"));SolarEdgeAggregatedDatahasmax_active_power. - coordinator.py:
_create_inverter_aggregatedsetsinverter_aggregated.max_active_power = getattr(inverter, "maxActivePower", None); debug log includesmax_active_powerwhen creating inverter aggregated. - sensor.py: New sensor type in aggregated maps (attr, translation key, units kW); value handling same as Peak power (round to 2 decimals).
- Translations: All locale files updated with
entity.sensor.max_active_power.name(e.g. "Max active power", "Max. Wirkleistung").
- const.py:
-
Inactive inverters: Max active power sensor is still created for inactive inverters (fixed inverter property); value may be unknown if layout does not provide it.
9. File descriptor and documentation refresh
-
File descriptor handling
- api.py: Protocol
close()docstring now states "Release resources, close sessions, and release file descriptors". - solaredgeoptimizers.py: Module docstring notes that
close()closes all tracked sessions to avoid leaking file descriptors on unload/removal. - solaredge_one_api.py: Module docstring notes no persistent sessions (context managers), and
close()clears tokens. - init.py: Docstring states that on unload or removal the API client is closed to release file descriptors.
- config_flow.py: Cleanup section now says "Closes API (releases file descriptors), then removes entities and devices".
- api.py: Protocol
-
Top-of-file / module comments
- solaredgeoptimizers.py: Data classes (SolarEdgeInverter maxActivePower, SolarEdgeAggregatedData max_active_power), Key Features (close/file descriptors).
- sensor.py: Aggregated sensors list includes "Inverter-level: Max active power (kW, from portal layout logical v2; One API only)".
- coordinator.py: Data aggregation includes "Inverter aggregated data includes max_active_power (kW) from layout logical v2 when available (One API)".
-...
v2.4.13
v2.4.13 Summary of changes — 13 March 2025
This document summarizes the documentation and related updates made on 13 March 2025.
Documentation updates
The following files were reviewed and updated so they accurately reflect the current integration behaviour, constants, and file layout.
README.md
- Layout cache: Corrected the legacy API layout (panels) cache from "1 hour" to 2 hours. Both SolarEdge One and legacy now document a 2 hour cache (
PANELS_CACHE_TTL_ONE,PANELS_CACHE_TTL_LEGACYinconst.py). - Lifetime energy (per-optimizer): Clarified that string totals are derived by summing that string’s optimizer entries (not from API string-level keys that can be site totals), and that site lifetime is the sum of inverters when reliable, or the portal total when aggregated data is unreliable (e.g. below
RELIABLE_THRESHOLD_KWH). - Code quality: Added a short note that the project uses Pylint and pycodestyle (e.g. via CodeFactor), with links to
pyproject.tomlandCODEQUALITY.mdfor tool config and intentional patterns/suppressions.
info.md
- Updates / lifetime energy: Replaced the previous “optimizer-level reliable / portal total directly” wording with the same behaviour as the README: string totals from optimizer sums; site lifetime from inverters when reliable or from the portal when unreliable (e.g. below
RELIABLE_THRESHOLD_KWH). - Debug logging: Documented that debug logging also covers unload (including “unload finally closing API sessions”) and API client closure (dual API: close both clients; config flow: API close on entry removal).
docs/Wiki-Home.md
- Layout cache: The update-behaviour table and the “Caching” subsection now state the layout (panels) cache is 2 hours for both One and legacy (previously legacy was described as 1 h).
- Configuration: The single-step config form description now includes Use SolarEdge One in the field order: Site ID → Username → Password → Entity ID prefix → Include Site ID in Entity ID → Use SolarEdge One.
- File structure (Section 14):
- Single consolidated
api.pyentry (SolarEdgeAPIProtocol). - Added
exceptions.py,CODEQUALITY.md, andpyproject.tomlto the repo layout. - Corrected doc filename to
internationalization.md(wasInternationalization.md). - Manual-install file list now includes
exceptions.py.
- Single consolidated
- Lifetime energy (Section 12 – Data and units): Documented that string-level lifetime is always derived by summing that string’s optimizer entries in the coordinator (
_build_lifetime_energy_lookup), that the legacy API can return a string key that matches a stringId but holds a site- or inverter-level total, and that the integration does not use that key when optimizer data is available, to avoid double-counting and inflated site totals.
docs/SolarEdge-One-API-Summary.md
- Polling / stale threshold: “legacy used 2 hours” was updated to “legacy uses 2 hours”.
- Layout cache: Added that site layout (panels) is cached for 2 hours (
PANELS_CACHE_TTL_ONE) when using SolarEdge One.
docs/internationalization.md
- No content changes. Confirmed it already matches the README (four-section translation structure, Use SolarEdge One, supported languages, link to README translations table).
Summary table
| File | Changes |
|---|---|
| README.md | Layout cache 2 h (both APIs); lifetime energy aggregation wording; code quality note. |
| info.md | Lifetime energy aggregation wording; debug logging (unload, API close). |
| Wiki-Home.md | Layout cache 2 h; config field order (Use SolarEdge One); file structure (api, exceptions, CODEQUALITY, pyproject, internationalization); manual-install list; lifetime aggregation (string sum, no double-count). |
| SolarEdge-One-API-Summary.md | Legacy “uses” 2 h; layout cache 2 h for One. |
| internationalization.md | Verified up to date; no edits. |
Constants reflected in the docs
- Layout cache:
PANELS_CACHE_TTL_ONEandPANELS_CACHE_TTL_LEGACYare both 2 hours (const.py). - Lifetime energy: String totals = sum of that string’s optimizers (coordinator); site total = sum of inverters when reliable, or portal total when below
RELIABLE_THRESHOLD_KWH(100 kWh). - Code quality:
pyproject.tomlandCODEQUALITY.mdare the reference for Pylint/pycodestyle and intentional patterns.
v2.4.12
v2.4.12 - SolarEdge Optimizers – Changes Summary
This document summarizes changes made to the integration, including recent work on constants, status handling, aggregation, debug logging, CodeFactor-related fixes, documentation, and resource cleanup.
1. Constants and configuration
- Centralized constants in
const.py: Cache TTLs (PANELS_CACHE_TTL_ONE,PANELS_CACHE_TTL_LEGACY,LIFETIME_ENERGY_CACHE_TTL,TEMPERATURE_CACHE_TTL), light-check intervals (LIGHT_CHECK_DESIRED_INTERVAL_FRESH,LIGHT_CHECK_DESIRED_INTERVAL_STALE), API URLs, OAuth client ID, locale-dependent measurement keys (MEASUREMENT_KEYS), and API source labels (OBTAINED_FROM_ONE,OBTAINED_FROM_LEGACY). - Module docstring in
const.pydocuments each constant and shared helpers. - Status helpers in
const.py:is_status_active(status),status_display_value(raw),status_icon(display_value)for consistent handling of blank, Active, Inactive, and other statuses across the integration.
2. Status handling
- Blank (empty) status is treated as active everywhere: aggregation, sorting, display, and icons.
- Display: Blank → shown as "blank" with active icon; "Active" / "Inactive" in proper case; any other value shown as-is with unknown (help-circle) icon.
- Icons: check-circle for Active/blank, alert-circle for Inactive, help-circle for unknown.
- Child counts (Optimizer count, String count, Inverter count) count only active devices (status blank or "Active").
- Duplicate resolution (suffixes a, b, c…): active (including blank) items are ordered first, then others.
3. Aggregation behaviour
- Aggregated values (power, current average, voltage average, lifetime energy) at string, inverter, and site level include data from all devices (any status) that have recent measurements. Averages use the count of devices that contributed data; totals sum all contributing devices.
- Child counts (Optimizer count per string, String count per inverter, Inverter count per site) count only active devices (status blank or "Active") and are always integers.
- Coordinator uses
optimizer_count/string_count/inverter_countfor average divisors andactive_optimizers/active_strings/active_invertersfor child_count sensors.
4. Debug logging
- Cache messages now include TTL where relevant:
- SolarEdge One: Panels, lifetime energy, and temperature cache hit/miss logs include TTL (e.g. "Panels cache miss (TTL=%s)", "Using cached panels (age=%s, TTL=%s)").
- Legacy: Panels and lifetime energy cache messages include TTL and consistent "(legacy)" prefix.
- Coordinator:
_log_update_cycle_debuglogsinterval_kind("fresh" or "stale") and splits the long format string across lines so it stays within line-length limits. - Temperature (One API): Added cache-miss debug before fetching when the temperature cache is expired.
- All debug calls remain guarded with
_LOGGER.isEnabledFor(logging.DEBUG).
5. CodeFactor-related fixes
- config_flow.py: Replaced
except Exception:withexcept Exception as e:in the user step and reauth step; log messages now include the exception (e.g._LOGGER.exception("Unexpected exception: %s", e)). - coordinator.py: Long debug format string in
_log_update_cycle_debugsplit across two lines with implicit string concatenation. - solaredgeoptimizers.py:
requestSystemData: Long URL split intobase+params+url.requestItemHistory: Long chartData URL split intobase+q+url; long dict comprehension rewritten as a multi-line dict comprehension.
- Custom exception:
SolarEdgeAPIErrorinexceptions.pyis used for API/processing errors instead of genericExceptionwhere appropriate. - Legacy client: Replaced
assertwith explicitValueErrorwhere parameter validation is required; replaced genericraise Exception(...)withraise SolarEdgeAPIError(...).
6. File descriptor and resource cleanup
- config_flow.py –
validate_input: API client is closed in afinallyblock after credential validation so temporary sessions are released. - config_flow.py –
async_remove_entry: When the integration is removed, the coordinator is popped fromhass.dataand the API client is closed (if present) before callingremove_entities_and_devices_for_entry, so file descriptors are released even if unload did not run or did not close the API. - init.py: On setup failure (before the coordinator is stored), the temporary API instance is closed in a
finallyblock. - api_dual.py:
close()closes both One and Legacy clients with explicit logging; exceptions from one client do not prevent closing the other. - Legacy client:
close()docstring clarifies thatSession.close()releases file descriptors and thread-local session is cleared.
7. Documentation updates
- README.md: Temperature refresh described as using the temperature cache expiry (30 minutes,
TEMPERATURE_CACHE_TTL). Inactive devices section updated: aggregation uses all devices (any status) with recent data; child counts use only active devices. - info.md: Inactive devices / aggregation paragraph updated to match (aggregation = all devices; child counts = active only).
- docs/Wiki-Home.md:
- Aggregation behaviour (Section 9) rewritten: aggregation values from all devices with recent data; child counts from active only; note on inactive devices contributing to aggregates when they have data.
- Temperature references updated from "15 min" to "30 min" /
TEMPERATURE_CACHE_TTL(data flow note, Section 7 table, Section 12 caching). - Removal cleanup:
async_remove_entrynow documented as closing the API client (releasing file descriptors) when the coordinator is still inhass.data, then calling the shared helper. Config flow table row updated similarly.
- coordinator.py module docstring: Polling interval described as coordinator tick every 5 minutes; minimum interval between full refreshes set to 5 minutes (
LIGHT_CHECK_MIN_INTERVAL). Data aggregation summary updated: power/current/voltage/lifetime from all devices with recent data; child counts from active only.
8. Timing and cache constants (reference)
| Constant | Value | Purpose |
|---|---|---|
UPDATE_DELAY |
5 min | Coordinator tick interval |
LIGHT_CHECK_MIN_INTERVAL |
5 min | Min time between a light-check-triggered full refresh and the next |
LIGHT_CHECK_DESIRED_INTERVAL_FRESH |
5 min | Desired light-check interval when data is fresh |
LIGHT_CHECK_DESIRED_INTERVAL_STALE |
30 min | Desired light-check interval when data is stale or missing |
PANELS_CACHE_TTL_ONE |
2 h | Layout cache (SolarEdge One) |
PANELS_CACHE_TTL_LEGACY |
1 h | Layout cache (legacy API) |
LIFETIME_ENERGY_CACHE_TTL |
1 h | Lifetime energy cache |
TEMPERATURE_CACHE_TTL |
30 min | Optimizer temperatures cache (One API only) |
9. File structure (relevant files)
const.py– Constants, status helpers,resolve_duplicate_indices, display-name parsers.exceptions.py–SolarEdgeAPIError.config_flow.py– Validation (dual API), API close after validation and on entry removal.__init__.py– Setup with API cleanup on failure;remove_entities_and_devices_for_entry.coordinator.py– Adaptive polling, aggregation (all statuses for values, active only for child counts), device registration.solaredgeoptimizers.py– Legacy API;SolarEdgeAPIError; cache TTLs from const.solaredge_one_api.py– One API; cache TTLs and status check from const.api_dual.py– Dual API;OBTAINED_FROM_*from const; robustclose().
This summary reflects the state of the integration after the documented changes. For installation and user-facing features, see the main README and Wiki-Home.
What's Changed
- Dev by @AndrewTapp in #34
New Contributors
- @AndrewTapp made their first contribution in #34
Full Changelog: 2.4.11...2.4.12
v2.4.11
v2.4.10
Bug fix, error in previous version.
Full Changelog: 2.4.9...2.4.10
v2.4.9
v2.4.8
v2.4.8 Inactive Devices Feature
As with previous versions you may need to delete and re-add this integtration after updating through HACS.
This document summarizes the inactive device handling feature added to the SolarEdge Optimizers integration.
Overview
When an optimizer, string, or inverter is marked as Inactive in the SolarEdge portal, the integration now:
- Excludes certain sensors that are not meaningful for inactive/disconnected devices
- Excludes inactive devices from aggregations so totals and averages only reflect active equipment
Sensors Excluded for Inactive Devices
Inactive Optimizers
The following sensors are not created for inactive optimizers:
| Sensor | Reason for exclusion |
|---|---|
| Azimuth | No meaningful orientation data for inactive panel |
| Current | No live current reading |
| Optimizer voltage | No live voltage reading |
| Power | No live power reading |
| Temperature | No live temperature reading |
| Tilt | No meaningful orientation data for inactive panel |
| Voltage | No live voltage reading |
The following sensors are still created for inactive optimizers:
| Sensor | Reason for inclusion |
|---|---|
| Lifetime energy | Historical data remains valuable |
| Last measurement | Shows when the optimizer was last active |
| Status | Shows the current status ("Inactive") |
Inactive Strings and Inverters
The following sensors are not created for inactive strings/inverters:
| Sensor | Reason for exclusion |
|---|---|
| Current (average) | No meaningful average from inactive device |
| Power | No live power reading |
| Voltage (average) | No meaningful average from inactive device |
The following sensors are still created for inactive strings/inverters:
| Sensor | Reason for inclusion |
|---|---|
| Lifetime energy | Historical data remains valuable |
| Last measurement | Shows when the device was last active |
| Child count | Shows how many child devices exist |
| Status | Shows the current status ("Inactive") |
Aggregation Behavior
Before This Change
All devices were included in aggregations regardless of status, which could result in:
- Stale or zero values from inactive devices skewing averages
- Misleading totals that included non-producing equipment
After This Change
Only active devices contribute to aggregations:
| Aggregation Level | What is included |
|---|---|
| String aggregations | Only optimizers with status "Active" |
| Inverter aggregations | Only strings with status "Active" |
| Site aggregations | Only inverters with status "Active" |
This ensures that:
- Current (average) reflects only actively reporting devices
- Power totals only include producing equipment
- Voltage (average) reflects only live readings
Implementation Details
Constants Added (const.py)
SENSOR_TYPE_INACTIVE_OPTIMIZER_EXCLUDE = [
SENSOR_TYPE_AZIMUTH,
SENSOR_TYPE_CURRENT,
SENSOR_TYPE_OPT_VOLTAGE,
SENSOR_TYPE_POWER,
SENSOR_TYPE_TEMPERATURE,
SENSOR_TYPE_TILT,
SENSOR_TYPE_VOLTAGE,
]
SENSOR_TYPE_INACTIVE_AGGREGATED_EXCLUDE = [
SENSOR_TYPE_CURRENT,
SENSOR_TYPE_POWER,
SENSOR_TYPE_VOLTAGE,
]Sensor Platform Changes (sensor.py)
_build_individual_optimizer_sensors(): Checks optimizer status and skips sensors inSENSOR_TYPE_INACTIVE_OPTIMIZER_EXCLUDEfor non-active optimizers_build_aggregated_sensors(): Checks string/inverter status and skips sensors inSENSOR_TYPE_INACTIVE_AGGREGATED_EXCLUDEfor non-active devices- Added logging to track active/inactive device counts and skipped sensors
Coordinator Changes (coordinator.py)
_aggregate_optimizers_in_string(): Only includes optimizer data in string aggregations whenoptimizer_status == "ACTIVE"_process_inverter_strings(): Only includes string data in inverter aggregations whenstring_is_active_calculate_aggregated_data(): Only includes inverter data in site aggregations wheninverter_is_active
Documentation Updated
The following documentation files were updated to reflect this feature:
| File | Changes |
|---|---|
README.md |
Added new "Inactive Devices" section |
info.md |
Added "Inactive devices" paragraph |
docs/Wiki-Home.md |
Added new section 9 with detailed tables; renumbered subsequent sections; updated table of contents; added new constants to reference |
docs/internationalization.md |
No changes needed (feature doesn't add new translation keys) |
User Impact
What Users Will See
- Fewer sensors for inactive devices (cleaner device pages)
- More accurate aggregated values at string, inverter, and site levels
- No change to active device behavior
Migration Notes
- No action required from users
- Existing inactive devices will have fewer sensors after updating
- Entity history for excluded sensors will no longer update (but historical data is preserved in Home Assistant's database)
Related Features
This feature complements existing functionality:
- Stale data handling: Live values (power, current, voltage) are zeroed when last measurement exceeds threshold (1h for One API, 2h for Legacy API)
- Duplicate name handling: Active devices sort first when resolving duplicate names
- Status sensor: Each device has a Status sensor showing "Active" or "Inactive"
Full Changelog: 2.4.7...2.4.8
v2.4.7
v2.4.7 New Sensors and Duplicate Name Handling
Make sure to delete and re-add the integration once it's been updated in HACS.
This document summarizes the changes made to add Status, Azimuth, and Tilt sensors, along with automatic duplicate name resolution for inverters, strings, and optimizers.
New Sensors
Per Optimizer
| Sensor | Unit | Description |
|---|---|---|
| Status | — | Optimizer status from the API (e.g. "Active", "Inactive"). Displayed in proper case. |
| Azimuth | ° | Panel compass direction (0–360°). Converted from radians to degrees, rounded to 2 decimal places. Only available when the API provides module orientation data. |
| Tilt | ° | Panel angle from horizontal. Converted from radians to degrees, rounded to 2 decimal places. Only available when the API provides module orientation data. |
Per String
| Sensor | Description |
|---|---|
| Status | String status from the API (e.g. "Active", "Inactive"). Displayed in proper case. |
Per Inverter
| Sensor | Description |
|---|---|
| Status | Inverter status from the API (e.g. "Active", "Inactive"). Displayed in proper case. |
Duplicate Handling
When the SolarEdge API returns multiple inverters, strings, or optimizers with the same position (e.g. after hardware replacement where both old and new units appear), the integration now handles duplicates at two levels:
1. Display Name Resolution (API Layer)
The solaredge_one_api.py resolves duplicate display names from the API:
| Entity Type | Sorting Order | Suffix Pattern |
|---|---|---|
| Inverters | Active first, then by serial number | Inverter 1, Inverter 1a, Inverter 1b, ... |
| Strings | Active first, then by position | String 1.0, String 1.0a, String 1.0b, ... |
| Optimizers | Active first, then by serial number | Optimizer 1.0.1, Optimizer 1.0.1a, Optimizer 1.0.1b, ... |
2. Entity ID Resolution (Sensor/Coordinator Layer)
The sensor.py and coordinator.py resolve duplicate entity IDs independently, ensuring all devices are shown even if they share the same logical position:
| Entity Type | Sorting Order | Entity ID Suffix |
|---|---|---|
| Inverters | Active first, then by serial number ascending | _1, _1a, _1b, ... |
| Strings | Active first, then by serial number/ID | _1_0, _1_0a, _1_0b, ... |
| Optimizers | Active first, then by serial number ascending | _1_0_1, _1_0_1a, _1_0_1b, ... |
How It Works
- No deduplication: All inverters, strings, and optimizers found in the API are shown
- Entities are grouped by their position key (parsed from display name or enumeration index)
- Within each group, entities are sorted:
- Active status items come first (status = "ACTIVE")
- Within each status group, sorted alphabetically by serial number
- The first item keeps the original position number
- Subsequent items receive alphabetical suffixes (a, b, c, ...)
Example
If the API returns two inverters both at position "1":
- Serial
ABC123with status "Active" → Inverter 1 (entity ID:..._1) - Serial
DEF456with status "Inactive" → Inverter 1a (entity ID:..._1a)
Entity IDs follow the same pattern:
sensor.lifetime_energy_2065855_1(first inverter)sensor.lifetime_energy_2065855_1a(second inverter)
Files Modified
Core Logic
| File | Changes |
|---|---|
const.py |
Added SENSOR_TYPE_STATUS, SENSOR_TYPE_AZIMUTH, SENSOR_TYPE_TILT constants; updated sensor type lists; added shared make_duplicate_sort_key() and resolve_duplicate_indices() functions for duplicate resolution |
solaredge_one_api.py |
Added _make_duplicate_sort_key() factory function and _resolve_duplicate_names_with_suffix() function for display name resolution; updated _v2_build_inverter_logical_node, _v2_build_string_logical_node, _v2_build_optimizer_logical_node to extract status; added _extract_azimuth_tilt_from_basic() for radian-to-degree conversion |
solaredgeoptimizers.py |
Updated SolarEdgeInverter, SolarEdgeString, SolarlEdgeOptimizer classes to store status; updated SolarEdgeOptimizerData to include azimuth, tilt, status; updated SolarEdgeAggregatedData to include status |
coordinator.py |
Updated _aggregate_optimizers_in_string() to copy status from site structure to optimizer data; updated _create_string_aggregated() and _create_inverter_aggregated() to copy status to aggregated data objects; added AggregationContext namedtuple to reduce parameter count; uses shared resolve_duplicate_indices() from const.py for entity ID duplicate resolution; added debug logging for status assignments and duplicate resolution |
sensor.py |
Added sensor handling for Status, Azimuth, Tilt; uses shared resolve_duplicate_indices() from const.py for entity ID duplicate resolution in _build_optimizer_tasks() and _build_aggregated_sensors(); updated debug logging to include new fields and duplicate resolution |
Translations (19 languages)
All translation files updated with new sensor names:
| Language | Status | Azimuth | Tilt |
|---|---|---|---|
| English (en) | Status | Azimuth | Tilt |
| German (de) | Status | Azimut | Neigung |
| French (fr) | Statut | Azimut | Inclinaison |
| Spanish (es) | Estado | Azimut | Inclinación |
| Italian (it) | Stato | Azimut | Inclinazione |
| Dutch (nl) | Status | Azimut | Hellingshoek |
| Portuguese (pt) | Estado | Azimute | Inclinação |
| Polish (pl) | Status | Azymut | Nachylenie |
| Swedish (sv) | Status | Azimut | Lutning |
| Czech (cs) | Stav | Azimut | Sklon |
| Danish (da) | Status | Azimut | Hældning |
| Greek (el) | Κατάσταση | Αζιμούθιο | Κλίση |
| Finnish (fi) | Tila | Atsimuutti | Kallistus |
| Hungarian (hu) | Állapot | Azimut | Dőlésszög |
| Norwegian (nb) | Status | Asimut | Helning |
| Russian (ru) | Статус | Азимут | Наклон |
| Turkish (tr) | Durum | Azimut | Eğim |
| Japanese (ja) | ステータス | 方位角 | 傾斜角 |
| Chinese (zh) | 状态 | 方位角 | 倾斜角 |
Documentation
| File | Changes |
|---|---|
README.md |
Added new sensors to "What Data You Get" section; added "Handling duplicate names" section |
info.md |
Updated overview and added duplicate name handling note |
docs/Wiki-Home.md |
Updated sensor tables and features list |
docs/internationalization.md |
Added new sensor names to translation reference |
Debug Logging
New debug log messages added for:
API Layer (solaredge_one_api.py)
- Optimizer node building:
SolarEdge One: Building optimizer node serial=%s name=%s status=%s - String node building:
SolarEdge One: Building string node id=%s name=%s status=%s - Inverter node building:
SolarEdge One: Building inverter node serial=%s name=%s status=%s manufacturer=%s model=%s - Duplicate name resolution:
SolarEdge One: Resolving %d duplicate names for '%s'andSolarEdge One: Resolved names for '%s' - Azimuth/Tilt extraction:
SolarEdge One: Optimizer %s azimuth=%s° tilt=%s°
Coordinator Layer (coordinator.py)
- Status assignment:
SolarEdge Optimizers: Set optimizer %s status=%s - String aggregated:
SolarEdge Optimizers: Created string aggregated %s status=%s child_count=%d active=%d - Inverter aggregated:
SolarEdge Optimizers: Created inverter aggregated %s status=%s child_count=%d active_optimizers=%d - Duplicate resolution:
SolarEdge Optimizers: Resolving %d duplicate keys for '%s'andSolarEdge Optimizers: Resolved suffixes for '%s' - Duplicate counts:
SolarEdge Optimizers: Found %d duplicate inverter positions...andSolarEdge Optimizers: Found %d duplicate string positions...
Sensor Layer (sensor.py)
- Status update:
SolarEdge Optimizers sensor: %s status updated to '%s' - Aggregated status update:
SolarEdge Optimizers sensor: %s aggregated status updated to '%s' - Sensor creation: Updated to include
status=%s azimuth=%s° tilt=%s° - Optimizer tasks:
SolarEdge Optimizers sensor: Built %d optimizer tasks from site structure - Duplicate resolution:
SolarEdge Optimizers sensor: Found %d duplicate optimizer positions, assigned suffixes (a, b, c...) - Aggregated sensors:
SolarEdge Optimizers sensor: Building aggregated sensors for %d inverters, %d strings
Shared (const.py)
- Duplicate key resolution:
SolarEdge Optimizers: Resolving %d duplicate keys for '%s': [(status, serial), ...] - Resolved suffixes:
SolarEdge Optimizers: Resolved suffixes for '%s': {idx: suffix, ...}
Entity ID Patterns
Status Sensors
| Level | Entity ID Pattern |
|---|---|
| Optimizer | sensor.[prefix]status_[site]_[inverter]_[string]_[optimizer] |
| String | sensor.[prefix]status_[site]_[inverter]_[string] |
| Inverter | sensor.[prefix]status_[site]_[inverter] |
Azimuth and Tilt Sensors (Optimizer only)
| Sensor | Entity ID Pattern |
|---|---|
| Azimuth | sensor.[prefix]azimuth_[site]_[inverter]_[string]_[optimizer] |
| Tilt | sensor.[prefix]tilt_[site]_[inverter]_[string]_[optimizer] |
Stale Data Handling
- Status, Azimuth, and Tilt sensors always show the last known value (not zeroed when stale)
- This matches the behavior of Lifetime energy and Last measurement sensors
Full Changelog: 2.4.6...2.4.7
v2.4.6
Display-name-based string and optimizer IDs
Brief summary of the change to use the API display name for device names and entity IDs at string and optimizer level.
What changed
Before: String and optimizer device names and entity IDs were always position-based (1-based from enumerate(..., start=1)). For a user whose API returns string display name "1.0" (zero-based), the device could show "String 1.0" (from the API) but the entity ID was sensor.lifetime_energy_1_1 — a mismatch.
After: At string and optimizer level, device names and entity IDs are based on the API display name when it parses:
- String display name
"1.0"→ device "String 1.0", entity IDs likesensor.lifetime_energy_1_0 - Optimizer display name
"1.0.1"→ device "Optimizer 1.0.1", entity IDs likesensor.lifetime_energy_1_0_1
If the API display name does not parse (wrong format), the integration falls back to position-based indices so behaviour stays correct. Site and inverter level remain position-based.
Code changes
| Location | Change |
|---|---|
| const.py | Added parse_string_display_name_path(display_name) → (inv, str) or None; parse_optimizer_display_name_to_indices(display_name) → (inv, str, opt) or None. |
| coordinator.py | String device creation and _create_string_aggregated use parsed display name for device id/name and entity_id_path when parse succeeds; otherwise use position. |
| sensor.py | _build_optimizer_tasks: use parsed optimizer display name for (inv_idx, str_idx, opt_idx) when parse succeeds. _build_aggregated_sensors: use parsed string display name for entity_id_path. _device_info_for_string: device name placeholder uses string.displayName (API value); device identifier uses entity_id_path (now display-name-based when parse succeeds). |
Documentation updated
- README.md – Hierarchy section, entity ID explanation, hardware replacement.
- info.md – In Home Assistant paragraph, hardware replacement.
- docs/Wiki-Home.md – Device identity, entity IDs, sensor platform table, API identity, file structure (const.py).
- docs/Internationalization.md – Device names and display-name placeholder; “not translated” note.
- docs/SolarEdge-One-API-Summary.md – Inverter information / 403 note.
Result
For users where the API uses zero-based (or other) string/optimizer numbering (e.g. "1.0", "1.0.1"), device names and entity IDs now match: "String 1.0" and sensor.lifetime_energy_1_0, "Optimizer 1.0.1" and sensor.lifetime_energy_1_0_1. Hardware replacement still works: data is keyed by position, so the same sensor continues to show the new unit’s data after a refresh.
Full Changelog: 2.4.5...2.4.6
v2.4.5
v2.4.5
Amendments for temperature conversions.
General tidying of code and comments.
Full Changelog: 2.4.4...2.4.5