Skip to content

Commit

Permalink
Merge pull request #63 from faanskit/dev
Browse files Browse the repository at this point in the history
Refactor CWR code to pyCheckwatt
  • Loading branch information
faanskit authored Feb 4, 2024
2 parents 7a4e169 + db36e59 commit 0d6f40f
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 116 deletions.
228 changes: 113 additions & 115 deletions custom_components/checkwatt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

from __future__ import annotations

import asyncio
from datetime import time, timedelta
import logging
import random
from typing import TypedDict

import aiohttp
from pycheckwatt import CheckwattManager
from pycheckwatt import CheckwattManager, CheckWattRankManager
import voluptuous as vol

from homeassistant.config_entries import ConfigEntry
Expand Down Expand Up @@ -51,6 +49,10 @@
}
)

PUSH_CWR_SERVICE_NAME = "push_checkwatt_rank"
PUSH_CWR_SCHEMA = None


CHECKWATTRANK_REPORTER = "HomeAssistantV2"


Expand Down Expand Up @@ -130,9 +132,9 @@ async def update_history_items(call: ServiceCall) -> ServiceResponse:
username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)
cwr_name = entry.options.get(CONF_CWR_NAME)
count = 0
total = 0
status = None
stored_items = 0
total_items = 0
async with CheckwattManager(username, password, INTEGRATION_NAME) as cw:
try:
# Login to EnergyInBalance
Expand Down Expand Up @@ -163,59 +165,86 @@ async def update_history_items(call: ServiceCall) -> ServiceResponse:
cw.energy_provider_id
)

data = {
"display_name": cwr_name if cwr_name != "" else cw.display_name,
"dso": cw.battery_registration["Dso"],
"electricity_area": cw.price_zone,
"installed_power": cw.battery_charge_peak_ac,
"electricity_company": energy_provider,
"reseller_id": cw.reseller_id,
"reporter": CHECKWATTRANK_REPORTER,
"historical_data": hd,
}
async with CheckWattRankManager() as cwr:
(
status,
stored_items,
total_items,
) = await cwr.push_history_to_checkwatt_rank(
display_name=(
cwr_name if cwr_name != "" else cw.display_name
),
dso=cw.battery_registration["Dso"],
electricity_company=energy_provider,
electricity_area=cw.price_zone,
installed_power=cw.battery_charge_peak_ac,
reseller_id=cw.reseller_id,
reporter=CHECKWATTRANK_REPORTER,
historical_data=hd,
)

# Post data to Netlify function
BASE_URL = "https://checkwattrank.netlify.app"
netlify_function_url = (
BASE_URL + "/.netlify/functions/publishHistory"
)
timeout_seconds = 10
async with aiohttp.ClientSession() as session: # noqa: SIM117
async with session.post(
netlify_function_url, json=data, timeout=timeout_seconds
) as response:
if response.status == 200:
result = await response.json()
count = result.get("count", 0)
total = result.get("total", 0)
status = result.get("message", 0)
_LOGGER.debug(
"Data posted successfully. Count: %s", count
)
else:
_LOGGER.debug(
"Failed to post data. Status code: %s",
response.status,
)
else:
status = "Failed to login."

except aiohttp.ClientError as e:
_LOGGER.error("Error pushing data to CheckWattRank: %s", e)
status = "Failed to push historical data."
except asyncio.TimeoutError:
_LOGGER.error(
"Request to CheckWattRank timed out after %s seconds",
timeout_seconds,
)
status = "Timeout pushing historical data."
except InvalidAuth as err:
raise ConfigEntryAuthFailed from err

except CheckwattError as err:
status = f"Failed to update CheckWattRank: {err}"

return {
"start_date": start_date_str,
"end_date": end_date_str,
"status": status,
"stored_items": count,
"total_items": total,
"stored_items": stored_items,
"total_items": total_items,
}

async def push_cwr(call: ServiceCall) -> ServiceResponse:
"""Push data to CheckWattRank."""
username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)
cwr_name = entry.options.get(CONF_CWR_NAME)
status = None
async with CheckwattManager(username, password, INTEGRATION_NAME) as cw:
try:
# Login to EnergyInBalance
if await cw.login():
# Fetch customer detail
if not await cw.get_customer_details():
_LOGGER.error("Failed to fetch customer details")
return {
"status": "Failed to fetch customer details",
}

if not await cw.get_price_zone():
_LOGGER.error("Failed to fetch prize zone")
return {
"status": "Failed to fetch prize zone",
}

if not await cw.get_fcrd_today_net_revenue():
raise UpdateFailed("Unknown error get_fcrd_revenue")

display_name = cwr_name if cwr_name != "" else cw.display_name
if await push_to_checkwatt_rank(
cw, display_name, cw.fcrd_today_net_revenue
):
status = "Data successfully sent to CheckWattRank"
else:
status = "Failed to update to CheckWattRank"

else:
status = "Failed to login."

except InvalidAuth as err:
raise ConfigEntryAuthFailed from err

except CheckwattError as err:
status = f"Failed to update CheckWattRank: {err}"

return {
"result": status,
}

hass.services.async_register(
Expand All @@ -226,6 +255,14 @@ async def update_history_items(call: ServiceCall) -> ServiceResponse:
supports_response=SupportsResponse.ONLY,
)

