From dc2a97e4046fb47eb01b4bf25a2905eccf4a83b6 Mon Sep 17 00:00:00 2001 From: Yordan Suarez Date: Sat, 5 Jun 2021 18:37:17 -0400 Subject: [PATCH] bug fixes --- .devcontainer/configuration.yaml | 2 +- custom_components/fpl/TestSensor.py | 28 +++ custom_components/fpl/__init__.py | 83 +++++--- custom_components/fpl/config_flow.py | 23 +-- custom_components/fpl/const.py | 22 +- .../fpl/fplDataUpdateCoordinator.py | 30 +++ custom_components/fpl/fplEntity.py | 49 +++++ custom_components/fpl/fplapi.py | 58 +++--- custom_components/fpl/manifest.json | 1 + custom_components/fpl/sensor.old.py | 123 +++++++++++ custom_components/fpl/sensor.py | 194 +++++++----------- custom_components/fpl/sensor_AllData.py | 24 +++ .../fpl/sensor_AverageDailySensor.py | 46 +++++ .../fpl/sensor_DailyUsageSensor.py | 28 +++ custom_components/fpl/sensor_DatesSensor.py | 66 ++++++ custom_components/fpl/sensor_KWHSensor.py | 40 ++++ .../fpl/sensor_ProjectedBillSensor.py | 73 +++++++ 17 files changed, 698 insertions(+), 192 deletions(-) create mode 100644 custom_components/fpl/TestSensor.py create mode 100644 custom_components/fpl/fplDataUpdateCoordinator.py create mode 100644 custom_components/fpl/fplEntity.py create mode 100644 custom_components/fpl/sensor.old.py create mode 100644 custom_components/fpl/sensor_AllData.py create mode 100644 custom_components/fpl/sensor_AverageDailySensor.py create mode 100644 custom_components/fpl/sensor_DailyUsageSensor.py create mode 100644 custom_components/fpl/sensor_DatesSensor.py create mode 100644 custom_components/fpl/sensor_KWHSensor.py create mode 100644 custom_components/fpl/sensor_ProjectedBillSensor.py diff --git a/.devcontainer/configuration.yaml b/.devcontainer/configuration.yaml index a8fcccb..8a4c31e 100644 --- a/.devcontainer/configuration.yaml +++ b/.devcontainer/configuration.yaml @@ -1,5 +1,5 @@ default_config: logger: - default: info + default: error logs: custom_components.fpl: debug \ No newline at end of file diff --git a/custom_components/fpl/TestSensor.py b/custom_components/fpl/TestSensor.py new file mode 100644 index 0000000..4d8b63d --- /dev/null +++ b/custom_components/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/__init__.py b/custom_components/fpl/__init__.py index dea2e9f..18af9fe 100644 --- a/custom_components/fpl/__init__.py +++ b/custom_components/fpl/__init__.py @@ -1,18 +1,29 @@ """ 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_DATA, CONF_USERNAME, CONF_PASSWORD +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(__name__) - -from .config_flow import FplFlowHandler -from .const import DOMAIN +_LOGGER = logging.getLogger(__package__) class FplData: @@ -39,32 +50,58 @@ async def async_setup(hass: HomeAssistant, config: Config) -> bool: return True -async def async_setup_entry(hass, config_entry): +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 = config_entry.data.get(CONF_USERNAME) - password = config_entry.data.get(CONF_PASSWORD) - - # Create DATA dict - hass.data[DOMAIN_DATA] = {} + username = entry.data.get(CONF_USERNAME) + password = entry.data.get(CONF_PASSWORD) # Configure the client. _LOGGER.info(f"Configuring the client") - client = FplApi(username, password, hass.loop) - fplData = FplData(hass, client) + session = async_get_clientsession(hass) + client = FplApi(username, password, session) - await fplData.update_data() + coordinator = FplDataUpdateCoordinator(hass, client=client) + await coordinator.async_refresh() - hass.data[DOMAIN_DATA]["client"] = fplData + 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.""" - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "sensor") + + 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 + ] + ) ) - return True + if unloaded: + hass.data[DOMAIN].pop(entry.entry_id) - -async def async_unload_entry(hass, config_entry): - """Unload a config entry.""" - await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") - return True + return unloaded diff --git a/custom_components/fpl/config_flow.py b/custom_components/fpl/config_flow.py index 6102283..fbb0960 100644 --- a/custom_components/fpl/config_flow.py +++ b/custom_components/fpl/config_flow.py @@ -4,7 +4,8 @@ import voluptuous as vol from .fplapi import FplApi from homeassistant import config_entries -import aiohttp +from homeassistant.helpers.aiohttp_client import async_create_clientsession + from .const import DOMAIN, CONF_USERNAME, CONF_PASSWORD, CONF_NAME from .fplapi import ( @@ -40,26 +41,28 @@ class FplFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """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 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): - api = FplApi(username, password, None) + session = async_create_clientsession(self.hass) + api = FplApi(username, password, session) result = await api.login() if result == LOGIN_RESULT_OK: - fplData = await api.get_data() + fplData = await api.async_get_data() accounts = fplData["accounts"] user_input["accounts"] = accounts - return self.async_create_entry(title="", data=user_input) + return self.async_create_entry(title=username, data=user_input) if result == LOGIN_RESULT_INVALIDUSER: self._errors[CONF_USERNAME] = "invalid_username" @@ -80,10 +83,6 @@ class FplFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _show_config_form(self, user_input): """Show the configuration form to edit location data.""" - # Defaults - username = "" - password = "" - if user_input is not None: if CONF_USERNAME in user_input: username = user_input[CONF_USERNAME] diff --git a/custom_components/fpl/const.py b/custom_components/fpl/const.py index 419b370..63615e0 100644 --- a/custom_components/fpl/const.py +++ b/custom_components/fpl/const.py @@ -1,5 +1,6 @@ """Constants for fpl.""" # Base component constants +NAME = "FPL Integration" DOMAIN = "fpl" DOMAIN_DATA = f"{DOMAIN}_data" VERSION = "0.0.1" @@ -14,7 +15,13 @@ REQUIRED_FILES = [ "switch.py", ] ISSUE_URL = "https://github.com/dotKrad/hass-fpl/issues" -ATTRIBUTION = "Data from this is provided by FPL." +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" @@ -29,4 +36,15 @@ CONF_USERNAME = "username" CONF_PASSWORD = "password" # Defaults -DEFAULT_NAME = DOMAIN \ No newline at end of file +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/fplDataUpdateCoordinator.py b/custom_components/fpl/fplDataUpdateCoordinator.py new file mode 100644 index 0000000..e4056ac --- /dev/null +++ b/custom_components/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=7200) + +_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/fplEntity.py b/custom_components/fpl/fplEntity.py new file mode 100644 index 0000000..fa7ba8c --- /dev/null +++ b/custom_components/fpl/fplEntity.py @@ -0,0 +1,49 @@ +"""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 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}", + "model": VERSION, + "manufacturer": "Florida Power & Light", + } + + def defineAttributes(self): + return {} + + @property + def device_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/fplapi.py b/custom_components/fpl/fplapi.py index 24631a0..6a2877d 100644 --- a/custom_components/fpl/fplapi.py +++ b/custom_components/fpl/fplapi.py @@ -19,7 +19,7 @@ LOGIN_RESULT_INVALIDPASSWORD = "FAILEDPASSWORD" LOGIN_RESULT_UNAUTHORIZED = "UNAUTHORIZED" LOGIN_RESULT_FAILURE = "FAILURE" -_LOGGER = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__package__) TIMEOUT = 30 URL_LOGIN = "https://www.fpl.com/api/resources/login" @@ -34,15 +34,14 @@ NOTENROLLED = "NOTENROLLED" class FplApi(object): """A class for getting energy usage information from Florida Power & Light.""" - def __init__(self, username, password, loop): + def __init__(self, username, password, session): """Initialize the data retrieval. Session should have BasicAuth flag set.""" self._username = username self._password = password - self._loop = loop - self._session = None + self._session = session - async def get_data(self) -> dict: - self._session = aiohttp.ClientSession() + async def async_get_data(self) -> dict: + # self._session = aiohttp.ClientSession() data = {} data["accounts"] = [] if await self.login() == LOGIN_RESULT_OK: @@ -53,24 +52,16 @@ class FplApi(object): accountData = await self.__async_get_data(account) data[account] = accountData - await self._session.close() - + await self.logout() return data async def login(self): - if self._session is not None: - session = self._session - close = False - else: - session = aiohttp.ClientSession() - close = True - - _LOGGER.info("Logging") + _LOGGER.info("Logging in") """login and get account information""" result = LOGIN_RESULT_OK try: - async with async_timeout.timeout(TIMEOUT, loop=self._loop): - response = await session.get( + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): + response = await self._session.get( URL_LOGIN, auth=aiohttp.BasicAuth(self._username, self._password) ) @@ -89,17 +80,19 @@ class FplApi(object): _LOGGER.error(f"Error {e} : {sys.exc_info()[0]}") result = LOGIN_RESULT_FAILURE - if close: - await session.close() - return result + async def logout(self): + _LOGGER.info("Logging out") + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): + await self._session.get("https://www.fpl.com/api/resources/logout") + async def async_get_open_accounts(self): _LOGGER.info(f"Getting accounts") result = [] try: - async with async_timeout.timeout(TIMEOUT, loop=self._loop): + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): response = await self._session.get(URL_RESOURCES_HEADER) js = await response.json() @@ -119,7 +112,7 @@ class FplApi(object): _LOGGER.info(f"Getting Data") data = {} - async with async_timeout.timeout(TIMEOUT, loop=self._loop): + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): response = await self._session.get( URL_RESOURCES_ACCOUNT.format(account=account) ) @@ -182,7 +175,7 @@ class FplApi(object): data = {} try: - async with async_timeout.timeout(TIMEOUT, loop=self._loop): + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): response = await self._session.get( URL_RESOURCES_PROJECTED_BILL.format( account=account, @@ -215,7 +208,7 @@ class FplApi(object): URL = "https://www.fpl.com/api/resources/account/{account}/budgetBillingGraph/premiseDetails" try: - async with async_timeout.timeout(TIMEOUT, loop=self._loop): + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): response = await self._session.get(URL.format(account=account)) if response.status == 200: r = (await response.json())["data"] @@ -249,7 +242,7 @@ class FplApi(object): URL = "https://www.fpl.com/api/resources/account/{account}/budgetBillingGraph" try: - async with async_timeout.timeout(TIMEOUT, loop=self._loop): + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): response = await self._session.get(URL.format(account=account)) if response.status == 200: r = (await response.json())["data"] @@ -286,13 +279,13 @@ class FplApi(object): data = {} - async with async_timeout.timeout(TIMEOUT, loop=self._loop): + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): response = await self._session.post(URL.format(account=account), json=JSON) if response.status == 200: r = (await response.json())["data"] dailyUsage = [] - totalPowerUsage = 0 + # totalPowerUsage = 0 if "data" in r["DailyUsage"]: for daily in r["DailyUsage"]["data"]: if ( @@ -309,11 +302,14 @@ class FplApi(object): "max_temperature": daily["averageHighTemperature"], } ) - totalPowerUsage += int(daily["kwhUsed"]) + # totalPowerUsage += int(daily["kwhUsed"]) - data["total_power_usage"] = totalPowerUsage + # data["total_power_usage"] = totalPowerUsage data["daily_usage"] = dailyUsage + data["projectedKWH"] = r["CurrentUsage"]["projectedKWH"] + data["dailyAverageKWH"] = r["CurrentUsage"]["dailyAverageKWH"] + data["billToDateKWH"] = r["CurrentUsage"]["billToDateKWH"] return data async def __getDataFromApplianceUsage(self, account, lastBilledDate) -> dict: @@ -322,7 +318,7 @@ class FplApi(object): JSON = {"startDate": str(lastBilledDate.strftime("%m%d%Y"))} data = {} try: - async with async_timeout.timeout(TIMEOUT, loop=self._loop): + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): response = await self._session.post( URL.format(account=account), json=JSON ) diff --git a/custom_components/fpl/manifest.json b/custom_components/fpl/manifest.json index 86b665c..805319d 100644 --- a/custom_components/fpl/manifest.json +++ b/custom_components/fpl/manifest.json @@ -2,6 +2,7 @@ "domain": "fpl", "name": "FPL", "documentation": "https://github.com/dotKrad/hass-fpl", + "iot_class": "cloud_polling", "dependencies": [], "config_flow": true, "codeowners": [ diff --git a/custom_components/fpl/sensor.old.py b/custom_components/fpl/sensor.old.py new file mode 100644 index 0000000..5d3a7e8 --- /dev/null +++ b/custom_components/fpl/sensor.old.py @@ -0,0 +1,123 @@ +import logging +from datetime import datetime, timedelta +from .fplapi import FplApi +import aiohttp +import asyncio +from homeassistant.helpers.entity import Entity +from homeassistant import util +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.event import async_call_later + +from homeassistant.const import ( + CONF_NAME, + EVENT_CORE_CONFIG_UPDATE, + STATE_UNKNOWN, + CONF_USERNAME, + CONF_PASSWORD, + STATE_UNKNOWN, + ATTR_FRIENDLY_NAME, +) +from .const import DOMAIN, DOMAIN_DATA, ATTRIBUTION +from .DailyUsageSensor import FplDailyUsageSensor +from .AverageDailySensor import FplAverageDailySensor +from .ProjectedBillSensor import FplProjectedBillSensor + +_LOGGER = logging.getLogger(__name__) + +MIN_TIME_BETWEEN_SCANS = timedelta(minutes=30) +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=60) + + +def setup(hass, config): + return True + + +async def async_setup_entry(hass, config_entry, async_add_entities): + try: + accounts = config_entry.data.get("accounts") + + fpl_accounts = [] + + for account in accounts: + _LOGGER.info(f"Adding fpl account: {account}") + fpl_accounts.append(FplSensor(hass, config_entry.data, account)) + fpl_accounts.append(FplDailyUsageSensor(hass, config_entry.data, account)) + fpl_accounts.append(FplAverageDailySensor(hass, config_entry.data, account)) + fpl_accounts.append( + FplProjectedBillSensor(hass, config_entry.data, account) + ) + + async_add_entities(fpl_accounts) + except: + raise ConfigEntryNotReady + + +class FplSensor(Entity): + def __init__(self, hass, config, account): + self._config = config + self._state = None + self.loop = hass.loop + + self._account = account + self._data = None + + async def async_added_to_hass(self): + await self.async_update() + + @property + def device_info(self): + return { + "identifiers": {(DOMAIN, self._account)}, + "name": f"Account {self._account}", + "manufacturer": "Florida Power & Light", + } + + @property + def unique_id(self): + """Return the ID of this device.""" + id = "{}{}".format(DOMAIN, self._account) + return id + + @property + def name(self): + return f"{DOMAIN.upper()} {self._account}" + + @property + def state(self): + data = self._data + + if type(data) is dict: + 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"] + + return self._state + + # @property + # def unit_of_measurement(self): + # return "$" + + @property + def icon(self): + return "mdi:flash" + + @property + def state_attributes(self): + return self._data + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_UPDATES) + async def async_update(self): + # Send update "signal" to the component + # await self.hass.data[DOMAIN_DATA]["client"].update_data() + + # Get new data (if any) + if "data" in self.hass.data[DOMAIN_DATA]: + data = self.hass.data[DOMAIN_DATA]["data"][self._account] + + if data != {}: + self._data = data + self._data["attribution"] = ATTRIBUTION \ No newline at end of file diff --git a/custom_components/fpl/sensor.py b/custom_components/fpl/sensor.py index 89ee7cd..ba887a5 100644 --- a/custom_components/fpl/sensor.py +++ b/custom_components/fpl/sensor.py @@ -1,123 +1,71 @@ -import logging -from datetime import datetime, timedelta -from .fplapi import FplApi -import aiohttp -import asyncio -from homeassistant.helpers.entity import Entity -from homeassistant import util -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers.event import async_call_later - -from homeassistant.const import ( - CONF_NAME, - EVENT_CORE_CONFIG_UPDATE, - STATE_UNKNOWN, - CONF_USERNAME, - CONF_PASSWORD, - STATE_UNKNOWN, - ATTR_FRIENDLY_NAME, -) -from .const import DOMAIN, DOMAIN_DATA, ATTRIBUTION -from .DailyUsageSensor import FplDailyUsageSensor -from .AverageDailySensor import FplAverageDailySensor -from .ProjectedBillSensor import FplProjectedBillSensor - -_LOGGER = logging.getLogger(__name__) - -MIN_TIME_BETWEEN_SCANS = timedelta(minutes=30) -MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=60) - - -def setup(hass, config): - return True - - -async def async_setup_entry(hass, config_entry, async_add_entities): - try: - accounts = config_entry.data.get("accounts") - - fpl_accounts = [] - - for account in accounts: - _LOGGER.info(f"Adding fpl account: {account}") - fpl_accounts.append(FplSensor(hass, config_entry.data, account)) - fpl_accounts.append(FplDailyUsageSensor(hass, config_entry.data, account)) - fpl_accounts.append(FplAverageDailySensor(hass, config_entry.data, account)) - fpl_accounts.append( - FplProjectedBillSensor(hass, config_entry.data, account) - ) - - async_add_entities(fpl_accounts) - except: - raise ConfigEntryNotReady - - -class FplSensor(Entity): - def __init__(self, hass, config, account): - self._config = config - self._state = None - self.loop = hass.loop - - self._account = account - self._data = None - - async def async_added_to_hass(self): - await self.async_update() - - @property - def device_info(self): - return { - "identifiers": {(DOMAIN, self._account)}, - "name": f"Account {self._account}", - "manufacturer": "Florida Power & Light", - } - - @property - def unique_id(self): - """Return the ID of this device.""" - id = "{}{}".format(DOMAIN, self._account) - return id - - @property - def name(self): - return f"{DOMAIN.upper()} {self._account}" - - @property - def state(self): - data = self._data - - if type(data) is dict: - 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"] - - return self._state - - # @property - # def unit_of_measurement(self): - # return "$" - - @property - def icon(self): - return "mdi:flash" - - @property - def state_attributes(self): - return self._data - - @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_UPDATES) - async def async_update(self): - # Send update "signal" to the component - # await self.hass.data[DOMAIN_DATA]["client"].update_data() - - # Get new data (if any) - if "data" in self.hass.data[DOMAIN_DATA]: - data = self.hass.data[DOMAIN_DATA]["data"][self._account] - - if data != {}: - self._data = data - self._data["attribution"] = ATTRIBUTION \ No newline at end of file +"""Sensor platform for integration_blueprint.""" + +from .sensor_KWHSensor import ( + ProjectedKWHSensor, + DailyAverageKWHSensor, + BillToDateKWHSensor, +) +from .sensor_DatesSensor import ( + CurrentBillDateSensor, + NextBillDateSensor, + ServiceDaysSensor, + AsOfDaysSensor, + RemainingDaysSensor, +) +from .sensor_ProjectedBillSensor import ( + FplProjectedBillSensor, + ProjectedBudgetBillSensor, + ProjectedActualBillSensor, + DeferedAmountSensor, +) +from .sensor_AverageDailySensor import ( + FplAverageDailySensor, + BudgetDailyAverageSensor, + ActualDailyAverageSensor, +) +from .sensor_DailyUsageSensor import FplDailyUsageSensor +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(ActualDailyAverageSensor(coordinator, entry, account)) + + fpl_accounts.append(FplDailyUsageSensor(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)) + + async_add_devices(fpl_accounts) diff --git a/custom_components/fpl/sensor_AllData.py b/custom_components/fpl/sensor_AllData.py new file mode 100644 index 0000000..98044b3 --- /dev/null +++ b/custom_components/fpl/sensor_AllData.py @@ -0,0 +1,24 @@ +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" diff --git a/custom_components/fpl/sensor_AverageDailySensor.py b/custom_components/fpl/sensor_AverageDailySensor.py new file mode 100644 index 0000000..66d25f1 --- /dev/null +++ b/custom_components/fpl/sensor_AverageDailySensor.py @@ -0,0 +1,46 @@ +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" + + +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" + + +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 new file mode 100644 index 0000000..812ca27 --- /dev/null +++ b/custom_components/fpl/sensor_DailyUsageSensor.py @@ -0,0 +1,28 @@ +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 len(data) > 0: + return data[-1]["cost"] + + return None + + def defineAttributes(self): + """Return the state attributes.""" + data = self.getData("daily_usage") + + if len(data) > 0: + return {"date": data[-1]["date"], "daily_usage": data} + + return {} + + @property + def icon(self): + return "mdi:currency-usd" diff --git a/custom_components/fpl/sensor_DatesSensor.py b/custom_components/fpl/sensor_DatesSensor.py new file mode 100644 index 0000000..b0414d7 --- /dev/null +++ b/custom_components/fpl/sensor_DatesSensor.py @@ -0,0 +1,66 @@ +from .fplEntity import FplEntity + + +class CurrentBillDateSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Current Bill Date") + + @property + def state(self): + return self.getData("current_bill_date") + + @property + def icon(self): + return "mdi:calendar" + + +class NextBillDateSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Next Bill Date") + + @property + def state(self): + return self.getData("next_bill_date") + + @property + def icon(self): + return "mdi:calendar" + + +class ServiceDaysSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Service Days") + + @property + def state(self): + return self.getData("service_days") + + @property + def icon(self): + return "mdi:calendar" + + +class AsOfDaysSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "As Of Days") + + @property + def state(self): + return self.getData("as_of_days") + + @property + def icon(self): + return "mdi:calendar" + + +class RemainingDaysSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Remaining Days") + + @property + def state(self): + return self.getData("remaining_days") + + @property + def icon(self): + return "mdi:calendar" diff --git a/custom_components/fpl/sensor_KWHSensor.py b/custom_components/fpl/sensor_KWHSensor.py new file mode 100644 index 0000000..b360e4d --- /dev/null +++ b/custom_components/fpl/sensor_KWHSensor.py @@ -0,0 +1,40 @@ +from .fplEntity import FplEntity + + +class ProjectedKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Projected KWH") + + @property + def state(self): + return self.getData("projectedKWH") + + @property + def icon(self): + return "mdi:flash" + + +class DailyAverageKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Daily Average KWH") + + @property + def state(self): + return self.getData("dailyAverageKWH") + + @property + def icon(self): + return "mdi:flash" + + +class BillToDateKWHSensor(FplEntity): + def __init__(self, coordinator, config, account): + super().__init__(coordinator, config, account, "Bill To Date KWH") + + @property + def state(self): + return self.getData("billToDateKWH") + + @property + def icon(self): + return "mdi:flash" diff --git a/custom_components/fpl/sensor_ProjectedBillSensor.py b/custom_components/fpl/sensor_ProjectedBillSensor.py new file mode 100644 index 0000000..acf7482 --- /dev/null +++ b/custom_components/fpl/sensor_ProjectedBillSensor.py @@ -0,0 +1,73 @@ +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 = {} + try: + if self.getData("budget_bill") == True: + attributes["budget_bill"] = self.getData("budget_bill") + except: + pass + + 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("budget_bill") == True: + return self.getData("defered_amount") + return 0 + + @property + def icon(self): + return "mdi:currency-usd" + + +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" + + +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"