generated from dotKrad/blueprint
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
1,121 additions
and
481 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,388 @@ | ||
"""FPL Main region data collection api client""" | ||
|
||
import json | ||
import logging | ||
from datetime import datetime, timedelta | ||
import aiohttp | ||
import async_timeout | ||
|
||
|
||
from .const import ( | ||
API_HOST, | ||
LOGIN_RESULT_FAILURE, | ||
LOGIN_RESULT_INVALIDPASSWORD, | ||
LOGIN_RESULT_INVALIDUSER, | ||
LOGIN_RESULT_OK, | ||
TIMEOUT, | ||
) | ||
|
||
STATUS_CATEGORY_OPEN = "OPEN" | ||
|
||
URL_LOGIN = API_HOST + "/api/resources/login" | ||
|
||
URL_BUDGET_BILLING_GRAPH = ( | ||
API_HOST + "/api/resources/account/{account}/budgetBillingGraph" | ||
) | ||
|
||
URL_RESOURCES_PROJECTED_BILL = ( | ||
API_HOST | ||
+ "/api/resources/account/{account}/projectedBill" | ||
+ "?premiseNumber={premise}&lastBilledDate={lastBillDate}" | ||
) | ||
|
||
|
||
URL_APPLIANCE_USAGE = ( | ||
API_HOST + "/dashboard-api/resources/account/{account}/applianceUsage/{account}" | ||
) | ||
URL_BUDGET_BILLING_PREMISE_DETAILS = ( | ||
API_HOST + "/api/resources/account/{account}/budgetBillingGraph/premiseDetails" | ||
) | ||
|
||
|
||
ENROLLED = "ENROLLED" | ||
NOTENROLLED = "NOTENROLLED" | ||
|
||
_LOGGER = logging.getLogger(__package__) | ||
|
||
|
||
class FplMainRegionApiClient: | ||
"""Fpl Main Region Api Client""" | ||
|
||
def __init__(self, username, password, loop, session) -> None: | ||
self.session = session | ||
self.username = username | ||
self.password = password | ||
self.loop = loop | ||
|
||
async def login(self): | ||
"""login into fpl""" | ||
|
||
# login and get account information | ||
|
||
async with async_timeout.timeout(TIMEOUT): | ||
response = await self.session.get( | ||
URL_LOGIN, | ||
auth=aiohttp.BasicAuth(self.username, self.password), | ||
) | ||
|
||
if response.status == 200: | ||
return LOGIN_RESULT_OK | ||
|
||
if response.status == 401: | ||
json_data = json.loads(await response.text()) | ||
|
||
if json_data["messageCode"] == LOGIN_RESULT_INVALIDUSER: | ||
return LOGIN_RESULT_INVALIDUSER | ||
|
||
if json_data["messageCode"] == LOGIN_RESULT_INVALIDPASSWORD: | ||
return LOGIN_RESULT_INVALIDPASSWORD | ||
|
||
return LOGIN_RESULT_FAILURE | ||
|
||
async def get_open_accounts(self): | ||
""" | ||
Get open accounts | ||
Returns array with active account numbers | ||
""" | ||
result = [] | ||
URL = API_HOST + "/api/resources/header" | ||
async with async_timeout.timeout(TIMEOUT): | ||
response = await self.session.get(URL) | ||
|
||
json_data = await response.json() | ||
accounts = json_data["data"]["accounts"]["data"]["data"] | ||
|
||
for account in accounts: | ||
if account["statusCategory"] == STATUS_CATEGORY_OPEN: | ||
result.append(account["accountNumber"]) | ||
|
||
return result | ||
|
||
async def logout(self): | ||
"""Logging out from fpl""" | ||
_LOGGER.info("Logging out") | ||
|
||
URL_LOGOUT = API_HOST + "/api/resources/logout" | ||
try: | ||
async with async_timeout.timeout(TIMEOUT): | ||
await self.session.get(URL_LOGOUT) | ||
except Exception: | ||
pass | ||
|
||
async def update(self, account) -> dict: | ||
"""Get data from resources endpoint""" | ||
data = {} | ||
|
||
URL_RESOURCES_ACCOUNT = API_HOST + "/api/resources/account/{account}" | ||
|
||
async with async_timeout.timeout(TIMEOUT): | ||
response = await self.session.get( | ||
URL_RESOURCES_ACCOUNT.format(account=account) | ||
) | ||
account_data = (await response.json())["data"] | ||
|
||
premise = account_data.get("premiseNumber").zfill(9) | ||
|
||
data["meterSerialNo"] = account_data["meterSerialNo"] | ||
|
||
# currentBillDate | ||
currentBillDate = datetime.strptime( | ||
account_data["currentBillDate"].replace("-", "").split("T")[0], "%Y%m%d" | ||
).date() | ||
|
||
# nextBillDate | ||
nextBillDate = datetime.strptime( | ||
account_data["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() | ||
|
||
data["service_days"] = (nextBillDate - currentBillDate).days | ||
data["as_of_days"] = (today - currentBillDate).days | ||
data["remaining_days"] = (nextBillDate - today).days | ||
|
||
# zip code | ||
# zip_code = accountData["serviceAddress"]["zip"] | ||
|
||
# projected bill | ||
pbData = await self.__getFromProjectedBill(account, premise, currentBillDate) | ||
data.update(pbData) | ||
|
||
# programs | ||
programsData = account_data["programs"]["data"] | ||
|
||
programs = dict() | ||
_LOGGER.info("Getting Programs") | ||
for program in programsData: | ||
if "enrollmentStatus" in program.keys(): | ||
key = program["name"] | ||
programs[key] = program["enrollmentStatus"] == ENROLLED | ||
|
||
def hasProgram(programName) -> bool: | ||
return programName in programs and programs[programName] | ||
|
||
# Budget Billing program | ||
if hasProgram("BBL"): | ||
data["budget_bill"] = True | ||
bbl_data = await self.__getBBL_async(account, data) | ||
data.update(bbl_data) | ||
else: | ||
data["budget_bill"] = False | ||
|
||
# Get data from energy service | ||
data.update( | ||
await self.__getDataFromEnergyService(account, premise, currentBillDate) | ||
) | ||
|
||
# Get data from energy service ( hourly ) | ||
# data.update( | ||
# await self.__getDataFromEnergyServiceHourly( | ||
# account, premise, currentBillDate | ||
# ) | ||
# ) | ||
|
||
data.update(await self.__getDataFromApplianceUsage(account, currentBillDate)) | ||
return data | ||
|
||
async def __getFromProjectedBill(self, account, premise, currentBillDate) -> dict: | ||
"""get data from projected bill endpoint""" | ||
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 Exception: | ||
pass | ||
|
||
return data | ||
|
||
async def __getBBL_async(self, account, projectedBillData) -> dict: | ||
"""Get budget billing data""" | ||
_LOGGER.info("Getting budget billing data") | ||
data = {} | ||
try: | ||
async with async_timeout.timeout(TIMEOUT): | ||
response = await self.session.get( | ||
URL_BUDGET_BILLING_PREMISE_DETAILS.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 Exception as e: | ||
_LOGGER.error("Error getting BBL: %s", e) | ||
|
||
try: | ||
async with async_timeout.timeout(TIMEOUT): | ||
response = await self.session.get( | ||
URL_BUDGET_BILLING_GRAPH.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 Exception as e: | ||
_LOGGER.error("Error getting BBL: %s", e) | ||
|
||
return data | ||
|
||
async def __getDataFromEnergyService( | ||
self, account, premise, lastBilledDate | ||
) -> dict: | ||
_LOGGER.info("Getting data from energy service") | ||
|
||
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", | ||
} | ||
URL_ENERGY_SERVICE = ( | ||
API_HOST | ||
+ "/dashboard-api/resources/account/{account}/energyService/{account}" | ||
) | ||
|
||
data = {} | ||
try: | ||
async with async_timeout.timeout(TIMEOUT): | ||
response = await self.session.post( | ||
URL_ENERGY_SERVICE.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"] | ||
if "netDeliveredKwh" in daily.keys() | ||
else 0, | ||
"netReceivedKwh": daily["netReceivedKwh"] | ||
if "netReceivedKwh" in daily.keys() | ||
else 0, | ||
"readTime": datetime.fromisoformat( | ||
daily[ | ||
"readTime" | ||
] # 2022-02-25T00:00:00.000-05:00 | ||
), | ||
} | ||
) | ||
# totalPowerUsage += int(daily["kwhUsed"]) | ||
|
||
# data["total_power_usage"] = totalPowerUsage | ||
data["daily_usage"] = dailyUsage | ||
|
||
data["projectedKWH"] = r["CurrentUsage"]["projectedKWH"] | ||
data["dailyAverageKWH"] = float( | ||
r["CurrentUsage"]["dailyAverageKWH"] | ||
) | ||
data["billToDateKWH"] = float(r["CurrentUsage"]["billToDateKWH"]) | ||
data["recMtrReading"] = int(r["CurrentUsage"]["recMtrReading"]) | ||
data["delMtrReading"] = int(r["CurrentUsage"]["delMtrReading"]) | ||
data["billStartDate"] = r["CurrentUsage"]["billStartDate"] | ||
except: | ||
pass | ||
|
||
return data | ||
|
||
async def __getDataFromApplianceUsage(self, account, lastBilledDate) -> dict: | ||
"""get data from appliance usage""" | ||
_LOGGER.info("Getting data from appliance usage") | ||
|
||
JSON = {"startDate": str(lastBilledDate.strftime("%m%d%Y"))} | ||
data = {} | ||
try: | ||
async with async_timeout.timeout(TIMEOUT): | ||
response = await self.session.post( | ||
URL_APPLIANCE_USAGE.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 Exception: | ||
pass | ||
|
||
return {"energy_percent_by_applicance": data} |
Oops, something went wrong.