hass.services.async_register(
DOMAIN,
PUSH_CWR_SERVICE_NAME,
push_cwr,
schema=PUSH_CWR_SCHEMA,
supports_response=SupportsResponse.ONLY,
)

return True


Expand All @@ -237,6 +274,27 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return unload_ok


async def push_to_checkwatt_rank(cw_inst, cwr_name, today_net_income):
"""Push data to CheckWattRank."""
if cw_inst.fcrd_today_net_revenue is not None:
energy_provider = await cw_inst.get_energy_trading_company(
cw_inst.energy_provider_id
)
async with CheckWattRankManager() as cwr:
if await cwr.push_to_checkwatt_rank(
display_name=(cwr_name if cwr_name != "" else cw_inst.display_name),
dso=cw_inst.battery_registration["Dso"],
electricity_company=energy_provider,
electricity_area=cw_inst.price_zone,
installed_power=cw_inst.battery_charge_peak_ac,
today_net_income=today_net_income,
reseller_id=cw_inst.reseller_id,
reporter=CHECKWATTRANK_REPORTER,
):
return True
return False


class CheckwattCoordinator(DataUpdateCoordinator[CheckwattResp]):
"""Data update coordinator."""

Expand Down Expand Up @@ -351,9 +409,12 @@ async def _async_update_data(self) -> CheckwattResp: # noqa: C901
and dt_util.start_of_local_day(dt_util.now())
!= dt_util.start_of_local_day(self.last_cw_rank_push)
):
_LOGGER.debug("Pushing to CheckWattRank")
if await self.push_to_checkwatt_rank(cw_inst, cwr_name):
self.last_cw_rank_push = dt_util.now()
if self.fcrd_today_net_revenue is not None:
_LOGGER.debug("Pushing to CheckWattRank")
if await push_to_checkwatt_rank(
cw_inst, cwr_name, self.fcrd_today_net_revenue
):
self.last_cw_rank_push = dt_util.now()

resp: CheckwattResp = {
"id": cw_inst.customer_details["Id"],
Expand Down Expand Up @@ -467,69 +528,6 @@ async def _async_update_data(self) -> CheckwattResp: # noqa: C901
except CheckwattError as err:
raise UpdateFailed(str(err)) from err

async def push_to_checkwatt_rank(self, cw_inst, cwr_name):
"""Push data to CheckWattRank."""
if self.fcrd_today_net_revenue is not None:
url = "https://checkwattrank.netlify.app/.netlify/functions/publishToSheet"
headers = {
"Content-Type": "application/json",
}
payload = {
"dso": cw_inst.battery_registration["Dso"],
"electricity_company": self.energy_provider,
"electricity_area": cw_inst.price_zone,
"installed_power": cw_inst.battery_charge_peak_ac,
"today_gross_income": 0,
"today_fee": 0,
"today_net_income": self.fcrd_today_net_revenue,
"reseller_id": cw_inst.reseller_id,
"reporter": CHECKWATTRANK_REPORTER,
}
if BASIC_TEST:
payload["display_name"] = "xxTESTxx"
elif cwr_name != "":
payload["display_name"] = cwr_name
else:
payload["display_name"] = cw_inst.display_name

# Specify a timeout value (in seconds)
timeout_seconds = 10

async with aiohttp.ClientSession() as session:
try:
async with session.post(
url, headers=headers, json=payload, timeout=timeout_seconds
) as response:
response.raise_for_status() # Raise an exception for HTTP errors
content_type = response.headers.get("Content-Type", "").lower()
_LOGGER.debug(
"CheckWattRank Push Response Content-Type: %s",
content_type,
)

if "application/json" in content_type:
result = await response.json()
_LOGGER.debug("CheckWattRank Push Response: %s", result)
return True
elif "text/plain" in content_type:
result = await response.text()
_LOGGER.debug("CheckWattRank Push Response: %s", result)
return True
else:
_LOGGER.warning("Unexpected Content-Type: %s", content_type)
result = await response.text()
_LOGGER.debug("CheckWattRank Push Response: %s", result)

except aiohttp.ClientError as e:
_LOGGER.error("Error pushing data to CheckWattRank: %s", e)
except asyncio.TimeoutError:
_LOGGER.error(
"Request to CheckWattRank timed out after %s seconds",
timeout_seconds,
)

return False


class CheckwattError(HomeAssistantError):
"""Base error."""
Expand Down
2 changes: 1 addition & 1 deletion custom_components/checkwatt/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"homekit": {},
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/faanskit/ha-checkwatt/issues",
"requirements": ["pycheckwatt>=0.2.1", "aiohttp>=3.9.1"],
"requirements": ["pycheckwatt>=0.2.2", "aiohttp>=3.9.1"],
"ssdp": [],
"version": "0.2.1",
"zeroconf": []
Expand Down
4 changes: 4 additions & 0 deletions custom_components/checkwatt/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ update_history:
example: "2024-01-01"
selector:
date:

push_checkwatt_rank:
name: "Push Today's Revenue to CheckWattRank"
description: "Updates CheckWattRank with current revenues from EnergyInBalances."

0 comments on commit 0d6f40f

Please sign in to comment.