From 38845694df0a50bea6c95f86d2d348951e95cc22 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Thu, 4 Nov 2021 08:24:20 -0700 Subject: [PATCH 01/19] add set_charge_mode function Utilizes the HTTP API to set the charge_mode configuration. --- openevsehttp/__init__.py | 40 +++++++++++++++++--------------------- openevsehttp/exceptions.py | 8 -------- setup.py | 2 +- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index d0f1402..3560747 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -9,13 +9,7 @@ import aiohttp # type: ignore from .const import MAX_AMPS, MIN_AMPS -from .exceptions import ( - AlreadyListening, - AuthenticationError, - MissingMethod, - ParseJSONError, - UnknownError, -) +from .exceptions import AuthenticationError, ParseJSONError, UnknownError _LOGGER = logging.getLogger(__name__) @@ -320,14 +314,6 @@ def ws_state(self) -> Any: assert self.websocket return self.websocket.state - async def get_schedule(self) -> list: - """Return the current schedule.""" - url = f"{self.url}schedule" - - _LOGGER.debug("Getting current schedule from %s", url) - response = await self.process_request(url=url, method="post") - return response - async def set_charge_mode(self, mode: str = "fast") -> None: """Set the charge mode.""" url = f"{self.url}config" @@ -336,15 +322,25 @@ async def set_charge_mode(self, mode: str = "fast") -> None: _LOGGER.error("Invalid value for charge_mode: %s", mode) raise ValueError - data = {"charge_mode": mode} + if self._user and self._pwd: + auth = aiohttp.BasicAuth(self._user, self._pwd) _LOGGER.debug("Setting charge mode to %s", mode) - response = await self.process_request( - url=url, method="post", data=data - ) # noqa: E501 - if response["msg"] != "done": - _LOGGER.error("Problem issuing command: %s", response["msg"]) - raise UnknownError + async with aiohttp.ClientSession() as session: + async with session.post(url, auth=auth) as resp: + message = await resp.json() + if resp.status == 400: + _LOGGER.debug("JSON error: %s", message["msg"]) + raise ParseJSONError + elif resp.status == 401: + _LOGGER.debug("Authentication error: %s", message["msg"]) + raise AuthenticationError + elif resp.status == 404: + _LOGGER.error("Error getting override status: %s", message["msg"]) + + if message["msg"] != "done": + _LOGGER.error("Problem issuing command: %s", message["msg"]) + raise UnknownError async def get_override(self) -> None: """Get the manual override status.""" diff --git a/openevsehttp/exceptions.py b/openevsehttp/exceptions.py index c384f24..5783cd7 100644 --- a/openevsehttp/exceptions.py +++ b/openevsehttp/exceptions.py @@ -11,11 +11,3 @@ class ParseJSONError(Exception): class UnknownError(Exception): """Exception for Unknown errors.""" - - -class MissingMethod(Exception): - """Exception for missing method variable.""" - - -class AlreadyListening(Exception): - """Exception for already listening websocket.""" diff --git a/setup.py b/setup.py index 48d6ea7..7afb880 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ PROJECT_DIR = Path(__file__).parent.resolve() README_FILE = PROJECT_DIR / "README.md" -VERSION = "0.1.12" +VERSION = "0.1.11" setup( From fa98a8d822b402ee7e8c4f5c194c8e35fd0f1b00 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Thu, 4 Nov 2021 12:59:03 -0700 Subject: [PATCH 02/19] refactor: move all aiohttp handling into separate function --- openevsehttp/__init__.py | 54 +++++++++++++++++++------------------- openevsehttp/exceptions.py | 8 ++++++ 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index 3560747..fea70f7 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -9,7 +9,13 @@ import aiohttp # type: ignore from .const import MAX_AMPS, MIN_AMPS -from .exceptions import AuthenticationError, ParseJSONError, UnknownError +from .exceptions import ( + AlreadyListening, + AuthenticationError, + MissingMethod, + ParseJSONError, + UnknownError, +) _LOGGER = logging.getLogger(__name__) @@ -198,14 +204,13 @@ async def process_request( if resp.status == 400: _LOGGER.error("%s", message["msg"]) raise ParseJSONError - if resp.status == 401: - error = await resp.text() - _LOGGER.error("Authentication error: %s", error) + elif resp.status == 401: + _LOGGER.error("Authentication error: %s", resp.text()) raise AuthenticationError - if resp.status == 404: + elif resp.status == 404: _LOGGER.error("%s", message["msg"]) raise UnknownError - if resp.status == 405: + elif resp.status == 405: _LOGGER.error("%s", message["msg"]) elif resp.status == 500: _LOGGER.error("%s", message["msg"]) @@ -246,9 +251,10 @@ async def update(self) -> None: self.websocket = OpenEVSEWebsocket( self.url, self._update_status, self._user, self._pwd ) + self.ws_start() def ws_start(self): - """Start the websocket listener.""" + """Method to start the websocket listener.""" if self._ws_listening: raise AlreadyListening self._start_listening() @@ -314,6 +320,14 @@ def ws_state(self) -> Any: assert self.websocket return self.websocket.state + async def get_schedule(self) -> list: + """Return the current schedule.""" + url = f"{self.url}schedule" + + _LOGGER.debug("Getting current schedule from %s", url) + response = await self.process_request(url=url, method="post") + return response + async def set_charge_mode(self, mode: str = "fast") -> None: """Set the charge mode.""" url = f"{self.url}config" @@ -322,25 +336,13 @@ async def set_charge_mode(self, mode: str = "fast") -> None: _LOGGER.error("Invalid value for charge_mode: %s", mode) raise ValueError - if self._user and self._pwd: - auth = aiohttp.BasicAuth(self._user, self._pwd) + data = {"charge_mode": mode} _LOGGER.debug("Setting charge mode to %s", mode) - async with aiohttp.ClientSession() as session: - async with session.post(url, auth=auth) as resp: - message = await resp.json() - if resp.status == 400: - _LOGGER.debug("JSON error: %s", message["msg"]) - raise ParseJSONError - elif resp.status == 401: - _LOGGER.debug("Authentication error: %s", message["msg"]) - raise AuthenticationError - elif resp.status == 404: - _LOGGER.error("Error getting override status: %s", message["msg"]) - - if message["msg"] != "done": - _LOGGER.error("Problem issuing command: %s", message["msg"]) - raise UnknownError + response = self.process_request(url=url, method="post", data=data) + if response["msg"] != "done": + _LOGGER.error("Problem issuing command: %s", response["msg"]) + raise UnknownError async def get_override(self) -> None: """Get the manual override status.""" @@ -375,9 +377,7 @@ async def set_override( } _LOGGER.debug("Setting override config on %s", url) - response = await self.process_request( - url=url, method="post", data=data - ) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) return response async def toggle_override(self) -> None: diff --git a/openevsehttp/exceptions.py b/openevsehttp/exceptions.py index 5783cd7..c384f24 100644 --- a/openevsehttp/exceptions.py +++ b/openevsehttp/exceptions.py @@ -11,3 +11,11 @@ class ParseJSONError(Exception): class UnknownError(Exception): """Exception for Unknown errors.""" + + +class MissingMethod(Exception): + """Exception for missing method variable.""" + + +class AlreadyListening(Exception): + """Exception for already listening websocket.""" From 96120cafb6e73dd72bae2dbf7ea5e562661bedfd Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 4 Nov 2021 15:53:00 -0700 Subject: [PATCH 03/19] fix: make websocket listener not automatic (#12) * fix tests --- openevsehttp/__init__.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index fea70f7..d0f1402 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -204,13 +204,14 @@ async def process_request( if resp.status == 400: _LOGGER.error("%s", message["msg"]) raise ParseJSONError - elif resp.status == 401: - _LOGGER.error("Authentication error: %s", resp.text()) + if resp.status == 401: + error = await resp.text() + _LOGGER.error("Authentication error: %s", error) raise AuthenticationError - elif resp.status == 404: + if resp.status == 404: _LOGGER.error("%s", message["msg"]) raise UnknownError - elif resp.status == 405: + if resp.status == 405: _LOGGER.error("%s", message["msg"]) elif resp.status == 500: _LOGGER.error("%s", message["msg"]) @@ -251,10 +252,9 @@ async def update(self) -> None: self.websocket = OpenEVSEWebsocket( self.url, self._update_status, self._user, self._pwd ) - self.ws_start() def ws_start(self): - """Method to start the websocket listener.""" + """Start the websocket listener.""" if self._ws_listening: raise AlreadyListening self._start_listening() @@ -339,7 +339,9 @@ async def set_charge_mode(self, mode: str = "fast") -> None: data = {"charge_mode": mode} _LOGGER.debug("Setting charge mode to %s", mode) - response = self.process_request(url=url, method="post", data=data) + response = await self.process_request( + url=url, method="post", data=data + ) # noqa: E501 if response["msg"] != "done": _LOGGER.error("Problem issuing command: %s", response["msg"]) raise UnknownError @@ -377,7 +379,9 @@ async def set_override( } _LOGGER.debug("Setting override config on %s", url) - response = await self.process_request(url=url, method="post", data=data) + response = await self.process_request( + url=url, method="post", data=data + ) # noqa: E501 return response async def toggle_override(self) -> None: From dfc0df1d352fe2f95eb0359d8c4858e73620d1be Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 5 Nov 2021 09:56:01 -0700 Subject: [PATCH 04/19] handle running loop better --- openevsehttp/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index d0f1402..4a0a8d4 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -275,7 +275,7 @@ def _start_listening(self): try: self._loop.run_until_complete(asyncio.gather(*pending)) except RuntimeError: - _LOGGER.info(LOOP_INFO) + _LOGGER.info("Event loop already running, not creating new one.") def _update_status(self, msgtype, data, error): """Update data from websocket listener.""" diff --git a/setup.py b/setup.py index 7afb880..48d6ea7 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ PROJECT_DIR = Path(__file__).parent.resolve() README_FILE = PROJECT_DIR / "README.md" -VERSION = "0.1.11" +VERSION = "0.1.12" setup( From 6543fcab3fc96e788876f44b7aa973b3b547d474 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 5 Nov 2021 09:59:58 -0700 Subject: [PATCH 05/19] formatting --- openevsehttp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index 4a0a8d4..d0f1402 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -275,7 +275,7 @@ def _start_listening(self): try: self._loop.run_until_complete(asyncio.gather(*pending)) except RuntimeError: - _LOGGER.info("Event loop already running, not creating new one.") + _LOGGER.info(LOOP_INFO) def _update_status(self, msgtype, data, error): """Update data from websocket listener.""" From c8a27dfa91fd7c28be385b52fc112421fa0d6300 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Fri, 5 Nov 2021 10:05:25 -0700 Subject: [PATCH 06/19] rename variable --- openevsehttp/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index d0f1402..b18cbc7 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -40,7 +40,7 @@ ERROR_UNKNOWN = "Unknown" ERROR_TIMEOUT = "Timeout while updating " -LOOP_INFO = "Event loop already running, not creating new one." +INFO_LOOP_RUNNING = "Event loop already running, not creating new one." MAX_FAILED_ATTEMPTS = 5 @@ -275,7 +275,7 @@ def _start_listening(self): try: self._loop.run_until_complete(asyncio.gather(*pending)) except RuntimeError: - _LOGGER.info(LOOP_INFO) + _LOGGER.info(INFO_LOOP_RUNNING) def _update_status(self, msgtype, data, error): """Update data from websocket listener.""" From ff365ef7b75665d14cc15d0f4854a669a59f2913 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Sun, 7 Nov 2021 17:44:02 -0700 Subject: [PATCH 07/19] refactor: update websocket error message with correct url --- openevsehttp/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index b18cbc7..a387558 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -281,12 +281,12 @@ def _update_status(self, msgtype, data, error): """Update data from websocket listener.""" if msgtype == SIGNAL_CONNECTION_STATE: if data == STATE_CONNECTED: - _LOGGER.debug("Websocket to %s successful", self.url) + _LOGGER.debug("Websocket to %s successful", self.websocket.uri) self._ws_listening = True elif data == STATE_DISCONNECTED: _LOGGER.debug( "Websocket to %s disconnected, retrying", - self.url, + self.websocket.uri, ) self._ws_listening = False # Stopped websockets without errors are expected during shutdown @@ -294,7 +294,7 @@ def _update_status(self, msgtype, data, error): elif data == STATE_STOPPED and error: _LOGGER.error( "Websocket to %s failed, aborting [Error: %s]", - self.url, + self.websocket.uri, error, ) self._ws_listening = False From 7021fc9e20b33738c716a9392c87204814f8e5d1 Mon Sep 17 00:00:00 2001 From: firstof9 Date: Sat, 13 Nov 2021 08:59:13 -0700 Subject: [PATCH 08/19] add python 3.10 to tests --- .github/workflows/test.yml | 5 ++++- setup.py | 1 + tox.ini | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5f49f9..8bfb18e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9] + python-version: + - "3.8" + - "3.9" + - "3.10" steps: - uses: actions/checkout@v2.3.4 diff --git a/setup.py b/setup.py index 48d6ea7..5d01307 100644 --- a/setup.py +++ b/setup.py @@ -31,5 +31,6 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ], ) diff --git a/tox.ini b/tox.ini index de8d0b8..b94fc40 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,12 @@ [tox] -envlist = py38, py39, lint, mypy +envlist = py38, py39, py310, lint, mypy skip_missing_interpreters = True [gh-actions] python = 3.8: py38, lint, mypy 3.9: py39 + 3.10: py310 [testenv] commands = From fabeea2c4eaea606a60443de6d504cf175151f33 Mon Sep 17 00:00:00 2001 From: Jose Carlos Garcia Sogo Date: Thu, 30 Dec 2021 20:37:32 +0100 Subject: [PATCH 09/19] Capture additional properties from OpenEVSE status (#15) * Add manual_override and divertmode * Add available_current, charge_rate and divert_active properties * Add smoothed_available_current * Add several additional properties - manual_override - divertmode - available_current - charge_rate - divert_active - smoothed_available_current --- openevsehttp/__init__.py | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index a387558..6e4ec4e 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -643,6 +643,47 @@ def ota_update(self) -> str: assert self._status is not None return self._status["ota_update"] + @property + def manual_override(self) -> str: + """Return if Manual Override is set.""" + assert self._status is not None + return self._status["manual_override"] + + @property + def divertmode(self) -> str: + """Return the divert mode.""" + assert self._status is not None + mode = self._status["divertmode"] + if mode == 1: + return "normal" + else: + return "eco" + + @property + def available_current(self) -> float: + """Return the computed available current for divert.""" + assert self._status is not None + return self._status["available_current"] + + @property + def smoothed_available_current(self) -> float: + """Return the computed smoothed available current for divert.""" + assert self._status is not None + return self._status["smoothed_available_current"] + + @property + def charge_rate(self) -> float: + """Return the divert charge rate.""" + assert self._status is not None + return self._status["charge_rate"] + + @property + def divert_active(self) -> bool: + """Return if divert is active""" + assert self._status is not None + return self._status["divert_active"] + + # There is currently no min/max amps JSON data # available via HTTP API methods @property From 7e26743d51a3e992f8ecfc32f713730bc9011fcf Mon Sep 17 00:00:00 2001 From: firstof9 Date: Thu, 30 Dec 2021 13:01:11 -0700 Subject: [PATCH 10/19] formatting --- openevsehttp/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index 6e4ec4e..de9e92d 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -670,7 +670,7 @@ def smoothed_available_current(self) -> float: """Return the computed smoothed available current for divert.""" assert self._status is not None return self._status["smoothed_available_current"] - + @property def charge_rate(self) -> float: """Return the divert charge rate.""" @@ -682,7 +682,6 @@ def divert_active(self) -> bool: """Return if divert is active""" assert self._status is not None return self._status["divert_active"] - # There is currently no min/max amps JSON data # available via HTTP API methods From 743f8b2e717162e80db22fee64e19be22551c72c Mon Sep 17 00:00:00 2001 From: firstof9 Date: Thu, 30 Dec 2021 13:03:18 -0700 Subject: [PATCH 11/19] linting --- openevsehttp/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index de9e92d..1b6b720 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -656,8 +656,7 @@ def divertmode(self) -> str: mode = self._status["divertmode"] if mode == 1: return "normal" - else: - return "eco" + return "eco" @property def available_current(self) -> float: @@ -679,7 +678,7 @@ def charge_rate(self) -> float: @property def divert_active(self) -> bool: - """Return if divert is active""" + """Return if divert is active.""" assert self._status is not None return self._status["divert_active"] From b1ddd831d804a9fb3b61ea1d56fae516c1db7152 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 11 Jan 2022 13:52:56 -0700 Subject: [PATCH 12/19] feat: add version check to toggle_override function (#24) * docs: placeholder * feat: add divertmode function * refactor: formatting and linting * feat: add version check to toggle_override * formatting and linting --- openevsehttp/__init__.py | 46 ++++++++++++++++++++++++++++++++++++---- requirements.txt | 1 + requirements_lint.txt | 1 + requirements_test.txt | 1 + tests/conftest.py | 6 +++--- tests/test_init.py | 2 ++ 6 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 requirements.txt diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index 1b6b720..2045213 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -7,6 +7,7 @@ from typing import Any, Callable, Optional import aiohttp # type: ignore +from awesomeversion import AwesomeVersion from .const import MAX_AMPS, MIN_AMPS from .exceptions import ( @@ -346,6 +347,29 @@ async def set_charge_mode(self, mode: str = "fast") -> None: _LOGGER.error("Problem issuing command: %s", response["msg"]) raise UnknownError + async def divert_mode(self, mode: str = "Normal") -> None: + """Set the divert mode to either Normal or Eco modes.""" + url = f"{self.url}divertmode" + + if mode != "Normal" or mode != "Eco": + _LOGGER.error("Invalid value for divertmode: %s", mode) + raise ValueError + + if mode == "Normal": + value = 1 + else: + value = 2 + + data = {"divertmode": value} + + _LOGGER.debug("Setting charge mode to %s", mode) + response = await self.process_request( + url=url, method="post", data=data + ) # noqa: E501 + if response["msg"] != "done": + _LOGGER.error("Problem issuing command: %s", response["msg"]) + raise UnknownError + async def get_override(self) -> None: """Get the manual override status.""" url = f"{self.url}override" @@ -386,11 +410,25 @@ async def set_override( async def toggle_override(self) -> None: """Toggle the manual override status.""" - url = f"{self.url}override" + # TODO: Add version check (self._config["version"]) + # 3.x: use RAPI commands $FE (enable) and $FS (sleep) + # 4.x: use HTTP API call - _LOGGER.debug("Toggling manual override %s", url) - response = await self.process_request(url=url, method="patch") - _LOGGER.debug("Toggle response: %s", response["msg"]) + cutoff = AwesomeVersion("4.0.0") + current = AwesomeVersion(self._config["version"]) + + if cutoff < current: + url = f"{self.url}override" + + _LOGGER.debug("Toggling manual override %s", url) + response = await self.process_request(url=url, method="patch") + _LOGGER.debug("Toggle response: %s", response["msg"]) + else: + # Older firmware use RAPI commands + _LOGGER.debug("Toggling manual override via RAPI") + command = "$FE" if self._config["state"] == "sleeping" else "$FS" + response = await self.send_command(command) + _LOGGER.debug("Toggle response: %s", response[1]) async def clear_override(self) -> None: """Clear the manual override status.""" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c0152f4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +awesomeversion \ No newline at end of file diff --git a/requirements_lint.txt b/requirements_lint.txt index 3bfa91a..89852cd 100644 --- a/requirements_lint.txt +++ b/requirements_lint.txt @@ -1,3 +1,4 @@ +-r requirements.txt black==21.7b0 flake8==3.9.2 mypy==0.910 diff --git a/requirements_test.txt b/requirements_test.txt index 1a08932..5c8fdc5 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,3 +1,4 @@ +-r requirements.txt pytest==6.2.4 pytest-cov==2.12.1 pytest-timeout==1.4.2 diff --git a/tests/conftest.py b/tests/conftest.py index 9c08faa..1657270 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,12 @@ """Provide common pytest fixtures.""" -import pytest import json +import pytest +from aioresponses import aioresponses + import openevsehttp from tests.common import load_fixture -from aioresponses import aioresponses - TEST_URL_STATUS = "http://openevse.test.tld/status" TEST_URL_CONFIG = "http://openevse.test.tld/config" TEST_URL_RAPI = "http://openevse.test.tld/r" diff --git a/tests/test_init.py b/tests/test_init.py index 3a303a0..6df057e 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,6 +1,8 @@ import asyncio import json + import pytest + import openevsehttp pytestmark = pytest.mark.asyncio From 726bcb8f342b4874d4f0bfe70676c01b0e9e98a6 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 11 Jan 2022 13:54:55 -0700 Subject: [PATCH 13/19] version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5d01307..02f11fd 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ PROJECT_DIR = Path(__file__).parent.resolve() README_FILE = PROJECT_DIR / "README.md" -VERSION = "0.1.12" +VERSION = "0.1.14" setup( From d508bb3653df4c36aa87e65bd7e835d4f2a11e62 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 11 Jan 2022 14:44:41 -0700 Subject: [PATCH 14/19] tests: update tests --- tests/test_init.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/test_init.py b/tests/test_init.py index 6df057e..51d6507 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -510,3 +510,61 @@ async def test_get_relayt(fixture, expected, request): await charger.update() status = charger.stuck_relay_check_enabled assert status == expected + + +@pytest.mark.parametrize( + "fixture, expected", [("test_charger", "normal"), ("test_charger_v2", "normal")] +) +async def test_get_divertmode(fixture, expected, request): + """Test v4 Status reply""" + charger = request.getfixturevalue(fixture) + await charger.update() + status = charger.divertmode + assert status == expected + + +@pytest.mark.parametrize( + "fixture, expected", [("test_charger", 0), ("test_charger_v2", 0)] +) +async def test_get_charge_rate(fixture, expected, request): + """Test v4 Status reply""" + charger = request.getfixturevalue(fixture) + await charger.update() + status = charger.charge_rate + assert status == expected + + +@pytest.mark.parametrize( + "fixture, expected", [("test_charger", 0), ("test_charger_v2", 0)] +) +async def test_get_available_current(fixture, expected, request): + """Test v4 Status reply""" + charger = request.getfixturevalue(fixture) + await charger.update() + with pytest.raises(KeyError): + status = charger.available_current + # assert status == expected + + +@pytest.mark.parametrize( + "fixture, expected", [("test_charger", 0), ("test_charger_v2", 0)] +) +async def test_get_smoothed_available_current(fixture, expected, request): + """Test v4 Status reply""" + charger = request.getfixturevalue(fixture) + await charger.update() + with pytest.raises(KeyError): + status = charger.smoothed_available_current + # assert status == expected + + +@pytest.mark.parametrize( + "fixture, expected", [("test_charger", 0), ("test_charger_v2", 0)] +) +async def test_get_divert_active(fixture, expected, request): + """Test v4 Status reply""" + charger = request.getfixturevalue(fixture) + await charger.update() + with pytest.raises(KeyError): + status = charger.divert_active + # assert status == expected From 8cf5ddca0033a1203f1acebeb07ea6bbf1f62033 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 11 Jan 2022 14:52:48 -0700 Subject: [PATCH 15/19] tests: more test updates --- tests/fixtures/v4_json/status.json | 2 +- tests/test_init.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/v4_json/status.json b/tests/fixtures/v4_json/status.json index 03a3bad..c35bb8b 100644 --- a/tests/fixtures/v4_json/status.json +++ b/tests/fixtures/v4_json/status.json @@ -27,7 +27,7 @@ "vehicle": 1, "colour": 6, "freeram": 223464, - "divertmode": 1, + "divertmode": 0, "srssi": -61, "elapsed": 246, "wattsec": 992549, diff --git a/tests/test_init.py b/tests/test_init.py index 51d6507..592a95e 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -513,7 +513,7 @@ async def test_get_relayt(fixture, expected, request): @pytest.mark.parametrize( - "fixture, expected", [("test_charger", "normal"), ("test_charger_v2", "normal")] + "fixture, expected", [("test_charger", "eco"), ("test_charger_v2", "normal")] ) async def test_get_divertmode(fixture, expected, request): """Test v4 Status reply""" @@ -568,3 +568,15 @@ async def test_get_divert_active(fixture, expected, request): with pytest.raises(KeyError): status = charger.divert_active # assert status == expected + + +@pytest.mark.parametrize( + "fixture, expected", [("test_charger", 0), ("test_charger_v2", 0)] +) +async def test_get_manual_override(fixture, expected, request): + """Test v4 Status reply""" + charger = request.getfixturevalue(fixture) + await charger.update() + with pytest.raises(KeyError): + status = charger.manual_override + # assert status == expected From a358566abd6c2828a2b7691d30a134e8291c8819 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 11 Jan 2022 15:16:37 -0700 Subject: [PATCH 16/19] tests: add another test --- openevsehttp/__init__.py | 1 - tests/test_init.py | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index 2045213..65b0c2e 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -410,7 +410,6 @@ async def set_override( async def toggle_override(self) -> None: """Toggle the manual override status.""" - # TODO: Add version check (self._config["version"]) # 3.x: use RAPI commands $FE (enable) and $FS (sleep) # 4.x: use HTTP API call diff --git a/tests/test_init.py b/tests/test_init.py index 592a95e..5f7f232 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -580,3 +580,15 @@ async def test_get_manual_override(fixture, expected, request): with pytest.raises(KeyError): status = charger.manual_override # assert status == expected + + +@pytest.mark.parametrize( + "fixture, expected", [("test_charger", 0), ("test_charger_v2", 0)] +) +async def test_toggle_override(fixture, expected, request, caplog): + """Test v4 Status reply""" + charger = request.getfixturevalue(fixture) + await charger.update() + status = charger.toggle_override + # assert "Toggling manual override" in caplog.text + # assert status == expected From 3aab4d58b994df2df4d603a1c995b12703022d56 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 11 Jan 2022 16:32:51 -0700 Subject: [PATCH 17/19] tests: add toggle_override test --- openevsehttp/__init__.py | 11 ++++++++--- tests/test_init.py | 31 ++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/openevsehttp/__init__.py b/openevsehttp/__init__.py index 65b0c2e..96304f6 100644 --- a/openevsehttp/__init__.py +++ b/openevsehttp/__init__.py @@ -4,6 +4,7 @@ import asyncio import datetime import logging +from json.decoder import JSONDecodeError from typing import Any, Callable, Optional import aiohttp # type: ignore @@ -201,6 +202,8 @@ async def process_request( message = await resp.json() except TimeoutError: _LOGGER.error("%s: %s", ERROR_TIMEOUT, url) + except JSONDecodeError: + message = {"msg": resp} if resp.status == 400: _LOGGER.error("%s", message["msg"]) @@ -416,16 +419,18 @@ async def toggle_override(self) -> None: cutoff = AwesomeVersion("4.0.0") current = AwesomeVersion(self._config["version"]) - if cutoff < current: + _LOGGER.debug("Detected firmware: %s", current) + + if cutoff <= current: url = f"{self.url}override" _LOGGER.debug("Toggling manual override %s", url) response = await self.process_request(url=url, method="patch") - _LOGGER.debug("Toggle response: %s", response["msg"]) + _LOGGER.debug("Toggle response: %s", response) else: # Older firmware use RAPI commands _LOGGER.debug("Toggling manual override via RAPI") - command = "$FE" if self._config["state"] == "sleeping" else "$FS" + command = "$FE" if self._status["state"] == "sleeping" else "$FS" response = await self.send_command(command) _LOGGER.debug("Toggle response: %s", response[1]) diff --git a/tests/test_init.py b/tests/test_init.py index 5f7f232..2d0691b 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -8,6 +8,7 @@ pytestmark = pytest.mark.asyncio TEST_URL_RAPI = "http://openevse.test.tld/r" +TEST_URL_OVERRIDE = "http://openevse.test.tld/override" async def test_get_status_auth(test_charger_auth): @@ -582,13 +583,25 @@ async def test_get_manual_override(fixture, expected, request): # assert status == expected -@pytest.mark.parametrize( - "fixture, expected", [("test_charger", 0), ("test_charger_v2", 0)] -) -async def test_toggle_override(fixture, expected, request, caplog): +async def test_toggle_override(test_charger, mock_aioclient, caplog): """Test v4 Status reply""" - charger = request.getfixturevalue(fixture) - await charger.update() - status = charger.toggle_override - # assert "Toggling manual override" in caplog.text - # assert status == expected + await test_charger.update() + mock_aioclient.patch( + TEST_URL_OVERRIDE, + status=200, + body="OK", + ) + await test_charger.toggle_override() + assert "Toggling manual override http" in caplog.text + +async def test_toggle_override_v2(test_charger_v2, mock_aioclient, caplog): + """Test v4 Status reply""" + await test_charger_v2.update() + value = {"cmd": "OK", "ret": "$OK^20"} + mock_aioclient.post( + TEST_URL_RAPI, + status=200, + body=json.dumps(value), + ) + await test_charger_v2.toggle_override() + assert "Toggling manual override via RAPI" in caplog.text From 293dcf15b751551af475e6122760bf524a57ec1c Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 11 Jan 2022 16:33:50 -0700 Subject: [PATCH 18/19] formatting --- tests/test_init.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 2d0691b..1ad89e4 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -590,10 +590,11 @@ async def test_toggle_override(test_charger, mock_aioclient, caplog): TEST_URL_OVERRIDE, status=200, body="OK", - ) + ) await test_charger.toggle_override() assert "Toggling manual override http" in caplog.text + async def test_toggle_override_v2(test_charger_v2, mock_aioclient, caplog): """Test v4 Status reply""" await test_charger_v2.update() @@ -602,6 +603,6 @@ async def test_toggle_override_v2(test_charger_v2, mock_aioclient, caplog): TEST_URL_RAPI, status=200, body=json.dumps(value), - ) + ) await test_charger_v2.toggle_override() assert "Toggling manual override via RAPI" in caplog.text From babf103fbd20decea4e8f8b1a6040f834ef452e3 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 11 Jan 2022 17:08:34 -0700 Subject: [PATCH 19/19] tests: fix test --- requirements_test.txt | 6 +++--- tests/test_init.py | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index 5c8fdc5..693a471 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ -r requirements.txt -pytest==6.2.4 -pytest-cov==2.12.1 -pytest-timeout==1.4.2 +pytest==6.2.5 +pytest-cov==3.0.0 +pytest-timeout==2.0.2 pytest-asyncio requests_mock aiohttp diff --git a/tests/test_init.py b/tests/test_init.py index 1ad89e4..0de1e62 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,6 +1,8 @@ import asyncio import json +import logging + import pytest import openevsehttp @@ -591,7 +593,8 @@ async def test_toggle_override(test_charger, mock_aioclient, caplog): status=200, body="OK", ) - await test_charger.toggle_override() + with caplog.at_level(logging.DEBUG): + await test_charger.toggle_override() assert "Toggling manual override http" in caplog.text @@ -604,5 +607,6 @@ async def test_toggle_override_v2(test_charger_v2, mock_aioclient, caplog): status=200, body=json.dumps(value), ) - await test_charger_v2.toggle_override() + with caplog.at_level(logging.DEBUG): + await test_charger_v2.toggle_override() assert "Toggling manual override via RAPI" in caplog.text