Skip to content

Commit

Permalink
Merge pull request #18 from zackslash/master
Browse files Browse the repository at this point in the history
New 'vehicle connected' binary sensor, bug fix & deprecation resolution
  • Loading branch information
twhittock authored Dec 19, 2024
2 parents e484848 + 47ce2ea commit 6e95904
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Setup Python
uses: "actions/setup-python@v1"
with:
python-version: "3.10"
python-version: "3.12"
- name: Install requirements
run: python3 -m pip install -r requirements_test.txt
- name: Run tests
Expand All @@ -52,4 +52,5 @@ jobs:
--cov custom_components.eo_mini \
-o console_output_style=count \
-p no:sugar \
--asyncio-mode=auto \
tests
15 changes: 7 additions & 8 deletions custom_components/eo_mini/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
For more details about this integration, please refer to
https://github.com/twhittock/eo_mini
"""

import asyncio
from datetime import timedelta
import logging

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import Config, HomeAssistant
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .api import EOApiClient, EOAuthError
Expand Down Expand Up @@ -42,7 +44,7 @@ def eo_model(hub_serial: str):


# pylint: disable-next=unused-argument
async def async_setup(hass: HomeAssistant, config: Config):
async def async_setup(hass: HomeAssistant, config: ConfigType):
"Setting up this integration using YAML is not supported."
return True

Expand All @@ -68,11 +70,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):

await coordinator.async_config_entry_first_refresh()

for platform in PLATFORMS:
coordinator.platforms.append(platform)
hass.async_add_job(
hass.config_entries.async_forward_entry_setup(entry, platform)
)
coordinator.platforms.extend(PLATFORMS)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

entry.async_on_unload(entry.add_update_listener(async_reload_entry))
return True
Expand Down Expand Up @@ -113,7 +112,7 @@ async def _async_update_data(self):
self.device = self._minis_list[0]
self.serial = self.device["hubSerial"]
self.model = eo_model(self.serial)

self.live_session = await self.api.async_get_session_liveness()
self.data = await self.api.async_get_session()

self.async_update_listeners()
Expand Down
16 changes: 16 additions & 0 deletions custom_components/eo_mini/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,20 @@ async def async_get_session(self) -> list[dict]:
"Get the current session if any"
return await self._async_api_wrapper("get", f"{self.base_url}/api/session")

async def async_get_session_liveness(self) -> bool:
"""
Determine if a vehicle is connected to the charger.
This call checks the session's liveness, indicating whether a vehicle
is connected to the charger. Note that "connected" refers to the physical
connection between the vehicle and the charger, regardless of whether
charging is actively in progress.
"""
live = await self._async_api_wrapper(
"get", f"{self.base_url}/api/session/alive"
)
return live is not None

async def async_post_disable(self, address) -> list[dict]:
"Disable the charger (lock)"
return await self._async_api_wrapper(
Expand Down Expand Up @@ -151,6 +165,8 @@ async def _async_api_wrapper(
text = await response.read()
_LOGGER.info("Response: %r", text)
return text
if response.status == 404:
return None
elif response.status == 400:
# Handle expired/invalid tokens
if not _reissue:
Expand Down
52 changes: 52 additions & 0 deletions custom_components/eo_mini/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Binary Sensor platform for EO Mini."""

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
BinarySensorDeviceClass,
)
from homeassistant.core import callback

from custom_components.eo_mini import EODataUpdateCoordinator
from .const import DOMAIN
from .entity import EOMiniChargerEntity


async def async_setup_entry(hass, entry, async_add_devices):
"""Setup binary sensor platform."""
coordinator: EODataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_devices(
[
EOMiniChargerVehicleConnectedSensor(coordinator),
]
)


class EOMiniChargerVehicleConnectedSensor(EOMiniChargerEntity, BinarySensorEntity):
"""EO Mini Charger vehicle connected binary sensor class."""

coordinator: EODataUpdateCoordinator

_attr_icon = "mdi:car-electric"
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY
_previous_state = None

def __init__(self, *args):
self.entity_description = BinarySensorEntityDescription(
key=BinarySensorDeviceClass.CONNECTIVITY,
device_class=BinarySensorDeviceClass.CONNECTIVITY,
name="Vehicle Connected",
)
self._attr_is_on = False
super().__init__(*args)

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._attr_is_on = self.coordinator.live_session
self.async_write_ha_state()

