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

fix: handle non-semver version strings better #227

Merged
merged 9 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
44 changes: 31 additions & 13 deletions openevsehttp/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import aiohttp # type: ignore
from aiohttp.client_exceptions import ContentTypeError, ServerTimeoutError
from awesomeversion import AwesomeVersion
from awesomeversion.exceptions import AwesomeVersionCompareException

from .const import MAX_AMPS, MIN_AMPS
from .exceptions import (
Expand Down Expand Up @@ -489,15 +490,22 @@ async def firmware_check(self) -> dict | None:
_LOGGER.debug("Stripping 'dev' from version.")
value = value.split(".")
value = ".".join(value[0:3])
_LOGGER.debug("Using version: %s", value)
current = AwesomeVersion(value)
elif "master" in self._config["version"]:
value = "dev"
else:
current = AwesomeVersion(self._config["version"])
value = self._config["version"]

if current >= cutoff:
url = f"{base_url}ESP32_WiFi_V4.x/releases/latest"
else:
url = f"{base_url}ESP8266_WiFi_v2.x/releases/latest"
_LOGGER.debug("Using version: %s", value)
current = AwesomeVersion(value)

try:
if current >= cutoff:
url = f"{base_url}ESP32_WiFi_V4.x/releases/latest"
else:
url = f"{base_url}ESP8266_WiFi_v2.x/releases/latest"
except AwesomeVersionCompareException:
_LOGGER.warning("Non-semver firmware version detected.")
return None

try:
async with aiohttp.ClientSession() as session:
Expand Down Expand Up @@ -551,16 +559,26 @@ def _version_check(self, min_version: str, max_version: str = "") -> bool:
_LOGGER.debug("Stripping 'dev' from version.")
value = value.split(".")
value = ".".join(value[0:3])
current = AwesomeVersion(value)
elif "master" in self._config["version"]:
value = "dev"
else:
current = AwesomeVersion(self._config["version"])
value = self._config["version"]

current = AwesomeVersion(value)

if limit:
if cutoff <= current <= limit:
return True
try:
if cutoff <= current <= limit:
return True
except AwesomeVersionCompareException:
_LOGGER.debug("Non-semver firmware version detected.")
return False
if current >= cutoff:
return True

try:
if current >= cutoff:
return True
except AwesomeVersionCompareException:
_LOGGER.debug("Non-semver firmware version detected.")
return False

@property
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

PROJECT_DIR = Path(__file__).parent.resolve()
README_FILE = PROJECT_DIR / "README.md"
VERSION = "0.1.50"
VERSION = "0.1.51"

setup(
name="python-openevse-http",
Expand Down
32 changes: 32 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,38 @@ def test_charger_broken(mock_aioclient):
return main.OpenEVSE(TEST_TLD)


@pytest.fixture(name="test_charger_broken_semver")
def test_charger_broken_semver(mock_aioclient):
"""Load the charger data."""
mock_aioclient.get(
TEST_URL_STATUS,
status=200,
body=load_fixture("v4_json/status.json"),
)
mock_aioclient.get(
TEST_URL_CONFIG,
status=200,
body=load_fixture("v4_json/config-broken-semver.json"),
)
return main.OpenEVSE(TEST_TLD)


@pytest.fixture(name="test_charger_unknown_semver")
def test_charger_unknown_semver(mock_aioclient):
"""Load the charger data."""
mock_aioclient.get(
TEST_URL_STATUS,
status=200,
body=load_fixture("v4_json/status.json"),
)
mock_aioclient.get(
TEST_URL_CONFIG,
status=200,
body=load_fixture("v4_json/config-unknown-semver.json"),
)
return main.OpenEVSE(TEST_TLD)


@pytest.fixture(name="test_charger_v2")
def test_charger_v2(mock_aioclient):
"""Load the charger data."""
Expand Down
66 changes: 66 additions & 0 deletions tests/fixtures/v4_json/config-broken-semver.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"firmware": "7.1.3",
"protocol": "-",
"espflash": 4194304,
"wifi_serial": "1234567890AB",
"version": "master_abcd123",
"diodet": 0,
"gfcit": 0,
"groundt": 0,
"relayt": 0,
"ventt": 0,
"tempt": 0,
"service": 2,
"scale": 220,
"offset": 0,
"max_current_soft": 48,
"min_current_hard": 6,
"max_current_hard": 48,
"mqtt_supported_protocols": [
"mqtt",
"mqtts"
],
"http_supported_protocols": [
"http",
"https"
],
"ssid": "Datanode-IoT",
"pass": "_DUMMY_PASSWORD",
"www_username": "",
"www_password": "",
"hostname": "openevse-7b2c",
"sntp_hostname": "0.us.pool.ntp.org",
"time_zone": "America/Phoenix|MST7",
"emoncms_server": "https://emoncms.collective.lan/",
"emoncms_node": "openevse",
"emoncms_apikey": "_DUMMY_PASSWORD",
"emoncms_fingerprint": "",
"mqtt_server": "192.168.1.198",
"mqtt_port": 1883,
"mqtt_topic": "openevse",
"mqtt_user": "devices",
"mqtt_pass": "_DUMMY_PASSWORD",
"mqtt_solar": "",
"mqtt_grid_ie": "home-assistant/power/watts",
"mqtt_vrms": "home-assistant/solar/watts",
"mqtt_announce_topic": "openevse/announce/7b2c",
"ohm": "",
"divert_PV_ratio": 1.1,
"divert_attack_smoothing_factor": 0.4,
"divert_decay_smoothing_factor": 0.05,
"divert_min_charge_time": 600,
"tesla_username": "",
"tesla_password": "",
"tesla_vehidx": -1,
"led_brightness": 128,
"flags": 522,
"emoncms_enabled": false,
"mqtt_enabled": true,
"mqtt_reject_unauthorized": true,
"ohm_enabled": false,
"sntp_enabled": true,
"tesla_enabled": false,
"pause_uses_disabled": false,
"mqtt_protocol": "mqtt",
"charge_mode": "fast"
}
66 changes: 66 additions & 0 deletions tests/fixtures/v4_json/config-unknown-semver.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"firmware": "7.1.3",
"protocol": "-",
"espflash": 4194304,
"wifi_serial": "1234567890AB",
"version": "random_a4f11e",
"diodet": 0,
"gfcit": 0,
"groundt": 0,
"relayt": 0,
"ventt": 0,
"tempt": 0,
"service": 2,
"scale": 220,
"offset": 0,
"max_current_soft": 48,
"min_current_hard": 6,
"max_current_hard": 48,
"mqtt_supported_protocols": [
"mqtt",
"mqtts"
],
"http_supported_protocols": [
"http",
"https"
],
"ssid": "Datanode-IoT",
"pass": "_DUMMY_PASSWORD",
"www_username": "",
"www_password": "",
"hostname": "openevse-7b2c",
"sntp_hostname": "0.us.pool.ntp.org",
"time_zone": "America/Phoenix|MST7",
"emoncms_server": "https://emoncms.collective.lan/",
"emoncms_node": "openevse",
"emoncms_apikey": "_DUMMY_PASSWORD",
"emoncms_fingerprint": "",
"mqtt_server": "192.168.1.198",
"mqtt_port": 1883,
"mqtt_topic": "openevse",
"mqtt_user": "devices",
"mqtt_pass": "_DUMMY_PASSWORD",
"mqtt_solar": "",
"mqtt_grid_ie": "home-assistant/power/watts",
"mqtt_vrms": "home-assistant/solar/watts",
"mqtt_announce_topic": "openevse/announce/7b2c",
"ohm": "",
"divert_PV_ratio": 1.1,
"divert_attack_smoothing_factor": 0.4,
"divert_decay_smoothing_factor": 0.05,
"divert_min_charge_time": 600,
"tesla_username": "",
"tesla_password": "",
"tesla_vehidx": -1,
"led_brightness": 128,
"flags": 522,
"emoncms_enabled": false,
"mqtt_enabled": true,
"mqtt_reject_unauthorized": true,
"ohm_enabled": false,
"sntp_enabled": true,
"tesla_enabled": false,
"pause_uses_disabled": false,
"mqtt_protocol": "mqtt",
"charge_mode": "fast"
}
60 changes: 57 additions & 3 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import logging
from unittest import mock

