forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
thimo.vandenbroek
committed
Dec 15, 2024
1 parent
412aa60
commit 2cef7b8
Showing
22 changed files
with
876 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# Peblar Home Assistant Integration | ||
|
||
The Peblar Home Assistant Integration provides seamless integration with Peblar devices, enabling monitoring and control directly from Home Assistant. This integration supports configuring connection details, authenticating with the Peblar API, and setting up sensors and number entities. | ||
|
||
--- | ||
|
||
## Features | ||
|
||
- **Connection Management:** Configure the IP address and access token for your Peblar device. | ||
- **Sensors:** Monitor key metrics like charging current, total energy, session energy, and charge power. | ||
- **Number Entities:** Control parameters such as maximum charging current. | ||
- **Reauthentication Support:** Easily reauthenticate in case of API authentication errors. | ||
|
||
--- | ||
|
||
## Installation | ||
|
||
### Installation via HACS | ||
|
||
1. Ensure that [HACS (Home Assistant Community Store)](https://hacs.xyz/) is installed in your Home Assistant setup. | ||
2. Go to **HACS > Integrations**. | ||
3. Click the three dots in the top-right corner and select **Custom Repositories**. | ||
4. Add the URL of this repository and select `Integration` as the category. | ||
5. Search for `Peblar` in HACS and click **Download**. | ||
|
||
### Manual Installation | ||
|
||
1. Download or clone the integration files. | ||
2. Place the files in the `custom_components/peblar` directory within your Home Assistant configuration folder. | ||
|
||
### Step 2: Restart Home Assistant | ||
|
||
Restart Home Assistant to load the new integration. | ||
|
||
### Step 3: Add the Integration | ||
|
||
1. Navigate to **Settings > Devices & Services** in Home Assistant. | ||
2. Click **Add Integration** and search for `Peblar`. | ||
3. Follow the prompts to configure the integration. | ||
--- | ||
|
||
## Configuration | ||
|
||
When setting up the Peblar integration, you will need: | ||
|
||
- **IP Address:** The IP address of your Peblar device. | ||
- **Access Token:** The access token for authenticating with the Peblar API. | ||
|
||
|
||
## Supported Entities | ||
|
||
### Sensors | ||
|
||
| Sensor | Unit | Device Class | State Class | | ||
|----------------------------|--------------------|--------------|------------------| | ||
| Charger Max Charging Current | mA | Current | Measurement | | ||
| Charger Total Energy | Wh | Energy | Measurement | | ||
| Charger Session Energy | Wh | Energy | Measurement | | ||
| Charger Charge Power | W | Power | Measurement | | ||
|
||
### Number Entities | ||
|
||
| Entity | Min Value | Max Value | Step | Description | | ||
|----------------------------|-----------|-----------|------|------------------------------| | ||
| Charger Max Charging Current | 0 | 20000 | 1 | Set the maximum charging current | | ||
|
||
--- | ||
|
||
## Error Handling | ||
|
||
### Common Errors | ||
|
||
- **`cannot_connect`**: Unable to connect to the Peblar device. Check the IP address and ensure the device is online. | ||
- **`invalid_auth`**: Authentication failed. Verify your access token. | ||
- **`reauth_invalid`**: Reauthentication failed. Ensure the IP address and access token are correct. | ||
|
||
### Reauthentication | ||
|
||
If reauthentication is required: | ||
|
||
1. Open the Peblar integration settings in Home Assistant. | ||
2. Update the IP address and/or access token. | ||
3. Save the changes to reauthenticate. | ||
|
||
--- | ||
|
||
### Contributions | ||
|
||
Contributions are welcome! Feel free to open issues or submit pull requests. | ||
|
||
--- | ||
|
||
## License | ||
|
||
This project is licensed under the MIT License. See the `LICENSE` file for details. | ||
|
||
--- | ||
|
||
For more information or support, please refer to the [Home Assistant documentation](https://www.home-assistant.io) or contact the integration maintainer. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
"""The Peblar integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from .peblar import Peblar | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_IP_ADDRESS, CONF_ACCESS_TOKEN, Platform | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryAuthFailed | ||
|
||
from .const import DOMAIN, UPDATE_INTERVAL | ||
from .coordinator import InvalidAuth, PeblarCoordinator, async_validate_input | ||
|
||
PLATFORMS = [Platform.NUMBER, Platform.SENSOR] | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Set up Peblar from a config entry.""" | ||
peblar = Peblar( | ||
entry.data[CONF_ACCESS_TOKEN], | ||
entry.data[CONF_IP_ADDRESS], | ||
) | ||
try: | ||
await async_validate_input(hass, peblar) | ||
except InvalidAuth as ex: | ||
raise ConfigEntryAuthFailed from ex | ||
|
||
peblar_coordinator = PeblarCoordinator( | ||
peblar, | ||
hass, | ||
) | ||
await peblar_coordinator.async_config_entry_first_refresh() | ||
|
||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = peblar_coordinator | ||
|
||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) | ||
if unload_ok: | ||
hass.data[DOMAIN].pop(entry.entry_id) | ||
|
||
return unload_ok |
Binary file not shown.
Binary file added
BIN
+3.67 KB
config/custom_components/peblar/__pycache__/config_flow.cpython-313.pyc
Binary file not shown.
Binary file added
BIN
+3.92 KB
config/custom_components/peblar/__pycache__/connectric.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file added
BIN
+4.56 KB
config/custom_components/peblar/__pycache__/coordinator.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"""Config flow for Peblar integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from collections.abc import Mapping | ||
from typing import Any | ||
|
||
import voluptuous as vol | ||
from .peblar import Peblar | ||
|
||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult | ||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_IP_ADDRESS | ||
from homeassistant.core import HomeAssistant | ||
|
||
from .const import DOMAIN | ||
from .coordinator import InvalidAuth, async_validate_input | ||
|
||
COMPONENT_DOMAIN = DOMAIN | ||
|
||
STEP_USER_DATA_SCHEMA = vol.Schema( | ||
{ | ||
vol.Required(CONF_IP_ADDRESS): str, | ||
vol.Required(CONF_ACCESS_TOKEN): str, | ||
} | ||
) | ||
|
||
|
||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: | ||
"""Validate the user input allows to connect. | ||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. | ||
""" | ||
peblar = Peblar(data["access_token"], data["ip_address"]) | ||
|
||
await async_validate_input(hass, peblar) | ||
|
||
# Return info that you want to store in the config entry. | ||
return {"title": "peblar"} | ||
|
||
|
||
class peblarConfigFlow(ConfigFlow, domain=COMPONENT_DOMAIN): | ||
"""Handle a config flow for peblar.""" | ||
|
||
async def async_step_reauth( | ||
self, entry_data: Mapping[str, Any] | ||
) -> ConfigFlowResult: | ||
"""Perform reauth upon an API authentication error.""" | ||
return await self.async_step_user() | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""Handle the initial step.""" | ||
if user_input is None: | ||
return self.async_show_form( | ||
step_id="user", | ||
data_schema=STEP_USER_DATA_SCHEMA, | ||
) | ||
|
||
errors = {} | ||
|
||
try: | ||
await self.async_set_unique_id(user_input["ip_address"]) | ||
if self.source != SOURCE_REAUTH: | ||
self._abort_if_unique_id_configured() | ||
info = await validate_input(self.hass, user_input) | ||
return self.async_create_entry(title=info["title"], data=user_input) | ||
reauth_entry = self._get_reauth_entry() | ||
if user_input["ip_address"] == reauth_entry.data["ip_address"]: | ||
return self.async_update_reload_and_abort(reauth_entry, data=user_input) | ||
errors["base"] = "reauth_invalid" | ||
except ConnectionError: | ||
errors["base"] = "cannot_connect" | ||
except InvalidAuth: | ||
errors["base"] = "invalid_auth" | ||
|
||
return self.async_show_form( | ||
step_id="user", | ||
data_schema=STEP_USER_DATA_SCHEMA, | ||
errors=errors, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
"""Constants for the Eneco peblar integration.""" | ||
|
||
DOMAIN = "peblar" | ||
UPDATE_INTERVAL = 30 | ||
|
||
|
||
CHARGER_CURRENT_VERSION_KEY = "FirmwareVersion" | ||
CHARGER_PART_NUMBER_KEY = "ProductPn" | ||
CHARGER_SERIAL_NUMBER_KEY = "ProductSn" | ||
CHARGER_SOFTWARE_KEY = "FirmwareVersion" | ||
CHARGER_MAX_CHARGING_CURRENT_KEY = "ChargeCurrentLimit" | ||
CHARGER_CHARGING_CURRENT_ACTUAL_KEY = "ChargeCurrentLimitActual" | ||
CHARGER_TOTAL_ENERGY_KEY = "EnergyTotal" | ||
CHARGER_SESSION_ENERGY_KEY = "EnergySession" | ||
CHARGER_CHARGE_POWER_KEY = "PowerTotal" | ||
|
||
# CHARGER_ADDED_DISCHARGED_ENERGY_KEY = "added_discharged_energy" | ||
# CHARGER_ADDED_ENERGY_KEY = "added_energy" | ||
# CHARGER_ADDED_RANGE_KEY = "added_range" | ||
# CHARGER_CHARGING_POWER_KEY = "charging_power" | ||
# CHARGER_CHARGING_SPEED_KEY = "charging_speed" | ||
# CHARGER_CHARGING_TIME_KEY = "charging_time" | ||
# CHARGER_COST_KEY = "cost" | ||
# CHARGER_CURRENT_MODE_KEY = "current_mode" | ||
# CHARGER_CURRENT_VERSION_KEY = "currentVersion" | ||
# CHARGER_CURRENCY_KEY = "currency" | ||
# CHARGER_DATA_KEY = "config_data" | ||
# CHARGER_DEPOT_PRICE_KEY = "depot_price" | ||
# CHARGER_ENERGY_PRICE_KEY = "energy_price" | ||
# CHARGER_FEATURES_KEY = "features" | ||
# CHARGER_SERIAL_NUMBER_KEY = "serial_number" | ||
# CHARGER_PART_NUMBER_KEY = "part_number" | ||
# CHARGER_PLAN_KEY = "plan" | ||
# CHARGER_POWER_BOOST_KEY = "POWER_BOOST" | ||
# CHARGER_SOFTWARE_KEY = "software" | ||
# CHARGER_MAX_AVAILABLE_POWER_KEY = "max_available_power" | ||
# CHARGER_MAX_CHARGING_CURRENT_KEY = "max_charging_current" | ||
# CHARGER_MAX_ICP_CURRENT_KEY = "icp_max_current" | ||
# CHARGER_PAUSE_RESUME_KEY = "paused" | ||
# CHARGER_LOCKED_UNLOCKED_KEY = "locked" | ||
# CHARGER_NAME_KEY = "name" | ||
# CHARGER_STATE_OF_CHARGE_KEY = "state_of_charge" | ||
# CHARGER_STATUS_ID_KEY = "status_id" | ||
# CHARGER_STATUS_DESCRIPTION_KEY = "status_description" | ||
# CHARGER_CONNECTIONS = "connections" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
"""DataUpdateCoordinator for the peblar integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from datetime import timedelta | ||
import logging | ||
from typing import Any | ||
|
||
import requests | ||
from .peblar import Peblar | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import HomeAssistantError | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
|
||
from .const import CHARGER_MAX_CHARGING_CURRENT_KEY, DOMAIN, UPDATE_INTERVAL | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
def _validate(peblar: Peblar) -> None: | ||
"""Authenticate using Peblar API.""" | ||
try: | ||
peblar.authenticate() | ||
except requests.exceptions.HTTPError as peblar_connection_error: | ||
if peblar_connection_error.response.status_code == 401: | ||
raise InvalidAuth from peblar_connection_error | ||
raise ConnectionError from peblar_connection_error | ||
|
||
|
||
async def async_validate_input(hass: HomeAssistant, peblar: Peblar) -> None: | ||
"""Get new sensor data for Peblar component.""" | ||
await hass.async_add_executor_job(_validate, peblar) | ||
|
||
|
||
class PeblarCoordinator(DataUpdateCoordinator[dict[str, Any]]): | ||
"""Peblar Coordinator class.""" | ||
|
||
def __init__(self, peblar: Peblar, hass: HomeAssistant) -> None: | ||
"""Initialize.""" | ||
self._peblar = peblar | ||
|
||
super().__init__( | ||
hass, | ||
_LOGGER, | ||
name=DOMAIN, | ||
update_interval=timedelta(seconds=UPDATE_INTERVAL), | ||
) | ||
|
||
def authenticate(self) -> None: | ||
"""Authenticate using Peblar API.""" | ||
self._peblar.authenticate() | ||
|
||
def _get_data(self) -> dict[str, Any]: | ||
"""Get new sensor data for Peblar component.""" | ||
data: dict[str, Any] = self._peblar.getChargerData() | ||
data[CHARGER_MAX_CHARGING_CURRENT_KEY] = data[CHARGER_MAX_CHARGING_CURRENT_KEY] | ||
return data | ||
|
||
async def _async_update_data(self) -> dict[str, Any]: | ||
"""Get new sensor data for Peblar component.""" | ||
return await self.hass.async_add_executor_job(self._get_data) | ||
|
||
def _set_charging_current(self, charging_current: float) -> None: | ||
"""Set maximum charging current for Peblar.""" | ||
try: | ||
self._peblar.setMaxChargingCurrent(charging_current) | ||
except requests.exceptions.HTTPError as peblar_connection_error: | ||
if peblar_connection_error.response.status_code == 403: | ||
raise InvalidAuth from peblar_connection_error | ||
raise | ||
|
||
async def async_set_charging_current(self, charging_current: float) -> None: | ||
"""Set maximum charging current for Peblar.""" | ||
await self.hass.async_add_executor_job( | ||
self._set_charging_current, charging_current | ||
) | ||
await self.async_request_refresh() | ||
|
||
|
||
class InvalidAuth(HomeAssistantError): | ||
"""Error to indicate there is invalid auth.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
"""Base entity for the peblar integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from homeassistant.helpers.device_registry import DeviceInfo | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
||
from .const import ( | ||
CHARGER_CURRENT_VERSION_KEY, | ||
CHARGER_PART_NUMBER_KEY, | ||
CHARGER_SERIAL_NUMBER_KEY, | ||
CHARGER_SOFTWARE_KEY, | ||
DOMAIN, | ||
) | ||
from .coordinator import PeblarCoordinator | ||
|
||
|
||
class PeblarEntity(CoordinatorEntity[PeblarCoordinator]): | ||
"""Defines a base Peblar entity.""" | ||
|
||
_attr_has_entity_name = True | ||
|
||
@property | ||
def device_info(self) -> DeviceInfo: | ||
"""Return device information about this Peblar device.""" | ||
return DeviceInfo( | ||
identifiers={ | ||
( | ||
DOMAIN, | ||
self.coordinator.data[CHARGER_SERIAL_NUMBER_KEY], | ||
) | ||
}, | ||
name=f"Peblar", | ||
manufacturer="Peblar", | ||
model_id=self.coordinator.data[CHARGER_PART_NUMBER_KEY], | ||
) |
Oops, something went wrong.