Added Code for fpl

This commit is contained in:
Yordan Suarez
2019-12-18 01:36:59 +00:00
parent 20afa2245f
commit adea133b3d
11 changed files with 363 additions and 20 deletions

View File

@@ -1,26 +1,8 @@
#!/usr/bin/env bash
function StartHomeAssistant {
echo "Copy configuration.yaml"
cp -f .devcontainer/configuration.yaml /config || echo ".devcontainer/configuration.yaml are missing!" exit 1
echo "Copy the custom component"
rm -R /config/custom_components/ || echo ""
cp -r custom_components /config/custom_components/ || echo "Could not copy the custom_component" exit 1
echo "Start Home Assistant"
hass -c /config
}
function UpdgradeHomeAssistantDev {
python -m pip install --upgrade git+https://github.com/home-assistant/home-assistant@dev
}
function SetHomeAssistantVersion {
read -p 'Version: ' version
python -m pip install --upgrade homeassistant==$version
}
function HomeAssistantConfigCheck {
hass -c /config --script check_config
}

2
.vscode/tasks.json vendored
View File

@@ -4,7 +4,7 @@
{
"label": "Start Home Assistant on port 8124",
"type": "shell",
"command": "source .devcontainer/custom_component_helper && StartHomeAssistant",
"command": "source .devcontainer/custom_component_helper",
"group": {
"kind": "test",
"isDefault": true,

View File

@@ -0,0 +1,21 @@
{
"config": {
"title": "Fpl",
"step": {
"user": {
"title": "Florida Power & Light",
"description": "If you need help with the configuration have a look here: https://github.com/custom-components/blueprint",
"data": {
"username": "Username",
"password": "Password"
}
}
},
"error": {
"auth": "Username/Password is wrong."
},
"abort": {
"single_instance_allowed": "Only a single configuration of Fpl is allowed."
}
}
}

View File

@@ -0,0 +1,21 @@
{
"config": {
"title": "Blueprint",
"step": {
"user": {
"title": "Blueprint",
"description": "Hvis du trenger hjep til konfigurasjon ta en titt her: https://github.com/custom-components/blueprint",
"data": {
"username": "Brukernavn",
"password": "Passord"
}
}
},
"error": {
"auth": "Brukernavn/Passord er feil."
},
"abort": {
"single_instance_allowed": "Du kan konfigurere Blueprint kun en gang."
}
}
}

View File

@@ -0,0 +1,5 @@
{
"state": {
"Some sample static text.": "Eksempel tekst."
}
}

View File

@@ -0,0 +1 @@
""" FPL Component """

View File

@@ -0,0 +1,84 @@
from collections import OrderedDict
import voluptuous as vol
from .fplapi import FplApi
from homeassistant import config_entries
import aiohttp
from .const import DOMAIN
@config_entries.HANDLERS.register(DOMAIN)
class FplFlowHandler(config_entries.ConfigFlow):
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:
valid = await self._test_credentials(
user_input["username"], user_input["password"]
)
if valid:
return self.async_create_entry(title="", data=user_input)
else:
self._errors["base"] = "auth"
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."""
# Defaults
username = ""
password = ""
if user_input is not None:
if "username" in user_input:
username = user_input["username"]
if "password" in user_input:
password = user_input["password"]
data_schema = OrderedDict()
data_schema[vol.Required("username", default=username)] = str
data_schema[vol.Required("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={})
async def _test_credentials(self, username, password):
"""Return true if credentials is valid."""
try:
# client = Client(username, password)
# client.get_data()
session = aiohttp.ClientSession()
api = FplApi(username, password, True, None, session)
await api.login()
return True
except Exception: # pylint: disable=broad-except
pass
return False

View File

@@ -0,0 +1,35 @@
"""Constants for fpl."""
# Base component constants
DOMAIN = "fpl"
DOMAIN_DATA = f"{DOMAIN}_data"
VERSION = "0.0.1"
PLATFORMS = ["binary_sensor", "sensor", "switch"]
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 = "Data from this is provided by blueprint."
# Icons
ICON = "mdi:format-quote-close"
# 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

View File

@@ -0,0 +1,119 @@
import asyncio
import logging
import re
from datetime import timedelta, date
import aiohttp
import async_timeout
from bs4 import BeautifulSoup
_LOGGER = logging.getLogger(__name__)
TIMEOUT = 5
class FplApi(object):
"""A class for getting energy usage information from Florida Power & Light."""
def __init__(self, username, password, is_tou, loop, session):
"""Initialize the data retrieval. Session should have BasicAuth flag set."""
self._username = username
self._password = password
self._loop = loop
self._session = session
self._is_tou = is_tou
self._account_number = None
self._premise_number = None
self.yesterday_kwh = None
self.yesterday_dollars = None
self.mtd_kwh = None
self.mtd_dollars = None
self.projected_bill = None
async def login(self):
"""login and get account information"""
async with async_timeout.timeout(TIMEOUT, loop=self._loop):
response = await self._session.get("https://www.fpl.com/api/resources/login",
auth=aiohttp.BasicAuth(self._username, self._password))
if (await response.json())["messages"][0]["messageCode"] != "login.success":
raise Exception('login failure')
async with async_timeout.timeout(TIMEOUT, loop=self._loop):
response = await self._session.get("https://www.fpl.com/api/resources/header")
json = await response.json()
self._account_number = json["data"]["selectedAccount"]["data"]["accountNumber"]
self._premise_number = json["data"]["selectedAccount"]["data"]["acctSecSettings"]["premiseNumber"]
async def async_get_yesterday_usage(self):
async with async_timeout.timeout(TIMEOUT, loop=self._loop):
response = await self._session.get(self._build_daily_url())
_LOGGER.debug("Response from API: %s", response.status)
if response.status != 200:
self.data = None
return
malformedXML = await response.read()
cleanerXML = str(malformedXML).replace(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>', '', 1
).split("<ARG>@@", 1)[0]
soup = BeautifulSoup(cleanerXML, 'html.parser')
tool_text = soup.find("dataset", seriesname="$") \
.find("set")["tooltext"]
match = re.search(r"\{br\}kWh Usage: (.*?) kWh \{br\}", tool_text)
if match:
self.yesterday_kwh = match.group(1)
match2 = re.search(r"\{br\}Approx\. Cost: (\$.*?) \{br\}", tool_text)
if match2:
self.yesterday_dollars = match2.group(1)
async def async_get_mtd_usage(self):
async with async_timeout.timeout(TIMEOUT, loop=self._loop):
response = await self._session.get(
"https://app.fpl.com/wps/myportal/EsfPortal")
soup = BeautifulSoup(await response.text(), 'html.parser')
self.mtd_kwh = soup.find(id="bpbsubcontainer") \
.find("table", class_="bpbtab_style_bill", width=430) \
.find_all("div", class_="bpbtabletxt")[-1].string
self.mtd_dollars = soup \
.find_all("div", class_="bpbusagebgnd")[1] \
.find("div", class_="bpbusagedollartxt").getText().strip()
self.projected_bill = soup.find(id="bpssmlsubcontainer") \
.find("div", class_="bpsmonthbillbgnd") \
.find("div", class_="bpsmnthbilldollartxt") \
.getText().strip().replace("$", "")
def _build_daily_url(self):
end_date = date.today()
start_date = end_date - timedelta(days=1)
return ("https://app.fpl.com/wps/PA_ESFPortalWeb/getDailyConsumption"
"?premiseNumber={premise_number}"
"&accountNumber={account_number}"
"&isTouUser={is_tou}"
"&startDate={start_date}"
"&endDate={end_date}"
"&userType=EXT"
"&isResidential=true"
"&certifiedDate=2000/01/01"
"&viewType=dollar"
"&tempType=max"
"&ecDayHumType=NoHum"
).format(
premise_number=self._premise_number,
account_number=self._account_number,
is_tou=str(self._is_tou),
start_date=start_date.strftime("%Y%m%d"),
end_date=end_date.strftime("%Y%m%d"),
)

View File

@@ -0,0 +1,15 @@
{
"domain": "fpl",
"name": "FPL",
"documentation": "https://github.com/custom-components/blueprint",
"dependencies": [],
"config_flow": true,
"codeowners": [
"@dotKrad"
],
"requirements": [
"bs4",
"integrationhelper"
],
"homeassistant": "0.96.0"
}

View File

@@ -0,0 +1,60 @@
from homeassistant import const
from datetime import datetime, timedelta
from .fplapi import FplApi
import aiohttp
import asyncio
from homeassistant.helpers.entity import Entity
from homeassistant import util
MIN_TIME_BETWEEN_SCANS = timedelta(minutes=1440)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1440)
def setup(hass, config):
return True
def setup_platform(hass, config, add_devices, discovery_info=None):
setup(hass, config)
add_devices([FplSensor(hass, config)])
class FplSensor(Entity):
def __init__(self, hass, config):
print("init")
self.username = config.get(const.CONF_USERNAME)
self.password = config.get(const.CONF_PASSWORD)
self._state = 0
self.loop = hass.loop
self.api = None
@property
def name(self):
"""Returns the name of the sensor."""
return "fpl"
@property
def state(self):
return self._state
@property
def state_attributes(self):
return {
# "yesterday_kwh": self.api.yesterday_kwh,
# "yesterday_dollars": self.api.yesterday_dollars.replace("$", ""),
"mtd_kwh": self.api.mtd_kwh,
"mtd_dollars": self.api.mtd_dollars.replace("$", ""),
"projected_bill": self.api.projected_bill,
}
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_UPDATES)
async def async_update(self):
session = aiohttp.ClientSession()
api = FplApi(self.username, self.password, True, self.loop, session)
await api.login()
# await api.async_get_yesterday_usage()
await api.async_get_mtd_usage()
await session.close()
self._state = api.projected_bill
self.api = api