from awesomeversion.exceptions import AwesomeVersionCompareException

import pytest
from aiohttp.client_exceptions import ContentTypeError, ServerTimeoutError

Expand Down Expand Up @@ -230,6 +232,7 @@ async def test_get_service_level(fixture, expected, request):
("test_charger", "4.1.2"),
("test_charger_v2", "2.9.1"),
("test_charger_dev", "4.1.5"),
("test_charger_broken_semver", "master_abcd123"),
],
)
async def test_get_wifi_firmware(fixture, expected, request):
Expand Down Expand Up @@ -641,7 +644,12 @@ async def test_get_manual_override(fixture, expected, request):


async def test_toggle_override(
test_charger, test_charger_dev, test_charger_new, mock_aioclient, caplog
test_charger,
test_charger_dev,
test_charger_new,
test_charger_unknown_semver,
mock_aioclient,
caplog,
):
"""Test v4 Status reply."""
await test_charger.update()
Expand Down Expand Up @@ -848,7 +856,12 @@ async def test_get_charging_power(fixture, expected, request):


async def test_set_divertmode(
test_charger_new, test_charger_v2, test_charger_broken, mock_aioclient, caplog
test_charger_new,
test_charger_v2,
test_charger_broken,
test_charger_unknown_semver,
mock_aioclient,
caplog,
):
"""Test v4 set divert mode."""
await test_charger_new.update()
Expand Down Expand Up @@ -896,6 +909,17 @@ async def test_set_divertmode(
with pytest.raises(UnsupportedFeature):
await test_charger_broken.divert_mode()

mock_aioclient.post(
TEST_URL_CONFIG,
status=200,
body=value,
)
await test_charger_unknown_semver.update()
with pytest.raises(UnsupportedFeature):
with caplog.at_level(logging.DEBUG):
await test_charger_unknown_semver.divert_mode()
assert "Non-semver firmware version detected." in caplog.text


async def test_test_and_get(test_charger, test_charger_v2, mock_aioclient, caplog):
"""Test v4 Status reply"""
Expand Down Expand Up @@ -931,6 +955,8 @@ async def test_firmware_check(
test_charger_dev,
test_charger_v2,
test_charger_broken,
test_charger_broken_semver,
test_charger_unknown_semver,
mock_aioclient,
caplog,
):
Expand Down Expand Up @@ -998,6 +1024,28 @@ async def test_firmware_check(
assert "Unable to find firmware version." in caplog.text
assert firmware is None

await test_charger_broken_semver.update()
mock_aioclient.get(
TEST_URL_GITHUB_v4,
status=200,
body=load_fixture("github_v4.json"),
)
firmware = await test_charger_broken_semver.firmware_check()
assert firmware["latest_version"] == "4.1.4"

await test_charger_unknown_semver.update()
assert test_charger_unknown_semver.wifi_firmware == "random_a4f11e"
mock_aioclient.get(
TEST_URL_GITHUB_v4,
status=200,
body=load_fixture("github_v4.json"),
)
with caplog.at_level(logging.DEBUG):
firmware = await test_charger_unknown_semver.firmware_check()
assert "Using version: random_a4f11e" in caplog.text
assert "Non-semver firmware version detected." in caplog.text
assert firmware is None


async def test_evse_restart(test_charger_v2, mock_aioclient, caplog):
"""Test EVSE module restart."""
Expand Down Expand Up @@ -1102,7 +1150,9 @@ async def test_max_current_soft(fixture, expected, request):
assert status == expected


async def test_set_override(test_charger, test_charger_v2, mock_aioclient, caplog):
async def test_set_override(
test_charger, test_charger_v2, test_charger_unknown_semver, mock_aioclient, caplog
):
"""Test set override function."""
await test_charger.update()
mock_aioclient.post(
Expand Down Expand Up @@ -1176,6 +1226,10 @@ async def test_set_override(test_charger, test_charger_v2, mock_aioclient, caplo
status = await test_charger_v2.set_override("active")
assert "Feature not supported for older firmware." in caplog.text

await test_charger_unknown_semver.update()
status = await test_charger_unknown_semver.set_override("active")
assert "Feature not supported for older firmware." in caplog.text


async def test_clear_override(test_charger, test_charger_v2, mock_aioclient, caplog):
"""Test clear override function."""
Expand Down