Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
thimo.vandenbroek committed Dec 15, 2024
1 parent 412aa60 commit 2cef7b8
Show file tree
Hide file tree
Showing 22 changed files with 876 additions and 0 deletions.
100 changes: 100 additions & 0 deletions config/custom_components/peblar/README.MD
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.

48 changes: 48 additions & 0 deletions config/custom_components/peblar/__init__.py
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 not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
81 changes: 81 additions & 0 deletions config/custom_components/peblar/config_flow.py
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,
)
45 changes: 45 additions & 0 deletions config/custom_components/peblar/const.py
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"
82 changes: 82 additions & 0 deletions config/custom_components/peblar/coordinator.py
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."""
36 changes: 36 additions & 0 deletions config/custom_components/peblar/entity.py
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],
)
Loading

0 comments on commit 2cef7b8

Please sign in to comment.