Skip to content

Commit

Permalink
fix: less agressive websocket connections (#438)
Browse files Browse the repository at this point in the history
* fix: less agressive websocket connections

* formatting

* update test configs

* code cleanup

* formatting

* add diagnostic tests
  • Loading branch information
firstof9 authored Feb 11, 2025
1 parent b10b7b2 commit 51bf7ae
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 81 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ jobs:
strategy:
matrix:
python-version:
# - "3.10"
# - "3.11"
- "3.12"
- "3.13"

steps:
- name: 📥 Checkout the repository
Expand Down
1 change: 0 additions & 1 deletion custom_components/openevse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
configuration_url=manager.url,
)

await coordinator.async_refresh()
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)

services = OpenEVSEServices(hass, config_entry)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/openevse/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/firstof9/openevse/issues",
"loggers": ["openevsehttp"],
"requirements": ["python-openevse-http==0.1.74"],
"requirements": ["python-openevse-http==0.1.77"],
"version": "0.0.0-dev",
"zeroconf": ["_openevse._tcp.local."]
}
24 changes: 12 additions & 12 deletions custom_components/openevse/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@

_LOGGER = logging.getLogger(__name__)

icon = {
"unknown": "mdi:help",
"not connected": "mdi:power-plug-off",
"connected": "mdi:power-plug",
"charging": "mdi:battery-charging",
"sleeping": "mdi:sleep",
"disabled": "mdi:car-off",
}


async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the OpenEVSE sensors."""
Expand Down Expand Up @@ -122,18 +131,9 @@ def should_poll(self) -> bool:

def update_icon(self) -> None:
"""Update status icon based on state."""
data = self.coordinator.data
if self._type == "state":
if self._state == "unknown":
self._icon = "mdi:help"
elif self._state == "not connected":
self._icon = "mdi:power-plug-off"
elif self._state == "connected":
self._icon = "mdi:power-plug"
elif self._state == "charging":
self._icon = "mdi:battery-charging"
elif self._state == "sleeping":
self._icon = "mdi:sleep"
elif self._state == "disabled":
self._icon = "mdi:car-off"
if data[self._type] in icon:
self._icon = icon[data[self._type]]
else:
self._icon = "mdi:alert-octagon"
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python-openevse-http==0.1.74
python-openevse-http==0.1.77
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[mypy]
python_version = 3.12
python_version = 3.13
show_error_codes = true
ignore_errors = true
follow_imports = silent
Expand Down
55 changes: 55 additions & 0 deletions tests/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,58 @@
"voltage": "sensor.grid_voltage",
"invert_grid": False,
}
DIAG_CONFIG_DATA = {
"name": "openevse",
"host": "openevse.test.tld",
"username": "testuser",
"password": "a-super-fake-password",
}
DIAG_DEVICE_RESULTS = {
"ambient_temperature": 50.3,
"ammeter_scale_factor": 220,
"charge_mode": "eco",
"charge_rate": 0,
"charge_time_elapsed": 246,
"charging_current": 32.2,
"charging_power": 7728.0,
"charging_voltage": 240,
"current_capacity": 40,
"divert_active": False,
"esp_temperature": 56.0,
"has_limit": False,
"ir_temperature": None,
"led_brightness": 64,
"manual_override": False,
"max_amps": 48,
"max_current": 48,
"max_current_soft": 28,
"min_amps": 6,
"mqtt_connected": True,
"openevse_firmware": "7.1.3",
"ota_update": False,
"override_state": "auto",
"protocol_version": None,
"rtc_temperature": 50.3,
"service_level": 2,
"shaper_active": True,
"shaper_available_current": 21,
"shaper_live_power": 2299,
"shaper_max_power": 4000,
"shaper_updated": False,
"state": "sleeping",
"status": "sleeping",
"total_day": None,
"total_month": None,
"total_week": None,
"total_year": None,
"usage_session": 275.71,
"usage_total": 64582,
"using_ethernet": False,
"vehicle": True,
"vehicle_eta": 18000,
"vehicle_eta_timestamp": 18000,
"vehicle_range": 468,
"vehicle_soc": 75,
"wifi_firmware": "v5.1.2",
"wifi_signal": -61,
}
50 changes: 50 additions & 0 deletions tests/test_diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Test the OpenEVSE diagnostics."""

from unittest.mock import patch

import pytest
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from pytest_homeassistant_custom_component.common import MockConfigEntry

from custom_components.openevse.const import DOMAIN
from custom_components.openevse.diagnostics import (
async_get_config_entry_diagnostics,
async_get_device_diagnostics,
)
from tests.const import DIAG_CONFIG_DATA, DIAG_DEVICE_RESULTS


@pytest.mark.asyncio
async def test_config_entry_diagnostics(hass, test_charger, mock_ws_start):
"""Test the config entry level diagnostics data dump."""
entry = MockConfigEntry(
domain=DOMAIN,
title="imap.test.email",
data=DIAG_CONFIG_DATA,
)

entry.add_to_hass(hass)
result = await async_get_config_entry_diagnostics(hass, entry)

assert isinstance(result, dict)
assert result["config"]["data"][CONF_HOST] == "openevse.test.tld"
assert result["config"]["data"][CONF_PASSWORD] == "**REDACTED**"
assert result["config"]["data"][CONF_USERNAME] == "testuser"


@pytest.mark.asyncio
async def test_device_diagnostics(hass, test_charger, mock_ws_start):
"""Test the device level diagnostics data dump."""
entry = MockConfigEntry(
domain=DOMAIN,
title="openevse",
data=DIAG_CONFIG_DATA,
)

entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()

result = await async_get_device_diagnostics(hass, entry, None)

