From b8ef0ad0df5a7515e17c27ed421024605568adf6 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Wed, 20 May 2026 17:05:35 -0700 Subject: [PATCH 1/4] Centralize version parsing and normalization --- openevsehttp/client.py | 35 +++++++++++++++-------------------- openevsehttp/commands.py | 21 +++++++-------------- openevsehttp/properties.py | 7 +++---- openevsehttp/utils.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 38 deletions(-) create mode 100644 openevsehttp/utils.py diff --git a/openevsehttp/client.py b/openevsehttp/client.py index 9901293..027b267 100644 --- a/openevsehttp/client.py +++ b/openevsehttp/client.py @@ -31,6 +31,7 @@ from .managers import ManagersMixin from .properties import PropertiesMixin from .sensors import SensorsMixin +from .utils import get_awesome_version from .websocket import ( SIGNAL_CONNECTION_STATE, STATE_CONNECTED, @@ -441,37 +442,31 @@ def _version_check(self, min_version: str, max_version: str = "") -> bool: _LOGGER.warning("Unable to find firmware version.") return False cutoff = AwesomeVersion(min_version) - current = "" limit = "" if max_version != "": limit = AwesomeVersion(max_version) - firmware_filtered = None + # Check if version is standard semver or has dev/master firmware_search = re.search(r"\d+\.\d+\.\d+", self._config["version"]) - if firmware_search: - firmware_filtered = firmware_search.group(0) - - if firmware_filtered is None: + if ( + not firmware_search + and "dev" not in self._config["version"] + and "master" not in self._config["version"] + ): _LOGGER.warning( "Non-standard versioning string: %s", self._config["version"] ) _LOGGER.debug("Non-semver firmware version detected.") return False - _LOGGER.debug("Detected firmware: %s", self._config["version"]) - _LOGGER.debug("Filtered firmware: %s", firmware_filtered) - - if "dev" in self._config["version"]: - value = self._config["version"] - _LOGGER.debug("Stripping 'dev' from version.") - value = value.split(".") - value = ".".join(value[0:3]) - elif "master" in self._config["version"]: - value = "dev" - else: - value = firmware_filtered - - current = AwesomeVersion(value) + try: + current = get_awesome_version(self._config["version"]) + except AwesomeVersionCompareException: + _LOGGER.warning( + "Non-standard versioning string: %s", self._config["version"] + ) + _LOGGER.debug("Non-semver firmware version detected.") + return False if limit: try: diff --git a/openevsehttp/commands.py b/openevsehttp/commands.py index 4ff0e65..648e35b 100644 --- a/openevsehttp/commands.py +++ b/openevsehttp/commands.py @@ -14,6 +14,7 @@ from .const import MAX_AMPS, MIN_AMPS, RAPI_ERRORS, divert_mode from .exceptions import UnknownError, UnsupportedFeature +from .utils import get_awesome_version _LOGGER = logging.getLogger(__name__) @@ -364,22 +365,14 @@ async def firmware_check(self) -> dict | None: method = "get" cutoff = AwesomeVersion("3.0.0") - current = "" - _LOGGER.debug("Detected firmware: %s", self._config["version"]) - if "dev" in self._config["version"]: - value = self._config["version"] - _LOGGER.debug("Stripping 'dev' from version.") - value = value.split(".") - value = ".".join(value[0:3]) - elif "master" in self._config["version"]: - value = "dev" - else: - value = self._config["version"] - - _LOGGER.debug("Using version: %s", value) - current = AwesomeVersion(value) + try: + current = get_awesome_version(self._config["version"]) + _LOGGER.debug("Using version: %s", current) + except AwesomeVersionCompareException: + _LOGGER.warning("Non-semver firmware version detected.") + return None try: if current >= cutoff: diff --git a/openevsehttp/properties.py b/openevsehttp/properties.py index a658f34..3025397 100644 --- a/openevsehttp/properties.py +++ b/openevsehttp/properties.py @@ -9,6 +9,7 @@ from .const import MAX_AMPS, MIN_AMPS, states from .exceptions import UnsupportedFeature +from .utils import normalize_version _LOGGER = logging.getLogger(__name__) @@ -130,10 +131,8 @@ def max_current(self) -> int | None: def wifi_firmware(self) -> str | None: """Return the ESP firmware version.""" value = self._config.get("version") - if value is not None and "dev" in value: - _LOGGER.debug("Stripping 'dev' from version.") - value = value.split(".") - value = ".".join(value[0:3]) + if value is not None: + value = normalize_version(value) return value @property diff --git a/openevsehttp/utils.py b/openevsehttp/utils.py new file mode 100644 index 0000000..c511cdc --- /dev/null +++ b/openevsehttp/utils.py @@ -0,0 +1,33 @@ +"""Utility functions for python-openevse-http.""" + +import logging +import re + +from awesomeversion import AwesomeVersion + +_LOGGER = logging.getLogger(__name__) + + +def normalize_version(version: str) -> str: + """Normalize the version string to strip 'dev' tag.""" + if "dev" in version: + _LOGGER.debug("Stripping 'dev' from version.") + value = version.split(".") + return ".".join(value[0:3]) + return version + + +def get_awesome_version(version: str) -> AwesomeVersion: + """Parse and normalize the version string, returning an AwesomeVersion.""" + if "dev" in version: + value = normalize_version(version) + elif "master" in version: + value = "dev" + else: + firmware_search = re.search(r"\d+\.\d+\.\d+", version) + if firmware_search: + value = firmware_search.group(0) + else: + value = version + + return AwesomeVersion(value) From 939e1dbf9f681af2d6b2540a8f04fcbe8aedb5dc Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Wed, 20 May 2026 17:34:41 -0700 Subject: [PATCH 2/4] Simplify try-except around get_awesome_version to improve patch coverage --- openevsehttp/client.py | 9 +-------- openevsehttp/commands.py | 8 ++------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/openevsehttp/client.py b/openevsehttp/client.py index 027b267..563a281 100644 --- a/openevsehttp/client.py +++ b/openevsehttp/client.py @@ -459,14 +459,7 @@ def _version_check(self, min_version: str, max_version: str = "") -> bool: _LOGGER.debug("Non-semver firmware version detected.") return False - try: - current = get_awesome_version(self._config["version"]) - except AwesomeVersionCompareException: - _LOGGER.warning( - "Non-standard versioning string: %s", self._config["version"] - ) - _LOGGER.debug("Non-semver firmware version detected.") - return False + current = get_awesome_version(self._config["version"]) if limit: try: diff --git a/openevsehttp/commands.py b/openevsehttp/commands.py index 648e35b..b3283c9 100644 --- a/openevsehttp/commands.py +++ b/openevsehttp/commands.py @@ -367,12 +367,8 @@ async def firmware_check(self) -> dict | None: cutoff = AwesomeVersion("3.0.0") _LOGGER.debug("Detected firmware: %s", self._config["version"]) - try: - current = get_awesome_version(self._config["version"]) - _LOGGER.debug("Using version: %s", current) - except AwesomeVersionCompareException: - _LOGGER.warning("Non-semver firmware version detected.") - return None + current = get_awesome_version(self._config["version"]) + _LOGGER.debug("Using version: %s", current) try: if current >= cutoff: From 99fee0a32f584aafb81defdc9981c6c2fe63f10c Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Wed, 20 May 2026 18:14:28 -0700 Subject: [PATCH 3/4] Simplify get_awesome_version by calling normalize_version first --- openevsehttp/utils.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/openevsehttp/utils.py b/openevsehttp/utils.py index c511cdc..8c8e30d 100644 --- a/openevsehttp/utils.py +++ b/openevsehttp/utils.py @@ -19,15 +19,11 @@ def normalize_version(version: str) -> str: def get_awesome_version(version: str) -> AwesomeVersion: """Parse and normalize the version string, returning an AwesomeVersion.""" - if "dev" in version: - value = normalize_version(version) - elif "master" in version: - value = "dev" - else: - firmware_search = re.search(r"\d+\.\d+\.\d+", version) + if "master" in version: + version = "dev" + value = normalize_version(version) + if "dev" not in version: + firmware_search = re.search(r"\d+\.\d+\.\d+", value) if firmware_search: value = firmware_search.group(0) - else: - value = version - return AwesomeVersion(value) From 6143fb0112e648c14d02dc505880adde64190097 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Wed, 20 May 2026 18:16:22 -0700 Subject: [PATCH 4/4] Rely on AwesomeVersion strategy unknown check to consolidate validation logic --- openevsehttp/client.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/openevsehttp/client.py b/openevsehttp/client.py index 563a281..5daf0dc 100644 --- a/openevsehttp/client.py +++ b/openevsehttp/client.py @@ -6,7 +6,6 @@ import inspect import json import logging -import re import threading from collections.abc import Callable, Mapping from typing import Any @@ -446,21 +445,14 @@ def _version_check(self, min_version: str, max_version: str = "") -> bool: if max_version != "": limit = AwesomeVersion(max_version) - # Check if version is standard semver or has dev/master - firmware_search = re.search(r"\d+\.\d+\.\d+", self._config["version"]) - if ( - not firmware_search - and "dev" not in self._config["version"] - and "master" not in self._config["version"] - ): + current = get_awesome_version(self._config["version"]) + if current.strategy == "unknown": _LOGGER.warning( "Non-standard versioning string: %s", self._config["version"] ) _LOGGER.debug("Non-semver firmware version detected.") return False - current = get_awesome_version(self._config["version"]) - if limit: try: if cutoff <= current < limit: