From 4697d4b2bf84f309dff3c6c7f58d9f039abe2f93 Mon Sep 17 00:00:00 2001 From: Aidan Keeshan Date: Sun, 4 Oct 2026 14:55:34 +0100 Subject: [PATCH 1/2] Fix solar capability detection when multiple solar modes are returned Ohme API returns multiple solar modes (e.g. ['PURE_SOLAR', 'ZERO_EXPORT']) in modelCapabilities['solarModes']. Previously, len(solar_modes) == 1 caused solar capability to be falsely reported as unsupported when both modes are supported. Also update solar_enabled property to return True for either ZERO_EXPORT or PURE_SOLAR. --- ohme/ohme.py | 6 +-- tests/test_solar.py | 91 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 tests/test_solar.py diff --git a/ohme/ohme.py b/ohme/ohme.py index 57cd882..79132f5 100644 --- a/ohme/ohme.py +++ b/ohme/ohme.py @@ -299,7 +299,7 @@ def slots_full(self) -> list[ChargeSlot]: @property def solar_enabled(self) -> bool: """Solar mode state.""" - return self._configuration.get("solarMode") == "ZERO_EXPORT" + return self._configuration.get("solarMode") in ("ZERO_EXPORT", "PURE_SOLAR") @property def next_slot_start(self) -> datetime.datetime | None: @@ -610,8 +610,8 @@ async def async_update_device_info(self) -> bool: if resp["tariff"] is not None and resp["tariff"]["dsrTariff"]: self.cap_available = False - solar_modes = device["modelCapabilities"]["solarModes"] - if isinstance(solar_modes, list) and len(solar_modes) == 1: + solar_modes = device["modelCapabilities"].get("solarModes") + if isinstance(solar_modes, list) and len(solar_modes) > 0: self._capabilities["solar"] = True return True diff --git a/tests/test_solar.py b/tests/test_solar.py new file mode 100644 index 0000000..4af3626 --- /dev/null +++ b/tests/test_solar.py @@ -0,0 +1,91 @@ +import unittest +from unittest.mock import AsyncMock, patch +from ohme import OhmeApiClient + + +class TestSolar(unittest.IsolatedAsyncioTestCase): + def test_solar_enabled_property(self): + client = OhmeApiClient("test@example.com", "password") + + # Off / IGNORE + client._configuration = {"solarMode": "IGNORE"} + self.assertFalse(client.solar_enabled) + + # ZERO_EXPORT + client._configuration = {"solarMode": "ZERO_EXPORT"} + self.assertTrue(client.solar_enabled) + + # PURE_SOLAR + client._configuration = {"solarMode": "PURE_SOLAR"} + self.assertTrue(client.solar_enabled) + + # None / Missing + client._configuration = {} + self.assertFalse(client.solar_enabled) + + async def test_solar_capability_multiple_modes(self): + client = OhmeApiClient("test@example.com", "password") + + account_resp = { + "user": {"id": "user123"}, + "tariff": None, + "chargeDevices": [{ + "id": "ohme123", + "modelTypeDisplayName": "Ohme Home Pro", + "firmwareVersionLabel": "v1.0.0", + "modelCapabilities": { + "solarModes": ["PURE_SOLAR", "ZERO_EXPORT"] + }, + "optionalSettings": {} + }] + } + + with patch.object(client, "_make_request", new=AsyncMock(return_value=account_resp)): + self.assertTrue(await client.async_update_device_info()) + self.assertTrue(client.is_capable("solar")) + + async def test_solar_capability_single_mode(self): + client = OhmeApiClient("test@example.com", "password") + + account_resp = { + "user": {"id": "user123"}, + "tariff": None, + "chargeDevices": [{ + "id": "ohme123", + "modelTypeDisplayName": "Ohme Home Pro", + "firmwareVersionLabel": "v1.0.0", + "modelCapabilities": { + "solarModes": ["ZERO_EXPORT"] + }, + "optionalSettings": {} + }] + } + + with patch.object(client, "_make_request", new=AsyncMock(return_value=account_resp)): + self.assertTrue(await client.async_update_device_info()) + self.assertTrue(client.is_capable("solar")) + + async def test_solar_capability_empty_or_missing(self): + client = OhmeApiClient("test@example.com", "password") + + account_resp = { + "user": {"id": "user123"}, + "tariff": None, + "chargeDevices": [{ + "id": "ohme123", + "modelTypeDisplayName": "Ohme Home Pro", + "firmwareVersionLabel": "v1.0.0", + "modelCapabilities": { + "solarModes": [] + }, + "optionalSettings": {} + }] + } + + with patch.object(client, "_make_request", new=AsyncMock(return_value=account_resp)): + self.assertTrue(await client.async_update_device_info()) + self.assertFalse(client.is_capable("solar")) + + +if __name__ == "__main__": + unittest.main() From 0a5772b827b7491935fb8e9c961ac158e4acd7e5 Mon Sep 17 00:00:00 2001 From: gemini Date: Thu, 8 Oct 2026 22:28:05 +0100 Subject: [PATCH 2/2] Add GitHub Actions workflow to run unit tests on PRs and pushes --- .github/workflows/tests.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..81150f9 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,34 @@ +name: Unit Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install . + + - name: Run unit tests + run: | + python -m unittest discover -s tests -v