Skip to content

Commit

Permalink
feat: Updated Home Pro to contact local API directly instead of via c…
Browse files Browse the repository at this point in the history
…ustom API (45 minutes dev time)
  • Loading branch information
BottlecapDave committed Dec 19, 2024
1 parent b16cd1d commit 0ace45e
Show file tree
Hide file tree
Showing 7 changed files with 31 additions and 54 deletions.
35 changes: 20 additions & 15 deletions custom_components/octopus_energy/api_client_home_pro/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ def _create_client_session(self):
async def async_ping(self):
try:
client = self._create_client_session()
url = f'{self._base_url}/get_meter_info?meter_type=elec'
url = f'{self._base_url}:3000/get_meter_consumption'
headers = { "Authorization": self._api_key }
async with client.get(url, headers=headers) as response:
data = { "meter_type": "elec" }
async with client.post(url, headers=headers, json=data) as response:
response_body = await self.__async_read_response__(response, url)
if (response_body is not None and "Status" in response_body):
status: str = response_body["Status"]
Expand All @@ -62,20 +63,23 @@ async def async_get_consumption(self, is_electricity: bool) -> list | None:
try:
client = self._create_client_session()
meter_type = 'elec' if is_electricity else 'gas'
url = f'{self._base_url}/get_meter_consumption?meter_type={meter_type}'
url = f'{self._base_url}:3000/get_meter_consumption'
headers = { "Authorization": self._api_key }
async with client.get(url, headers=headers) as response:
data = { "meter_type": meter_type }
async with client.post(url, headers=headers, json=data) as response:
response_body = await self.__async_read_response__(response, url)
if (response_body is not None and "meter_consump" in response_body and "consum" in response_body["meter_consump"]):
data = response_body["meter_consump"]["consum"]
divisor = int(data["raw"]["divisor"], 16)
return [{
"total_consumption": int(data["consumption"]) / divisor if divisor > 0 else None,
"demand": float(data["instdmand"]) if "instdmand" in data else None,
"start": datetime.fromtimestamp(int(response_body["meter_consump"]["time"]), timezone.utc),
"end": datetime.fromtimestamp(int(response_body["meter_consump"]["time"]), timezone.utc),
"is_kwh": data["unit"] == 0
}]
if (response_body is not None and "meter_consump"):

This comment has been minimized.

Copy link
@notroj

notroj Dec 30, 2024

Is that supposed to be "meter_consump" in response_body?

This comment has been minimized.

Copy link
@BottlecapDave

BottlecapDave Dec 30, 2024

Author Owner

Yes it is. Good catch. I'll add the fix in the next release

meter_consump = json.loads(response_body["meter_consump"])
if "consum" in meter_consump:
data = meter_consump["consum"]
divisor = int(data["raw"]["divisor"], 16)
return [{
"total_consumption": int(data["consumption"]) / divisor if divisor > 0 else None,
"demand": float(data["instdmand"]) if "instdmand" in data else None,
"start": datetime.fromtimestamp(int(meter_consump["time"]), timezone.utc),
"end": datetime.fromtimestamp(int(meter_consump["time"]), timezone.utc),
"is_kwh": data["unit"] == 0
}]

return None

Expand All @@ -88,7 +92,7 @@ async def async_set_screen(self, value: str, animation_type: str, type: str, bri

try:
client = self._create_client_session()
url = f'{self._base_url}/screen'
url = f'{self._base_url}:8000/screen'
headers = { "Authorization": self._api_key }
payload = {
# API doesn't support none or empty string as a valid value
Expand All @@ -110,6 +114,7 @@ async def __async_read_response__(self, response, url):
"""Reads the response, logging any json errors"""

text = await response.text()
_LOGGER.debug(f"response: {text}")

if response.status >= 400:
if response.status >= 500:
Expand Down
4 changes: 4 additions & 0 deletions custom_components/octopus_energy/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ async def async_migrate_main_config(version: int, data: {}):
new_data[CONFIG_ACCOUNT_ID] = new_data[CONFIG_MAIN_OLD_ACCOUNT_ID]
del new_data[CONFIG_MAIN_OLD_ACCOUNT_ID]

if (version <= 5):
if CONFIG_MAIN_HOME_PRO_ADDRESS in new_data:
new_data[CONFIG_MAIN_HOME_PRO_ADDRESS] = f"{new_data[CONFIG_MAIN_HOME_PRO_ADDRESS]}".replace(":8000", "")

return new_data

def merge_main_config(data: dict, options: dict, updated_config: dict = None):
Expand Down
2 changes: 1 addition & 1 deletion custom_components/octopus_energy/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
REFRESH_RATE_IN_MINUTES_GREENNESS_FORECAST = 180
REFRESH_RATE_IN_MINUTES_HOME_PRO_CONSUMPTION = 0.17

CONFIG_VERSION = 4
CONFIG_VERSION = 5

CONFIG_KIND = "kind"
CONFIG_KIND_ACCOUNT = "account"
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import logging
import os
from datetime import datetime, timedelta

logging.getLogger().setLevel(logging.DEBUG)

class TestContext:
api_key: str
account_id: str
Expand Down
3 changes: 3 additions & 0 deletions tests/local_integration/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import logging
import os

logging.getLogger().setLevel(logging.DEBUG)

class TestContext:
def __init__(self, api_key: str, base_url: str):
self.api_key = api_key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,6 @@
from custom_components.octopus_energy.api_client import AuthenticationException
from custom_components.octopus_energy.api_client_home_pro import OctopusEnergyHomeProApiClient

@pytest.mark.asyncio
@pytest.mark.parametrize("is_electricity",[
(True),
(False)
])
async def test_when_get_consumption_is_called_and_api_key_is_invalid_then_exception_is_raised(is_electricity: bool):
# Arrange
context = get_test_context()

client = OctopusEnergyHomeProApiClient(context.base_url, "invalid-api-key")

# Act
exception_raised = False
try:
await client.async_get_consumption(is_electricity)
except AuthenticationException:
exception_raised = True

# Assert
assert exception_raised == True

@pytest.mark.asyncio
@pytest.mark.parametrize("is_electricity",[
(True),
Expand Down
17 changes: 0 additions & 17 deletions tests/local_integration/api_client_home_pro/test_async_ping.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,6 @@
from custom_components.octopus_energy.api_client import AuthenticationException
from custom_components.octopus_energy.api_client_home_pro import OctopusEnergyHomeProApiClient

@pytest.mark.asyncio
async def test_when_ping_is_called_and_api_key_is_invalid_then_exception_is_raised():
# Arrange
context = get_test_context()

client = OctopusEnergyHomeProApiClient(context.base_url, "invalid-api-key")

# Act
exception_raised = False
try:
await client.async_ping()
except AuthenticationException:
exception_raised = True

# Assert
assert exception_raised == True

@pytest.mark.asyncio
async def test_when_ping_is_called_then_data_is_returned():
# Arrange
Expand Down

0 comments on commit 0ace45e

Please sign in to comment.