From 0a75b189b779c067649d04096b428fd1d1771007 Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Thu, 30 Dec 2021 14:09:25 -0500 Subject: [PATCH 1/7] Adding new sensors and updating metadata. --- .../fpl/fpl/AverageDailySensor.py | 20 + custom_components/fpl/fpl/DailyUsageSensor.py | 31 ++ .../fpl/fpl/ProjectedBillSensor.py | 37 ++ custom_components/fpl/fpl/TestSensor.py | 28 ++ custom_components/fpl/fpl/__init__.py | 107 ++++++ custom_components/fpl/fpl/config_flow.py | 110 ++++++ custom_components/fpl/fpl/const.py | 50 +++ .../fpl/fpl/fplDataUpdateCoordinator.py | 30 ++ custom_components/fpl/fpl/fplEntity.py | 45 +++ custom_components/fpl/fpl/fplapi.py | 346 ++++++++++++++++++ custom_components/fpl/fpl/manifest.json | 17 + custom_components/fpl/fpl/sensor.py | 82 +++++ custom_components/fpl/fpl/sensor_AllData.py | 33 ++ .../fpl/fpl/sensor_AverageDailySensor.py | 51 +++ .../fpl/fpl/sensor_DailyUsageSensor.py | 120 ++++++ .../fpl/fpl/sensor_DatesSensor.py | 97 +++++ custom_components/fpl/fpl/sensor_KWHSensor.py | 110 ++++++ .../fpl/fpl/sensor_ProjectedBillSensor.py | 99 +++++ 18 files changed, 1413 insertions(+) create mode 100644 custom_components/fpl/fpl/AverageDailySensor.py create mode 100644 custom_components/fpl/fpl/DailyUsageSensor.py create mode 100644 custom_components/fpl/fpl/ProjectedBillSensor.py create mode 100644 custom_components/fpl/fpl/TestSensor.py create mode 100644 custom_components/fpl/fpl/__init__.py create mode 100644 custom_components/fpl/fpl/config_flow.py create mode 100644 custom_components/fpl/fpl/const.py create mode 100644 custom_components/fpl/fpl/fplDataUpdateCoordinator.py create mode 100644 custom_components/fpl/fpl/fplEntity.py create mode 100644 custom_components/fpl/fpl/fplapi.py create mode 100644 custom_components/fpl/fpl/manifest.json create mode 100644 custom_components/fpl/fpl/sensor.py create mode 100644 custom_components/fpl/fpl/sensor_AllData.py create mode 100644 custom_components/fpl/fpl/sensor_AverageDailySensor.py create mode 100644 custom_components/fpl/fpl/sensor_DailyUsageSensor.py create mode 100644 custom_components/fpl/fpl/sensor_DatesSensor.py create mode 100644 custom_components/fpl/fpl/sensor_KWHSensor.py create mode 100644 custom_components/fpl/fpl/sensor_ProjectedBillSensor.py diff --git a/custom_components/fpl/fpl/AverageDailySensor.py b/custom_components/fpl/fpl/AverageDailySensor.py new file mode 100644 index 0000000..d0f4778 --- /dev/null +++ b/custom_components/fpl/fpl/AverageDailySensor.py @@ -0,0 +1,20 @@ +from .FplSensor import FplSensor + + +class FplAverageDailySensor(FplSensor): + def __init__(self, hass, config, account): + FplSensor.__init__(self, hass, config, account, "Average Daily") + + @property + def state(self): + try: + if "daily_avg" in self.data: + self._state = self.data["daily_avg"] + except: + pass + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self.attr \ No newline at end of file diff --git a/custom_components/fpl/fpl/DailyUsageSensor.py b/custom_components/fpl/fpl/DailyUsageSensor.py new file mode 100644 index 0000000..255e0e7 --- /dev/null +++ b/custom_components/fpl/fpl/DailyUsageSensor.py @@ -0,0 +1,31 @@ +from .FplSensor import FplSensor + + +class FplDailyUsageSensor(FplSensor): + def __init__(self, hass, config, account): + FplSensor.__init__(self, hass, config, account, "Daily Usage") + + @property + def state(self): + try: + if "daily_usage" in self.data: + if len(self.data["daily_usage"]) > 0: + if "cost" in self.data["daily_usage"][-1]: + self._state = self.data["daily_usage"][-1]["cost"] + except: + pass + + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + try: + if "daily_usage" in self.data: + if len(self.data["daily_usage"]) > 0: + if "date" in self.data["daily_usage"][-1]: + self.attr["date"] = self.data["daily_usage"][-1]["date"] + except: + pass + + return self.attr diff --git a/custom_components/fpl/fpl/ProjectedBillSensor.py b/custom_components/fpl/fpl/ProjectedBillSensor.py new file mode 100644 index 0000000..bf14a32 --- /dev/null +++ b/custom_components/fpl/fpl/ProjectedBillSensor.py @@ -0,0 +1,37 @@ +from .FplSensor import FplSensor + + +class FplProjectedBillSensor(FplSensor): + def __init__(self, hass, config, account): + FplSensor.__init__(self, hass, config, account, "Projected Bill") + + @property + def state(self): + data = self.data + try: + if "budget_bill" in data.keys(): + if data["budget_bill"]: + if "budget_billing_projected_bill" in data.keys(): + self._state = data["budget_billing_projected_bill"] + else: + if "projected_bill" in data.keys(): + self._state = data["projected_bill"] + except: + pass + + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + try: + if "budget_bill" in self.data.keys(): + self.attr["budget_bill"] = self.data["budget_bill"] + except: + pass + + return self.attr + + @property + def icon(self): + return "mdi:currency-usd" \ No newline at end of file diff --git a/custom_components/fpl/fpl/TestSensor.py b/custom_components/fpl/fpl/TestSensor.py new file mode 100644 index 0000000..4d8b63d --- /dev/null +++ b/custom_components/fpl/fpl/TestSensor.py @@ -0,0 +1,28 @@ +from .fplEntity import FplEntity +import pprint + + +class TestSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Test Sensor") + + @property + def state(self): + pprint.pprint(self.coordinator.data) + + return self.getData("projected_bill") + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + try: + if self.getData("budget_bill"): + attributes["budget_bill"] = self.getData("budget_bill") + except: + pass + + return attributes + + @property + def icon(self): + return "mdi:currency-usd" diff --git a/custom_components/fpl/fpl/__init__.py b/custom_components/fpl/fpl/__init__.py new file mode 100644 index 0000000..18af9fe --- /dev/null +++ b/custom_components/fpl/fpl/__init__.py @@ -0,0 +1,107 @@ +""" FPL Component """ + + +import logging +import asyncio + +from datetime import timedelta +from homeassistant.core import Config, HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.util import Throttle + +from .fplapi import FplApi +from .const import ( + DOMAIN, + DOMAIN_DATA, + CONF_USERNAME, + CONF_PASSWORD, + PLATFORMS, + STARTUP_MESSAGE, +) +from .fplDataUpdateCoordinator import FplDataUpdateCoordinator + +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) + +_LOGGER = logging.getLogger(__package__) + + +class FplData: + """This class handle communication and stores the data.""" + + def __init__(self, hass, client): + """Initialize the class.""" + self.hass = hass + self.client = client + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + async def update_data(self): + """Update data.""" + # This is where the main logic to update platform data goes. + try: + data = await self.client.get_data() + self.hass.data[DOMAIN_DATA]["data"] = data + except Exception as error: # pylint: disable=broad-except + _LOGGER.error("Could not update data - %s", error) + + +async def async_setup(hass: HomeAssistant, config: Config) -> bool: + """Set up configured Fpl.""" + return True + + +async def async_setup_entry(hass, entry): + """Set up this integration using UI.""" + if hass.data.get(DOMAIN) is None: + hass.data.setdefault(DOMAIN, {}) + _LOGGER.info(STARTUP_MESSAGE) + + # Get "global" configuration. + username = entry.data.get(CONF_USERNAME) + password = entry.data.get(CONF_PASSWORD) + + # Configure the client. + _LOGGER.info(f"Configuring the client") + session = async_get_clientsession(hass) + client = FplApi(username, password, session) + + coordinator = FplDataUpdateCoordinator(hass, client=client) + await coordinator.async_refresh() + + hass.data[DOMAIN][entry.entry_id] = coordinator + + for platform in PLATFORMS: + if entry.options.get(platform, True): + coordinator.platforms.append(platform) + hass.async_add_job( + hass.config_entries.async_forward_entry_setup(entry, platform) + ) + + """Set up Fpl as config entry.""" + + entry.add_update_listener(async_reload_entry) + return True + + +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Reload config entry.""" + await async_unload_entry(hass, entry) + await async_setup_entry(hass, entry) + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Handle removal of an entry.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + unloaded = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + if platform in coordinator.platforms + ] + ) + ) + if unloaded: + hass.data[DOMAIN].pop(entry.entry_id) + + return unloaded diff --git a/custom_components/fpl/fpl/config_flow.py b/custom_components/fpl/fpl/config_flow.py new file mode 100644 index 0000000..b94d54d --- /dev/null +++ b/custom_components/fpl/fpl/config_flow.py @@ -0,0 +1,110 @@ +from collections import OrderedDict + +import voluptuous as vol +from .fplapi import FplApi + +from homeassistant import config_entries +from homeassistant.helpers.aiohttp_client import async_create_clientsession + +from .const import DOMAIN, CONF_USERNAME, CONF_PASSWORD, CONF_NAME + +from .fplapi import ( + LOGIN_RESULT_OK, + LOGIN_RESULT_INVALIDUSER, + LOGIN_RESULT_INVALIDPASSWORD, +) + +from homeassistant.core import callback + + +@callback +def configured_instances(hass): + """Return a set of configured SimpliSafe instances.""" + entites = [] + for entry in hass.config_entries.async_entries(DOMAIN): + entites.append(f"{entry.data.get(CONF_USERNAME)}") + return set(entites) + + +class FplFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Initialize.""" + self._errors = {} + + async def async_step_user( + self, user_input={} + ): # pylint: disable=dangerous-default-value + """Handle a flow initialized by the user.""" + self._errors = {} + + # if self._async_current_entries(): + # return self.async_abort(reason="single_instance_allowed") + + # if self.hass.data.get(DOMAIN): + # return self.async_abort(reason="single_instance_allowed") + + if user_input is not None: + username = user_input[CONF_USERNAME] + password = user_input[CONF_PASSWORD] + + if username not in configured_instances(self.hass): + session = async_create_clientsession(self.hass) + api = FplApi(username, password, session) + result = await api.login() + + if result == LOGIN_RESULT_OK: + fplData = await api.async_get_data() + accounts = fplData["accounts"] + + user_input["accounts"] = accounts + + return self.async_create_entry(title=username, data=user_input) + + if result == LOGIN_RESULT_INVALIDUSER: + self._errors[CONF_USERNAME] = "invalid_username" + + if result == LOGIN_RESULT_INVALIDPASSWORD: + self._errors[CONF_PASSWORD] = "invalid_password" + + if result == None: + self._errors["base"] = "auth" + + else: + self._errors[CONF_NAME] = "name_exists" + + return await self._show_config_form(user_input) + + return await self._show_config_form(user_input) + + async def _show_config_form(self, user_input): + """Show the configuration form to edit location data.""" + username = "" + password = "" + + if user_input is not None: + if CONF_USERNAME in user_input: + username = user_input[CONF_USERNAME] + if CONF_PASSWORD in user_input: + password = user_input[CONF_PASSWORD] + + data_schema = OrderedDict() + data_schema[vol.Required(CONF_USERNAME, default=username)] = str + data_schema[vol.Required(CONF_PASSWORD, default=password)] = str + + return self.async_show_form( + step_id="user", data_schema=vol.Schema(data_schema), errors=self._errors + ) + + async def async_step_import(self, user_input): # pylint: disable=unused-argument + """Import a config entry. + Special type of import, we're not actually going to store any data. + Instead, we're going to rely on the values that are in config file. + """ + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return self.async_create_entry(title="configuration.yaml", data={}) diff --git a/custom_components/fpl/fpl/const.py b/custom_components/fpl/fpl/const.py new file mode 100644 index 0000000..5a418bb --- /dev/null +++ b/custom_components/fpl/fpl/const.py @@ -0,0 +1,50 @@ +"""Constants for fpl.""" +# Base component constants +NAME = "FPL Integration" +DOMAIN = "fpl" +DOMAIN_DATA = f"{DOMAIN}_data" +VERSION = "0.1.0" +PLATFORMS = ["sensor"] +REQUIRED_FILES = [ + ".translations/en.json", + "binary_sensor.py", + "const.py", + "config_flow.py", + "manifest.json", + "sensor.py", + "switch.py", +] +ISSUE_URL = "https://github.com/dotKrad/hass-fpl/issues" +ATTRIBUTION = "This data is provided by FPL." + +# Platforms +BINARY_SENSOR = "binary_sensor" +SENSOR = "sensor" +SWITCH = "switch" +PLATFORMS = [SENSOR] + +# Device classes +BINARY_SENSOR_DEVICE_CLASS = "connectivity" + +# Configuration +CONF_BINARY_SENSOR = "binary_sensor" +CONF_SENSOR = "sensor" +CONF_SWITCH = "switch" +CONF_ENABLED = "enabled" +CONF_NAME = "name" +CONF_USERNAME = "username" +CONF_PASSWORD = "password" + +# Defaults +DEFAULT_NAME = DOMAIN + + +STARTUP_MESSAGE = f""" +------------------------------------------------------------------- +{NAME} +Version: {VERSION} +This is a custom integration! +If you have any issues with this you need to open an issue here: +{ISSUE_URL} +------------------------------------------------------------------- +""" diff --git a/custom_components/fpl/fpl/fplDataUpdateCoordinator.py b/custom_components/fpl/fpl/fplDataUpdateCoordinator.py new file mode 100644 index 0000000..5679cbf --- /dev/null +++ b/custom_components/fpl/fpl/fplDataUpdateCoordinator.py @@ -0,0 +1,30 @@ +import logging + +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=1200) + +_LOGGER: logging.Logger = logging.getLogger(__package__) + + +class FplDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching data from the API.""" + + def __init__(self, hass: HomeAssistant, client: FplApi) -> None: + """Initialize.""" + self.api = client + self.platforms = [] + + super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) + + async def _async_update_data(self): + """Update data via library.""" + try: + return await self.api.async_get_data() + except Exception as exception: + raise UpdateFailed() from exception diff --git a/custom_components/fpl/fpl/fplEntity.py b/custom_components/fpl/fpl/fplEntity.py new file mode 100644 index 0000000..e9bf200 --- /dev/null +++ b/custom_components/fpl/fpl/fplEntity.py @@ -0,0 +1,45 @@ +"""BlueprintEntity class""" +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, VERSION, ATTRIBUTION + + +class FplEntity(CoordinatorEntity): + def __init__(self, coordinator, config_entry, account, sensorName): + super().__init__(coordinator) + self.config_entry = config_entry + self.account = account + self.sensorName = sensorName + + @property + def unique_id(self): + """Return the ID of this device.""" + id = "{}{}{}".format( + DOMAIN, self.account, self.sensorName.lower().replace(" ", "") + ) + return id + + @property + def device_info(self): + return { + "identifiers": {(DOMAIN, self.account)}, + "name": f"FPL Account {self.account}", + "model": VERSION, + "manufacturer": "Florida Power & Light", + } + + def defineAttributes(self): + return {} + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + attributes = { + "attribution": ATTRIBUTION, + "integration": "FPL", + } + attributes.update(self.defineAttributes()) + return attributes + + def getData(self, field): + return self.coordinator.data.get(self.account).get(field) diff --git a/custom_components/fpl/fpl/fplapi.py b/custom_components/fpl/fpl/fplapi.py new file mode 100644 index 0000000..4790cd0 --- /dev/null +++ b/custom_components/fpl/fpl/fplapi.py @@ -0,0 +1,346 @@ +import asyncio +import logging +import re +from datetime import timedelta, datetime, date as dt + +import aiohttp +import async_timeout +import json +import sys + + +from bs4 import BeautifulSoup + +STATUS_CATEGORY_OPEN = "OPEN" +# Api login result +LOGIN_RESULT_OK = "OK" +LOGIN_RESULT_INVALIDUSER = "NOTVALIDUSER" +LOGIN_RESULT_INVALIDPASSWORD = "FAILEDPASSWORD" +LOGIN_RESULT_UNAUTHORIZED = "UNAUTHORIZED" +LOGIN_RESULT_FAILURE = "FAILURE" + +_LOGGER = logging.getLogger(__package__) +TIMEOUT = 30 + +URL_LOGIN = "https://www.fpl.com/api/resources/login" +URL_RESOURCES_HEADER = "https://www.fpl.com/api/resources/header" +URL_RESOURCES_ACCOUNT = "https://www.fpl.com/api/resources/account/{account}" +URL_RESOURCES_PROJECTED_BILL = "https://www.fpl.com/api/resources/account/{account}/projectedBill?premiseNumber={premise}&lastBilledDate={lastBillDate}" + +ENROLLED = "ENROLLED" +NOTENROLLED = "NOTENROLLED" + + +class FplApi(object): + """A class for getting energy usage information from Florida Power & Light.""" + + def __init__(self, username, password, session): + """Initialize the data retrieval. Session should have BasicAuth flag set.""" + self._username = username + self._password = password + self._session = session + + async def async_get_data(self) -> dict: + # self._session = aiohttp.ClientSession() + data = {} + data["accounts"] = [] + if await self.login() == LOGIN_RESULT_OK: + accounts = await self.async_get_open_accounts() + + data["accounts"] = accounts + for account in accounts: + accountData = await self.__async_get_data(account) + data[account] = accountData + + await self.logout() + return data + + async def login(self): + _LOGGER.info("Logging in") + """login and get account information""" + result = LOGIN_RESULT_OK + try: + async with async_timeout.timeout(TIMEOUT): + response = await self._session.get( + URL_LOGIN, auth=aiohttp.BasicAuth(self._username, self._password) + ) + + js = json.loads(await response.text()) + + if response.reason == "Unauthorized": + result = LOGIN_RESULT_UNAUTHORIZED + + if js["messages"][0]["messageCode"] != "login.success": + _LOGGER.error(f"Logging Failure") + result = LOGIN_RESULT_FAILURE + + _LOGGER.info(f"Logging Successful") + + except Exception as e: + _LOGGER.error(f"Error {e} : {sys.exc_info()[0]}") + result = LOGIN_RESULT_FAILURE + + return result + + async def logout(self): + _LOGGER.info("Logging out") + URL = "https://www.fpl.com/api/resources/logout" + async with async_timeout.timeout(TIMEOUT): + await self._session.get(URL) + + async def async_get_open_accounts(self): + _LOGGER.info(f"Getting accounts") + result = [] + + try: + async with async_timeout.timeout(TIMEOUT): + response = await self._session.get(URL_RESOURCES_HEADER) + + js = await response.json() + accounts = js["data"]["accounts"]["data"]["data"] + + for account in accounts: + if account["statusCategory"] == STATUS_CATEGORY_OPEN: + result.append(account["accountNumber"]) + except Exception as e: + _LOGGER.error(f"Getting accounts {e}") + + # self._account_number = js["data"]["selectedAccount"]["data"]["accountNumber"] + # self._premise_number = js["data"]["selectedAccount"]["data"]["acctSecSettings"]["premiseNumber"] + return result + + async def __async_get_data(self, account) -> dict: + _LOGGER.info(f"Getting Data") + data = {} + + async with async_timeout.timeout(TIMEOUT): + response = await self._session.get( + URL_RESOURCES_ACCOUNT.format(account=account) + ) + accountData = (await response.json())["data"] + + premise = accountData["premiseNumber"].zfill(9) + + # currentBillDate + currentBillDate = datetime.strptime( + accountData["currentBillDate"].replace("-", "").split("T")[0], "%Y%m%d" + ).date() + + # nextBillDate + nextBillDate = datetime.strptime( + accountData["nextBillDate"].replace("-", "").split("T")[0], "%Y%m%d" + ).date() + + data["current_bill_date"] = str(currentBillDate) + data["next_bill_date"] = str(nextBillDate) + + today = datetime.now().date() + remaining = (nextBillDate - today).days + days = (today - currentBillDate).days + + data["service_days"] = (nextBillDate - currentBillDate).days + data["as_of_days"] = days + data["remaining_days"] = remaining + + # zip code + zip_code = accountData["serviceAddress"]["zip"] + + # projected bill + pbData = await self.__getFromProjectedBill(account, premise, currentBillDate) + data.update(pbData) + + # programs + programsData = accountData["programs"]["data"] + + programs = dict() + _LOGGER.info(f"Getting Programs") + for program in programsData: + if "enrollmentStatus" in program.keys(): + key = program["name"] + programs[key] = program["enrollmentStatus"] == ENROLLED + + if programs["BBL"]: + # budget billing + data["budget_bill"] = True + bblData = await self.__getBBL_async(account, data) + data.update(bblData) + + data.update( + await self.__getDataFromEnergyService(account, premise, currentBillDate) + ) + + data.update(await self.__getDataFromApplianceUsage(account, currentBillDate)) + return data + + async def __getFromProjectedBill(self, account, premise, currentBillDate) -> dict: + data = {} + + try: + async with async_timeout.timeout(TIMEOUT): + response = await self._session.get( + URL_RESOURCES_PROJECTED_BILL.format( + account=account, + premise=premise, + lastBillDate=currentBillDate.strftime("%m%d%Y"), + ) + ) + + if response.status == 200: + + projectedBillData = (await response.json())["data"] + + billToDate = float(projectedBillData["billToDate"]) + projectedBill = float(projectedBillData["projectedBill"]) + dailyAvg = float(projectedBillData["dailyAvg"]) + avgHighTemp = int(projectedBillData["avgHighTemp"]) + + data["bill_to_date"] = billToDate + data["projected_bill"] = projectedBill + data["daily_avg"] = dailyAvg + data["avg_high_temp"] = avgHighTemp + except: + pass + + return data + + async def __getBBL_async(self, account, projectedBillData) -> dict: + _LOGGER.info(f"Getting budget billing data") + data = {} + + URL = "https://www.fpl.com/api/resources/account/{account}/budgetBillingGraph/premiseDetails" + try: + async with async_timeout.timeout(TIMEOUT): + response = await self._session.get(URL.format(account=account)) + if response.status == 200: + r = (await response.json())["data"] + dataList = r["graphData"] + + startIndex = len(dataList) - 1 + + billingCharge = 0 + budgetBillDeferBalance = r["defAmt"] + + projectedBill = projectedBillData["projected_bill"] + asOfDays = projectedBillData["as_of_days"] + + for det in dataList: + billingCharge += det["actuallBillAmt"] + + calc1 = (projectedBill + billingCharge) / 12 + calc2 = (1 / 12) * (budgetBillDeferBalance) + + projectedBudgetBill = round(calc1 + calc2, 2) + bbDailyAvg = round(projectedBudgetBill / 30, 2) + bbAsOfDateAmt = round(projectedBudgetBill / 30 * asOfDays, 2) + + data["budget_billing_daily_avg"] = bbDailyAvg + data["budget_billing_bill_to_date"] = bbAsOfDateAmt + + data["budget_billing_projected_bill"] = float(projectedBudgetBill) + except: + pass + + URL = "https://www.fpl.com/api/resources/account/{account}/budgetBillingGraph" + + try: + async with async_timeout.timeout(TIMEOUT): + response = await self._session.get(URL.format(account=account)) + if response.status == 200: + r = (await response.json())["data"] + data["bill_to_date"] = float(r["eleAmt"]) + data["defered_amount"] = float(r["defAmt"]) + except: + pass + + return data + + async def __getDataFromEnergyService( + self, account, premise, lastBilledDate + ) -> dict: + _LOGGER.info(f"Getting data from energy service") + URL = "https://www.fpl.com/dashboard-api/resources/account/{account}/energyService/{account}" + + date = str(lastBilledDate.strftime("%m%d%Y")) + JSON = { + "recordCount": 24, + "status": 2, + "channel": "WEB", + "amrFlag": "Y", + "accountType": "RESIDENTIAL", + "revCode": "1", + "premiseNumber": premise, + "projectedBillFlag": True, + "billComparisionFlag": True, + "monthlyFlag": True, + "frequencyType": "Daily", + "lastBilledDate": date, + "applicationPage": "resDashBoard", + } + + data = {} + + async with async_timeout.timeout(TIMEOUT): + response = await self._session.post(URL.format(account=account), json=JSON) + if response.status == 200: + r = (await response.json())["data"] + dailyUsage = [] + + # totalPowerUsage = 0 + if "data" in r["DailyUsage"]: + for daily in r["DailyUsage"]["data"]: + if ( + "kwhUsed" in daily.keys() + and "billingCharge" in daily.keys() + and "date" in daily.keys() + and "averageHighTemperature" in daily.keys() + ): + dailyUsage.append( + { + "usage": daily["kwhUsed"], + "cost": daily["billingCharge"], + "date": daily["date"], + "max_temperature": daily["averageHighTemperature"], + "netDeliveredKwh": daily["netDeliveredKwh"], + "netReceivedKwh": daily["netReceivedKwh"], + "readTime": daily["readTime"], + } + ) + # totalPowerUsage += int(daily["kwhUsed"]) + + # data["total_power_usage"] = totalPowerUsage + data["daily_usage"] = dailyUsage + + data["projectedKWH"] = r["CurrentUsage"]["projectedKWH"] + data["dailyAverageKWH"] = r["CurrentUsage"]["dailyAverageKWH"] + 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: + _LOGGER.info(f"Getting data from applicance usage") + URL = "https://www.fpl.com/dashboard-api/resources/account/{account}/applianceUsage/{account}" + JSON = {"startDate": str(lastBilledDate.strftime("%m%d%Y"))} + data = {} + try: + async with async_timeout.timeout(TIMEOUT): + response = await self._session.post( + URL.format(account=account), json=JSON + ) + if response.status == 200: + electric = (await response.json())["data"]["electric"] + + full = 100 + for e in electric: + rr = round(float(e["percentageDollar"])) + if rr < full: + full = full - rr + else: + rr = full + data[e["category"].replace(" ", "_")] = rr + + except: + pass + + return {"energy_percent_by_applicance": data} diff --git a/custom_components/fpl/fpl/manifest.json b/custom_components/fpl/fpl/manifest.json new file mode 100644 index 0000000..805319d --- /dev/null +++ b/custom_components/fpl/fpl/manifest.json @@ -0,0 +1,17 @@ +{ + "domain": "fpl", + "name": "FPL", + "documentation": "https://github.com/dotKrad/hass-fpl", + "iot_class": "cloud_polling", + "dependencies": [], + "config_flow": true, + "codeowners": [ + "@dotKrad" + ], + "requirements": [ + "bs4", + "integrationhelper" + ], + "homeassistant": "0.96.0", + "version": "1.0.0" +} \ No newline at end of file diff --git a/custom_components/fpl/fpl/sensor.py b/custom_components/fpl/fpl/sensor.py new file mode 100644 index 0000000..6b2c85d --- /dev/null +++ b/custom_components/fpl/fpl/sensor.py @@ -0,0 +1,82 @@ +"""Sensor platform for integration_blueprint.""" + +from .sensor_KWHSensor import ( + ProjectedKWHSensor, + DailyAverageKWHSensor, + BillToDateKWHSensor, + NetReceivedKWHSensor, + NetDeliveredKWHSensor, +) +from .sensor_DatesSensor import ( + CurrentBillDateSensor, + NextBillDateSensor, + ServiceDaysSensor, + AsOfDaysSensor, + RemainingDaysSensor, +) +from .sensor_ProjectedBillSensor import ( + FplProjectedBillSensor, + ProjectedBudgetBillSensor, + ProjectedActualBillSensor, + DeferedAmountSensor, +) +from .sensor_AverageDailySensor import ( + FplAverageDailySensor, + BudgetDailyAverageSensor, +) +from .sensor_DailyUsageSensor import ( + FplDailyUsageKWHSensor, + FplDailyUsageSensor, + FplDailyDeliveredKWHSensor, + FplDailyReceivedKWHSensor, +) + +from .const import DOMAIN + +from .sensor_AllData import AllDataSensor +from .TestSensor import TestSensor + + +async def async_setup_entry(hass, entry, async_add_devices): + """Setup sensor platform.""" + accounts = entry.data.get("accounts") + + coordinator = hass.data[DOMAIN][entry.entry_id] + fpl_accounts = [] + + for account in accounts: + # Test Sensor + # fpl_accounts.append(TestSensor(coordinator, entry, account)) + # All data sensor + # fpl_accounts.append(AllDataSensor(coordinator, entry, account)) + + # bill sensors + fpl_accounts.append(FplProjectedBillSensor(coordinator, entry, account)) + fpl_accounts.append(ProjectedBudgetBillSensor(coordinator, entry, account)) + fpl_accounts.append(ProjectedActualBillSensor(coordinator, entry, account)) + fpl_accounts.append(DeferedAmountSensor(coordinator, entry, account)) + + # usage sensors + fpl_accounts.append(FplAverageDailySensor(coordinator, entry, account)) + fpl_accounts.append(BudgetDailyAverageSensor(coordinator, entry, account)) + fpl_accounts.append(FplDailyUsageSensor(coordinator, entry, account)) + fpl_accounts.append(FplDailyUsageKWHSensor(coordinator, entry, account)) + fpl_accounts.append(FplDailyReceivedKWHSensor(coordinator, entry, account)) + fpl_accounts.append(FplDailyDeliveredKWHSensor(coordinator, entry, account)) + + # date sensors + fpl_accounts.append(CurrentBillDateSensor(coordinator, entry, account)) + fpl_accounts.append(NextBillDateSensor(coordinator, entry, account)) + fpl_accounts.append(ServiceDaysSensor(coordinator, entry, account)) + fpl_accounts.append(AsOfDaysSensor(coordinator, entry, account)) + fpl_accounts.append(RemainingDaysSensor(coordinator, entry, account)) + + # KWH sensors + fpl_accounts.append(ProjectedKWHSensor(coordinator, entry, account)) + fpl_accounts.append(DailyAverageKWHSensor(coordinator, entry, account)) + fpl_accounts.append(BillToDateKWHSensor(coordinator, entry, account)) + fpl_accounts.append(NetReceivedKWHSensor(coordinator, entry, account)) + fpl_accounts.append(NetDeliveredKWHSensor(coordinator, entry, account)) + + + async_add_devices(fpl_accounts) diff --git a/custom_components/fpl/fpl/sensor_AllData.py b/custom_components/fpl/fpl/sensor_AllData.py new file mode 100644 index 0000000..9f9840d --- /dev/null +++ b/custom_components/fpl/fpl/sensor_AllData.py @@ -0,0 +1,33 @@ +from .fplEntity import FplEntity + + +class AllDataSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "") + + @property + def state(self): + 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: + return self.getData("budget_billing_projected_bill") + + return self.getData("projected_bill") + + def defineAttributes(self): + """Return the state attributes.""" + return self.coordinator.data.get(self.account) + + @property + def icon(self): + return "mdi:currency-usd" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Budget Projected Bill" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" + return attributes \ No newline at end of file diff --git a/custom_components/fpl/fpl/sensor_AverageDailySensor.py b/custom_components/fpl/fpl/sensor_AverageDailySensor.py new file mode 100644 index 0000000..d170470 --- /dev/null +++ b/custom_components/fpl/fpl/sensor_AverageDailySensor.py @@ -0,0 +1,51 @@ +from .fplEntity import FplEntity + + +class FplAverageDailySensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Average") + + @property + def state(self): + 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: + return self.getData("budget_billing_daily_avg") + + return self.getData("daily_avg") + + @property + def icon(self): + return "mdi:currency-usd" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Daily Average" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" + return attributes + +class BudgetDailyAverageSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Budget Daily Average") + + @property + def state(self): + return self.getData("budget_billing_daily_avg") + + @property + def icon(self): + return "mdi:currency-usd" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Budget Daily Average" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" + return attributes + diff --git a/custom_components/fpl/fpl/sensor_DailyUsageSensor.py b/custom_components/fpl/fpl/sensor_DailyUsageSensor.py new file mode 100644 index 0000000..3ec2928 --- /dev/null +++ b/custom_components/fpl/fpl/sensor_DailyUsageSensor.py @@ -0,0 +1,120 @@ +from .fplEntity import FplEntity + + +class FplDailyUsageSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Usage") + + @property + def state(self): + data = self.getData("daily_usage") + + if ((data is not None) and (len(data) > 0)): + return data[-1]["cost"] + + return None + + def defineAttributes(self): + """Return the state attributes.""" + data = self.getData("daily_usage") + attributes = {} + attributes["friendly_name"] = "Daily Usage" + attributes["device_class"] = "monetary" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "$" + if ((data is not None) and (data[-1]["cost"] is not None)): + attributes["date"] = data[-1]["readTime"] + return attributes + + @property + def icon(self): + return "mdi:currency-usd" + + +class FplDailyUsageKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Usage KWH") + + @property + def state(self): + data = self.getData("daily_usage") + + if ((data is not None) and (data[-1]["usage"] is not None)): + return data[-1]["usage"] + + return None + + def defineAttributes(self): + """Return the state attributes.""" + data = self.getData("daily_usage") + + attributes = {} + attributes["friendly_name"] = "Daily Usage" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + + if data is not None: + if ((data[-1] is not None) and (data[-1]["readTime"] is not None)): + attributes["date"] = data[-1]["readTime"] + if ((data[-2] is not None) and (data[-2]["readTime"] is not None)): + attributes["last_reset"] = data[-2]["readTime"] + + return attributes + + @property + def icon(self): + return "mdi:flash" + +class FplDailyReceivedKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Received KWH") + + @property + def state(self): + data = self.getData("daily_usage") + return data[-1]["netReceivedKwh"] + + def defineAttributes(self): + """Return the state attributes.""" + data = self.getData("daily_usage") + + attributes = {} + attributes["friendly_name"] = "Daily Return to Grid" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + attributes["date"] = data[-1]["readTime"] + attributes["last_reset"] = data[-2]["readTime"] + return attributes + + + @property + def icon(self): + return "mdi:flash" + +class FplDailyDeliveredKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Delivered KWH") + + @property + def state(self): + data = self.getData("daily_usage") + return data[-1]["netDeliveredKwh"] + + def defineAttributes(self): + """Return the state attributes.""" + data = self.getData("daily_usage") + + attributes = {} + attributes["friendly_name"] = "Daily Consumption" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + attributes["date"] = data[-1]["readTime"] + attributes["last_reset"] = data[-2]["readTime"] + return attributes + + @property + def icon(self): + return "mdi:flash" diff --git a/custom_components/fpl/fpl/sensor_DatesSensor.py b/custom_components/fpl/fpl/sensor_DatesSensor.py new file mode 100644 index 0000000..a301847 --- /dev/null +++ b/custom_components/fpl/fpl/sensor_DatesSensor.py @@ -0,0 +1,97 @@ +from .fplEntity import FplEntity +import datetime + +class CurrentBillDateSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Billing Current Date") + + @property + def state(self): + return datetime.date.fromisoformat(self.getData("current_bill_date")) + + @property + def icon(self): + return "mdi:calendar" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["device_class"] = "date" + attributes["friendly_name"] = "Billing Current" + return attributes + +class NextBillDateSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Billing Next") + + @property + def state(self): + return datetime.date.fromisoformat(self.getData("next_bill_date")) + + @property + def icon(self): + return "mdi:calendar" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["device_class"] = "date" + attributes["friendly_name"] = "Billing Next" + return attributes + +class ServiceDaysSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Billing Total Days") + + @property + def state(self): + return self.getData("service_days") + + @property + def icon(self): + return "mdi:calendar" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["unit_of_measurement"] = "days" + attributes["friendly_name"] = "Billing Total" + return attributes + +class AsOfDaysSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Billing As Of") + + @property + def state(self): + return self.getData("as_of_days") + + @property + def icon(self): + return "mdi:calendar" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["unit_of_measurement"] = "days" + attributes["friendly_name"] = "Billing As Of" + return attributes + +class RemainingDaysSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Billing Remaining") + + @property + def state(self): + return self.getData("remaining_days") + + @property + def icon(self): + return "mdi:calendar" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["unit_of_measurement"] = "days" + attributes["friendly_name"] = "Billing Remaining" + return attributes diff --git a/custom_components/fpl/fpl/sensor_KWHSensor.py b/custom_components/fpl/fpl/sensor_KWHSensor.py new file mode 100644 index 0000000..449f893 --- /dev/null +++ b/custom_components/fpl/fpl/sensor_KWHSensor.py @@ -0,0 +1,110 @@ +from .fplEntity import FplEntity + + +class ProjectedKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Projected") + + @property + def state(self): + return self.getData("projectedKWH") + + @property + def icon(self): + return "mdi:flash" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Projected KWH" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + return attributes + +class DailyAverageKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Average") + + @property + def state(self): + return self.getData("dailyAverageKWH") + + @property + def icon(self): + return "mdi:flash" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Daily Average" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + return attributes + +class BillToDateKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Bill To Date") + + @property + def state(self): + return self.getData("billToDateKWH") + + @property + def icon(self): + return "mdi:flash" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Bill To Date" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + return attributes + +class NetReceivedKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Received Reading") + + @property + def state(self): + return self.getData("recMtrReading") + + @property + def icon(self): + return "mdi:flash" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Meter Return to Grid" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + attributes["last_reset"] = self.getData("billStartDate") + + return attributes + +class NetDeliveredKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Delivered Reading") + + @property + def state(self): + return self.getData("delMtrReading") + + @property + def icon(self): + return "mdi:flash" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Meter Consumption" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + attributes["last_reset"] = self.getData("billStartDate") + + return attributes diff --git a/custom_components/fpl/fpl/sensor_ProjectedBillSensor.py b/custom_components/fpl/fpl/sensor_ProjectedBillSensor.py new file mode 100644 index 0000000..44650f0 --- /dev/null +++ b/custom_components/fpl/fpl/sensor_ProjectedBillSensor.py @@ -0,0 +1,99 @@ +from .fplEntity import FplEntity + + +class FplProjectedBillSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Projected Bill") + + @property + def state(self): + 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: + return self.getData("budget_billing_projected_bill") + + return self.getData("projected_bill") + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Projected Bill" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" + return attributes + + @property + def icon(self): + return "mdi:currency-usd" + + +# Defered Amount +class DeferedAmountSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Defered Amount") + + @property + def state(self): + if self.getData("defered_amount") is not None: + return self.getData("defered_amount") + return 0 + + @property + def icon(self): + return "mdi:currency-usd" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Defered Amount" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" + return attributes + + +class ProjectedBudgetBillSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Projected Budget Bill") + + @property + def state(self): + return self.getData("budget_billing_projected_bill") + + @property + def icon(self): + return "mdi:currency-usd" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Projected Budget Bill" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes['unit_of_measurement'] = "$" + return attributes + + +class ProjectedActualBillSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Projected Actual Bill") + + @property + def state(self): + return self.getData("projected_bill") + + @property + def icon(self): + return "mdi:currency-usd" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Projected Actual Bill" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes['unit_of_measurement'] = "$" + + return attributes From 621fea3042200cea233a43b82371bff706a4bc98 Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Thu, 30 Dec 2021 14:44:27 -0500 Subject: [PATCH 2/7] merge changes into primary folder --- custom_components/fpl/const.py | 2 +- .../fpl/fpl/AverageDailySensor.py | 20 - custom_components/fpl/fpl/DailyUsageSensor.py | 31 -- .../fpl/fpl/ProjectedBillSensor.py | 37 -- custom_components/fpl/fpl/TestSensor.py | 28 -- custom_components/fpl/fpl/__init__.py | 107 ------ custom_components/fpl/fpl/config_flow.py | 110 ------ custom_components/fpl/fpl/const.py | 50 --- .../fpl/fpl/fplDataUpdateCoordinator.py | 30 -- custom_components/fpl/fpl/fplEntity.py | 45 --- custom_components/fpl/fpl/fplapi.py | 346 ------------------ custom_components/fpl/fpl/manifest.json | 17 - custom_components/fpl/fpl/sensor.py | 82 ----- custom_components/fpl/fpl/sensor_AllData.py | 33 -- .../fpl/fpl/sensor_AverageDailySensor.py | 51 --- .../fpl/fpl/sensor_DailyUsageSensor.py | 120 ------ .../fpl/fpl/sensor_DatesSensor.py | 97 ----- custom_components/fpl/fpl/sensor_KWHSensor.py | 110 ------ .../fpl/fpl/sensor_ProjectedBillSensor.py | 99 ----- .../fpl/fplDataUpdateCoordinator.py | 2 +- custom_components/fpl/fplEntity.py | 6 +- custom_components/fpl/fplapi.py | 4 + custom_components/fpl/sensor.py | 16 +- custom_components/fpl/sensor_AllData.py | 9 + .../fpl/sensor_AverageDailySensor.py | 27 +- .../fpl/sensor_DailyUsageSensor.py | 87 ++++- custom_components/fpl/sensor_DatesSensor.py | 47 ++- custom_components/fpl/sensor_KWHSensor.py | 58 ++- .../fpl/sensor_ProjectedBillSensor.py | 40 +- 29 files changed, 244 insertions(+), 1467 deletions(-) delete mode 100644 custom_components/fpl/fpl/AverageDailySensor.py delete mode 100644 custom_components/fpl/fpl/DailyUsageSensor.py delete mode 100644 custom_components/fpl/fpl/ProjectedBillSensor.py delete mode 100644 custom_components/fpl/fpl/TestSensor.py delete mode 100644 custom_components/fpl/fpl/__init__.py delete mode 100644 custom_components/fpl/fpl/config_flow.py delete mode 100644 custom_components/fpl/fpl/const.py delete mode 100644 custom_components/fpl/fpl/fplDataUpdateCoordinator.py delete mode 100644 custom_components/fpl/fpl/fplEntity.py delete mode 100644 custom_components/fpl/fpl/fplapi.py delete mode 100644 custom_components/fpl/fpl/manifest.json delete mode 100644 custom_components/fpl/fpl/sensor.py delete mode 100644 custom_components/fpl/fpl/sensor_AllData.py delete mode 100644 custom_components/fpl/fpl/sensor_AverageDailySensor.py delete mode 100644 custom_components/fpl/fpl/sensor_DailyUsageSensor.py delete mode 100644 custom_components/fpl/fpl/sensor_DatesSensor.py delete mode 100644 custom_components/fpl/fpl/sensor_KWHSensor.py delete mode 100644 custom_components/fpl/fpl/sensor_ProjectedBillSensor.py diff --git a/custom_components/fpl/const.py b/custom_components/fpl/const.py index 63615e0..5a418bb 100644 --- a/custom_components/fpl/const.py +++ b/custom_components/fpl/const.py @@ -3,7 +3,7 @@ NAME = "FPL Integration" DOMAIN = "fpl" DOMAIN_DATA = f"{DOMAIN}_data" -VERSION = "0.0.1" +VERSION = "0.1.0" PLATFORMS = ["sensor"] REQUIRED_FILES = [ ".translations/en.json", diff --git a/custom_components/fpl/fpl/AverageDailySensor.py b/custom_components/fpl/fpl/AverageDailySensor.py deleted file mode 100644 index d0f4778..0000000 --- a/custom_components/fpl/fpl/AverageDailySensor.py +++ /dev/null @@ -1,20 +0,0 @@ -from .FplSensor import FplSensor - - -class FplAverageDailySensor(FplSensor): - def __init__(self, hass, config, account): - FplSensor.__init__(self, hass, config, account, "Average Daily") - - @property - def state(self): - try: - if "daily_avg" in self.data: - self._state = self.data["daily_avg"] - except: - pass - return self._state - - @property - def device_state_attributes(self): - """Return the state attributes.""" - return self.attr \ No newline at end of file diff --git a/custom_components/fpl/fpl/DailyUsageSensor.py b/custom_components/fpl/fpl/DailyUsageSensor.py deleted file mode 100644 index 255e0e7..0000000 --- a/custom_components/fpl/fpl/DailyUsageSensor.py +++ /dev/null @@ -1,31 +0,0 @@ -from .FplSensor import FplSensor - - -class FplDailyUsageSensor(FplSensor): - def __init__(self, hass, config, account): - FplSensor.__init__(self, hass, config, account, "Daily Usage") - - @property - def state(self): - try: - if "daily_usage" in self.data: - if len(self.data["daily_usage"]) > 0: - if "cost" in self.data["daily_usage"][-1]: - self._state = self.data["daily_usage"][-1]["cost"] - except: - pass - - return self._state - - @property - def device_state_attributes(self): - """Return the state attributes.""" - try: - if "daily_usage" in self.data: - if len(self.data["daily_usage"]) > 0: - if "date" in self.data["daily_usage"][-1]: - self.attr["date"] = self.data["daily_usage"][-1]["date"] - except: - pass - - return self.attr diff --git a/custom_components/fpl/fpl/ProjectedBillSensor.py b/custom_components/fpl/fpl/ProjectedBillSensor.py deleted file mode 100644 index bf14a32..0000000 --- a/custom_components/fpl/fpl/ProjectedBillSensor.py +++ /dev/null @@ -1,37 +0,0 @@ -from .FplSensor import FplSensor - - -class FplProjectedBillSensor(FplSensor): - def __init__(self, hass, config, account): - FplSensor.__init__(self, hass, config, account, "Projected Bill") - - @property - def state(self): - data = self.data - try: - if "budget_bill" in data.keys(): - if data["budget_bill"]: - if "budget_billing_projected_bill" in data.keys(): - self._state = data["budget_billing_projected_bill"] - else: - if "projected_bill" in data.keys(): - self._state = data["projected_bill"] - except: - pass - - return self._state - - @property - def device_state_attributes(self): - """Return the state attributes.""" - try: - if "budget_bill" in self.data.keys(): - self.attr["budget_bill"] = self.data["budget_bill"] - except: - pass - - return self.attr - - @property - def icon(self): - return "mdi:currency-usd" \ No newline at end of file diff --git a/custom_components/fpl/fpl/TestSensor.py b/custom_components/fpl/fpl/TestSensor.py deleted file mode 100644 index 4d8b63d..0000000 --- a/custom_components/fpl/fpl/TestSensor.py +++ /dev/null @@ -1,28 +0,0 @@ -from .fplEntity import FplEntity -import pprint - - -class TestSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Test Sensor") - - @property - def state(self): - pprint.pprint(self.coordinator.data) - - return self.getData("projected_bill") - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - try: - if self.getData("budget_bill"): - attributes["budget_bill"] = self.getData("budget_bill") - except: - pass - - return attributes - - @property - def icon(self): - return "mdi:currency-usd" diff --git a/custom_components/fpl/fpl/__init__.py b/custom_components/fpl/fpl/__init__.py deleted file mode 100644 index 18af9fe..0000000 --- a/custom_components/fpl/fpl/__init__.py +++ /dev/null @@ -1,107 +0,0 @@ -""" FPL Component """ - - -import logging -import asyncio - -from datetime import timedelta -from homeassistant.core import Config, HomeAssistant -from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.util import Throttle - -from .fplapi import FplApi -from .const import ( - DOMAIN, - DOMAIN_DATA, - CONF_USERNAME, - CONF_PASSWORD, - PLATFORMS, - STARTUP_MESSAGE, -) -from .fplDataUpdateCoordinator import FplDataUpdateCoordinator - -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) - -_LOGGER = logging.getLogger(__package__) - - -class FplData: - """This class handle communication and stores the data.""" - - def __init__(self, hass, client): - """Initialize the class.""" - self.hass = hass - self.client = client - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - async def update_data(self): - """Update data.""" - # This is where the main logic to update platform data goes. - try: - data = await self.client.get_data() - self.hass.data[DOMAIN_DATA]["data"] = data - except Exception as error: # pylint: disable=broad-except - _LOGGER.error("Could not update data - %s", error) - - -async def async_setup(hass: HomeAssistant, config: Config) -> bool: - """Set up configured Fpl.""" - return True - - -async def async_setup_entry(hass, entry): - """Set up this integration using UI.""" - if hass.data.get(DOMAIN) is None: - hass.data.setdefault(DOMAIN, {}) - _LOGGER.info(STARTUP_MESSAGE) - - # Get "global" configuration. - username = entry.data.get(CONF_USERNAME) - password = entry.data.get(CONF_PASSWORD) - - # Configure the client. - _LOGGER.info(f"Configuring the client") - session = async_get_clientsession(hass) - client = FplApi(username, password, session) - - coordinator = FplDataUpdateCoordinator(hass, client=client) - await coordinator.async_refresh() - - hass.data[DOMAIN][entry.entry_id] = coordinator - - for platform in PLATFORMS: - if entry.options.get(platform, True): - coordinator.platforms.append(platform) - hass.async_add_job( - hass.config_entries.async_forward_entry_setup(entry, platform) - ) - - """Set up Fpl as config entry.""" - - entry.add_update_listener(async_reload_entry) - return True - - -async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Reload config entry.""" - await async_unload_entry(hass, entry) - await async_setup_entry(hass, entry) - - -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Handle removal of an entry.""" - coordinator = hass.data[DOMAIN][entry.entry_id] - unloaded = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in PLATFORMS - if platform in coordinator.platforms - ] - ) - ) - if unloaded: - hass.data[DOMAIN].pop(entry.entry_id) - - return unloaded diff --git a/custom_components/fpl/fpl/config_flow.py b/custom_components/fpl/fpl/config_flow.py deleted file mode 100644 index b94d54d..0000000 --- a/custom_components/fpl/fpl/config_flow.py +++ /dev/null @@ -1,110 +0,0 @@ -from collections import OrderedDict - -import voluptuous as vol -from .fplapi import FplApi - -from homeassistant import config_entries -from homeassistant.helpers.aiohttp_client import async_create_clientsession - -from .const import DOMAIN, CONF_USERNAME, CONF_PASSWORD, CONF_NAME - -from .fplapi import ( - LOGIN_RESULT_OK, - LOGIN_RESULT_INVALIDUSER, - LOGIN_RESULT_INVALIDPASSWORD, -) - -from homeassistant.core import callback - - -@callback -def configured_instances(hass): - """Return a set of configured SimpliSafe instances.""" - entites = [] - for entry in hass.config_entries.async_entries(DOMAIN): - entites.append(f"{entry.data.get(CONF_USERNAME)}") - return set(entites) - - -class FplFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - - VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - - def __init__(self): - """Initialize.""" - self._errors = {} - - async def async_step_user( - self, user_input={} - ): # pylint: disable=dangerous-default-value - """Handle a flow initialized by the user.""" - self._errors = {} - - # if self._async_current_entries(): - # return self.async_abort(reason="single_instance_allowed") - - # if self.hass.data.get(DOMAIN): - # return self.async_abort(reason="single_instance_allowed") - - if user_input is not None: - username = user_input[CONF_USERNAME] - password = user_input[CONF_PASSWORD] - - if username not in configured_instances(self.hass): - session = async_create_clientsession(self.hass) - api = FplApi(username, password, session) - result = await api.login() - - if result == LOGIN_RESULT_OK: - fplData = await api.async_get_data() - accounts = fplData["accounts"] - - user_input["accounts"] = accounts - - return self.async_create_entry(title=username, data=user_input) - - if result == LOGIN_RESULT_INVALIDUSER: - self._errors[CONF_USERNAME] = "invalid_username" - - if result == LOGIN_RESULT_INVALIDPASSWORD: - self._errors[CONF_PASSWORD] = "invalid_password" - - if result == None: - self._errors["base"] = "auth" - - else: - self._errors[CONF_NAME] = "name_exists" - - return await self._show_config_form(user_input) - - return await self._show_config_form(user_input) - - async def _show_config_form(self, user_input): - """Show the configuration form to edit location data.""" - username = "" - password = "" - - if user_input is not None: - if CONF_USERNAME in user_input: - username = user_input[CONF_USERNAME] - if CONF_PASSWORD in user_input: - password = user_input[CONF_PASSWORD] - - data_schema = OrderedDict() - data_schema[vol.Required(CONF_USERNAME, default=username)] = str - data_schema[vol.Required(CONF_PASSWORD, default=password)] = str - - return self.async_show_form( - step_id="user", data_schema=vol.Schema(data_schema), errors=self._errors - ) - - async def async_step_import(self, user_input): # pylint: disable=unused-argument - """Import a config entry. - Special type of import, we're not actually going to store any data. - Instead, we're going to rely on the values that are in config file. - """ - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - - return self.async_create_entry(title="configuration.yaml", data={}) diff --git a/custom_components/fpl/fpl/const.py b/custom_components/fpl/fpl/const.py deleted file mode 100644 index 5a418bb..0000000 --- a/custom_components/fpl/fpl/const.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Constants for fpl.""" -# Base component constants -NAME = "FPL Integration" -DOMAIN = "fpl" -DOMAIN_DATA = f"{DOMAIN}_data" -VERSION = "0.1.0" -PLATFORMS = ["sensor"] -REQUIRED_FILES = [ - ".translations/en.json", - "binary_sensor.py", - "const.py", - "config_flow.py", - "manifest.json", - "sensor.py", - "switch.py", -] -ISSUE_URL = "https://github.com/dotKrad/hass-fpl/issues" -ATTRIBUTION = "This data is provided by FPL." - -# Platforms -BINARY_SENSOR = "binary_sensor" -SENSOR = "sensor" -SWITCH = "switch" -PLATFORMS = [SENSOR] - -# Device classes -BINARY_SENSOR_DEVICE_CLASS = "connectivity" - -# Configuration -CONF_BINARY_SENSOR = "binary_sensor" -CONF_SENSOR = "sensor" -CONF_SWITCH = "switch" -CONF_ENABLED = "enabled" -CONF_NAME = "name" -CONF_USERNAME = "username" -CONF_PASSWORD = "password" - -# Defaults -DEFAULT_NAME = DOMAIN - - -STARTUP_MESSAGE = f""" -------------------------------------------------------------------- -{NAME} -Version: {VERSION} -This is a custom integration! -If you have any issues with this you need to open an issue here: -{ISSUE_URL} -------------------------------------------------------------------- -""" diff --git a/custom_components/fpl/fpl/fplDataUpdateCoordinator.py b/custom_components/fpl/fpl/fplDataUpdateCoordinator.py deleted file mode 100644 index 5679cbf..0000000 --- a/custom_components/fpl/fpl/fplDataUpdateCoordinator.py +++ /dev/null @@ -1,30 +0,0 @@ -import logging - -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=1200) - -_LOGGER: logging.Logger = logging.getLogger(__package__) - - -class FplDataUpdateCoordinator(DataUpdateCoordinator): - """Class to manage fetching data from the API.""" - - def __init__(self, hass: HomeAssistant, client: FplApi) -> None: - """Initialize.""" - self.api = client - self.platforms = [] - - super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) - - async def _async_update_data(self): - """Update data via library.""" - try: - return await self.api.async_get_data() - except Exception as exception: - raise UpdateFailed() from exception diff --git a/custom_components/fpl/fpl/fplEntity.py b/custom_components/fpl/fpl/fplEntity.py deleted file mode 100644 index e9bf200..0000000 --- a/custom_components/fpl/fpl/fplEntity.py +++ /dev/null @@ -1,45 +0,0 @@ -"""BlueprintEntity class""" -from homeassistant.helpers.update_coordinator import CoordinatorEntity - -from .const import DOMAIN, VERSION, ATTRIBUTION - - -class FplEntity(CoordinatorEntity): - def __init__(self, coordinator, config_entry, account, sensorName): - super().__init__(coordinator) - self.config_entry = config_entry - self.account = account - self.sensorName = sensorName - - @property - def unique_id(self): - """Return the ID of this device.""" - id = "{}{}{}".format( - DOMAIN, self.account, self.sensorName.lower().replace(" ", "") - ) - return id - - @property - def device_info(self): - return { - "identifiers": {(DOMAIN, self.account)}, - "name": f"FPL Account {self.account}", - "model": VERSION, - "manufacturer": "Florida Power & Light", - } - - def defineAttributes(self): - return {} - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - attributes = { - "attribution": ATTRIBUTION, - "integration": "FPL", - } - attributes.update(self.defineAttributes()) - return attributes - - def getData(self, field): - return self.coordinator.data.get(self.account).get(field) diff --git a/custom_components/fpl/fpl/fplapi.py b/custom_components/fpl/fpl/fplapi.py deleted file mode 100644 index 4790cd0..0000000 --- a/custom_components/fpl/fpl/fplapi.py +++ /dev/null @@ -1,346 +0,0 @@ -import asyncio -import logging -import re -from datetime import timedelta, datetime, date as dt - -import aiohttp -import async_timeout -import json -import sys - - -from bs4 import BeautifulSoup - -STATUS_CATEGORY_OPEN = "OPEN" -# Api login result -LOGIN_RESULT_OK = "OK" -LOGIN_RESULT_INVALIDUSER = "NOTVALIDUSER" -LOGIN_RESULT_INVALIDPASSWORD = "FAILEDPASSWORD" -LOGIN_RESULT_UNAUTHORIZED = "UNAUTHORIZED" -LOGIN_RESULT_FAILURE = "FAILURE" - -_LOGGER = logging.getLogger(__package__) -TIMEOUT = 30 - -URL_LOGIN = "https://www.fpl.com/api/resources/login" -URL_RESOURCES_HEADER = "https://www.fpl.com/api/resources/header" -URL_RESOURCES_ACCOUNT = "https://www.fpl.com/api/resources/account/{account}" -URL_RESOURCES_PROJECTED_BILL = "https://www.fpl.com/api/resources/account/{account}/projectedBill?premiseNumber={premise}&lastBilledDate={lastBillDate}" - -ENROLLED = "ENROLLED" -NOTENROLLED = "NOTENROLLED" - - -class FplApi(object): - """A class for getting energy usage information from Florida Power & Light.""" - - def __init__(self, username, password, session): - """Initialize the data retrieval. Session should have BasicAuth flag set.""" - self._username = username - self._password = password - self._session = session - - async def async_get_data(self) -> dict: - # self._session = aiohttp.ClientSession() - data = {} - data["accounts"] = [] - if await self.login() == LOGIN_RESULT_OK: - accounts = await self.async_get_open_accounts() - - data["accounts"] = accounts - for account in accounts: - accountData = await self.__async_get_data(account) - data[account] = accountData - - await self.logout() - return data - - async def login(self): - _LOGGER.info("Logging in") - """login and get account information""" - result = LOGIN_RESULT_OK - try: - async with async_timeout.timeout(TIMEOUT): - response = await self._session.get( - URL_LOGIN, auth=aiohttp.BasicAuth(self._username, self._password) - ) - - js = json.loads(await response.text()) - - if response.reason == "Unauthorized": - result = LOGIN_RESULT_UNAUTHORIZED - - if js["messages"][0]["messageCode"] != "login.success": - _LOGGER.error(f"Logging Failure") - result = LOGIN_RESULT_FAILURE - - _LOGGER.info(f"Logging Successful") - - except Exception as e: - _LOGGER.error(f"Error {e} : {sys.exc_info()[0]}") - result = LOGIN_RESULT_FAILURE - - return result - - async def logout(self): - _LOGGER.info("Logging out") - URL = "https://www.fpl.com/api/resources/logout" - async with async_timeout.timeout(TIMEOUT): - await self._session.get(URL) - - async def async_get_open_accounts(self): - _LOGGER.info(f"Getting accounts") - result = [] - - try: - async with async_timeout.timeout(TIMEOUT): - response = await self._session.get(URL_RESOURCES_HEADER) - - js = await response.json() - accounts = js["data"]["accounts"]["data"]["data"] - - for account in accounts: - if account["statusCategory"] == STATUS_CATEGORY_OPEN: - result.append(account["accountNumber"]) - except Exception as e: - _LOGGER.error(f"Getting accounts {e}") - - # self._account_number = js["data"]["selectedAccount"]["data"]["accountNumber"] - # self._premise_number = js["data"]["selectedAccount"]["data"]["acctSecSettings"]["premiseNumber"] - return result - - async def __async_get_data(self, account) -> dict: - _LOGGER.info(f"Getting Data") - data = {} - - async with async_timeout.timeout(TIMEOUT): - response = await self._session.get( - URL_RESOURCES_ACCOUNT.format(account=account) - ) - accountData = (await response.json())["data"] - - premise = accountData["premiseNumber"].zfill(9) - - # currentBillDate - currentBillDate = datetime.strptime( - accountData["currentBillDate"].replace("-", "").split("T")[0], "%Y%m%d" - ).date() - - # nextBillDate - nextBillDate = datetime.strptime( - accountData["nextBillDate"].replace("-", "").split("T")[0], "%Y%m%d" - ).date() - - data["current_bill_date"] = str(currentBillDate) - data["next_bill_date"] = str(nextBillDate) - - today = datetime.now().date() - remaining = (nextBillDate - today).days - days = (today - currentBillDate).days - - data["service_days"] = (nextBillDate - currentBillDate).days - data["as_of_days"] = days - data["remaining_days"] = remaining - - # zip code - zip_code = accountData["serviceAddress"]["zip"] - - # projected bill - pbData = await self.__getFromProjectedBill(account, premise, currentBillDate) - data.update(pbData) - - # programs - programsData = accountData["programs"]["data"] - - programs = dict() - _LOGGER.info(f"Getting Programs") - for program in programsData: - if "enrollmentStatus" in program.keys(): - key = program["name"] - programs[key] = program["enrollmentStatus"] == ENROLLED - - if programs["BBL"]: - # budget billing - data["budget_bill"] = True - bblData = await self.__getBBL_async(account, data) - data.update(bblData) - - data.update( - await self.__getDataFromEnergyService(account, premise, currentBillDate) - ) - - data.update(await self.__getDataFromApplianceUsage(account, currentBillDate)) - return data - - async def __getFromProjectedBill(self, account, premise, currentBillDate) -> dict: - data = {} - - try: - async with async_timeout.timeout(TIMEOUT): - response = await self._session.get( - URL_RESOURCES_PROJECTED_BILL.format( - account=account, - premise=premise, - lastBillDate=currentBillDate.strftime("%m%d%Y"), - ) - ) - - if response.status == 200: - - projectedBillData = (await response.json())["data"] - - billToDate = float(projectedBillData["billToDate"]) - projectedBill = float(projectedBillData["projectedBill"]) - dailyAvg = float(projectedBillData["dailyAvg"]) - avgHighTemp = int(projectedBillData["avgHighTemp"]) - - data["bill_to_date"] = billToDate - data["projected_bill"] = projectedBill - data["daily_avg"] = dailyAvg - data["avg_high_temp"] = avgHighTemp - except: - pass - - return data - - async def __getBBL_async(self, account, projectedBillData) -> dict: - _LOGGER.info(f"Getting budget billing data") - data = {} - - URL = "https://www.fpl.com/api/resources/account/{account}/budgetBillingGraph/premiseDetails" - try: - async with async_timeout.timeout(TIMEOUT): - response = await self._session.get(URL.format(account=account)) - if response.status == 200: - r = (await response.json())["data"] - dataList = r["graphData"] - - startIndex = len(dataList) - 1 - - billingCharge = 0 - budgetBillDeferBalance = r["defAmt"] - - projectedBill = projectedBillData["projected_bill"] - asOfDays = projectedBillData["as_of_days"] - - for det in dataList: - billingCharge += det["actuallBillAmt"] - - calc1 = (projectedBill + billingCharge) / 12 - calc2 = (1 / 12) * (budgetBillDeferBalance) - - projectedBudgetBill = round(calc1 + calc2, 2) - bbDailyAvg = round(projectedBudgetBill / 30, 2) - bbAsOfDateAmt = round(projectedBudgetBill / 30 * asOfDays, 2) - - data["budget_billing_daily_avg"] = bbDailyAvg - data["budget_billing_bill_to_date"] = bbAsOfDateAmt - - data["budget_billing_projected_bill"] = float(projectedBudgetBill) - except: - pass - - URL = "https://www.fpl.com/api/resources/account/{account}/budgetBillingGraph" - - try: - async with async_timeout.timeout(TIMEOUT): - response = await self._session.get(URL.format(account=account)) - if response.status == 200: - r = (await response.json())["data"] - data["bill_to_date"] = float(r["eleAmt"]) - data["defered_amount"] = float(r["defAmt"]) - except: - pass - - return data - - async def __getDataFromEnergyService( - self, account, premise, lastBilledDate - ) -> dict: - _LOGGER.info(f"Getting data from energy service") - URL = "https://www.fpl.com/dashboard-api/resources/account/{account}/energyService/{account}" - - date = str(lastBilledDate.strftime("%m%d%Y")) - JSON = { - "recordCount": 24, - "status": 2, - "channel": "WEB", - "amrFlag": "Y", - "accountType": "RESIDENTIAL", - "revCode": "1", - "premiseNumber": premise, - "projectedBillFlag": True, - "billComparisionFlag": True, - "monthlyFlag": True, - "frequencyType": "Daily", - "lastBilledDate": date, - "applicationPage": "resDashBoard", - } - - data = {} - - async with async_timeout.timeout(TIMEOUT): - response = await self._session.post(URL.format(account=account), json=JSON) - if response.status == 200: - r = (await response.json())["data"] - dailyUsage = [] - - # totalPowerUsage = 0 - if "data" in r["DailyUsage"]: - for daily in r["DailyUsage"]["data"]: - if ( - "kwhUsed" in daily.keys() - and "billingCharge" in daily.keys() - and "date" in daily.keys() - and "averageHighTemperature" in daily.keys() - ): - dailyUsage.append( - { - "usage": daily["kwhUsed"], - "cost": daily["billingCharge"], - "date": daily["date"], - "max_temperature": daily["averageHighTemperature"], - "netDeliveredKwh": daily["netDeliveredKwh"], - "netReceivedKwh": daily["netReceivedKwh"], - "readTime": daily["readTime"], - } - ) - # totalPowerUsage += int(daily["kwhUsed"]) - - # data["total_power_usage"] = totalPowerUsage - data["daily_usage"] = dailyUsage - - data["projectedKWH"] = r["CurrentUsage"]["projectedKWH"] - data["dailyAverageKWH"] = r["CurrentUsage"]["dailyAverageKWH"] - 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: - _LOGGER.info(f"Getting data from applicance usage") - URL = "https://www.fpl.com/dashboard-api/resources/account/{account}/applianceUsage/{account}" - JSON = {"startDate": str(lastBilledDate.strftime("%m%d%Y"))} - data = {} - try: - async with async_timeout.timeout(TIMEOUT): - response = await self._session.post( - URL.format(account=account), json=JSON - ) - if response.status == 200: - electric = (await response.json())["data"]["electric"] - - full = 100 - for e in electric: - rr = round(float(e["percentageDollar"])) - if rr < full: - full = full - rr - else: - rr = full - data[e["category"].replace(" ", "_")] = rr - - except: - pass - - return {"energy_percent_by_applicance": data} diff --git a/custom_components/fpl/fpl/manifest.json b/custom_components/fpl/fpl/manifest.json deleted file mode 100644 index 805319d..0000000 --- a/custom_components/fpl/fpl/manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "domain": "fpl", - "name": "FPL", - "documentation": "https://github.com/dotKrad/hass-fpl", - "iot_class": "cloud_polling", - "dependencies": [], - "config_flow": true, - "codeowners": [ - "@dotKrad" - ], - "requirements": [ - "bs4", - "integrationhelper" - ], - "homeassistant": "0.96.0", - "version": "1.0.0" -} \ No newline at end of file diff --git a/custom_components/fpl/fpl/sensor.py b/custom_components/fpl/fpl/sensor.py deleted file mode 100644 index 6b2c85d..0000000 --- a/custom_components/fpl/fpl/sensor.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Sensor platform for integration_blueprint.""" - -from .sensor_KWHSensor import ( - ProjectedKWHSensor, - DailyAverageKWHSensor, - BillToDateKWHSensor, - NetReceivedKWHSensor, - NetDeliveredKWHSensor, -) -from .sensor_DatesSensor import ( - CurrentBillDateSensor, - NextBillDateSensor, - ServiceDaysSensor, - AsOfDaysSensor, - RemainingDaysSensor, -) -from .sensor_ProjectedBillSensor import ( - FplProjectedBillSensor, - ProjectedBudgetBillSensor, - ProjectedActualBillSensor, - DeferedAmountSensor, -) -from .sensor_AverageDailySensor import ( - FplAverageDailySensor, - BudgetDailyAverageSensor, -) -from .sensor_DailyUsageSensor import ( - FplDailyUsageKWHSensor, - FplDailyUsageSensor, - FplDailyDeliveredKWHSensor, - FplDailyReceivedKWHSensor, -) - -from .const import DOMAIN - -from .sensor_AllData import AllDataSensor -from .TestSensor import TestSensor - - -async def async_setup_entry(hass, entry, async_add_devices): - """Setup sensor platform.""" - accounts = entry.data.get("accounts") - - coordinator = hass.data[DOMAIN][entry.entry_id] - fpl_accounts = [] - - for account in accounts: - # Test Sensor - # fpl_accounts.append(TestSensor(coordinator, entry, account)) - # All data sensor - # fpl_accounts.append(AllDataSensor(coordinator, entry, account)) - - # bill sensors - fpl_accounts.append(FplProjectedBillSensor(coordinator, entry, account)) - fpl_accounts.append(ProjectedBudgetBillSensor(coordinator, entry, account)) - fpl_accounts.append(ProjectedActualBillSensor(coordinator, entry, account)) - fpl_accounts.append(DeferedAmountSensor(coordinator, entry, account)) - - # usage sensors - fpl_accounts.append(FplAverageDailySensor(coordinator, entry, account)) - fpl_accounts.append(BudgetDailyAverageSensor(coordinator, entry, account)) - fpl_accounts.append(FplDailyUsageSensor(coordinator, entry, account)) - fpl_accounts.append(FplDailyUsageKWHSensor(coordinator, entry, account)) - fpl_accounts.append(FplDailyReceivedKWHSensor(coordinator, entry, account)) - fpl_accounts.append(FplDailyDeliveredKWHSensor(coordinator, entry, account)) - - # date sensors - fpl_accounts.append(CurrentBillDateSensor(coordinator, entry, account)) - fpl_accounts.append(NextBillDateSensor(coordinator, entry, account)) - fpl_accounts.append(ServiceDaysSensor(coordinator, entry, account)) - fpl_accounts.append(AsOfDaysSensor(coordinator, entry, account)) - fpl_accounts.append(RemainingDaysSensor(coordinator, entry, account)) - - # KWH sensors - fpl_accounts.append(ProjectedKWHSensor(coordinator, entry, account)) - fpl_accounts.append(DailyAverageKWHSensor(coordinator, entry, account)) - fpl_accounts.append(BillToDateKWHSensor(coordinator, entry, account)) - fpl_accounts.append(NetReceivedKWHSensor(coordinator, entry, account)) - fpl_accounts.append(NetDeliveredKWHSensor(coordinator, entry, account)) - - - async_add_devices(fpl_accounts) diff --git a/custom_components/fpl/fpl/sensor_AllData.py b/custom_components/fpl/fpl/sensor_AllData.py deleted file mode 100644 index 9f9840d..0000000 --- a/custom_components/fpl/fpl/sensor_AllData.py +++ /dev/null @@ -1,33 +0,0 @@ -from .fplEntity import FplEntity - - -class AllDataSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "") - - @property - def state(self): - 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: - return self.getData("budget_billing_projected_bill") - - return self.getData("projected_bill") - - def defineAttributes(self): - """Return the state attributes.""" - return self.coordinator.data.get(self.account) - - @property - def icon(self): - return "mdi:currency-usd" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Budget Projected Bill" - attributes["device_class"] = "monitary" - attributes["state_class"] = "total" - attributes["unit_of_measurement"] = "$" - return attributes \ No newline at end of file diff --git a/custom_components/fpl/fpl/sensor_AverageDailySensor.py b/custom_components/fpl/fpl/sensor_AverageDailySensor.py deleted file mode 100644 index d170470..0000000 --- a/custom_components/fpl/fpl/sensor_AverageDailySensor.py +++ /dev/null @@ -1,51 +0,0 @@ -from .fplEntity import FplEntity - - -class FplAverageDailySensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Daily Average") - - @property - def state(self): - 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: - return self.getData("budget_billing_daily_avg") - - return self.getData("daily_avg") - - @property - def icon(self): - return "mdi:currency-usd" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Daily Average" - attributes["device_class"] = "monitary" - attributes["state_class"] = "total" - attributes["unit_of_measurement"] = "$" - return attributes - -class BudgetDailyAverageSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Budget Daily Average") - - @property - def state(self): - return self.getData("budget_billing_daily_avg") - - @property - def icon(self): - return "mdi:currency-usd" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Budget Daily Average" - attributes["device_class"] = "monitary" - attributes["state_class"] = "total" - attributes["unit_of_measurement"] = "$" - return attributes - diff --git a/custom_components/fpl/fpl/sensor_DailyUsageSensor.py b/custom_components/fpl/fpl/sensor_DailyUsageSensor.py deleted file mode 100644 index 3ec2928..0000000 --- a/custom_components/fpl/fpl/sensor_DailyUsageSensor.py +++ /dev/null @@ -1,120 +0,0 @@ -from .fplEntity import FplEntity - - -class FplDailyUsageSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Daily Usage") - - @property - def state(self): - data = self.getData("daily_usage") - - if ((data is not None) and (len(data) > 0)): - return data[-1]["cost"] - - return None - - def defineAttributes(self): - """Return the state attributes.""" - data = self.getData("daily_usage") - attributes = {} - attributes["friendly_name"] = "Daily Usage" - attributes["device_class"] = "monetary" - attributes["state_class"] = "total_increasing" - attributes["unit_of_measurement"] = "$" - if ((data is not None) and (data[-1]["cost"] is not None)): - attributes["date"] = data[-1]["readTime"] - return attributes - - @property - def icon(self): - return "mdi:currency-usd" - - -class FplDailyUsageKWHSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Daily Usage KWH") - - @property - def state(self): - data = self.getData("daily_usage") - - if ((data is not None) and (data[-1]["usage"] is not None)): - return data[-1]["usage"] - - return None - - def defineAttributes(self): - """Return the state attributes.""" - data = self.getData("daily_usage") - - attributes = {} - attributes["friendly_name"] = "Daily Usage" - attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" - attributes["unit_of_measurement"] = "kWh" - - if data is not None: - if ((data[-1] is not None) and (data[-1]["readTime"] is not None)): - attributes["date"] = data[-1]["readTime"] - if ((data[-2] is not None) and (data[-2]["readTime"] is not None)): - attributes["last_reset"] = data[-2]["readTime"] - - return attributes - - @property - def icon(self): - return "mdi:flash" - -class FplDailyReceivedKWHSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Daily Received KWH") - - @property - def state(self): - data = self.getData("daily_usage") - return data[-1]["netReceivedKwh"] - - def defineAttributes(self): - """Return the state attributes.""" - data = self.getData("daily_usage") - - attributes = {} - attributes["friendly_name"] = "Daily Return to Grid" - attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" - attributes["unit_of_measurement"] = "kWh" - attributes["date"] = data[-1]["readTime"] - attributes["last_reset"] = data[-2]["readTime"] - return attributes - - - @property - def icon(self): - return "mdi:flash" - -class FplDailyDeliveredKWHSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Daily Delivered KWH") - - @property - def state(self): - data = self.getData("daily_usage") - return data[-1]["netDeliveredKwh"] - - def defineAttributes(self): - """Return the state attributes.""" - data = self.getData("daily_usage") - - attributes = {} - attributes["friendly_name"] = "Daily Consumption" - attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" - attributes["unit_of_measurement"] = "kWh" - attributes["date"] = data[-1]["readTime"] - attributes["last_reset"] = data[-2]["readTime"] - return attributes - - @property - def icon(self): - return "mdi:flash" diff --git a/custom_components/fpl/fpl/sensor_DatesSensor.py b/custom_components/fpl/fpl/sensor_DatesSensor.py deleted file mode 100644 index a301847..0000000 --- a/custom_components/fpl/fpl/sensor_DatesSensor.py +++ /dev/null @@ -1,97 +0,0 @@ -from .fplEntity import FplEntity -import datetime - -class CurrentBillDateSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Billing Current Date") - - @property - def state(self): - return datetime.date.fromisoformat(self.getData("current_bill_date")) - - @property - def icon(self): - return "mdi:calendar" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["device_class"] = "date" - attributes["friendly_name"] = "Billing Current" - return attributes - -class NextBillDateSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Billing Next") - - @property - def state(self): - return datetime.date.fromisoformat(self.getData("next_bill_date")) - - @property - def icon(self): - return "mdi:calendar" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["device_class"] = "date" - attributes["friendly_name"] = "Billing Next" - return attributes - -class ServiceDaysSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Billing Total Days") - - @property - def state(self): - return self.getData("service_days") - - @property - def icon(self): - return "mdi:calendar" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["unit_of_measurement"] = "days" - attributes["friendly_name"] = "Billing Total" - return attributes - -class AsOfDaysSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Billing As Of") - - @property - def state(self): - return self.getData("as_of_days") - - @property - def icon(self): - return "mdi:calendar" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["unit_of_measurement"] = "days" - attributes["friendly_name"] = "Billing As Of" - return attributes - -class RemainingDaysSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Billing Remaining") - - @property - def state(self): - return self.getData("remaining_days") - - @property - def icon(self): - return "mdi:calendar" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["unit_of_measurement"] = "days" - attributes["friendly_name"] = "Billing Remaining" - return attributes diff --git a/custom_components/fpl/fpl/sensor_KWHSensor.py b/custom_components/fpl/fpl/sensor_KWHSensor.py deleted file mode 100644 index 449f893..0000000 --- a/custom_components/fpl/fpl/sensor_KWHSensor.py +++ /dev/null @@ -1,110 +0,0 @@ -from .fplEntity import FplEntity - - -class ProjectedKWHSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Projected") - - @property - def state(self): - return self.getData("projectedKWH") - - @property - def icon(self): - return "mdi:flash" - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Projected KWH" - attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" - attributes["unit_of_measurement"] = "kWh" - return attributes - -class DailyAverageKWHSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Daily Average") - - @property - def state(self): - return self.getData("dailyAverageKWH") - - @property - def icon(self): - return "mdi:flash" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Daily Average" - attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" - attributes["unit_of_measurement"] = "kWh" - return attributes - -class BillToDateKWHSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Bill To Date") - - @property - def state(self): - return self.getData("billToDateKWH") - - @property - def icon(self): - return "mdi:flash" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Bill To Date" - attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" - attributes["unit_of_measurement"] = "kWh" - return attributes - -class NetReceivedKWHSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Received Reading") - - @property - def state(self): - return self.getData("recMtrReading") - - @property - def icon(self): - return "mdi:flash" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Meter Return to Grid" - attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" - attributes["unit_of_measurement"] = "kWh" - attributes["last_reset"] = self.getData("billStartDate") - - return attributes - -class NetDeliveredKWHSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Delivered Reading") - - @property - def state(self): - return self.getData("delMtrReading") - - @property - def icon(self): - return "mdi:flash" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Meter Consumption" - attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" - attributes["unit_of_measurement"] = "kWh" - attributes["last_reset"] = self.getData("billStartDate") - - return attributes diff --git a/custom_components/fpl/fpl/sensor_ProjectedBillSensor.py b/custom_components/fpl/fpl/sensor_ProjectedBillSensor.py deleted file mode 100644 index 44650f0..0000000 --- a/custom_components/fpl/fpl/sensor_ProjectedBillSensor.py +++ /dev/null @@ -1,99 +0,0 @@ -from .fplEntity import FplEntity - - -class FplProjectedBillSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Projected Bill") - - @property - def state(self): - 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: - return self.getData("budget_billing_projected_bill") - - return self.getData("projected_bill") - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Projected Bill" - attributes["device_class"] = "monitary" - attributes["state_class"] = "total" - attributes["unit_of_measurement"] = "$" - return attributes - - @property - def icon(self): - return "mdi:currency-usd" - - -# Defered Amount -class DeferedAmountSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Defered Amount") - - @property - def state(self): - if self.getData("defered_amount") is not None: - return self.getData("defered_amount") - return 0 - - @property - def icon(self): - return "mdi:currency-usd" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Defered Amount" - attributes["device_class"] = "monitary" - attributes["state_class"] = "total" - attributes["unit_of_measurement"] = "$" - return attributes - - -class ProjectedBudgetBillSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Projected Budget Bill") - - @property - def state(self): - return self.getData("budget_billing_projected_bill") - - @property - def icon(self): - return "mdi:currency-usd" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Projected Budget Bill" - attributes["device_class"] = "monitary" - attributes["state_class"] = "total" - attributes['unit_of_measurement'] = "$" - return attributes - - -class ProjectedActualBillSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Projected Actual Bill") - - @property - def state(self): - return self.getData("projected_bill") - - @property - def icon(self): - return "mdi:currency-usd" - - def defineAttributes(self): - """Return the state attributes.""" - attributes = {} - attributes["friendly_name"] = "Projected Actual Bill" - attributes["device_class"] = "monitary" - attributes["state_class"] = "total" - attributes['unit_of_measurement'] = "$" - - return attributes diff --git a/custom_components/fpl/fplDataUpdateCoordinator.py b/custom_components/fpl/fplDataUpdateCoordinator.py index e4056ac..5679cbf 100644 --- a/custom_components/fpl/fplDataUpdateCoordinator.py +++ b/custom_components/fpl/fplDataUpdateCoordinator.py @@ -7,7 +7,7 @@ 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 7b30d27..e9bf200 100644 --- a/custom_components/fpl/fplEntity.py +++ b/custom_components/fpl/fplEntity.py @@ -19,15 +19,11 @@ class FplEntity(CoordinatorEntity): ) return id - @property - def name(self): - return f"{DOMAIN.upper()} {self.account} {self.sensorName}" - @property def device_info(self): return { "identifiers": {(DOMAIN, self.account)}, - "name": f"Account {self.account}", + "name": f"FPL Account {self.account}", "model": VERSION, "manufacturer": "Florida Power & Light", } diff --git a/custom_components/fpl/fplapi.py b/custom_components/fpl/fplapi.py index 3265f81..4790cd0 100644 --- a/custom_components/fpl/fplapi.py +++ b/custom_components/fpl/fplapi.py @@ -300,6 +300,9 @@ class FplApi(object): "cost": daily["billingCharge"], "date": daily["date"], "max_temperature": daily["averageHighTemperature"], + "netDeliveredKwh": daily["netDeliveredKwh"], + "netReceivedKwh": daily["netReceivedKwh"], + "readTime": daily["readTime"], } ) # totalPowerUsage += int(daily["kwhUsed"]) @@ -312,6 +315,7 @@ class FplApi(object): 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: diff --git a/custom_components/fpl/sensor.py b/custom_components/fpl/sensor.py index a868e1e..6b2c85d 100644 --- a/custom_components/fpl/sensor.py +++ b/custom_components/fpl/sensor.py @@ -23,9 +23,14 @@ from .sensor_ProjectedBillSensor import ( from .sensor_AverageDailySensor import ( FplAverageDailySensor, BudgetDailyAverageSensor, - ActualDailyAverageSensor, ) -from .sensor_DailyUsageSensor import FplDailyUsageKWHSensor, FplDailyUsageSensor +from .sensor_DailyUsageSensor import ( + FplDailyUsageKWHSensor, + FplDailyUsageSensor, + FplDailyDeliveredKWHSensor, + FplDailyReceivedKWHSensor, +) + from .const import DOMAIN from .sensor_AllData import AllDataSensor @@ -54,10 +59,10 @@ async def async_setup_entry(hass, entry, async_add_devices): # usage sensors fpl_accounts.append(FplAverageDailySensor(coordinator, entry, account)) fpl_accounts.append(BudgetDailyAverageSensor(coordinator, entry, account)) - fpl_accounts.append(ActualDailyAverageSensor(coordinator, entry, account)) - fpl_accounts.append(FplDailyUsageSensor(coordinator, entry, account)) fpl_accounts.append(FplDailyUsageKWHSensor(coordinator, entry, account)) + fpl_accounts.append(FplDailyReceivedKWHSensor(coordinator, entry, account)) + fpl_accounts.append(FplDailyDeliveredKWHSensor(coordinator, entry, account)) # date sensors fpl_accounts.append(CurrentBillDateSensor(coordinator, entry, account)) @@ -70,5 +75,8 @@ async def async_setup_entry(hass, entry, async_add_devices): fpl_accounts.append(ProjectedKWHSensor(coordinator, entry, account)) fpl_accounts.append(DailyAverageKWHSensor(coordinator, entry, account)) fpl_accounts.append(BillToDateKWHSensor(coordinator, entry, account)) + fpl_accounts.append(NetReceivedKWHSensor(coordinator, entry, account)) + fpl_accounts.append(NetDeliveredKWHSensor(coordinator, entry, account)) + async_add_devices(fpl_accounts) diff --git a/custom_components/fpl/sensor_AllData.py b/custom_components/fpl/sensor_AllData.py index 98044b3..9f9840d 100644 --- a/custom_components/fpl/sensor_AllData.py +++ b/custom_components/fpl/sensor_AllData.py @@ -22,3 +22,12 @@ class AllDataSensor(FplEntity): @property def icon(self): return "mdi:currency-usd" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Budget Projected Bill" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" + return attributes \ No newline at end of file diff --git a/custom_components/fpl/sensor_AverageDailySensor.py b/custom_components/fpl/sensor_AverageDailySensor.py index 66d25f1..d170470 100644 --- a/custom_components/fpl/sensor_AverageDailySensor.py +++ b/custom_components/fpl/sensor_AverageDailySensor.py @@ -19,6 +19,14 @@ class FplAverageDailySensor(FplEntity): def icon(self): return "mdi:currency-usd" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Daily Average" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" + return attributes class BudgetDailyAverageSensor(FplEntity): def __init__(self, coordinator, config, account): @@ -32,15 +40,12 @@ class BudgetDailyAverageSensor(FplEntity): def icon(self): return "mdi:currency-usd" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Budget Daily Average" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" + return attributes -class ActualDailyAverageSensor(FplEntity): - def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Actual Daily Average") - - @property - def state(self): - return self.getData("daily_avg") - - @property - def icon(self): - return "mdi:currency-usd" diff --git a/custom_components/fpl/sensor_DailyUsageSensor.py b/custom_components/fpl/sensor_DailyUsageSensor.py index 299188e..3ec2928 100644 --- a/custom_components/fpl/sensor_DailyUsageSensor.py +++ b/custom_components/fpl/sensor_DailyUsageSensor.py @@ -9,7 +9,7 @@ class FplDailyUsageSensor(FplEntity): def state(self): data = self.getData("daily_usage") - if len(data) > 0: + if ((data is not None) and (len(data) > 0)): return data[-1]["cost"] return None @@ -17,11 +17,14 @@ class FplDailyUsageSensor(FplEntity): def defineAttributes(self): """Return the state attributes.""" data = self.getData("daily_usage") - - if len(data) > 0: - return {"date": data[-1]["date"]} - - return {} + attributes = {} + attributes["friendly_name"] = "Daily Usage" + attributes["device_class"] = "monetary" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "$" + if ((data is not None) and (data[-1]["cost"] is not None)): + attributes["date"] = data[-1]["readTime"] + return attributes @property def icon(self): @@ -36,7 +39,7 @@ class FplDailyUsageKWHSensor(FplEntity): def state(self): data = self.getData("daily_usage") - if len(data) > 0: + if ((data is not None) and (data[-1]["usage"] is not None)): return data[-1]["usage"] return None @@ -45,11 +48,73 @@ class FplDailyUsageKWHSensor(FplEntity): """Return the state attributes.""" data = self.getData("daily_usage") - if len(data) > 0: - return {"date": data[-1]["date"]} + attributes = {} + attributes["friendly_name"] = "Daily Usage" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" - return {} + if data is not None: + if ((data[-1] is not None) and (data[-1]["readTime"] is not None)): + attributes["date"] = data[-1]["readTime"] + if ((data[-2] is not None) and (data[-2]["readTime"] is not None)): + attributes["last_reset"] = data[-2]["readTime"] + + return attributes @property def icon(self): - return "mdi:currency-usd" + return "mdi:flash" + +class FplDailyReceivedKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Received KWH") + + @property + def state(self): + data = self.getData("daily_usage") + return data[-1]["netReceivedKwh"] + + def defineAttributes(self): + """Return the state attributes.""" + data = self.getData("daily_usage") + + attributes = {} + attributes["friendly_name"] = "Daily Return to Grid" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + attributes["date"] = data[-1]["readTime"] + attributes["last_reset"] = data[-2]["readTime"] + return attributes + + + @property + def icon(self): + return "mdi:flash" + +class FplDailyDeliveredKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Delivered KWH") + + @property + def state(self): + data = self.getData("daily_usage") + return data[-1]["netDeliveredKwh"] + + def defineAttributes(self): + """Return the state attributes.""" + data = self.getData("daily_usage") + + attributes = {} + attributes["friendly_name"] = "Daily Consumption" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + attributes["date"] = data[-1]["readTime"] + attributes["last_reset"] = data[-2]["readTime"] + return attributes + + @property + def icon(self): + return "mdi:flash" diff --git a/custom_components/fpl/sensor_DatesSensor.py b/custom_components/fpl/sensor_DatesSensor.py index b0414d7..a301847 100644 --- a/custom_components/fpl/sensor_DatesSensor.py +++ b/custom_components/fpl/sensor_DatesSensor.py @@ -1,35 +1,47 @@ from .fplEntity import FplEntity - +import datetime class CurrentBillDateSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Current Bill Date") + super().__init__(coordinator, config, account, "Billing Current Date") @property def state(self): - return self.getData("current_bill_date") + return datetime.date.fromisoformat(self.getData("current_bill_date")) @property def icon(self): return "mdi:calendar" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["device_class"] = "date" + attributes["friendly_name"] = "Billing Current" + return attributes class NextBillDateSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Next Bill Date") + super().__init__(coordinator, config, account, "Billing Next") @property def state(self): - return self.getData("next_bill_date") + return datetime.date.fromisoformat(self.getData("next_bill_date")) @property def icon(self): return "mdi:calendar" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["device_class"] = "date" + attributes["friendly_name"] = "Billing Next" + return attributes class ServiceDaysSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Service Days") + super().__init__(coordinator, config, account, "Billing Total Days") @property def state(self): @@ -39,10 +51,16 @@ class ServiceDaysSensor(FplEntity): def icon(self): return "mdi:calendar" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["unit_of_measurement"] = "days" + attributes["friendly_name"] = "Billing Total" + return attributes class AsOfDaysSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "As Of Days") + super().__init__(coordinator, config, account, "Billing As Of") @property def state(self): @@ -52,10 +70,16 @@ class AsOfDaysSensor(FplEntity): def icon(self): return "mdi:calendar" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["unit_of_measurement"] = "days" + attributes["friendly_name"] = "Billing As Of" + return attributes class RemainingDaysSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Remaining Days") + super().__init__(coordinator, config, account, "Billing Remaining") @property def state(self): @@ -64,3 +88,10 @@ class RemainingDaysSensor(FplEntity): @property def icon(self): return "mdi:calendar" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["unit_of_measurement"] = "days" + attributes["friendly_name"] = "Billing Remaining" + return attributes diff --git a/custom_components/fpl/sensor_KWHSensor.py b/custom_components/fpl/sensor_KWHSensor.py index ba2d24a..449f893 100644 --- a/custom_components/fpl/sensor_KWHSensor.py +++ b/custom_components/fpl/sensor_KWHSensor.py @@ -3,7 +3,7 @@ from .fplEntity import FplEntity class ProjectedKWHSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Projected KWH") + super().__init__(coordinator, config, account, "Projected") @property def state(self): @@ -12,11 +12,18 @@ class ProjectedKWHSensor(FplEntity): @property def icon(self): return "mdi:flash" - + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Projected KWH" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + return attributes class DailyAverageKWHSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Daily Average KWH") + super().__init__(coordinator, config, account, "Daily Average") @property def state(self): @@ -26,10 +33,18 @@ class DailyAverageKWHSensor(FplEntity): def icon(self): return "mdi:flash" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Daily Average" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + return attributes class BillToDateKWHSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Bill To Date KWH") + super().__init__(coordinator, config, account, "Bill To Date") @property def state(self): @@ -39,9 +54,18 @@ class BillToDateKWHSensor(FplEntity): def icon(self): return "mdi:flash" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Bill To Date" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + return attributes + class NetReceivedKWHSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Received Meter Reading KWH") + super().__init__(coordinator, config, account, "Received Reading") @property def state(self): @@ -51,9 +75,20 @@ class NetReceivedKWHSensor(FplEntity): def icon(self): return "mdi:flash" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Meter Return to Grid" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + attributes["last_reset"] = self.getData("billStartDate") + + return attributes + class NetDeliveredKWHSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Delivered Meter Reading KWH") + super().__init__(coordinator, config, account, "Delivered Reading") @property def state(self): @@ -62,3 +97,14 @@ class NetDeliveredKWHSensor(FplEntity): @property def icon(self): return "mdi:flash" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Meter Consumption" + attributes["device_class"] = "energy" + attributes["state_class"] = "total_increasing" + attributes["unit_of_measurement"] = "kWh" + attributes["last_reset"] = self.getData("billStartDate") + + return attributes diff --git a/custom_components/fpl/sensor_ProjectedBillSensor.py b/custom_components/fpl/sensor_ProjectedBillSensor.py index acf7482..44650f0 100644 --- a/custom_components/fpl/sensor_ProjectedBillSensor.py +++ b/custom_components/fpl/sensor_ProjectedBillSensor.py @@ -18,12 +18,10 @@ class FplProjectedBillSensor(FplEntity): 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["friendly_name"] = "Projected Bill" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" return attributes @property @@ -38,7 +36,7 @@ class DeferedAmountSensor(FplEntity): @property def state(self): - if self.getData("budget_bill") == True: + if self.getData("defered_amount") is not None: return self.getData("defered_amount") return 0 @@ -46,6 +44,15 @@ class DeferedAmountSensor(FplEntity): def icon(self): return "mdi:currency-usd" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Defered Amount" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes["unit_of_measurement"] = "$" + return attributes + class ProjectedBudgetBillSensor(FplEntity): def __init__(self, coordinator, config, account): @@ -59,6 +66,15 @@ class ProjectedBudgetBillSensor(FplEntity): def icon(self): return "mdi:currency-usd" + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Projected Budget Bill" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes['unit_of_measurement'] = "$" + return attributes + class ProjectedActualBillSensor(FplEntity): def __init__(self, coordinator, config, account): @@ -71,3 +87,13 @@ class ProjectedActualBillSensor(FplEntity): @property def icon(self): return "mdi:currency-usd" + + def defineAttributes(self): + """Return the state attributes.""" + attributes = {} + attributes["friendly_name"] = "Projected Actual Bill" + attributes["device_class"] = "monitary" + attributes["state_class"] = "total" + attributes['unit_of_measurement'] = "$" + + return attributes From 485717d662fd334e8452806c0b233136aacc9183 Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Thu, 30 Dec 2021 15:07:04 -0500 Subject: [PATCH 3/7] Fixing sensor metadata --- custom_components/fpl/sensor_AllData.py | 2 +- .../fpl/sensor_AverageDailySensor.py | 2 +- .../fpl/sensor_DailyUsageSensor.py | 41 ++++++++++++------- custom_components/fpl/sensor_KWHSensor.py | 17 +++++--- .../fpl/sensor_ProjectedBillSensor.py | 14 +++---- 5 files changed, 48 insertions(+), 28 deletions(-) diff --git a/custom_components/fpl/sensor_AllData.py b/custom_components/fpl/sensor_AllData.py index 9f9840d..0e1ccad 100644 --- a/custom_components/fpl/sensor_AllData.py +++ b/custom_components/fpl/sensor_AllData.py @@ -27,7 +27,7 @@ class AllDataSensor(FplEntity): """Return the state attributes.""" attributes = {} attributes["friendly_name"] = "Budget Projected Bill" - attributes["device_class"] = "monitary" + attributes["device_class"] = "monetary" attributes["state_class"] = "total" attributes["unit_of_measurement"] = "$" return attributes \ No newline at end of file diff --git a/custom_components/fpl/sensor_AverageDailySensor.py b/custom_components/fpl/sensor_AverageDailySensor.py index d170470..10bc13c 100644 --- a/custom_components/fpl/sensor_AverageDailySensor.py +++ b/custom_components/fpl/sensor_AverageDailySensor.py @@ -23,7 +23,7 @@ class FplAverageDailySensor(FplEntity): """Return the state attributes.""" attributes = {} attributes["friendly_name"] = "Daily Average" - attributes["device_class"] = "monitary" + attributes["device_class"] = "monetary" attributes["state_class"] = "total" attributes["unit_of_measurement"] = "$" return attributes diff --git a/custom_components/fpl/sensor_DailyUsageSensor.py b/custom_components/fpl/sensor_DailyUsageSensor.py index 3ec2928..594b686 100644 --- a/custom_components/fpl/sensor_DailyUsageSensor.py +++ b/custom_components/fpl/sensor_DailyUsageSensor.py @@ -9,7 +9,7 @@ class FplDailyUsageSensor(FplEntity): def state(self): data = self.getData("daily_usage") - if ((data is not None) and (len(data) > 0)): + if (data is not None) and (len(data) > 0): return data[-1]["cost"] return None @@ -22,8 +22,11 @@ class FplDailyUsageSensor(FplEntity): attributes["device_class"] = "monetary" attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "$" - if ((data is not None) and (data[-1]["cost"] is not None)): - attributes["date"] = data[-1]["readTime"] + if data is not None: + if (data[-1] is not None) and (data[-1]["readTime"] is not None): + attributes["date"] = data[-1]["readTime"] + if (data[-2] is not None) and (data[-2]["readTime"] is not None): + attributes["last_reset"] = data[-2]["readTime"] return attributes @property @@ -39,7 +42,7 @@ class FplDailyUsageKWHSensor(FplEntity): def state(self): data = self.getData("daily_usage") - if ((data is not None) and (data[-1]["usage"] is not None)): + if (data is not None) and (data[-1]["usage"] is not None): return data[-1]["usage"] return None @@ -55,17 +58,18 @@ class FplDailyUsageKWHSensor(FplEntity): attributes["unit_of_measurement"] = "kWh" if data is not None: - if ((data[-1] is not None) and (data[-1]["readTime"] is not None)): + if (data[-1] is not None) and (data[-1]["readTime"] is not None): attributes["date"] = data[-1]["readTime"] - if ((data[-2] is not None) and (data[-2]["readTime"] is not None)): + if (data[-2] is not None) and (data[-2]["readTime"] is not None): attributes["last_reset"] = data[-2]["readTime"] - + return attributes @property def icon(self): return "mdi:flash" + class FplDailyReceivedKWHSensor(FplEntity): def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Daily Received KWH") @@ -73,7 +77,10 @@ class FplDailyReceivedKWHSensor(FplEntity): @property def state(self): data = self.getData("daily_usage") - return data[-1]["netReceivedKwh"] + try: + return data[-1]["netReceivedKwh"] + except: + return 0 def defineAttributes(self): """Return the state attributes.""" @@ -84,15 +91,18 @@ class FplDailyReceivedKWHSensor(FplEntity): attributes["device_class"] = "energy" attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "kWh" - attributes["date"] = data[-1]["readTime"] - attributes["last_reset"] = data[-2]["readTime"] + if data is not None: + if (data[-1] is not None) and (data[-1]["readTime"] is not None): + attributes["date"] = data[-1]["readTime"] + if (data[-2] is not None) and (data[-2]["readTime"] is not None): + attributes["last_reset"] = data[-2]["readTime"] return attributes - @property def icon(self): return "mdi:flash" + class FplDailyDeliveredKWHSensor(FplEntity): def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Daily Delivered KWH") @@ -100,7 +110,7 @@ class FplDailyDeliveredKWHSensor(FplEntity): @property def state(self): data = self.getData("daily_usage") - return data[-1]["netDeliveredKwh"] + return data[-1]["netDeliveredKwh"] def defineAttributes(self): """Return the state attributes.""" @@ -111,8 +121,11 @@ class FplDailyDeliveredKWHSensor(FplEntity): attributes["device_class"] = "energy" attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "kWh" - attributes["date"] = data[-1]["readTime"] - attributes["last_reset"] = data[-2]["readTime"] + if data is not None: + if (data[-1] is not None) and (data[-1]["readTime"] is not None): + attributes["date"] = data[-1]["readTime"] + if (data[-2] is not None) and (data[-2]["readTime"] is not None): + attributes["last_reset"] = data[-2]["readTime"] return attributes @property diff --git a/custom_components/fpl/sensor_KWHSensor.py b/custom_components/fpl/sensor_KWHSensor.py index 449f893..92b7992 100644 --- a/custom_components/fpl/sensor_KWHSensor.py +++ b/custom_components/fpl/sensor_KWHSensor.py @@ -17,7 +17,7 @@ class ProjectedKWHSensor(FplEntity): attributes = {} attributes["friendly_name"] = "Projected KWH" attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" + attributes["state_class"] = "total" attributes["unit_of_measurement"] = "kWh" return attributes @@ -38,7 +38,7 @@ class DailyAverageKWHSensor(FplEntity): attributes = {} attributes["friendly_name"] = "Daily Average" attributes["device_class"] = "energy" - attributes["state_class"] = "total_increasing" + attributes["state_class"] = "total" attributes["unit_of_measurement"] = "kWh" return attributes @@ -57,10 +57,11 @@ class BillToDateKWHSensor(FplEntity): def defineAttributes(self): """Return the state attributes.""" attributes = {} - attributes["friendly_name"] = "Bill To Date" + attributes["friendly_name"] = "Billing Usage" attributes["device_class"] = "energy" attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "kWh" + attributes["last_reset"] = self.getData("billStartDate") return attributes class NetReceivedKWHSensor(FplEntity): @@ -69,7 +70,10 @@ class NetReceivedKWHSensor(FplEntity): @property def state(self): - return self.getData("recMtrReading") + try: + return self.getData("recMtrReading") + except: + return 0 @property def icon(self): @@ -92,7 +96,10 @@ class NetDeliveredKWHSensor(FplEntity): @property def state(self): - return self.getData("delMtrReading") + try: + return self.getData("delMtrReading") + except: + return self.getData("billToDateKWH") @property def icon(self): diff --git a/custom_components/fpl/sensor_ProjectedBillSensor.py b/custom_components/fpl/sensor_ProjectedBillSensor.py index 44650f0..1113dd9 100644 --- a/custom_components/fpl/sensor_ProjectedBillSensor.py +++ b/custom_components/fpl/sensor_ProjectedBillSensor.py @@ -18,8 +18,8 @@ class FplProjectedBillSensor(FplEntity): def defineAttributes(self): """Return the state attributes.""" attributes = {} - attributes["friendly_name"] = "Projected Bill" - attributes["device_class"] = "monitary" + attributes["friendly_name"] = "Projected Bill Payment Due" + attributes["device_class"] = "monetary" attributes["state_class"] = "total" attributes["unit_of_measurement"] = "$" return attributes @@ -32,12 +32,12 @@ class FplProjectedBillSensor(FplEntity): # Defered Amount class DeferedAmountSensor(FplEntity): def __init__(self, coordinator, config, account): - super().__init__(coordinator, config, account, "Defered Amount") + super().__init__(coordinator, config, account, "Deferred Amount") @property def state(self): - if self.getData("defered_amount") is not None: - return self.getData("defered_amount") + if self.getData("deferred_amount") is not None: + return self.getData("deferred_amount") return 0 @property @@ -47,8 +47,8 @@ class DeferedAmountSensor(FplEntity): def defineAttributes(self): """Return the state attributes.""" attributes = {} - attributes["friendly_name"] = "Defered Amount" - attributes["device_class"] = "monitary" + attributes["friendly_name"] = "Deferred Amount" + attributes["device_class"] = "monetary" attributes["state_class"] = "total" attributes["unit_of_measurement"] = "$" return attributes From a3a59505bc4aee657ed584507c1d565e24c41d3d Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Thu, 30 Dec 2021 15:17:38 -0500 Subject: [PATCH 4/7] Correcting issue where sensor is not available and goes offline instead of reporting 0 --- custom_components/fpl/sensor_DailyUsageSensor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/custom_components/fpl/sensor_DailyUsageSensor.py b/custom_components/fpl/sensor_DailyUsageSensor.py index 594b686..cb36367 100644 --- a/custom_components/fpl/sensor_DailyUsageSensor.py +++ b/custom_components/fpl/sensor_DailyUsageSensor.py @@ -110,7 +110,10 @@ class FplDailyDeliveredKWHSensor(FplEntity): @property def state(self): data = self.getData("daily_usage") - return data[-1]["netDeliveredKwh"] + try: + return data[-1]["netDeliveredKwh"] + except: + return 0 def defineAttributes(self): """Return the state attributes.""" From ba77a5e2c2cd218ae8b31bef52e9c0b7df32774f Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Thu, 30 Dec 2021 15:34:18 -0500 Subject: [PATCH 5/7] return none if no data is available --- custom_components/fpl/sensor_DailyUsageSensor.py | 4 ++-- custom_components/fpl/sensor_KWHSensor.py | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/custom_components/fpl/sensor_DailyUsageSensor.py b/custom_components/fpl/sensor_DailyUsageSensor.py index cb36367..bbbb254 100644 --- a/custom_components/fpl/sensor_DailyUsageSensor.py +++ b/custom_components/fpl/sensor_DailyUsageSensor.py @@ -80,7 +80,7 @@ class FplDailyReceivedKWHSensor(FplEntity): try: return data[-1]["netReceivedKwh"] except: - return 0 + return None def defineAttributes(self): """Return the state attributes.""" @@ -113,7 +113,7 @@ class FplDailyDeliveredKWHSensor(FplEntity): try: return data[-1]["netDeliveredKwh"] except: - return 0 + return None def defineAttributes(self): """Return the state attributes.""" diff --git a/custom_components/fpl/sensor_KWHSensor.py b/custom_components/fpl/sensor_KWHSensor.py index 92b7992..038df97 100644 --- a/custom_components/fpl/sensor_KWHSensor.py +++ b/custom_components/fpl/sensor_KWHSensor.py @@ -61,7 +61,8 @@ class BillToDateKWHSensor(FplEntity): attributes["device_class"] = "energy" attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "kWh" - attributes["last_reset"] = self.getData("billStartDate") + if self.getData("billStartDate") is not None: + attributes["last_reset"] = self.getData("billStartDate") return attributes class NetReceivedKWHSensor(FplEntity): @@ -73,7 +74,7 @@ class NetReceivedKWHSensor(FplEntity): try: return self.getData("recMtrReading") except: - return 0 + return None @property def icon(self): @@ -86,7 +87,8 @@ class NetReceivedKWHSensor(FplEntity): attributes["device_class"] = "energy" attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "kWh" - attributes["last_reset"] = self.getData("billStartDate") + if self.getData("billStartDate") is not None: + attributes["last_reset"] = self.getData("billStartDate") return attributes @@ -99,7 +101,10 @@ class NetDeliveredKWHSensor(FplEntity): try: return self.getData("delMtrReading") except: - return self.getData("billToDateKWH") + try: + return self.getData("billToDateKWH") + except: + return None @property def icon(self): @@ -112,6 +117,7 @@ class NetDeliveredKWHSensor(FplEntity): attributes["device_class"] = "energy" attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "kWh" - attributes["last_reset"] = self.getData("billStartDate") + if self.getData("billStartDate") is not None: + attributes["last_reset"] = self.getData("billStartDate") return attributes From 0b4f5abb5bfcaf36e7cff32b84d021dad666f20c Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Sat, 1 Jan 2022 12:17:37 -0500 Subject: [PATCH 6/7] fill in missing gaps with previous state --- custom_components/fpl/ProjectedBillSensor.py | 11 ++- custom_components/fpl/fplEntity.py | 3 +- custom_components/fpl/manifest.json | 2 +- custom_components/fpl/sensor_AllData.py | 10 +-- .../fpl/sensor_AverageDailySensor.py | 12 ++- .../fpl/sensor_DailyUsageSensor.py | 78 ++++++++++++++----- custom_components/fpl/sensor_DatesSensor.py | 30 +++++-- custom_components/fpl/sensor_KWHSensor.py | 35 +++++++-- .../fpl/sensor_ProjectedBillSensor.py | 39 +++++++--- 9 files changed, 159 insertions(+), 61 deletions(-) diff --git a/custom_components/fpl/ProjectedBillSensor.py b/custom_components/fpl/ProjectedBillSensor.py index bf14a32..8945dd0 100644 --- a/custom_components/fpl/ProjectedBillSensor.py +++ b/custom_components/fpl/ProjectedBillSensor.py @@ -24,13 +24,12 @@ class FplProjectedBillSensor(FplSensor): @property def device_state_attributes(self): """Return the state attributes.""" - try: - if "budget_bill" in self.data.keys(): - self.attr["budget_bill"] = self.data["budget_bill"] - except: - pass - return self.attr + if "budget_bill" in self.data.keys(): + self.attr["budget_bill"] = self.data["budget_bill"] + + + return self._state @property def icon(self): diff --git a/custom_components/fpl/fplEntity.py b/custom_components/fpl/fplEntity.py index e9bf200..c19937a 100644 --- a/custom_components/fpl/fplEntity.py +++ b/custom_components/fpl/fplEntity.py @@ -24,7 +24,8 @@ class FplEntity(CoordinatorEntity): return { "identifiers": {(DOMAIN, self.account)}, "name": f"FPL Account {self.account}", - "model": VERSION, + "model": "FPL Monitoring API", + "sw_version": VERSION, "manufacturer": "Florida Power & Light", } diff --git a/custom_components/fpl/manifest.json b/custom_components/fpl/manifest.json index 805319d..4993feb 100644 --- a/custom_components/fpl/manifest.json +++ b/custom_components/fpl/manifest.json @@ -12,6 +12,6 @@ "bs4", "integrationhelper" ], - "homeassistant": "0.96.0", + "homeassistant": "2021.12.7", "version": "1.0.0" } \ No newline at end of file diff --git a/custom_components/fpl/sensor_AllData.py b/custom_components/fpl/sensor_AllData.py index 0e1ccad..d8395fd 100644 --- a/custom_components/fpl/sensor_AllData.py +++ b/custom_components/fpl/sensor_AllData.py @@ -13,11 +13,11 @@ class AllDataSensor(FplEntity): if budget == True and budget_billing_projected_bill is not None: return self.getData("budget_billing_projected_bill") - return self.getData("projected_bill") - - def defineAttributes(self): - """Return the state attributes.""" - return self.coordinator.data.get(self.account) + try: + self._state=self.getData("projected_bill") + except: + pass + return self._state @property def icon(self): diff --git a/custom_components/fpl/sensor_AverageDailySensor.py b/custom_components/fpl/sensor_AverageDailySensor.py index 10bc13c..2b40f75 100644 --- a/custom_components/fpl/sensor_AverageDailySensor.py +++ b/custom_components/fpl/sensor_AverageDailySensor.py @@ -13,7 +13,11 @@ class FplAverageDailySensor(FplEntity): if budget == True and budget_billing_projected_bill is not None: return self.getData("budget_billing_daily_avg") - return self.getData("daily_avg") + try: + self._state=self.getData("daily_avg") + except: + pass + return self._state @property def icon(self): @@ -34,7 +38,11 @@ class BudgetDailyAverageSensor(FplEntity): @property def state(self): - return self.getData("budget_billing_daily_avg") + try: + self._state= self.getData("budget_billing_daily_avg") + except: + pass + return self._state @property def icon(self): diff --git a/custom_components/fpl/sensor_DailyUsageSensor.py b/custom_components/fpl/sensor_DailyUsageSensor.py index bbbb254..98cdeb5 100644 --- a/custom_components/fpl/sensor_DailyUsageSensor.py +++ b/custom_components/fpl/sensor_DailyUsageSensor.py @@ -9,10 +9,11 @@ class FplDailyUsageSensor(FplEntity): def state(self): data = self.getData("daily_usage") - if (data is not None) and (len(data) > 0): - return data[-1]["cost"] - - return None + try: + self._state = data[-1]["cost"] + except: + pass + return self._state def defineAttributes(self): """Return the state attributes.""" @@ -23,9 +24,17 @@ class FplDailyUsageSensor(FplEntity): attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "$" if data is not None: - if (data[-1] is not None) and (data[-1]["readTime"] is not None): + if ( + (len(data) > 0) + and (data[-1] is not None) + and (data[-1]["readTime"] is not None) + ): attributes["date"] = data[-1]["readTime"] - if (data[-2] is not None) and (data[-2]["readTime"] is not None): + if ( + (len(data) > 1) + and (data[-2] is not None) + and (data[-2]["readTime"] is not None) + ): attributes["last_reset"] = data[-2]["readTime"] return attributes @@ -42,10 +51,11 @@ class FplDailyUsageKWHSensor(FplEntity): def state(self): data = self.getData("daily_usage") - if (data is not None) and (data[-1]["usage"] is not None): - return data[-1]["usage"] - - return None + try: + self._state = data[-1]["usage"] + except: + pass + return self._state def defineAttributes(self): """Return the state attributes.""" @@ -58,9 +68,17 @@ class FplDailyUsageKWHSensor(FplEntity): attributes["unit_of_measurement"] = "kWh" if data is not None: - if (data[-1] is not None) and (data[-1]["readTime"] is not None): + if ( + (len(data) > 0) + and (data[-1] is not None) + and (data[-1]["readTime"] is not None) + ): attributes["date"] = data[-1]["readTime"] - if (data[-2] is not None) and (data[-2]["readTime"] is not None): + if ( + (len(data) > 1) + and (data[-2] is not None) + and (data[-2]["readTime"] is not None) + ): attributes["last_reset"] = data[-2]["readTime"] return attributes @@ -78,9 +96,10 @@ class FplDailyReceivedKWHSensor(FplEntity): def state(self): data = self.getData("daily_usage") try: - return data[-1]["netReceivedKwh"] + self._state = data[-1]["netReceivedKwh"] except: - return None + pass + return self._state def defineAttributes(self): """Return the state attributes.""" @@ -92,9 +111,17 @@ class FplDailyReceivedKWHSensor(FplEntity): attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "kWh" if data is not None: - if (data[-1] is not None) and (data[-1]["readTime"] is not None): + if ( + (len(data) > 0) + and (data[-1] is not None) + and (data[-1]["readTime"] is not None) + ): attributes["date"] = data[-1]["readTime"] - if (data[-2] is not None) and (data[-2]["readTime"] is not None): + if ( + (len(data) > 1) + and (data[-2] is not None) + and (data[-2]["readTime"] is not None) + ): attributes["last_reset"] = data[-2]["readTime"] return attributes @@ -110,10 +137,11 @@ class FplDailyDeliveredKWHSensor(FplEntity): @property def state(self): data = self.getData("daily_usage") - try: - return data[-1]["netDeliveredKwh"] + try: + self._state = data[-1]["netDeliveredKwh"] except: - return None + pass + return self._state def defineAttributes(self): """Return the state attributes.""" @@ -125,9 +153,17 @@ class FplDailyDeliveredKWHSensor(FplEntity): attributes["state_class"] = "total_increasing" attributes["unit_of_measurement"] = "kWh" if data is not None: - if (data[-1] is not None) and (data[-1]["readTime"] is not None): + if ( + (len(data) > 0) + and (data[-1] is not None) + and (data[-1]["readTime"] is not None) + ): attributes["date"] = data[-1]["readTime"] - if (data[-2] is not None) and (data[-2]["readTime"] is not None): + if ( + (len(data) > 1) + and (data[-2] is not None) + and (data[-2]["readTime"] is not None) + ): 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 a301847..cfc05a9 100644 --- a/custom_components/fpl/sensor_DatesSensor.py +++ b/custom_components/fpl/sensor_DatesSensor.py @@ -7,7 +7,11 @@ class CurrentBillDateSensor(FplEntity): @property def state(self): - return datetime.date.fromisoformat(self.getData("current_bill_date")) + try: + self._state= datetime.date.fromisoformat(self.getData("current_bill_date")) + except: + pass + return self._state @property def icon(self): @@ -26,7 +30,11 @@ class NextBillDateSensor(FplEntity): @property def state(self): - return datetime.date.fromisoformat(self.getData("next_bill_date")) + try: + self._state= datetime.date.fromisoformat(self.getData("next_bill_date")) + except: + pass + return self._state @property def icon(self): @@ -45,7 +53,11 @@ class ServiceDaysSensor(FplEntity): @property def state(self): - return self.getData("service_days") + try: + self._state= self.getData("service_days") + except: + pass + return self._state @property def icon(self): @@ -64,7 +76,11 @@ class AsOfDaysSensor(FplEntity): @property def state(self): - return self.getData("as_of_days") + try: + self._state= self.getData("as_of_days") + except: + pass + return self._state @property def icon(self): @@ -83,7 +99,11 @@ class RemainingDaysSensor(FplEntity): @property def state(self): - return self.getData("remaining_days") + try: + self._state= self.getData("remaining_days") + except: + pass + return self._state @property def icon(self): diff --git a/custom_components/fpl/sensor_KWHSensor.py b/custom_components/fpl/sensor_KWHSensor.py index 038df97..965d541 100644 --- a/custom_components/fpl/sensor_KWHSensor.py +++ b/custom_components/fpl/sensor_KWHSensor.py @@ -7,11 +7,16 @@ class ProjectedKWHSensor(FplEntity): @property def state(self): - return self.getData("projectedKWH") + try: + self._state = self.getData("projectedKWH") + except: + pass + return self._state @property def icon(self): return "mdi:flash" + def defineAttributes(self): """Return the state attributes.""" attributes = {} @@ -21,13 +26,18 @@ class ProjectedKWHSensor(FplEntity): attributes["unit_of_measurement"] = "kWh" return attributes + class DailyAverageKWHSensor(FplEntity): def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Daily Average") @property def state(self): - return self.getData("dailyAverageKWH") + try: + self._state = self.getData("dailyAverageKWH") + except: + pass + return self._state @property def icon(self): @@ -42,13 +52,18 @@ class DailyAverageKWHSensor(FplEntity): attributes["unit_of_measurement"] = "kWh" return attributes + class BillToDateKWHSensor(FplEntity): def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Bill To Date") @property def state(self): - return self.getData("billToDateKWH") + try: + self._state = self.getData("billToDateKWH") + except: + pass + return self._state @property def icon(self): @@ -65,6 +80,7 @@ class BillToDateKWHSensor(FplEntity): attributes["last_reset"] = self.getData("billStartDate") return attributes + class NetReceivedKWHSensor(FplEntity): def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Received Reading") @@ -72,9 +88,10 @@ class NetReceivedKWHSensor(FplEntity): @property def state(self): try: - return self.getData("recMtrReading") + self._state = self.getData("recMtrReading") except: - return None + pass + return self._state @property def icon(self): @@ -92,6 +109,7 @@ class NetReceivedKWHSensor(FplEntity): return attributes + class NetDeliveredKWHSensor(FplEntity): def __init__(self, coordinator, config, account): super().__init__(coordinator, config, account, "Delivered Reading") @@ -99,12 +117,13 @@ class NetDeliveredKWHSensor(FplEntity): @property def state(self): try: - return self.getData("delMtrReading") + self._state = self.getData("delMtrReading") except: try: - return self.getData("billToDateKWH") + self._state = self.getData("billToDateKWH") except: - return None + pass + return self._state @property def icon(self): diff --git a/custom_components/fpl/sensor_ProjectedBillSensor.py b/custom_components/fpl/sensor_ProjectedBillSensor.py index 1113dd9..524db5c 100644 --- a/custom_components/fpl/sensor_ProjectedBillSensor.py +++ b/custom_components/fpl/sensor_ProjectedBillSensor.py @@ -10,10 +10,14 @@ class FplProjectedBillSensor(FplEntity): 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: - return self.getData("budget_billing_projected_bill") - - return self.getData("projected_bill") + try: + if budget == True and budget_billing_projected_bill is not None: + self._state = self.getData("budget_billing_projected_bill") + else: + self._state = self.getData("projected_bill") + except: + self._state = None + return self._state def defineAttributes(self): """Return the state attributes.""" @@ -36,9 +40,12 @@ class DeferedAmountSensor(FplEntity): @property def state(self): - if self.getData("deferred_amount") is not None: - return self.getData("deferred_amount") - return 0 + try: + self._state = self.getData("deferred_amount") + except: + self._state = 0 + pass + return self._state @property def icon(self): @@ -60,7 +67,11 @@ class ProjectedBudgetBillSensor(FplEntity): @property def state(self): - return self.getData("budget_billing_projected_bill") + try: + self._state = self.getData("budget_billing_projected_bill") + except: + pass + return self._state @property def icon(self): @@ -72,7 +83,7 @@ class ProjectedBudgetBillSensor(FplEntity): attributes["friendly_name"] = "Projected Budget Bill" attributes["device_class"] = "monitary" attributes["state_class"] = "total" - attributes['unit_of_measurement'] = "$" + attributes["unit_of_measurement"] = "$" return attributes @@ -82,7 +93,11 @@ class ProjectedActualBillSensor(FplEntity): @property def state(self): - return self.getData("projected_bill") + try: + self._state = self.getData("projected_bill") + except: + pass + return self._state @property def icon(self): @@ -94,6 +109,6 @@ class ProjectedActualBillSensor(FplEntity): attributes["friendly_name"] = "Projected Actual Bill" attributes["device_class"] = "monitary" attributes["state_class"] = "total" - attributes['unit_of_measurement'] = "$" - + attributes["unit_of_measurement"] = "$" + return attributes From 8e3681070127a91074eb05a21d29d22b8ceb6758 Mon Sep 17 00:00:00 2001 From: Yordan Suarez Date: Wed, 12 Jan 2022 19:26:11 -0500 Subject: [PATCH 7/7] added hacs.json and update manifest --- custom_components/fpl/manifest.json | 3 ++- hacs.json | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 hacs.json diff --git a/custom_components/fpl/manifest.json b/custom_components/fpl/manifest.json index 4993feb..563ebdf 100644 --- a/custom_components/fpl/manifest.json +++ b/custom_components/fpl/manifest.json @@ -13,5 +13,6 @@ "integrationhelper" ], "homeassistant": "2021.12.7", - "version": "1.0.0" + "version": "1.0.0", + "issue_tracker" : "https://github.com/dotKrad/hass-fpl/issues" } \ No newline at end of file diff --git a/hacs.json b/hacs.json new file mode 100644 index 0000000..03554ea --- /dev/null +++ b/hacs.json @@ -0,0 +1,4 @@ +{ + "name": "Hass Fpl", + "country": ["US"] +} \ No newline at end of file