diff --git a/.vscode/settings.json b/.vscode/settings.json index a3d535d..4224bfc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,10 @@ "python.linting.pylintEnabled": true, "python.linting.enabled": true, "python.pythonPath": "/usr/local/bin/python", + "python.linting.pylintArgs": [ + "--disable=C0103", + "--max-line-length=200" + ], "files.associations": { "*.yaml": "home-assistant" } diff --git a/custom_components/fpl/fplDataUpdateCoordinator.py b/custom_components/fpl/fplDataUpdateCoordinator.py index e4056ac..01a5bc4 100644 --- a/custom_components/fpl/fplDataUpdateCoordinator.py +++ b/custom_components/fpl/fplDataUpdateCoordinator.py @@ -1,13 +1,15 @@ +"""Data Update Coordinator""" import logging +from datetime import timedelta from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.core import HomeAssistant -from datetime import timedelta + from .fplapi import FplApi from .const import DOMAIN -SCAN_INTERVAL = timedelta(seconds=7200) +SCAN_INTERVAL = timedelta(seconds=1200) _LOGGER: logging.Logger = logging.getLogger(__package__) diff --git a/custom_components/fpl/fplEntity.py b/custom_components/fpl/fplEntity.py index 69a5d43..0af1072 100644 --- a/custom_components/fpl/fplEntity.py +++ b/custom_components/fpl/fplEntity.py @@ -58,8 +58,8 @@ class FplEntity(CoordinatorEntity, SensorEntity): return attributes def getData(self, field): - """method is called to update sensor data""" - return self.coordinator.data.get(self.account).get(field) + """call this method to retrieve sensor data""" + return self.coordinator.data.get(self.account).get(field, None) class FplEnergyEntity(FplEntity): @@ -123,3 +123,21 @@ class FplDateEntity(FplEntity): @property def icon(self): return "mdi:calendar" + + +class FplDayEntity(FplEntity): + """Represents a date or days""" + + # @property + # def device_class(self) -> str: + # """Return the class of this device, from component DEVICE_CLASSES.""" + # return DEVICE_CLASS_DATE + + @property + def icon(self): + return "mdi:calendar" + + @property + def unit_of_measurement(self) -> str: + """Return the unit of measurement of this entity, if any.""" + return "days" diff --git a/custom_components/fpl/fplapi.py b/custom_components/fpl/fplapi.py index 25a22ad..bf37f9f 100644 --- a/custom_components/fpl/fplapi.py +++ b/custom_components/fpl/fplapi.py @@ -1,5 +1,4 @@ """Custom FPl api client""" -from asyncio import exceptions as asyncio_exceptions import logging from datetime import datetime @@ -17,7 +16,7 @@ LOGIN_RESULT_UNAUTHORIZED = "UNAUTHORIZED" LOGIN_RESULT_FAILURE = "FAILURE" _LOGGER = logging.getLogger(__package__) -TIMEOUT = 30 +TIMEOUT = 5 API_HOST = "https://www.fpl.com" @@ -98,7 +97,7 @@ class FplApi: if json_data["messageCode"] == LOGIN_RESULT_INVALIDPASSWORD: return LOGIN_RESULT_INVALIDPASSWORD - except asyncio_exceptions as exception: + except Exception as exception: _LOGGER.error("Error %s : %s", exception, sys.exc_info()[0]) return LOGIN_RESULT_FAILURE @@ -110,7 +109,7 @@ class FplApi: try: async with async_timeout.timeout(TIMEOUT): await self._session.get(URL_LOGOUT) - except asyncio_exceptions: + except Exception: pass async def async_get_open_accounts(self): @@ -129,8 +128,8 @@ class FplApi: if account["statusCategory"] == STATUS_CATEGORY_OPEN: result.append(account["accountNumber"]) - except asyncio_exceptions as exception: - _LOGGER.error("Getting accounts %s", exception) + except Exception: + _LOGGER.error("Getting accounts %s", sys.exc_info()) return result @@ -186,11 +185,13 @@ class FplApi: def hasProgram(programName) -> bool: return programName in programs.keys() and programs[programName] + # Budget Billing program if hasProgram("BBL"): - # budget billing data["budget_bill"] = True bbl_data = await self.__getBBL_async(account, data) data.update(bbl_data) + else: + data["budget_bill"] = False data.update( await self.__getDataFromEnergyService(account, premise, currentBillDate) @@ -227,7 +228,7 @@ class FplApi: data["daily_avg"] = dailyAvg data["avg_high_temp"] = avgHighTemp - except asyncio_exceptions: + except Exception: pass return data @@ -267,7 +268,7 @@ class FplApi: data["budget_billing_bill_to_date"] = bbAsOfDateAmt data["budget_billing_projected_bill"] = float(projectedBudgetBill) - except asyncio_exceptions: + except Exception: pass try: @@ -279,7 +280,7 @@ class FplApi: r = (await response.json())["data"] data["bill_to_date"] = float(r["eleAmt"]) data["defered_amount"] = float(r["defAmt"]) - except asyncio_exceptions: + except Exception: pass return data @@ -329,8 +330,15 @@ class FplApi: { "usage": daily["kwhUsed"], "cost": daily["billingCharge"], - "date": daily["date"], + # "date": daily["date"], "max_temperature": daily["averageHighTemperature"], + "netDeliveredKwh": daily["netDeliveredKwh"] + if "netDeliveredKwh" in daily.keys() + else 0, + "netReceivedKwh": daily["netReceivedKwh"] + if "netReceivedKwh" in daily.keys() + else 0, + "readTime": daily["readTime"], } ) # totalPowerUsage += int(daily["kwhUsed"]) @@ -343,6 +351,7 @@ class FplApi: data["billToDateKWH"] = r["CurrentUsage"]["billToDateKWH"] data["recMtrReading"] = r["CurrentUsage"]["recMtrReading"] data["delMtrReading"] = r["CurrentUsage"]["delMtrReading"] + data["billStartDate"] = r["CurrentUsage"]["billStartDate"] return data async def __getDataFromApplianceUsage(self, account, lastBilledDate) -> dict: @@ -368,7 +377,7 @@ class FplApi: rr = full data[e["category"].replace(" ", "_")] = rr - except asyncio_exceptions: + except Exception: pass return {"energy_percent_by_applicance": data} diff --git a/custom_components/fpl/sensor.py b/custom_components/fpl/sensor.py index 64d651a..45bb018 100644 --- a/custom_components/fpl/sensor.py +++ b/custom_components/fpl/sensor.py @@ -21,14 +21,19 @@ from .sensor_ProjectedBillSensor import ( DeferedAmountSensor, ) from .sensor_AverageDailySensor import ( - FplAverageDailySensor, + DailyAverageSensor, BudgetDailyAverageSensor, ActualDailyAverageSensor, ) -from .sensor_DailyUsageSensor import FplDailyUsageKWHSensor, FplDailyUsageSensor +from .sensor_DailyUsageSensor import ( + FplDailyUsageKWHSensor, + FplDailyUsageSensor, + FplDailyDeliveredKWHSensor, + FplDailyReceivedKWHSensor, +) from .const import DOMAIN -from .TestSensor import TestSensor +# from .TestSensor import TestSensor async def async_setup_entry(hass, entry, async_add_devices): @@ -49,7 +54,7 @@ async def async_setup_entry(hass, entry, async_add_devices): fpl_accounts.append(DeferedAmountSensor(coordinator, entry, account)) # usage sensors - fpl_accounts.append(FplAverageDailySensor(coordinator, entry, account)) + fpl_accounts.append(DailyAverageSensor(coordinator, entry, account)) fpl_accounts.append(BudgetDailyAverageSensor(coordinator, entry, account)) fpl_accounts.append(ActualDailyAverageSensor(coordinator, entry, account)) @@ -71,4 +76,7 @@ async def async_setup_entry(hass, entry, async_add_devices): fpl_accounts.append(NetReceivedKWHSensor(coordinator, entry, account)) fpl_accounts.append(NetDeliveredKWHSensor(coordinator, entry, account)) + fpl_accounts.append(FplDailyReceivedKWHSensor(coordinator, entry, account)) + fpl_accounts.append(FplDailyDeliveredKWHSensor(coordinator, entry, account)) + async_add_devices(fpl_accounts) diff --git a/custom_components/fpl/sensor_AverageDailySensor.py b/custom_components/fpl/sensor_AverageDailySensor.py index 3399ded..5d5a139 100644 --- a/custom_components/fpl/sensor_AverageDailySensor.py +++ b/custom_components/fpl/sensor_AverageDailySensor.py @@ -1,7 +1,10 @@ +"""Average daily sensors""" from .fplEntity import FplMoneyEntity -class FplAverageDailySensor(FplMoneyEntity): +class DailyAverageSensor(FplMoneyEntity): + """average daily sensor, use budget value if available, otherwise use actual daily values""" + def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Daily Average") @@ -10,13 +13,21 @@ class FplAverageDailySensor(FplMoneyEntity): budget = self.getData("budget_bill") budget_billing_projected_bill = self.getData("budget_billing_daily_avg") - if budget == True and budget_billing_projected_bill is not None: + if budget and budget_billing_projected_bill is not None: return self.getData("budget_billing_daily_avg") return self.getData("daily_avg") + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = "total" + return attributes + class BudgetDailyAverageSensor(FplMoneyEntity): + """budget daily average sensor""" + def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Budget Daily Average") @@ -24,11 +35,25 @@ class BudgetDailyAverageSensor(FplMoneyEntity): def state(self): return self.getData("budget_billing_daily_avg") + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = "total" + return attributes + class ActualDailyAverageSensor(FplMoneyEntity): + """Actual daily average sensor""" + def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Actual Daily Average") @property def state(self): return self.getData("daily_avg") + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = "total" + return attributes diff --git a/custom_components/fpl/sensor_DailyUsageSensor.py b/custom_components/fpl/sensor_DailyUsageSensor.py index 8a63fcf..a3e1f06 100644 --- a/custom_components/fpl/sensor_DailyUsageSensor.py +++ b/custom_components/fpl/sensor_DailyUsageSensor.py @@ -20,11 +20,12 @@ class FplDailyUsageSensor(FplMoneyEntity): def defineAttributes(self): """Return the state attributes.""" data = self.getData("daily_usage") + attributes = {} + attributes["state_class"] = "total_increasing" + if data is not None and len(data) > 0 and "readTime" in data[-1].keys(): + attributes["date"] = data[-1]["readTime"] - if data is not None and len(data) > 0 and "date" in data[-1].keys(): - return {"date": data[-1]["date"]} - - return {} + return attributes class FplDailyUsageKWHSensor(FplEnergyEntity): @@ -45,8 +46,61 @@ class FplDailyUsageKWHSensor(FplEnergyEntity): def defineAttributes(self): """Return the state attributes.""" data = self.getData("daily_usage") + attributes = {} + attributes["state_class"] = "total_increasing" - if data is not None and len(data) > 0 and "date" in data[-1].keys(): - return {"date": data[-1]["date"]} + if data is not None: + if data[-1] is not None and "readTime" in data[-1].keys(): + attributes["date"] = data[-1]["readTime"] + if data[-2] is not None and "readTime" in data[-2].keys(): + attributes["last_reset"] = data[-2]["readTime"] - return {} + return attributes + + +class FplDailyReceivedKWHSensor(FplEnergyEntity): + """daily received Kwh sensor""" + + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Received KWH") + + @property + def state(self): + data = self.getData("daily_usage") + if data is not None and len(data) > 0 and "netReceivedKwh" in data[-1].keys(): + return data[-1]["netReceivedKwh"] + return 0 + + def defineAttributes(self): + """Return the state attributes.""" + data = self.getData("daily_usage") + + attributes = {} + attributes["state_class"] = "total_increasing" + attributes["date"] = data[-1]["readTime"] + attributes["last_reset"] = data[-2]["readTime"] + return attributes + + +class FplDailyDeliveredKWHSensor(FplEnergyEntity): + """daily delivered Kwh sensor""" + + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Delivered KWH") + + @property + def state(self): + data = self.getData("daily_usage") + if data is not None and len(data) > 0 and "netDeliveredKwh" in data[-1].keys(): + return data[-1]["netDeliveredKwh"] + return 0 + + def defineAttributes(self): + """Return the state attributes.""" + data = self.getData("daily_usage") + + attributes = {} + attributes["state_class"] = "total_increasing" + attributes["date"] = data[-1]["readTime"] + attributes["last_reset"] = data[-2]["readTime"] + return attributes diff --git a/custom_components/fpl/sensor_DatesSensor.py b/custom_components/fpl/sensor_DatesSensor.py index b2f2fdb..0a54844 100644 --- a/custom_components/fpl/sensor_DatesSensor.py +++ b/custom_components/fpl/sensor_DatesSensor.py @@ -1,4 +1,6 @@ -from .fplEntity import FplDateEntity +"""dates sensors""" +import datetime +from .fplEntity import FplDateEntity, FplDayEntity class CurrentBillDateSensor(FplDateEntity): @@ -7,7 +9,7 @@ class CurrentBillDateSensor(FplDateEntity): @property def state(self): - return self.getData("current_bill_date") + return datetime.date.fromisoformat(self.getData("current_bill_date")) class NextBillDateSensor(FplDateEntity): @@ -16,10 +18,10 @@ class NextBillDateSensor(FplDateEntity): @property def state(self): - return self.getData("next_bill_date") + return datetime.date.fromisoformat(self.getData("next_bill_date")) -class ServiceDaysSensor(FplDateEntity): +class ServiceDaysSensor(FplDayEntity): def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Service Days") @@ -28,7 +30,7 @@ class ServiceDaysSensor(FplDateEntity): return self.getData("service_days") -class AsOfDaysSensor(FplDateEntity): +class AsOfDaysSensor(FplDayEntity): def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "As Of Days") @@ -37,7 +39,7 @@ class AsOfDaysSensor(FplDateEntity): return self.getData("as_of_days") -class RemainingDaysSensor(FplDateEntity): +class RemainingDaysSensor(FplDayEntity): def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Remaining Days") diff --git a/custom_components/fpl/sensor_KWHSensor.py b/custom_components/fpl/sensor_KWHSensor.py index 6d48d03..865becb 100644 --- a/custom_components/fpl/sensor_KWHSensor.py +++ b/custom_components/fpl/sensor_KWHSensor.py @@ -1,4 +1,8 @@ -from homeassistant.components.sensor import STATE_CLASS_TOTAL_INCREASING +"""energy sensors""" +from homeassistant.components.sensor import ( + STATE_CLASS_TOTAL_INCREASING, + STATE_CLASS_TOTAL, +) from .fplEntity import FplEnergyEntity @@ -10,6 +14,12 @@ class ProjectedKWHSensor(FplEnergyEntity): def state(self): return self.getData("projectedKWH") + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = STATE_CLASS_TOTAL + return attributes + class DailyAverageKWHSensor(FplEnergyEntity): def __init__(self, coordinator, config, account): @@ -19,6 +29,12 @@ class DailyAverageKWHSensor(FplEnergyEntity): def state(self): return self.getData("dailyAverageKWH") + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = STATE_CLASS_TOTAL + return attributes + class BillToDateKWHSensor(FplEnergyEntity): def __init__(self, coordinator, config, account): @@ -28,9 +44,11 @@ class BillToDateKWHSensor(FplEnergyEntity): def state(self): return self.getData("billToDateKWH") - @property - def state_class(self) -> str: - """Return the state class of this entity, from STATE_CLASSES, if any.""" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = STATE_CLASS_TOTAL_INCREASING + return attributes class NetReceivedKWHSensor(FplEnergyEntity): @@ -41,9 +59,11 @@ class NetReceivedKWHSensor(FplEnergyEntity): def state(self): return self.getData("recMtrReading") - @property - def icon(self): - return "mdi:flash" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = STATE_CLASS_TOTAL_INCREASING + return attributes class NetDeliveredKWHSensor(FplEnergyEntity): @@ -54,6 +74,8 @@ class NetDeliveredKWHSensor(FplEnergyEntity): def state(self): return self.getData("delMtrReading") - @property - def icon(self): - return "mdi:flash" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = STATE_CLASS_TOTAL_INCREASING + return attributes diff --git a/custom_components/fpl/sensor_ProjectedBillSensor.py b/custom_components/fpl/sensor_ProjectedBillSensor.py index 7f6a515..f8c3671 100644 --- a/custom_components/fpl/sensor_ProjectedBillSensor.py +++ b/custom_components/fpl/sensor_ProjectedBillSensor.py @@ -1,7 +1,10 @@ +"""Projected bill sensors""" from .fplEntity import FplMoneyEntity class FplProjectedBillSensor(FplMoneyEntity): + """projected bill sensor""" + def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Projected Bill") @@ -10,7 +13,7 @@ class FplProjectedBillSensor(FplMoneyEntity): budget = self.getData("budget_bill") budget_billing_projected_bill = self.getData("budget_billing_projected_bill") - if budget == True and budget_billing_projected_bill is not None: + if budget and budget_billing_projected_bill is not None: return self.getData("budget_billing_projected_bill") return self.getData("projected_bill") @@ -18,28 +21,34 @@ class FplProjectedBillSensor(FplMoneyEntity): def defineAttributes(self): """Return the state attributes.""" attributes = {} - try: - if self.getData("budget_bill") == True: - attributes["budget_bill"] = self.getData("budget_bill") - except: - pass - + attributes["state_class"] = "total" + attributes["budget_bill"] = self.getData("budget_bill") return attributes # Defered Amount class DeferedAmountSensor(FplMoneyEntity): + """Defered amount sensor""" + def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Defered Amount") @property def state(self): - if self.getData("budget_bill") == True: + if self.getData("budget_bill"): return self.getData("defered_amount") return 0 + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = "total" + return attributes + class ProjectedBudgetBillSensor(FplMoneyEntity): + """projected budget bill sensor""" + def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Projected Budget Bill") @@ -47,11 +56,25 @@ class ProjectedBudgetBillSensor(FplMoneyEntity): def state(self): return self.getData("budget_billing_projected_bill") + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = "total" + return attributes + class ProjectedActualBillSensor(FplMoneyEntity): + """projeted actual bill sensor""" + def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Projected Actual Bill") @property def state(self): return self.getData("projected_bill") + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["state_class"] = "total" + return attributes