@property
def unique_id(self):
"""Return a unique ID for this entity."""
return f"{DOMAIN}_charger_{self.coordinator.serial}_vehicle_connected"
10 changes: 6 additions & 4 deletions custom_components/eo_mini/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Adds config flow for Blueprint."""

import logging
import traceback
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_create_clientsession

Expand All @@ -18,7 +20,7 @@
_LOGGER: logging.Logger = logging.getLogger(__package__)


class EOMiniFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
class EOMiniFlowHandler(ConfigFlow, domain=DOMAIN):
"Config flow for EO Mini."

VERSION = 1
Expand Down Expand Up @@ -96,12 +98,12 @@ async def _show_config_form(self, user_input):
)


class EOMiniOptionsFlowHandler(config_entries.OptionsFlow):
class EOMiniOptionsFlowHandler(OptionsFlow):
"EO Mini config flow options handler."

def __init__(self, config_entry):
def __init__(self, config_entry: ConfigEntry):
"Initialize."
self.config_entry = config_entry
self._entry = config_entry
self.options = dict(config_entry.options)

async def async_step_init(self, user_input=None): # pylint: disable=unused-argument
Expand Down
3 changes: 2 additions & 1 deletion custom_components/eo_mini/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
# Platforms
SENSOR = "sensor"
SWITCH = "switch"
PLATFORMS = [SENSOR, SWITCH]
BINARY_SENSOR = "binary_sensor"
PLATFORMS = [SENSOR, BINARY_SENSOR, SWITCH]


# Configuration and options
Expand Down
5 changes: 2 additions & 3 deletions custom_components/eo_mini/sensor.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Sensor platform for EO Mini."""

from datetime import datetime
from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
SensorStateClass,
SensorDeviceClass,
)

from homeassistant.const import UnitOfTime, UnitOfEnergy
from homeassistant.core import callback

Expand Down Expand Up @@ -87,9 +89,6 @@ def _handle_coordinator_update(self) -> None:

if self.coordinator.data:
if self.coordinator.data["ESKWH"] == 0:
self._attr_last_reset = datetime.fromtimestamp(
self.coordinator.data["PiTime"]
)
self._attr_native_value = 0
else:
self._attr_native_value = self.coordinator.data["ChargingTime"]
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
colorlog==6.7.0
homeassistant==2024.3.1
homeassistant==2024.12.1
pip>=21.0,<23.2
ruff==0.0.292
1 change: 1 addition & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pytest-homeassistant-custom-component
homeassistant==2024.12.1
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ This will install `homeassistant`, `pytest`, and `pytest-homeassistant-custom-co
Command | Description
------- | -----------
`pytest tests/` | This will run all tests in `tests/` and tell you how many passed/failed
`pytest --durations=10 --cov-report term-missing --cov=custom_components.eo_mini tests` | This tells `pytest` that your target module to test is `custom_components.eo_mini` so that it can give you a [code coverage](https://en.wikipedia.org/wiki/Code_coverage) summary, including % of code that was executed and the line numbers of missed executions.
`pytest --durations=10 --cov-report term-missing --cov=custom_components.eo_mini tests --asyncio-mode=auto` | This tells `pytest` that your target module to test is `custom_components.eo_mini` so that it can give you a [code coverage](https://en.wikipedia.org/wiki/Code_coverage) summary, including % of code that was executed and the line numbers of missed executions.
`pytest tests/test_init.py -k test_setup_unload_and_reload_entry` | Runs the `test_setup_unload_and_reload_entry` test function located in `tests/test_init.py`
1 change: 1 addition & 0 deletions tests/example_data/session_liveness.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
null
5 changes: 4 additions & 1 deletion tests/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
@pytest.fixture(autouse=True)
def bypass_setup_fixture():
"""Prevent setup."""
with patch("custom_components.eo_mini.async_setup", return_value=True,), patch(
with patch(
"custom_components.eo_mini.async_setup",
return_value=True,
), patch(
"custom_components.eo_mini.async_setup_entry",
return_value=True,
):
Expand Down
8 changes: 7 additions & 1 deletion tests/test_init.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"Test integration_blueprint setup process."
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.config_entries import ConfigEntryState
import pytest
import aiohttp
from unittest.mock import patch
Expand All @@ -20,7 +21,9 @@
async def test_setup_unload_and_reload_entry(hass):
"""Test entry setup and unload."""
# Create a mock entry so we don't have to go through config flow
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test")
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_CONFIG, entry_id="test", state=ConfigEntryState.LOADED
)

with patch(
"custom_components.eo_mini.EOApiClient.async_get_list",
Expand All @@ -31,6 +34,9 @@ async def test_setup_unload_and_reload_entry(hass):
), patch(
"custom_components.eo_mini.EOApiClient.async_get_session",
return_value=json_load_file("session_charging.json"),
), patch(
"custom_components.eo_mini.EOApiClient.async_get_session_liveness",
return_value=json_load_file("session_liveness.json"),
):
# Set up the entry and assert that the values set during setup are where we expect
# them to be. Because we have patched the BlueprintDataUpdateCoordinator.async_get_data
Expand Down

0 comments on commit 6e95904

Please sign in to comment.