Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
Merge pull request #2 from mjmeli/update-select-default-meter
Browse files Browse the repository at this point in the history
update select_default_meter logic to better handle edge cases
  • Loading branch information
mjmeli authored Oct 20, 2021
2 parents 98ff61a + e44216f commit dba83ee
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 17 deletions.
1 change: 1 addition & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ disable=
too-many-instance-attributes,
too-many-lines,
too-many-arguments,
broad-except,

good-names=
i,j,k,ex,Run,_, # defaults
Expand Down
7 changes: 3 additions & 4 deletions examples/example_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,9 @@ async def main() -> None: # noqa
jsonpickle.encode(account_details, indent=2, unpicklable=False)
)

meter = account_details.meter_infos[0]

_LOGGER.info(f"Selecting meter (not an API call) {meter.serial_num}")
duke_energy.select_meter(meter)
_LOGGER.info(f"Searching for default meter")
meter = await duke_energy.select_default_meter()
_LOGGER.info(f"Selected default meter {meter.serial_num}")

_LOGGER.info("Getting gateway status:")
gw_status = await duke_energy.get_gateway_status()
Expand Down
119 changes: 106 additions & 13 deletions src/pyduke_energy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
OAUTH_ENDPOINT,
SMARTMETER_AUTH_ENDPOINT,
)
from pyduke_energy.errors import InputError, RequestError
from pyduke_energy.errors import DukeEnergyError, InputError, RequestError
from pyduke_energy.types import (
Account,
AccountDetails,
Expand All @@ -34,7 +34,7 @@
)
from pyduke_energy.utils import date_to_utc_timestamp

_LOGGER = logging.getLogger(__name__)
_DEFAULT_LOGGER = logging.getLogger(__name__)


class _BaseAuthInfo:
Expand Down Expand Up @@ -84,11 +84,16 @@ class DukeEnergyClient:
"""The Duke Energy API client."""

def __init__(
self, email: str, password: str, session: Optional[ClientSession] = None
self,
email: str,
password: str,
session: Optional[ClientSession] = None,
logger: Optional[logging.Logger] = None,
):
self._email = email
self._password = password
self._session = session
self._logger = logger if logger else _DEFAULT_LOGGER

# Authentication
self._oauth_auth_info = _OAuthAuthInfo()
Expand Down Expand Up @@ -144,15 +149,103 @@ def select_meter(self, meter: MeterInfo) -> None:

def select_meter_by_id(self, meter_id: str, activation_date: date) -> None:
"""Select which meter will be used for gateway API calls."""
self.reset_selected_meter()
self._gateway_auth_info.meter_id = meter_id
self._gateway_auth_info.activation_date = activation_date
self._gateway_auth_info.clear_access_token() # resets

async def select_default_meter(self):
"""Select first meter of first account."""
accounts = await self.get_account_list()
meters = await self.get_account_details(accounts[0])
self.select_meter(meters.meter_infos[0])
def reset_selected_meter(self) -> None:
"""Reset which meter was previously selected and clear authentication."""
self._gateway_auth_info.meter_id = None
self._gateway_auth_info.activation_date = None
self._gateway_auth_info.clear_access_token() # resets authentication

async def select_default_meter(self) -> MeterInfo:
"""Find the meter that is used for the gateway by iterating through the accounts and meters."""
account_list = await self.get_account_list()
self._logger.debug(
"Accounts to check for gateway (%d): %s",
len(account_list),
",".join(["'" + a.src_acct_id + "'" for a in account_list]),
)

for account in account_list:
found_meter = await self._check_account_for_default_meter(account)
if found_meter:
return found_meter

# No meters were found. This is an error.
self.reset_selected_meter() # in case any were set by the failing code above
raise DukeEnergyError(
"Failed to identify any smart meter with gateway access on your account"
)

async def _check_account_for_default_meter(
self, account: Account
) -> Optional[MeterInfo]:
try:
self._logger.debug("Checking account '%s' for gateway", account.src_acct_id)
account_details = await self.get_account_details(account)
self._logger.debug(
"Meters to check for gateway (%d): %s",
len(account_details.meter_infos),
",".join(
["'" + m.serial_num + "'" for m in account_details.meter_infos]
),
)
for meter in account_details.meter_infos:
found_meter = await self._check_account_meter_for_default_meter(
account, meter
)
if found_meter:
return found_meter
except Exception as ex:
# Try the next account if anything fails above
self._logger.debug(
"Failed to find meter on account '%s': %s",
account.src_acct_id,
ex,
)

return None

async def _check_account_meter_for_default_meter(
self, account: Account, meter: MeterInfo
) -> Optional[MeterInfo]:
try:
self._logger.debug(
"Checking meter '%s' for gateway [meter_type=%s, is_certified_smart_meter=%s]",
meter.serial_num,
meter.meter_type,
str(meter.is_certified_smart_meter),
)
if (
meter.serial_num # sometimes blank meters show up
and meter.meter_type.upper() == "ELECTRIC"
and meter.is_certified_smart_meter
):
self.select_meter(meter)
gw_status = await self.get_gateway_status()

if gw_status is not None:
# Found a meter
self._logger.debug(
"Found meter '%s' with gateway '%s'",
meter.serial_num,
gw_status.id,
)
return meter

self._logger.debug("No gateway status for meter '%s'", meter.serial_num)
except Exception as ex:
# Try the next meter if anything fails above
self._logger.debug(
"Failed to check meter '%s' on account '%s': %s",
meter.serial_num,
account.src_acct_id,
ex,
)

return None

async def get_gateway_status(self) -> GatewayStatus:
"""Get the status of the selected gateway."""
Expand All @@ -179,7 +272,7 @@ async def get_gateway_usage(
) # API expects dates to be UTC
end_hour = range_end.astimezone(timezone.utc).strftime(dt_format)
params = {"startHourDt": start_hour, "endHourDt": end_hour}
_LOGGER.debug(
self._logger.debug(
"Requesting usage between %s UTC and %s UTC", start_hour, end_hour
)

Expand Down Expand Up @@ -220,12 +313,12 @@ async def start_smartmeter_fastpoll(self):
)
# Not real accurate since it doesnt care about the response.
tstart = time.perf_counter()
_LOGGER.debug("Smartmeter fastpoll requested")
self._logger.debug("Smartmeter fastpoll requested")
return tstart

async def _oauth_login(self) -> None:
"""Hit the OAuth login endpoint to generate a new access token."""
_LOGGER.debug("Getting new OAuth auth")
self._logger.debug("Getting new OAuth auth")

headers = {"Authorization": BASIC_AUTH}
request = {
Expand Down Expand Up @@ -258,7 +351,7 @@ async def _gateway_login(self) -> None:
"Gateway needs to be selected before calling gateway functions"
)

_LOGGER.debug("Getting new gateway auth")
self._logger.debug("Getting new gateway auth")

headers = await self._get_oauth_headers()
request = {
Expand Down

0 comments on commit dba83ee

Please sign in to comment.