Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor CWR code to pyCheckwatt #63

Merged
merged 2 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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."
Loading