Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New 'vehicle connected' binary sensor, bug fix & deprecation resolution #18

Merged
merged 16 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
9 changes: 9 additions & 0 deletions custom_components/eo_mini/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ 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:
"Get the session liveness state"
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 +158,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
Loading