bug fixes
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
default_config:
|
||||
logger:
|
||||
default: info
|
||||
default: error
|
||||
logs:
|
||||
custom_components.fpl: debug
|
||||
28
custom_components/fpl/TestSensor.py
Normal file
28
custom_components/fpl/TestSensor.py
Normal file
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
@@ -30,3 +37,14 @@ 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}
|
||||
-------------------------------------------------------------------
|
||||
"""
|
||||
|
||||
30
custom_components/fpl/fplDataUpdateCoordinator.py
Normal file
30
custom_components/fpl/fplDataUpdateCoordinator.py
Normal file
@@ -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
|
||||
49
custom_components/fpl/fplEntity.py
Normal file
49
custom_components/fpl/fplEntity.py
Normal file
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"domain": "fpl",
|
||||
"name": "FPL",
|
||||
"documentation": "https://github.com/dotKrad/hass-fpl",
|
||||
"iot_class": "cloud_polling",
|
||||
"dependencies": [],
|
||||
"config_flow": true,
|
||||
"codeowners": [
|
||||
|
||||
123
custom_components/fpl/sensor.old.py
Normal file
123
custom_components/fpl/sensor.old.py
Normal file
@@ -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
|
||||
@@ -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
|
||||
"""Sensor platform for integration_blueprint."""
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
EVENT_CORE_CONFIG_UPDATE,
|
||||
STATE_UNKNOWN,
|
||||
CONF_USERNAME,
|
||||
CONF_PASSWORD,
|
||||
STATE_UNKNOWN,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
from .sensor_KWHSensor import (
|
||||
ProjectedKWHSensor,
|
||||
DailyAverageKWHSensor,
|
||||
BillToDateKWHSensor,
|
||||
)
|
||||
from .const import DOMAIN, DOMAIN_DATA, ATTRIBUTION
|
||||
from .DailyUsageSensor import FplDailyUsageSensor
|
||||
from .AverageDailySensor import FplAverageDailySensor
|
||||
from .ProjectedBillSensor import FplProjectedBillSensor
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(minutes=30)
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=60)
|
||||
from .sensor_AllData import AllDataSensor
|
||||
from .TestSensor import TestSensor
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
return True
|
||||
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 = []
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
try:
|
||||
accounts = config_entry.data.get("accounts")
|
||||
for account in accounts:
|
||||
# Test Sensor
|
||||
# fpl_accounts.append(TestSensor(coordinator, entry, account))
|
||||
# All data sensor
|
||||
# fpl_accounts.append(AllDataSensor(coordinator, entry, account))
|
||||
|
||||
fpl_accounts = []
|
||||
# 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))
|
||||
|
||||
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)
|
||||
)
|
||||
# usage sensors
|
||||
fpl_accounts.append(FplAverageDailySensor(coordinator, entry, account))
|
||||
fpl_accounts.append(BudgetDailyAverageSensor(coordinator, entry, account))
|
||||
fpl_accounts.append(ActualDailyAverageSensor(coordinator, entry, account))
|
||||
|
||||
async_add_entities(fpl_accounts)
|
||||
except:
|
||||
raise ConfigEntryNotReady
|
||||
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))
|
||||
|
||||
class FplSensor(Entity):
|
||||
def __init__(self, hass, config, account):
|
||||
self._config = config
|
||||
self._state = None
|
||||
self.loop = hass.loop
|
||||
# KWH sensors
|
||||
fpl_accounts.append(ProjectedKWHSensor(coordinator, entry, account))
|
||||
fpl_accounts.append(DailyAverageKWHSensor(coordinator, entry, account))
|
||||
fpl_accounts.append(BillToDateKWHSensor(coordinator, entry, account))
|
||||
|
||||
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
|
||||
async_add_devices(fpl_accounts)
|
||||
|
||||
24
custom_components/fpl/sensor_AllData.py
Normal file
24
custom_components/fpl/sensor_AllData.py
Normal file
@@ -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"
|
||||
46
custom_components/fpl/sensor_AverageDailySensor.py
Normal file
46
custom_components/fpl/sensor_AverageDailySensor.py
Normal file
@@ -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"
|
||||
28
custom_components/fpl/sensor_DailyUsageSensor.py
Normal file
28
custom_components/fpl/sensor_DailyUsageSensor.py
Normal file
@@ -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"
|
||||
66
custom_components/fpl/sensor_DatesSensor.py
Normal file
66
custom_components/fpl/sensor_DatesSensor.py
Normal file
@@ -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"
|
||||
40
custom_components/fpl/sensor_KWHSensor.py
Normal file
40
custom_components/fpl/sensor_KWHSensor.py
Normal file
@@ -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"
|
||||
73
custom_components/fpl/sensor_ProjectedBillSensor.py
Normal file
73
custom_components/fpl/sensor_ProjectedBillSensor.py
Normal file
@@ -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"
|
||||
Reference in New Issue
Block a user