Skip to content

Improve Charger Support/Use & more QOL#243

Merged
Poshy163 merged 13 commits intomainfrom
Alpha-Charging-improvements
Mar 31, 2026
Merged

Improve Charger Support/Use & more QOL#243
Poshy163 merged 13 commits intomainfrom
Alpha-Charging-improvements

Conversation

@Poshy163
Copy link
Copy Markdown
Collaborator

@Poshy163 Poshy163 commented Mar 1, 2026

Added daily history sensors sourced from getOneDateEnergyBySn / OneDateEnergy:

  • Daily PV Generation
  • Daily Grid Consumption
  • Daily Feed-in
  • Daily Grid Charge
  • Daily Battery Charge
  • Daily Battery Discharge
  • Daily EV Charging Energy
  • Daily Energy Date

(The data is grabbed from your current date/timezone)

Added one currency diagnostic sensor:

  • Currency Code (directly mapped from SumData.moneyType)

  • Configurable scan interval in options flow

  • Add charger diagnostics (e.g., whether the charger is able to charge). This makes it much easier to incorporate into automations, particularly for users who schedule charging based on energy pricing, solar generation, or battery levels.

image
  • Added EV command guardrails so start/stop requests are only sent when charger state is compatible
  • EV connector power entities only expose real connector data, no more blank "unavailable" (existing will become unavailable)
  • One-time startup cleanup migration (for stale EV entities mentioned above)

@Poshy163 Poshy163 changed the title Improve Charger Support/Use Improve Charger Support/Use & more QOL Mar 1, 2026
@Poshy163 Poshy163 marked this pull request as ready for review March 30, 2026 07:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the AlphaESS Home Assistant integration with additional daily energy/history sensors, improved EV charger usability (diagnostics + command guardrails), and adds an options-flow setting to configure the coordinator scan interval.

Changes:

  • Added daily energy breakdown sensors plus diagnostic sensors for energy date and currency code.
  • Added EV charger “readiness” diagnostic binary sensors and guarded start/stop commands based on charger state.
  • Added configurable scan interval (seconds) via the options flow and wired it into the coordinator update interval.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
custom_components/alphaess/translations/en.json Adds options-flow label for scan interval.
custom_components/alphaess/strings.json Adds options-flow label for scan interval (base strings).
custom_components/alphaess/sensorlist.py Adds daily energy + currency diagnostic sensor descriptions; adds EV readiness binary sensor descriptions.
custom_components/alphaess/sensor.py Currency unit normalization + EV entity availability/creation refinements; EV status parsing robustness.
custom_components/alphaess/manifest.json Bumps integration version to 0.8.4.
custom_components/alphaess/enums.py Adds enum keys for new daily/currency sensors and EV readiness sensors.
custom_components/alphaess/entity.py Adds AlphaESSBinarySensorDescription.
custom_components/alphaess/coordinator.py Adds currency mapping, daily energy fields, EV connector power filtering, scan interval injection, EV control guardrails.
custom_components/alphaess/const.py Adds binary_sensor platform + scan interval bounds/constants; expands known chargers list.
custom_components/alphaess/config_flow.py Adds scan interval option and preserves internal option flags across saves.
custom_components/alphaess/button.py Adds EV start/stop guardrails with optional persistent notifications.
custom_components/alphaess/binary_sensor.py New platform providing “Can Start Charging” / “Can Stop Charging” diagnostics.
custom_components/alphaess/init.py Applies options-driven scan interval and adds one-time EV entity cleanup migration.
README.md Documents EV charger controls/guardrails and the new currency + daily sensors.
Comments suppressed due to low confidence (1)