assert result == DIAG_DEVICE_RESULTS
15 changes: 12 additions & 3 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ async def test_setup_entry_state_change_timeout(
hass.states.async_set(grid_entity, "-200")
await hass.async_block_till_done()

assert "Self-production response: {'msg': 'Timeout while updating'}" in caplog.text
assert (
"Timeout error connecting to device: , please check your network connection."
in caplog.text
)


async def test_setup_entry_state_change_2(hass, test_charger, mock_ws_start, caplog):
Expand Down Expand Up @@ -206,8 +209,14 @@ async def test_setup_entry_state_change_2_bad_post(

hass.states.async_set(solar_entity, "2317")
await hass.async_block_till_done()
assert "Self-production response: {'msg': 'Timeout while updating'}" in caplog.text
assert (
"Timeout error connecting to device: , please check your network connection."
in caplog.text
)

hass.states.async_set(voltage_entity, "113")
await hass.async_block_till_done()
assert "Self-production response: {'msg': 'Timeout while updating'}" in caplog.text
assert (
"Timeout error connecting to device: , please check your network connection."
in caplog.text
)
118 changes: 62 additions & 56 deletions tests/test_sensor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test openevse sensors."""

import logging
import json
from datetime import timedelta
from unittest.mock import patch
Expand Down Expand Up @@ -30,66 +31,71 @@ async def test_sensors(
mock_ws_start,
mock_aioclient,
entity_registry: er.EntityRegistry,
caplog,
):
"""Test setup_entry."""
entry = MockConfigEntry(
domain=DOMAIN,
title=CHARGER_NAME,
data=CONFIG_DATA,
)

entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()

assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 22
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SELECT_DOMAIN)) == 2
assert len(hass.states.async_entity_ids(NUMBER_DOMAIN)) == 1
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1

assert DOMAIN in hass.config.components

state = hass.states.get("sensor.openevse_wifi_firmware_version")
assert state
assert state.state == "v5.1.2"
state = hass.states.get("sensor.openevse_charge_time_elapsed")
assert state
assert state.state == "4.1"
state = hass.states.get("sensor.openevse_total_usage")
assert state
assert state.state == "64.582"
state = hass.states.get("sensor.openevse_max_current")
assert state
assert state.state == "48"

state = hass.states.get("sensor.openevse_override_state")
assert state
assert state.state == "auto"

# enable disabled sensor
entity_id = "sensor.openevse_vehicle_charge_completion_time"
entity_entry = entity_registry.async_get(entity_id)

assert entity_entry
assert entity_entry.disabled
assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION

updated_entry = entity_registry.async_update_entity(
entity_entry.entity_id, disabled_by=None
)
assert updated_entry != entity_entry
assert updated_entry.disabled is False

# reload the integration
assert await hass.config_entries.async_forward_entry_unload(entry, "sensor")
await hass.config_entries.async_forward_entry_setups(entry, ["sensor"])
await hass.async_block_till_done()

state = hass.states.get("sensor.openevse_vehicle_charge_completion_time")
assert state
assert state.state == (dt_util.utcnow() + timedelta(seconds=18000)).isoformat(
timespec="seconds"
)
with caplog.at_level(logging.DEBUG):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()

assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 22
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4
assert len(hass.states.async_entity_ids(SELECT_DOMAIN)) == 2
assert len(hass.states.async_entity_ids(NUMBER_DOMAIN)) == 1
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1

assert DOMAIN in hass.config.components

state = hass.states.get("sensor.openevse_wifi_firmware_version")
assert state
assert state.state == "v5.1.2"
state = hass.states.get("sensor.openevse_charge_time_elapsed")
assert state
assert state.state == "4.1"
state = hass.states.get("sensor.openevse_total_usage")
assert state
assert state.state == "64.582"
state = hass.states.get("sensor.openevse_max_current")
assert state
assert state.state == "48"

state = hass.states.get("sensor.openevse_override_state")
assert state
assert state.state == "auto"

state = hass.states.get("sensor.openevse_charging_status")
assert state
assert state.attributes.get("icon") == "mdi:sleep"

# enable disabled sensor
entity_id = "sensor.openevse_vehicle_charge_completion_time"
entity_entry = entity_registry.async_get(entity_id)

assert entity_entry
assert entity_entry.disabled
assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION

updated_entry = entity_registry.async_update_entity(
entity_entry.entity_id, disabled_by=None
)
assert updated_entry != entity_entry
assert updated_entry.disabled is False

# reload the integration
assert await hass.config_entries.async_forward_entry_unload(entry, "sensor")
await hass.config_entries.async_forward_entry_setups(entry, ["sensor"])
await hass.async_block_till_done()

state = hass.states.get("sensor.openevse_vehicle_charge_completion_time")
assert state
assert state.state == (dt_util.utcnow() + timedelta(seconds=18000)).isoformat(
timespec="seconds"
)
2 changes: 1 addition & 1 deletion tests/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ async def test_set_limit(
status=200,
body='{"type": "energy", "value": 10}',
repeat=True,
)
)
mock_aioclient.get(
TEST_URL_OVERRIDE,
status=200,
Expand Down
5 changes: 3 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[tox]
skipsdist = true
envlist = py310, py311, py312, lint, mypy
envlist = py310, py311, py312, py313, lint, mypy
skip_missing_interpreters = True

[gh-actions]
python =
3.10: py310
3.11: py311
3.12: py312, lint, mypy
3.12: py312
3.13: py313, lint, mypy

[pytest]
asyncio_default_fixture_loop_scope=function
Expand Down

0 comments on commit 51bf7ae

Please sign in to comment.