custom_components/alphaess/coordinator.py:442

  • control_ev() converts direction to an int for validation but still passes the original (possibly string) value through to remoteControlEvCharger(). This can lead to type/format mismatches and also raises ValueError if direction is ever non-numeric. Consider changing the method signature to accept an int, wrapping the conversion in a try/except (or removing it), and passing the validated/parsed integer through to the API call.
    async def control_ev(self, serial: str, ev_serial: str, direction: str) -> None:
        """Control EV charger."""
        parsed_direction = int(direction)
        if not self.can_control_ev(serial, parsed_direction):
            _LOGGER.warning(
                "Skipping EV control command for %s (%s), direction=%s due to incompatible state=%s",
                serial,
                ev_serial,
                direction,
                self.get_ev_charger_status_raw(serial),
            )
            return

        result = await self.api.remoteControlEvCharger(serial, ev_serial, direction)
        _LOGGER.info(
            f"Control EV Charger: {ev_serial} for serial: {serial} "
            f"Direction: {direction} - Result: {result}"
        )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to +18
from .sensorlist import EV_CHARGER_BINARY_SENSORS
from .sensor import _build_ev_charger_device_info

Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

binary_sensor.py imports _build_ev_charger_device_info from sensor.py, but the leading underscore indicates it is a private helper. Since it’s now used across modules, consider moving it to a shared utility module (or renaming it without the underscore) to make the intended public usage explicit and avoid future refactors accidentally breaking the binary sensor platform.

Copilot uses AI. Check for mistakes.
…rger_device_info` into a new `device.py` module.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +419 to +490
AlphaESSSensorDescription(
key=AlphaESSNames.DailyPvGeneration,
name="Daily PV Generation",
icon="mdi:solar-power-variant",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
),
AlphaESSSensorDescription(
key=AlphaESSNames.DailyGridConsumption,
name="Daily Grid Consumption",
icon="mdi:transmission-tower-import",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
),
AlphaESSSensorDescription(
key=AlphaESSNames.DailyFeedIn,
name="Daily Feed-in",
icon="mdi:transmission-tower-export",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
),
AlphaESSSensorDescription(
key=AlphaESSNames.DailyGridCharge,
name="Daily Grid Charge",
icon="mdi:battery-arrow-down",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
),
AlphaESSSensorDescription(
key=AlphaESSNames.DailyBatteryCharge,
name="Daily Battery Charge",
icon="mdi:battery-plus",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
),
AlphaESSSensorDescription(
key=AlphaESSNames.DailyBatteryDischarge,
name="Daily Battery Discharge",
icon="mdi:battery-minus",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
),
AlphaESSSensorDescription(
key=AlphaESSNames.DailyEvChargingEnergy,
name="Daily EV Charging Energy",
icon="mdi:car-electric",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
),
AlphaESSSensorDescription(
key=AlphaESSNames.DailyEnergyDate,
name="Daily Energy Date",
icon="mdi:calendar",
native_unit_of_measurement=None,
state_class=None,
entity_category=EntityCategory.DIAGNOSTIC,
),
AlphaESSSensorDescription(
key=AlphaESSNames.CurrencyCode,
name="Currency Code",
icon="mdi:currency-usd",
native_unit_of_measurement=None,
state_class=None,
entity_category=EntityCategory.DIAGNOSTIC,
),
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new daily-energy and currency sensor descriptions are duplicated verbatim in both FULL_SENSOR_DESCRIPTIONS and LIMITED_SENSOR_DESCRIPTIONS. This duplication is error-prone (future tweaks can easily land in only one list). Consider extracting these shared descriptions into a single list and concatenating it into both FULL and LIMITED lists.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +47
ev_subentry_serials = {
sub.data.get(CONF_SERIAL_NUMBER)
for sub in entry.subentries.values()
if sub.subentry_type == SUBENTRY_TYPE_EV_CHARGER
}
if ev_charger in ev_subentry_serials:
continue
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In async_setup_entry(), ev_subentry_serials is rebuilt inside the inverter-subentry loop for every inverter. Since it only depends on entry.subentries, it can be computed once before iterating to avoid repeated work and keep the setup logic consistent with the other platforms.

Copilot uses AI. Check for mistakes.
Comment on lines 425 to 440
async def control_ev(self, serial: str, ev_serial: str, direction: str) -> None:
"""Control EV charger."""
parsed_direction = int(direction)
if not self.can_control_ev(serial, parsed_direction):
_LOGGER.warning(
"Skipping EV control command for %s (%s), direction=%s due to incompatible state=%s",
serial,
ev_serial,
direction,
self.get_ev_charger_status_raw(serial),
)
return

result = await self.api.remoteControlEvCharger(serial, ev_serial, direction)
_LOGGER.info(
f"Control EV Charger: {ev_serial} for serial: {serial} "
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

control_ev() parses direction into parsed_direction for validation, but then still passes the original direction to remoteControlEvCharger(). This makes parsed_direction effectively dead code and risks sending an unexpected type (int vs str) to the API. Consider changing the method signature to accept an int direction and consistently pass the validated/parsed value to the API call and log output (or remove the parse if the API must receive the original type).

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +40
def build_inverter_device_info(
coordinator: AlphaESSDataUpdateCoordinator,
serial: str,
data: dict,
) -> DeviceInfo:
"""Build DeviceInfo for an inverter."""
serial_upper = serial.upper()

kwargs = {
"entry_type": DeviceEntryType.SERVICE,
"identifiers": {(DOMAIN, serial_upper)},
"manufacturer": "AlphaESS",
"model": data.get("Model"),
"model_id": serial,
"name": f"Alpha ESS Energy Statistics : {serial_upper}",
}

if "Local IP" in data and data.get("Local IP") != "0" and data.get("Device Status") is not None:
kwargs["serial_number"] = data.get("Device Serial Number")
kwargs["sw_version"] = data.get("Software Version")
kwargs["hw_version"] = data.get("Hardware Version")
kwargs["configuration_url"] = f"http://{data['Local IP']}"

return DeviceInfo(**kwargs)


def build_ev_charger_device_info(
coordinator: AlphaESSDataUpdateCoordinator,
data: dict,
) -> DeviceInfo:
"""Build DeviceInfo for an EV charger."""
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both build_inverter_device_info() and build_ev_charger_device_info() accept a coordinator argument but do not use it. This adds noise at each call site and can confuse future readers about hidden dependencies. Either remove the parameter entirely, or rename it to _coordinator to make the intent explicit.

Copilot uses AI. Check for mistakes.
…fo` by removing unused coordinator parameter, deduplicate common sensor definitions.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…rger handling, and update sensor state classes to `TOTAL_INCREASING`.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

ev_charger = data.get("EV Charger S/N")
ev_model = data.get("EV Charger Model")
ev_device_info = _build_ev_charger_device_info(coordinator, data)
ev_device_info = build_ev_charger_device_info(data)
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build_ev_charger_device_info is defined to take (coordinator, data), but here it's called with only data, which will raise a TypeError at runtime when EV entities are set up. Align the call sites and device.py signatures (either pass coordinator here, or drop the unused coordinator parameter from the helper and update all callers consistently).

Suggested change
ev_device_info = build_ev_charger_device_info(data)
ev_device_info = build_ev_charger_device_info(coordinator, data)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

existing_ev_serials.add(ev_sn)

# One-time cleanup: remove stale EV entities no longer supported by data.
if not entry.options.get("_ev_entity_cleanup_done", False):
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_cleanup_stale_ev_entities() runs unconditionally on first startup. If the coordinator is currently running in local-fallback mode (cloud_available == False), EV-related keys (including EV charger serial / connector powers) are intentionally absent, which would cause this migration to remove valid EV entities from the registry permanently. Gate the cleanup on cloud availability (and/or confirmed EV charger presence) so it only removes entities when authoritative EV cloud data is present.

Suggested change
if not entry.options.get("_ev_entity_cleanup_done", False):
cloud_available = getattr(_coordinator, "cloud_available", True)
if cloud_available and not entry.options.get("_ev_entity_cleanup_done", False):

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Poshy163
Copy link
Copy Markdown
Collaborator Author

good to go!~

@Poshy163 Poshy163 merged commit c01a4db into main Mar 31, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants