Giter Club home page Giter Club logo

opower's Introduction

opower

A Python library for getting historical and forecasted usage/cost from utilities that use opower.com such as PG&E.

Supported utilities (in alphabetical order):

  • American Electric Power (AEP) subsidiaries
    • AEP Ohio
    • AEP Texas
    • Appalachian Power
    • Indiana Michigan Power
    • Kentucky Power
    • Public Service Company of Oklahoma (PSO)
    • Southwestern Electric Power Company (SWEPCO)
  • City of Austin Utilities
  • Consolidated Edison (ConEd)
    • Orange & Rockland Utilities (ORU)
  • Duquesne Light Company (DQE)
  • Enmax Energy
  • Evergy
  • Exelon subsidiaries
    • Atlantic City Electric
    • Baltimore Gas and Electric (BGE)
    • Commonwealth Edison (ComEd)
    • Delmarva Power
    • PECO Energy Company (PECO)
    • Potomac Electric Power Company (Pepco)
  • Mercury NZ Limited
  • National Grid NY Upstate
  • Pacific Gas & Electric (PG&E)
  • Portland General Electric (PGE)
  • Puget Sound Energy (PSE)
  • Sacramento Municipal Utility District (SMUD)
  • Seattle City Light (SCL)

Support a new utility

To add support for a new utility that uses opower JSON API (you can tell if the energy dashboard of your utility makes network requests to opower.com, e.g. pge.opower.com in the network tab of your browser's developer tools) add a file similar to pge.py or pse.py or bge.py etc.

Name the file after the utility website, e.g. pge.py for pge.com.

Since this library is used by Home Assistant, see https://www.home-assistant.io/integrations/opower/, per https://github.com/home-assistant/architecture/blob/master/adr/0004-webscraping.md we cannot have a dependency on a headless browser and we can only parse HTML during login.

An exception is made for the authentication phase. An integration is allowed to extract fields from forms. To make it more robust, data should not be gathered by scraping individual fields but instead scrape all fields at once.

So follow that advice and try to scrape all fields at once, similar to the get_form_action_url_and_hidden_inputs in helpers.py.

Development environment

python3 -m venv .venv
source .venv/bin/activate
# for Windows CMD:
# .venv\Scripts\activate.bat
# for Windows PowerShell:
# .venv\Scripts\Activate.ps1

# Install dependencies
python -m pip install --upgrade pip
python -m pip install .

# Run pre-commit
python -m pip install pre-commit
pre-commit install
pre-commit run --all-files

# Alternative: run formatter, lint, and type checking
python -m pip install isort black flake8 ruff mypy
isort . ; black . ; flake8 . ; ruff check . --fix ; mypy --install-types .

# Run tests
python -m pip install pytest python-dotenv
pytest

# Run command line
python -m opower --help
# To output debug logs and API responses to a file run:
python -m opower -vv 2> out.txt

# Build package
python -m pip install build
python -m build

opower's People

Contributors

akoebbe avatar aman207 avatar benhoff avatar brianhenryie avatar chen-ye avatar dewdropawoo avatar domoritz avatar drewclauson avatar fabaff avatar francoishuet avatar joewashear007 avatar jrigling avatar levelonedev avatar mark-ignacio avatar max2697 avatar mjrsnyder avatar paultyng avatar rvsit avatar sebmaster avatar splicednz avatar swartzd avatar tboyce021 avatar trancefam avatar tronikos avatar x-sam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

opower's Issues

Exelon utilities don't work when there are multiple accounts (login to select-account instead of dashboard)

I'm getting a 400 Bad Request with the following HTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Request Error</title>
    <style>BODY { color: #000000; background-color: white; font-family: Verdana; margin-left: 0px; margin-top: 0px; } #content { margin-left: 30px; font-size: .70em; padding-bottom: 2em; } A:link { color: #336699; font-weight: bold; text-decoration: underline; } A:visited { color: #6699cc; font-weight: bold; text-decoration: underline; } A:active { color: #336699; font-weight: bold; text-decoration: underline; } .heading1 { background-color: #003366; border-bottom: #336699 6px solid; color: #ffffff; font-family: Tahoma; font-size: 26px; font-weight: normal;margin: 0em 0em 10px -20px; padding-bottom: 8px; padding-left: 30px;padding-top: 16px;} pre { font-size:small; background-color: #e5e5cc; padding: 5px; font-family: Courier New; margin-top: 0px; border: 1px #f0f0e0 solid; white-space: pre-wrap; white-space: -pre-wrap; word-wrap: break-word; } table { border-collapse: collapse; border-spacing: 0px; font-family: Verdana;} table th { border-right: 2px white solid; border-bottom: 2px white solid; font-weight: bold; background-color: #cecf9c;} table td { border-right: 2px white solid; border-bottom: 2px white solid; background-color: #e5e5cc;}</style>    
  </head>
  <body>
    <div id="content">
      <p class="heading1">Request Error</p>
      <p>The server encountered an error processing the request. The exception message is 'Object reference not set to an instance of an object.'. See server logs for more details. The exception stack trace is: </p>
      <p>   at Exelon.Web.WebServices.OpowerService.GetOpowerToken() in D:\a\1\s\Exelon.Web.WebServices\Services\OpowerService.svc.cs:line 516
   at SyncInvokeGetOpowerToken(Object , Object[] , Object[] )
   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]&amp; outputs)
   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&amp; rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&amp; rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc&amp; rpc)
   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)</p>
    </div>
  </body>
</html>

I compared this to what I see in the browser and I can't see anything specific with the request that jumps out at me immediately. I'm going to go over it more closely and see if I can figure it out but just wanted to post this here if the server side callstack is recognizable to you as something you had seen before.

Quick question on utility class naming

I'm starting to work on an integration with the Kansas City power utility, Evergy. It was formerly known as KCP&L (Kansas City Power and Light) and the Opower subdomain is "kcpk" (I think someone might have typo'd that and it stuck). So would you recommend I name the file and class "evergy", the actual utility name, or "kcpk", the Opower subdomain?

Request: Support opower guest accounts for coautilities

For coautilities (not sure about others), you can create a guest account at https://dss-coa.opower.com/dss/account/manage-web-account under "Connected accounts > + Add guest user". This allows you to set the access level to effectively read-only ("Usage & Financial Info"). It'd be nice if we could take advantage of this access control to apply the principle of least privilege.

The main login credentials work fine with demo.py (release version 0.4.6).

I can use this guest login via the browser to access usage data, but demo.py fails with:

DEBUG:/config/custom_components/opower-0.4.6/src/opower/opower.py:Fetching: https://dss-coa.opower.com/webcenter/edge/apis/dss-invite-v1/cws/v1/utilities/connectedaccounts?pageOffset=0&pageLimit=100
Level 9:/config/custom_components/opower-0.4.6/src/opower/opower.py:Fetched: {
  "accounts": [],
  "totalRecords": 0
}
Traceback (most recent call last):
  File "/config/custom_components/opower-0.4.6/src/demo.py", line 172, in <module>
    asyncio.run(_main())
  File "/usr/local/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 685, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/config/custom_components/opower-0.4.6/src/demo.py", line 91, in _main
    for forecast in await opower.async_get_forecast():
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/opower-0.4.6/src/opower/opower.py", line 247, in async_get_forecast
    for customer in await self._async_get_customers():
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/opower-0.4.6/src/opower/opower.py", line 327, in _async_get_customers
    await self._async_get_user_accounts()
  File "/config/custom_components/opower-0.4.6/src/opower/opower.py", line 374, in _async_get_user_accounts
    assert self.user_accounts
AssertionError

Tracing the network calls when using the guest account from a browser session, I see

Presumably guest logins require a less-direct method to dig up the missing accounts info?

ConEd multiple accounts - no forecast data

I'm up to date with what's merged into homeassistant:dev so far (between 2023.8.2 and the yet to be released 2023.8.3). I'm not getting opower sensorss, but with request debugging enabled it looks like I'm getting back forecast data:

2023-08-13 14:51:37.621 DEBUG (MainThread) [/config/opower/opower.py] Fetching: https://cned.opower.com/ei/edge/apis/bill-forecast-cws-v1/cws/cned/customers/XXX-customer-385-duplex-uuid-XXX/combined-forecast
2023-08-13 14:51:37.962 DEBUG (MainThread) [/config/opower/opower.py] Fetched: {
  "isValidUser": true,
  "totalForecast": {
    "meterType": "COMBINED",
    "startDate": "2023-08-10",
    "endDate": "2023-09-08",
    "currentDate": "2023-08-13",
    "daysInPeriod": 30,
    "currentDay": 4,
    "daysLeftInBill": 26,
    "forecastedUsage": 1008,
    "forecastedCost": 0,
    "typicalUsage": 1027,
    "typicalCost": 0,
    "budgetBilling": false,
    "costToDate": 0,
    "usageToDate": 61,
    "currencySymbol": "$"
  },
  "totalMetadata": [
    "NO_FORECASTED_COST"
  ],
  "accountForecasts": [
    {
      "unitOfMeasure": "KWH",
      "meterType": "ELEC",
      "startDate": "2023-08-10",
      "endDate": "2023-09-08",
      "currentDate": "2023-08-13",
      "daysInPeriod": 30,
      "currentDay": 4,
      "daysLeftInBill": 26,
      "forecastedUsage": 1008,
      "typicalUsage": 1027,
      "budgetBilling": false,
      "usageToDate": 61,
      "currencySymbol": "$",
      "preferredUtilityAccountId": "5085809",
      "accountUuids": [
        "XXX-utilacct-duplex-elec-XXX"
      ],
      "isSolar": false
    }
  ]
}
2023-08-13 14:51:37.962 DEBUG (MainThread) [/config/opower/opower.py] Fetching: https://cned.opower.com/ei/edge/apis/bill-forecast-cws-v1/cws/cned/customers/XXX-customer-384-halls-uuid-XXX/combined-forecast
2023-08-13 14:51:38.090 DEBUG (MainThread) [custom_components.opower.coordinator] Updating sensor data with: []

What else can I do to debug this?

PSE Unable to Supply Daily/Hourly Bill Cost

As mentioned in home-assistant/core#99674, I noticed PSE seems to be unable to provide daily or hourly costs and all values will show 0, including the current bill/forecast. The costs are only provided in the bill view.

Assuming PSE never fixes this, should opower maybe request the data twice, first with the bill aggregate type to calculate the rates for each billing period, then request the desired data and use the previously calculated rates to estimate the provider's daily/hourly cost? Or should other utilities, such as HomeAssistant's integration be responsible for something like that?

Subdomains for PEPCO and Delmarva Power are incorrect for some states

Hi,

I was trying to setup the Home Assistant plugin and kept getting this error (not sure if related or separate issue):

2023-08-02 22:02:58.615 ERROR (MainThread) [homeassistant.components.opower.coordinator] Unexpected error fetching Opower data: 'NoneType' object has no attribute 'group'
Traceback (most recent call last):
File "/lsiopy/lib/python3.11/site-packages/homeassistant/helpers/update_coordinator.py", line 283, in _async_refresh
self.data = await self._async_update_data()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/lsiopy/lib/python3.11/site-packages/homeassistant/components/opower/coordinator.py", line 67, in _async_update_data
await self.api.async_login()
File "/lsiopy/lib/python3.11/site-packages/opower/opower.py", line 151, in async_login
self.access_token = await self.utility.async_login(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/lsiopy/lib/python3.11/site-packages/opower/utilities/exelon.py", line 44, in async_login
settings = json.loads(re.search(r"var SETTINGS = ({.*});", result).group(1))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'group'

So instead I tried to use this python package directly outside of HA, which gave me the error:

raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 403, message='Forbidden', url=URL('https://pep.opower.com/ei/edge/apis/multi-account-v1/cws/pep/customers?offset=0&batchSize=100&addressFilter=')

When loading https://secure.pepco.com/api/Services/MyAccountService.svc/GetConfiguration I can see the following values

image

When logging into my account, all requests seem to prefer oPowerURLBaseJurisdiction:

image

I was able to get the python script working by changing this to pepd.

I am unsure if this is dynamic, as it seemed to work when it was initially added in #3

Evergy uses a dynamic opower.com subdomain

It looks like Evergy is the odd one out, as it's the only utility in this library that requires an API call to determine whether to use kcpk.opower.com or kcpl.opower.com.

I opened this issue because it looks like this might require making greater changes in order to make UtilityBase.subdomain() to be a cached call that doesn't hit a non-opower API every time, and I don't really want to make larger changes than #9 without talking about it first. 😅

To me it appears that the general, overarching issue is that UtilityBase subclasses aren't structurally meant to be stateful and aren't instantiated. They're currently instead used as a sort of function collection registry, which bars the ability for UtilityBase.subdomain() to return different values depending on what account is connected.

opower/src/opower/opower.py

Lines 122 to 128 in 8c07c33

for utility in UtilityBase.subclasses:
if name_or_subdomain.lower() in [
utility.name().lower(),
utility.__name__.lower(),
utility.subdomain().lower(),
]:
return utility

The way I'd personally prefer to make Evergy work is for the Opower class to instantiate the UtilityBase subclass it gets here, which would allow the subclass to handle any statefulness itself, should it choose to do so:

self.utility: type[UtilityBase] = _select_utility(utility)

This would prove handy because self.utility.subdomain() calls could be implemented as normal methods instead of static methods without changing any of its callsites like so:

opower/src/opower/opower.py

Lines 170 to 172 in 8c07c33

f"{self.utility.subdomain()}"
".opower.com/ei/edge/apis/bill-forecast-cws-v1/cws/"
f"{self.utility.subdomain()}"

The idea with this implementation is that Evergy.subdomain() would be able to returned a cached value whenever anything calls Evergy.async_login().

The second required change is to _select_utility, hopefully without having to change every other utility class. I'd prefer to create a classmethod called UtilityBase.subdomains() -> set[str], where the default implementation is just return cls.subdomain(). This allows the Evergy class to be addressed by both kcpk or kcpl with a minor code change to _select_utility to use that instead.

Definitely open to feedback and any suggestions on how you'd like this to be implemented. Even if you'd prefer to make the change yourself!

0.0.29 demo.py fails with 401 unauthorized for ConEd fetching customers

I pulled in the latest from your repo to get 0.0.29 changes. Now ConEd login fails with 401 unauthorized while trying to fetch /ei/edge/apis/multi-account-v1/cws/cned/customers

I think if it was a real login problem, I should have gotten the error earlier?

I'm still trying to dig into this. I did check that I can still log in via the web UI.

If I checkout v0.0.28, I don't run into this problem.

DEBUG:/mnt/s2/home/rct/proj/coned/tronikos/opower/src/opower/opower.py:Fetching: https://cned.opower.com/ei/edge/apis/multi-account-v1/cws/cned/customers?offset=0&batchSize=100&addressFilter=
Traceback (most recent call last):
  File "/mnt/s2/home/rct/proj/coned/tronikos/opower/src/demo.py", line 162, in <module>
    asyncio.run(_main())
  File "/opt/anaconda/anaconda3/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/opt/anaconda/anaconda3/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/anaconda/anaconda3/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/mnt/s2/home/rct/proj/coned/tronikos/opower/src/demo.py", line 81, in _main
    for forecast in await opower.async_get_forecast():
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/s2/home/rct/proj/coned/tronikos/opower/src/opower/opower.py", line 237, in async_get_forecast
    for customer in await self._async_get_customers():
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/s2/home/rct/proj/coned/tronikos/opower/src/opower/opower.py", line 298, in _async_get_customers
    async with self.session.get(
  File "/srv/pyvenvs/opower/lib/python3.11/site-packages/aiohttp/client.py", line 1141, in __aenter__
    self._resp = await self._coro
                 ^^^^^^^^^^^^^^^^
  File "/srv/pyvenvs/opower/lib/python3.11/site-packages/aiohttp/client.py", line 643, in _request
    resp.raise_for_status()
  File "/srv/pyvenvs/opower/lib/python3.11/site-packages/aiohttp/client_reqrep.py", line 1005, in raise_for_status
    raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 401, message='Unauthorized', url=URL('https://cned.opower.com/ei/edge/apis/multi-account-v1/cws/cned/customers?offset=0&batchSize=100&addressFilter=')

City of Austin Utilities fetching from "customers" endpoint fails with 403 (Forbidden)

Running latest in main branch in a dev container, using my own credentials. I believe prior calls are working (e.g., call to the "connectedaccounts" endpoint seems to succeed).

Happy to run again and try various things to help run this down!

From debugger, full stack:

  File "/workspaces/opower/src/opower/opower.py", line 309, in _async_get_customers
    async with self.session.get(
  File "/workspaces/opower/src/opower/opower.py", line 229, in async_get_forecast
    for customer in await self._async_get_customers():
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/opower/src/demo.py", line 89, in _main
    for forecast in await opower.async_get_forecast():
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/opower/src/demo.py", line 170, in <module>
    asyncio.run(_main())
aiohttp.client_exceptions.ClientResponseError: 403, message='Forbidden', url=URL('https://dss-coa.opower.com/webcenter/edge/apis/multi-account-v1/cws/coa/customers?offset=0&batchSize=100&addressFilter=')```

New Provider: FirstEnergy and subsidiaries

FirstEnergy and it's subsidiaries are using opower. Creating this issue to support the addition of FirstEnergy.

I've done some of the initial work to support this. Will continue over the course of the next few days

login page - https://www.firstenergycorp.com/content/customer/log_in.html
or
https://www.firstenergycorp.com/log_in.fecorplogin.html

https://www.firstenergycorp.com/my_account/analyze-usage.html

First Energy: Ohio Edison uses feoe.opower.com

Others appear to be: (need verified)
(?) https://fepa.opower.com/
MetEd(?) - https://feme.opower.com/
PennPower (?) - https://fepp.opower.com/
(?) - https://fepn.opower.com/
ToledoEdison (?) - https://fete.opower.com/

Working on a new provider

I been analyzing my energy provider usage of opower and they do it through embedding. The only way I find the auth token is via a script that gets embedded in-line on a page (not after authorization). Looking through the requests so far I have not been able to find where it is added. Have you dealt with providers using embedded API calls to opower and can provide any further guidance?

From the looks of it, it is pretty easy to extract the token from Javascript since their listener that they add to window accepts a function that it passes the token to. I created this javascript below to pull it. Unfortunately I saw the note about web scrapping so I don't think it is very useful.
var token = null; window.dispatchEvent(new CustomEvent("opower:unauthorized", { detail: { authorize: (n, a) => token = a.accessToken }})); token;

I do have an export of this from Firefox dev tools I can privately share, since it does contain tokens albeit expired now.

Sanitize TOTP secret to prevent binascii.Error: Non-base32 digit found

Slight usability problem with TOTP providers like ConEd - if the user enters a TOTP secret with trailing(*) white space, the exception binascii.Error: Non-base32 digit found is raised by the base64 module. For Home Assistant, the user sees Unknown error which can be resolved by trimming out spaces that might arise from copy and paste. However, this isn't obvious to the user and seems like a problem with the integration.

See home-assistant/core#101829 (comment)

(*) I only tested with trailing white space but I suspect leading white space would cause the same problem.

The Opower config flow in the Home Assistant integration should probably do the cleaning and/or have a check for a TOTP secret with invalid characters.

However, the Opower module itself could remove leading/trailing spaces from the secret and/or validate that the secret s a valid base32 string that can be decoded earlier during configuration. (Right now I think the string that can't be decoded isn't detected until a login is attempted.)

Edit: Forgot to mention, ideally if the opower module detects that the TOTP secret can't be decoded, it should throw an error that will result in a more meaningful error message to the end user.

New provider: City of Austin Utilities

Hello!

Currently working on adding a new provider, City of Austin Utilities (max2697#1). It's also working on opower but I found some differences from this repos code.

First of all it's failing when it try to call _async_get_customers. This endpoint just redirecting to some html page via 302 code.
I made some research and found different endpoint with the same data:
https://dss-coa.opower.com/webcenter/edge/apis/multi-account-v1/cws/coa/customers

Some differences:

  • dss-{utilityCode} as a subdomain, {utilityCode} as a part of URI
  • /webcenter/ root instead of /ei/
  • This API required not only Bearer token but also header opower-selected-entities: ["urn:session:account:REDACTED"]

Found all 4 endpoints in my version of portal:

       /ei/edge/apis/DataBrowser-v1/cws/cost/utilityAccount/...
/webcenter/edge/apis/DataBrowser-v1/cws/cost/utilityAccount/...

       /ei/edge/apis/DataBrowser-v1/cws/utilities/{subdomain}/utilityAccounts/.../reads
/webcenter/edge/apis/DataBrowser-v1/cws/utilities/{utilityCode}/utilityAccounts/.../reads

       /ei/edge/apis/bill-forecast-cws-v1/cws/{subdomain}/customers/.../combined-forecast
/webcenter/edge/apis/bill-forecast-cws-v1/cws/{utilityCode}/customers/.../combined-forecast

       /ei/edge/apis/multi-account-v1/cws/{subdomain}/customers
/webcenter/edge/apis/multi-account-v1/cws/{utilityCode}/customers

So basically City of Austin Utilities is using different version of a Digital Self Servicedescribe on this page: https://docs.oracle.com/en/industries/energy-water/digital-self-service/transactions-seamless/Content/Transactions/SeamlessIntegration/Introduction_Seamless.htm

Have anybody heard about this different version of Opower web interface?
What's the address of home page for Opower for other providers? Mine is https://dss-coa.opower.com/dss/overview

demo.py --aggregate_type is failing

When using --aggregate_type hour on demo.py, I'm getting the following error:

demo.py: error: argument --aggregate_type: invalid choice: 'hour' (choose from <AggregateType.BILL: 'bill'>, <AggregateType.DAY: 'day'>, <AggregateType.HOUR: 'hour'>)

I get the general reason for the error, but I'm not sure how best to fix it. In the mean time, I've just been replacing the default in demo.py, line 36, as needed.

   parser.add_argument(
        "--aggregate_type",
        help="How to aggregate historical data. Defaults to day",
        choices=list(AggregateType),
        default=AggregateType.HOUR, # <-- Was AggregateType.DAY

ConEd unable to authenticate after backup/restore

I am attempting to add my ConEd account after having to revert to an old backup for an unrelated Zwave issue. When I try to setup I add my username and password and then enter my OTP secret. I get an error saying "Invalid authentication". I have tried the credentials on a clean install and it works perfectly. I don't see anything in the log but I am not sure where to look to troubleshoot this further.

New provider: Southern Maryland Electric COoperative (SMECO)

Hi,

My provider, the Southern Maryland Electric COoperative (SMECO) uses OPOWER.

I looked through the source code for some of the other providers, and it looks like the user isn't directly using the OPOWER domain (although I am not quite tracking the subdomain use), but they are going through the utility's main website.

For example, when I am logged in, I am at a URL like:

https://dss-smcc.opower.com/dss/energy/use-details?ou-data-browser=/neighbors/electricity/year/2022-11-19?accountUuid%XXXXXXX

I have a screenshot below.

What would be the closest code to start with in trying to create something for this provider? Thanks!

Screenshot 2023-11-10 at 10 54 28 AM

403 Forbidden error - PG&E - HA

This issue is related to home-assistant/core#120797

Getting the following error when attempting to access data. (Censored UUIDs, IDs, Username and Passwords with asterisks)

$ python -m opower --utility pge --username ****** --password ******

Current bill forecast: Forecast(account=Account(customer=Customer(uuid='***'), uuid='***', ut
ility_account_id='***', id='***', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=None), start_date=datetime.date(2024, 6, 17), end_date=dat
etime.date(2024, 7, 17), current_date=datetime.date(2024, 7, 1), unit_of_measure=<UnitOfMeasure.KWH: 'KWH'>, usage_to_date=792.0, cost_to_date=300.0, forecasted_usage=2086.0, forecasted_cost=870.0, typical_usage=2445.0, typical_cost=250.0)

Current bill forecast: Forecast(account=Account(customer=Customer(uuid='***'), uuid='***', ut
ility_account_id='***', id='***', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=None), start_date=datetime.date(2024, 6, 18), end_date=dat
etime.date(2024, 7, 18), current_date=datetime.date(2024, 7, 1), unit_of_measure=<UnitOfMeasure.KWH: 'KWH'>, usage_to_date=9.0, cost_to_date=4.0, forecasted_usage=30.0, forecasted_cost=12.0, typical_usage=56.0, typical_cost=12.0)

Getting historical data: account= Account(customer=Customer(uuid='***'), uuid='***', utility_
account_id='***', id='***', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=<ReadResolution.HOUR: 'HOUR'>) aggregate_type= day start_date= 2024-06-24 16:03:16.714456 end_date= 2024-07-01 16:03:16.714456
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Users\indie\PycharmProjects\opower\.venv\Lib\site-packages\opower\__main__.py", line 214, in <module>
    asyncio.run(_main())
  File "C:\Python311\Lib\asyncio\runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\asyncio\base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "C:\Users\indie\PycharmProjects\opower\.venv\Lib\site-packages\opower\__main__.py", line 166, in _main
    cost_data = await opower.async_get_cost_reads(
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\indie\PycharmProjects\opower\.venv\Lib\site-packages\opower\opower.py", line 394, in async_get_cost_reads
    reads = await self._async_get_dated_data(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\indie\PycharmProjects\opower\.venv\Lib\site-packages\opower\opower.py", line 503, in _async_get_dated_data
    reads = await self._async_fetch(
            ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\indie\PycharmProjects\opower\.venv\Lib\site-packages\opower\opower.py", line 566, in _async_fetch
    raise err
  File "C:\Users\indie\PycharmProjects\opower\.venv\Lib\site-packages\opower\opower.py", line 553, in _async_fetch
    async with self.session.get(
  File "C:\Users\indie\PycharmProjects\opower\.venv\Lib\site-packages\aiohttp\client.py", line 1197, in __aenter__
    self._resp = await self._coro
                 ^^^^^^^^^^^^^^^^
  File "C:\Users\indie\PycharmProjects\opower\.venv\Lib\site-packages\aiohttp\client.py", line 696, in _request
    resp.raise_for_status()
  File "C:\Users\indie\PycharmProjects\opower\.venv\Lib\site-packages\aiohttp\client_reqrep.py", line 1070, in raise_for_status
    raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 403, message='Forbidden', url=URL('https://pge.opower.com/ei/edge/apis/DataBrowser-v1/cws/cost/utilityAccount/***?aggregateType=day&startDate=2024-06-24T00:00:00-07:00&endDate=2024-07-02T00:00:00-07:00')

Don't fetch with aggregations less than `billing` when account's read resolution is `billing`

As discussed in #30, with ConEd, I'm seeing the exception, ValueError: Requested aggregate_type: hour not supported by account's read_resolution: BILLING from both demo.py and the current Home Assistant integration.

I'm not sure if I'm following along correctly, but it seems If the account's read_resolution is billing, than possibly opower shouldn't try to use an aggregation_type "smaller" than billing?

I believe this exception might also be causing the sensor forecast data not to be populated for the account that has quarter hour read resolution.

This is the exception from the home assistant log:

2023-08-12 19:33:39.187 DEBUG (MainThread) [custom_components.opower.coordinator] Updating Statistics for opower:cned_elec_xxxx921_energy_cost and opower:cned_elec_xxxxxxx921_energy_consumption
2023-08-12 19:33:39.198 ERROR (MainThread) [custom_components.opower.coordinator] Unexpected error fetching Opower data: Requested aggregate_type: hour not supported by account's read_resolution: BILLING
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 283, in _async_refresh
    self.data = await self._async_update_data()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/opower/coordinator.py", line 73, in _async_update_data
    await self._insert_statistics()
  File "/config/custom_components/opower/coordinator.py", line 104, in _insert_statistics
    cost_reads = await self._async_get_recent_cost_reads(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/opower/coordinator.py", line 214, in _async_get_recent_cost_reads
    return await self.api.async_get_cost_reads(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/opower/opower.py", line 332, in async_get_cost_reads
    reads = await self._async_get_dated_data(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/opower/opower.py", line 403, in _async_get_dated_data
    raise ValueError(
ValueError: Requested aggregate_type: hour not supported by account's read_resolution: BILLING
2023-08-12 19:33:39.215 DEBUG (MainThread) [custom_components.opower.coordinator] Finished fetching Opower data in 6.651 seconds (success: False)

My ConEd user returns 3 utilityAccounts:

  • Meter type: elec with quarter hour readResolution
  • Meter type: elec with billing readResolution
  • Meter type: gas with billing readResolution

Here's the (sanitized) results of the ei/edge/apis/multi-account-v1/cws/cned/customers query:

2023-08-09 16:36:52.055 DEBUG (MainThread) [/config/opower/opower.py] Fetching: https://cned.opower.com/
ei/edge/apis/multi-account-v1/cws/cned/customers?offset=0&batchSize=100&addressFilter=
2023-08-09 16:36:52.526 DEBUG (MainThread) [/config/opower/opower.py] Fetched: {
  "customers": [
    {
      "id": xxx385,
      "uuid": "XXX-customer-385-duplex-uuid-XXX",
      "legacyOpowerId": "snip",
      "accountNumber": "XXX-cust-385-duplex-acct-num-XXX",
      "accountName": "",
      "address": { 
...SNIP...
      },
      "type": "RESIDENTIAL",
      "utilityAccounts": [
        {
          "id": xxx537,
          "uuid": "XXX-utilacct-duplex-elec-XXX",
          "utilityAccountId": "XXX-cust-385-duplex-acct-num-XXX",
          "utilityAccountId2": "xxxxxxx",
          "servicePointId": 379095,
          "meterType": "ELEC",
          "preferredUtilityAccountId": "XXX-cust-385-duplex-acct-num-XXX",
          "readResolution": "QUARTER_HOUR"
        }
      ]
    },
    {
      "id": 228384,
      "uuid": "XXX-customer-384-halls-uuid-XXX",
      "legacyOpowerId": "snip",
      "accountNumber": "XXX-cust-384-halls-acct-num-XXX",
      "accountName": "",
      "address": {
...SNIP...
      },
      "type": "SMB",
      "utilityAccounts": [
        {
          "id": xxx535,
          "uuid": "XXX-utilacct-halls-elec-XXX",
          "utilityAccountId": "XXX-cust-384-halls-acct-num-XXX",
          "utilityAccountId2": "xxxxxxx",
          "servicePointId": 379093,
          "meterType": "ELEC",
          "preferredUtilityAccountId": "XXX-cust-384-halls-acct-num-XXX",
          "readResolution": "BILLING"
        },
        {
          "id": xxx536,
          "uuid": "XXX-utilacct-halss-gas-xxx",
          "utilityAccountId": "XXX-cust-384-halls-acct-num-XXX",
          "utilityAccountId2": "3584863",
          "servicePointId": 379094,
          "meterType": "GAS",
          "preferredUtilityAccountId": "XXX-cust-384-halls-acct-num-XXX",
          "readResolution": "BILLING"
        }
      ]
    }
  ],
  "offset": 0,
  "batchSize": 100,
  "total": 2
}

pse "AssertionError: Failed to parse __RequestVerificationToken"

PSE is failing with ""AssertionError: Failed to parse __RequestVerificationToken""

Linux ip-172-30-3-187 5.10.0-20-cloud-arm64 #1 SMP Debian 5.10.158-2 (2022-12-13) aarch64 GNU/Linux

(.venv) admin@ip-172-30-3-187:~/opower$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

(.venv) admin@ip-172-30-3-187:~/opower$ python -V
Python 3.9.2

(.venv) admin@ip-172-30-3-187:~/opower$ python src/demo.py --verbose --utility pse --username murasakijou
Password:
Traceback (most recent call last):
File "/home/admin/opower/src/demo.py", line 162, in
asyncio.run(_main())
File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/home/admin/opower/src/demo.py", line 80, in _main
await opower.async_login()
File "/home/admin/opower/src/opower/opower.py", line 202, in async_login
self.access_token = await self.utility.async_login(
File "/home/admin/opower/src/opower/utilities/pse.py", line 96, in async_login
assert (
AssertionError: Failed to parse __RequestVerificationToken

I would post more logs if I knew how. I did set
DEBUG_LOG_RESPONSE = True

New provider: Enmax (Canada)

Good afternoon, I'm super eager to use this library (as part of home-assistant) but my python abilities are lacking. If I can provide all of the information from a browser session to login is there someone that can help add/validate the new provider? I'd be happy to assist in any-way that I can.

the subdomain that I see the requests going through are at enmx.opower.com (the missing a is intentional) and the authenticate urls are also mentioning the sso at sso.enmax.com.

When I log into my provider's website at enmax.com I am never presented with a url in my browser visible at opower.com but i see many hits to the enmx.opower.com api in the network tab of chrome's dev tools.

New login flow for Duquesne Light (DQE) breaks Opower integration

Hi there, I noticed (within the last few days) that Home Assistant wasn't updating my Duquesne Light Opower stats.

Full logs (from Home Assistant):

Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/opower/opower.py", line 197, in async_login
    self.access_token = await self.utility.async_login(
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/opower/utilities/duquesnelight.py", line 91, in async_login
    async with session.post(
  File "/usr/local/lib/python3.12/site-packages/aiohttp/client.py", line 1197, in __aenter__
    self._resp = await self._coro
                 ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/aiohttp/client.py", line 696, in _request
    resp.raise_for_status()
  File "/usr/local/lib/python3.12/site-packages/aiohttp/client_reqrep.py", line 1070, in raise_for_status
    raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 404, message='Not Found', url=URL('https://www.duquesnelight.com/login/login')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/homeassistant/helpers/update_coordinator.py", line 312, in _async_refresh
    self.data = await self._async_update_data()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/homeassistant/components/opower/coordinator.py", line 81, in _async_update_data
    await self.api.async_login()
  File "/usr/local/lib/python3.12/site-packages/opower/opower.py", line 205, in async_login
    raise CannotConnect(err)
opower.exceptions.CannotConnect: 404, message='Not Found', url=URL('https://www.duquesnelight.com/login/login')

Indeed, I'm not able to access https://www.duquesnelight.com/login/login in the browser either.

From the Duquesne Light homepage, it appears login has moved to https://auth.duquesnelight.com/oauth/authorize/?client_id=33292caf-08ce-4631-a7a1-7bc3e30d318c&response_type=code&redirect_uri=https%3a%2f%2fduquesnelight.com%2fdlc%2flogin%3fredirectUrl%3d%252faccount-billing%252faccount-summary (concise, right?).

I saw that @swartzd was the original creator of this utility integration in #68. I poked around in the code, replacing all of the headers and payload with the new values that I copied from my Chrome network tools console, however I'm getting opower.exceptions.CannotConnect: 408, message='Request Timeout', url=URL('https://auth.duquesnelight.com/oauth/authorize?client_id=33292caf-08ce-4631-a7a1-7bc3e30d318c&response_type=code&redirect_uri=https://www.duquesnelight.com/dlc/login?redirectUrl%3D%252faccount-billing%252faccount-summary').

Would appreciate it if someone else could take a look. Thanks!!

Delmarva uses CCF as the Unit of Measure for natural gas

When running demo.py after changing the domain to dpld to work around #14 I am receiving the following error.

DEBUG:/home/csnyder/Documents/code/opower/src/opower/opower.py:Fetching: https://dpld.opower.com/ei/edge/apis/multi-account-v1/cws/dpld/customers?offset=0&batchSize=100&addressFilter=
DEBUG:/home/csnyder/Documents/code/opower/src/opower/opower.py:Fetching: https://dpld.opower.com/ei/edge/apis/bill-forecast-cws-v1/cws/dpld/customers/<uid>/combined-forecast
Traceback (most recent call last):
  File "/home/csnyder/Documents/code/opower/src/demo.py", line 142, in <module>
    asyncio.run(_main())
  File "/home/csnyder/.pyenv/versions/3.11.1/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/home/csnyder/.pyenv/versions/3.11.1/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/csnyder/.pyenv/versions/3.11.1/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/csnyder/Documents/code/opower/src/demo.py", line 72, in _main
    forecasts = await opower.async_get_forecast()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/csnyder/Documents/code/opower/src/opower/opower.py", line 197, in async_get_forecast
    unit_of_measure=UnitOfMeasure(forecast["unitOfMeasure"]),
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/csnyder/.pyenv/versions/3.11.1/lib/python3.11/enum.py", line 715, in __call__
    return cls.__new__(cls, value)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/csnyder/.pyenv/versions/3.11.1/lib/python3.11/enum.py", line 1131, in __new__
    raise ve_exc
ValueError: 'CCF' is not a valid UnitOfMeasure

The UI on the Delmarva site does display CCF as the UoM
ccf

Requests to ComEd fail with too large of a date range

In reference to home-assistant/core#97814.

The HA integration requests day aggregate information from ComEd from 2022-06-13T00:00:00-05:00 to 2023-06-11T00:00:00-05:00. This fails with the following response:

{
	"error": {
		"httpStatus": 500,
		"serviceErrorCode": "UPSTREAM_ERROR",
		"details": "Could not get rated costs and usages for utility account UUID: XXX"
	}
}

Here's what I ran in the terminal:

➜ python src/demo.py --utility comed --username "[email protected]" --password 'XXX' --start_date 2022-06-13T00:00:00-05:00 --end_date 2023-06-11T00:00:00-05:00

Current bill forecast: Forecast(account=Account(customer=Customer(uuid='9e967f23-9e95-11eb-bf8b-0200170058ac'), uuid='a3ba62b5-9e95-11eb-bf8b-0200170058ac', utility_account_id='XXX', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=None), start_date=datetime.date(2023, 8, 1), end_date=datetime.date(2023, 8, 30), current_date=datetime.date(2023, 8, 10), unit_of_measure=<UnitOfMeasure.KWH: 'KWH'>, usage_to_date=309.0, cost_to_date=0.0, forecasted_usage=1337.0, forecasted_cost=0.0, typical_usage=1099.0, typical_cost=0.0)

Getting historical data: account= Account(customer=Customer(uuid='9e967f23-9e95-11eb-bf8b-0200170058ac'), uuid='a3ba62b5-9e95-11eb-bf8b-0200170058ac', utility_account_id='XXX', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=<ReadResolution.HALF_HOUR: 'HALF_HOUR'>) aggregate_type= day start_date= 2022-06-13 00:00:00-05:00 end_date= 2023-06-11 00:00:00-05:00
Traceback (most recent call last):
  File "/Users/brett/pcode/opower/src/demo.py", line 151, in <module>
    asyncio.run(_main())
  File "/Users/brett/.pyenv/versions/3.10.1/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/brett/.pyenv/versions/3.10.1/lib/python3.10/asyncio/base_events.py", line 641, in run_until_complete
    return future.result()
  File "/Users/brett/pcode/opower/src/demo.py", line 121, in _main
    cost_data = await opower.async_get_cost_reads(
  File "/Users/brett/pcode/opower/src/opower/opower.py", line 327, in async_get_cost_reads
    reads = await self._async_get_dated_data(
  File "/Users/brett/pcode/opower/src/opower/opower.py", line 431, in _async_get_dated_data
    reads = await self._async_fetch(
  File "/Users/brett/pcode/opower/src/opower/opower.py", line 475, in _async_fetch
    raise err
  File "/Users/brett/pcode/opower/src/opower/opower.py", line 463, in _async_fetch
    async with self.session.get(
  File "/Users/brett/.virtualenvs/opower-quohth9L/lib/python3.10/site-packages/aiohttp/client.py", line 1141, in __aenter__
    self._resp = await self._coro
  File "/Users/brett/.virtualenvs/opower-quohth9L/lib/python3.10/site-packages/aiohttp/client.py", line 643, in _request
    resp.raise_for_status()
  File "/Users/brett/.virtualenvs/opower-quohth9L/lib/python3.10/site-packages/aiohttp/client_reqrep.py", line 1005, in raise_for_status
    raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 500, message='Internal Server Error', url=URL('https://cec.opower.com/ei/edge/apis/DataBrowser-v1/cws/cost/utilityAccount/a3ba62b5-9e95-11eb-bf8b-0200170058ac?aggregateType=day&startDate=2022-06-14T00:00:00-05:00&endDate=2023-06-12T00:00:00-05:00')

This data is available on ComEd's website.

However, if I change the time frame with opower's demo.py, keeping the start date the same, but decreasing the end date, the request succeeds. I think that opower may be limiting the number of days for which data can be requested, and possibly may be configured per utility.

➜ python src/demo.py --utility comed --username "[email protected]" --password 'XXX' --start_date 2022-06-13T00:00:00-05:00 --end_date 2022-10-11T00:00:00-05:00

Current bill forecast: Forecast(account=Account(customer=Customer(uuid='9e967f23-9e95-11eb-bf8b-0200170058ac'), uuid='a3ba62b5-9e95-11eb-bf8b-0200170058ac', utility_account_id='XXX', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=None), start_date=datetime.date(2023, 8, 1), end_date=datetime.date(2023, 8, 30), current_date=datetime.date(2023, 8, 10), unit_of_measure=<UnitOfMeasure.KWH: 'KWH'>, usage_to_date=309.0, cost_to_date=0.0, forecasted_usage=1337.0, forecasted_cost=0.0, typical_usage=1099.0, typical_cost=0.0)

Getting historical data: account= Account(customer=Customer(uuid='9e967f23-9e95-11eb-bf8b-0200170058ac'), uuid='a3ba62b5-9e95-11eb-bf8b-0200170058ac', utility_account_id='XXX', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=<ReadResolution.HALF_HOUR: 'HALF_HOUR'>) aggregate_type= day start_date= 2022-06-13 00:00:00-05:00 end_date= 2022-10-11 00:00:00-05:00
start_time	end_time	consumption	provided_cost	start_minus_prev_end	end_minus_prev_end
2022-06-13 00:00:00-05:00	2022-06-14 00:00:00-05:00	28.9075	4.626645375	None	None
2022-06-14 00:00:00-05:00	2022-06-15 00:00:00-05:00	41.215	6.59646075	0:00:00	1 day, 0:00:00
2022-06-15 00:00:00-05:00	2022-06-16 00:00:00-05:00	59.275	9.48696375	0:00:00	1 day, 0:00:00
2022-06-16 00:00:00-05:00	2022-06-17 00:00:00-05:00	51.265	8.20496325	0:00:00	1 day, 0:00:00
2022-06-17 00:00:00-05:00	2022-06-18 00:00:00-05:00	36.6475	5.865432375	0:00:00	1 day, 0:00:00
2022-06-18 00:00:00-05:00	2022-06-19 00:00:00-05:00	19.6875	3.150984375	0:00:00	1 day, 0:00:00
2022-06-19 00:00:00-05:00	2022-06-20 00:00:00-05:00	20.095	3.21620475	0:00:00	1 day, 0:00:00
...

My account has been active since sometime in April/May 2021 and had a smart meter before activation.

If you'd like access to my utility account, let me know an email and I can share credentials or a HAR file of requests on ComEd's usage page with you.

ConEd first authentication can fail, succeeds on reload

For some users, the initial ConEd authentication through Home Assistant can fail. However, valid credentials have been entered and the authentication succeeds when the integration is reloaded or Home Assistant is restarted.

The error is Config entry 'Consolidated Edison (ConEd) (user@host)' for opower integration could not authenticate: 2FA code was invalid. Is the secret wrong?

@Sebmaster didn't run into the problem with his own account during development, but I think his theory was (from my logs in discussion #30) that a second authentication attempt was occurring which reused the TOTP code before it rolled over, possibly due to multiple accounts under the same user.

The problem does not occur with demo.py. The logging that seems to be available with debug doesn't yield much.

I've seen other reports of this problem on the hass forum IIRC.

Should Opower work for all Evergy customers?

I was pleased to see my provider Evergy in the list of supported providers, but even though I have authenticated with my Evergy details no power consumption has appeared. Evergy was formed by the merger of Westar with Kansas Power and Light - I’m a former Westar customer. Should this integration work for me, or does it only work for the former Kansas Power and Light customers?

When it was Westar I was able to download data about my consumption in the ‘Blue Button’ format, but that feature was removed post merger and I’ve not found a way to view/analyze my data on anything less than a week/day, wherea previously I could see it for every 15 mins of the day.

many thanks,

ourstanley

ConEd 500 Error on GetOpowerToken since account changes.

As mentioned in home-assistant/core#101829, ConEd made a number of changes to their systems around 2023-10-06. Since then for me I'm getting an error 500 response from the GetOpowerToken request.

Running demo.py using tronikos/main, I'm getting:

Traceback (most recent call last):
  File "/mnt/s2/home/rct/proj/coned/tronikos/opower/src/opower/opower.py", line 193, in async_login
    self.access_token = await self.utility.async_login(
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/s2/home/rct/proj/coned/tronikos/opower/src/opower/utilities/coned.py", line 129, in async_login
    async with session.get(
  File "/srv/pyvenvs/opower/lib/python3.11/site-packages/aiohttp/client.py", line 1141, in __aenter__
    self._resp = await self._coro
                 ^^^^^^^^^^^^^^^^
  File "/srv/pyvenvs/opower/lib/python3.11/site-packages/aiohttp/client.py", line 643, in _request
    resp.raise_for_status()
  File "/srv/pyvenvs/opower/lib/python3.11/site-packages/aiohttp/client_reqrep.py", line 1005, in raise_for_status
    raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 500, message='Internal Server Error', url=URL('https://www.coned.com/sitecore/api/ssc/ConEd-Cms-Services-Controllers-Opower/OpowerService/0/GetOPowerToken')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/mnt/s2/home/rct/proj/coned/tronikos/opower/src/./demo.py", line 170, in <module>
    asyncio.run(_main())
  File "/opt/anaconda/anaconda3/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/opt/anaconda/anaconda3/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/anaconda/anaconda3/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/mnt/s2/home/rct/proj/coned/tronikos/opower/src/./demo.py", line 86, in _main
    await opower.async_login()
  File "/mnt/s2/home/rct/proj/coned/tronikos/opower/src/opower/opower.py", line 201, in async_login
    raise CannotConnect(err)
opower.exceptions.CannotConnect: 500, message='Internal Server Error', url=URL('https://www.coned.com/sitecore/api/ssc/ConEd-Cms-Services-Controllers-Opower/OpowerService/0/GetOPowerToken')

@Sebmaster - Is it working for you? Any ideas?

ssl cert validation failing with coned

currently using opower integration in home assistant. as of this afternoon when trying to load the integration i am getting the following error:

Failed setup, will retry: Cannot connect to host www.coned.com:443 ssl:True [SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)')]

additionally all sensors for gas related info are gone. electricity sensors are there but show as unavailable due to ssl cert validation failing.

Ambiguous: "Re-login to make sure code handles already logged in sessions."

In demo.py, the login is performed twice.

opower/src/demo.py

Lines 86 to 88 in 1900209

await opower.async_login()
# Re-login to make sure code handles already logged in sessions.
await opower.async_login()

Is this to test that it remembers the existing login, or is the correct behaviour to discard cookies and force a refreshed login?

I spent time at the weekend working on #54andylittle/opower-smud/pull/1 – where I'm returning early if the cookies are already set.

15m granularity electric data via green button

at the bottom right, PG&E has this button
image
which shows this interface
image
which lets you download a zip file of gas and electric data. the gas is still 24h granularity but the electric is the raw 15m data off the smart meter rather than 1h aggregations

I was able to pull the data with just the basic headers

async with aiohttp.ClientSession() as session:
    client = opower.Opower(session, 'pge', username, password)
    await client.async_login()
    await client.async_login()

    (customer,) = await client._async_get_customers()
    customer_uuid = customer['uuid']
    url = f'https://pge.opower.com/ei/edge/apis/DataBrowser-v1/cws/utilities/pge/customers/{customer_uuid}/usage_export/download?format=csv&startDate=2023-08-01&endDate=2023-09-01'
    async with session.get(url, headers=client._get_headers(), raise_for_status=True) as resp:
        zip = await resp.read()
    with open('data.zip', 'wb') as f:
        f.write(zip)

if you wanna get fancier you can pull the data straight out using zipfile: https://github.com/raylu/power/blob/main/get_data.py
it also seems to support at least 24 concurrent queries (my code pulls 2010-10 to now, one month at a time)

SMUD cannot log in; forbidden 403

I just tried to run the demo.py, and it threw back a 403 error

Traceback (most recent call last):
  File "/Users/serge/Library/Python/3.9/lib/python/site-packages/opower/opower.py", line 197, in async_login
    self.access_token = await self.utility.async_login(
  File "/Users/serge/Library/Python/3.9/lib/python/site-packages/opower/utilities/smud.py", line 306, in async_login
    opower_sso_acs_response = await session.post(
  File "/Users/serge/Library/Python/3.9/lib/python/site-packages/aiohttp/client.py", line 696, in _request
    resp.raise_for_status()
  File "/Users/serge/Library/Python/3.9/lib/python/site-packages/aiohttp/client_reqrep.py", line 1070, in raise_for_status
    raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 403, message='Forbidden', url=URL('https://smud.opower.com/ei/x/error-response-code?error-response-code=403')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/serge/Downloads/Opower demo.py", line 172, in <module>
    asyncio.run(_main())
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/Users/serge/Downloads/Opower demo.py", line 88, in _main
    await opower.async_login()
  File "/Users/serge/Library/Python/3.9/lib/python/site-packages/opower/opower.py", line 203, in async_login
    raise InvalidAuth(err)
opower.exceptions.InvalidAuth: 403, message='Forbidden', url=URL('https://smud.opower.com/ei/x/error-response-code?error-response-code=403')

Is there a different URL, or is something blocked on my account to use it? My SMUD account is about a week old

I did demo.py without any arguments, and input into the prompts

New Provider: SMUD

I have the login to the SMUD page working. After opening the link to energy usage, a series of redirects occur. By setting allow_redirects=True, I am able to regex the token from the redirect history. Is this the correct way to handle getting the token from the redirects or am I missing a concept here?

Discussion: suggestions for handling First Energy?

First Energy corp is the parent company of my utility, JCP&L (as well as several others). I see that JCP&L is using opower on the fenj subdomain.

However, the problem I'm having that's preventing contributing a jcpl utility is the login process. The login form itself seems simple enough, but there is an encrypted js file added to the login page that intercepts the form submit action and injects 6 hidden form fields with encrypted or hashed data to accompany the login/password fields. Since these inputs are added JIT before submitting the form, we can't just extract those values from the login/landing page. Examples of the injected inputs:

# large values truncated for obscurity and brevity
-d "r8loFrsvDB-a=esnC<...5386 bytes total...>oCY" \
-d "r8loFrsvDB-z=q" \
-d "r8loFrsvDB-d=AB<...80...>M" \
-d "r8loFrsvDB-c=AG<...61...>O-S" \
-d "r8loFrsvDB-b=-3pntgr" \
-d "r8loFrsvDB-f=Axg<...93...>AAAA%3D%3D"

To make matters worse, it appears that there is also a tripwire attached to this honeypot, where if you don't send the exact required values, your IP is blocked for a period of time (seems to be 5-15 min or so).

Looking at the other utility providers, none of them appear to have anything close to this level of security involved (why FirstEnergy requires such security..? 🤷)

I haven't tried something like headless chrome, which I think that would likely work.. But I suspect that would not be a welcome addition to this library.

Are there any other recommendations for how this could be handled?

Request: Solar customers who sell power back to PG&E: Sensor displaying Credit $$ for kW sent back to PG&E

Is it possible to add a new sensor that retrieves the current credit $ on our bill (for PG&E PTO Solar customers who export Solar power back to PG&E)?

If that data is not available in the API, maybe it's possible to add a new sensor that retrieves the current cost per Kilowatt at the time of day (based on the plan we subscribe to)?

If that's not possible, at least retrieve the current schedule/respective price per kilowatt.

After upgrading to Home Assistant OS 11.1 no more OPower sensors

I just upgraded to Home Assistant 11.1, after it rebooted, I noticed that all the entities provided by this integration show as Unavailable in red font. I removed the integration and added it again, this time it didn't create any sensors.

I'm not sure if this is because something changed in Home Assistant or the PGE API endpoint is down currently.

New provider: National Grid

Let's gooooo!
I'd love to jump onboard! This library looks good...
There's a lot of articles talking about national Grid and Opower relations but I can't seem to find a login page. Can you direct me in the right direction?

Help Adding FirstEnergy Support

I am attempting to add support for FirstEnergy which has the following electric companies:

  • Ohio Edison
  • Illuminating Company
  • Toledo Edison
  • Met-Ed
  • Penelec
  • Penn Power
  • West Penn Power
  • JCP&L
  • Mon Power
  • Potomac Edison

The problem is all the r8loFrsvDB-? values in the POST body are created by javascript as far as I can tell. I tested by disabling javascript and the post failed with these values missing.

Login URL:
https://www.firstenergycorp.com/log_in.fecorplogin.html

Login Post Example:

image

image

Once logged in you can see they do make calls to opower.com, so this should be a valid provider.

image

I'm hoping the community could help come up with a way to get around this issue.

Unknown error occurred

When attempting to add Indiana Michigan Power through Home Assistant. I have validated my authentication credentials.

Home Assistant Version:
Core 2024.7.3
Frontend 20240710.0

(Containerized Installation)

Logger: aiohttp.server
Source: /usr/local/lib/python3.12/site-packages/aiohttp/web_protocol.py:421
First occurred: 3:30:19 PM (3 occurrences)
Last logged: 3:34:07 PM

Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/aiohttp/web_protocol.py", line 452, in _handle_request
    resp = await request_handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/aiohttp/web_app.py", line 543, in _handle
    resp = await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/aiohttp/web_middlewares.py", line 114, in impl
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/security_filter.py", line 92, in security_filter_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/forwarded.py", line 210, in forwarded_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/request_context.py", line 26, in request_context_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/ban.py", line 85, in ban_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/auth.py", line 242, in auth_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/headers.py", line 32, in headers_middleware
    response = await handler(request)
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/http.py", line 73, in handle
    result = await handler(request, **request.match_info)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/decorators.py", line 81, in with_admin
    return await func(self, request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/config/config_entries.py", line 222, in post
    return await super().post(request, flow_id)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/data_validator.py", line 74, in wrapper
    return await method(view, request, data, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/data_entry_flow.py", line 122, in post
    result = await self._flow_mgr.async_configure(flow_id, data)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 368, in async_configure
    result = await self._async_configure(flow_id, user_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 415, in _async_configure
    result = await self._async_handle_step(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 518, in _async_handle_step
    result: _FlowResultT = await getattr(flow, method)(user_input)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/opower/config_flow.py", line 85, in async_step_user
    errors = await _validate_login(self.hass, user_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/opower/config_flow.py", line 51, in _validate_login
    await api.async_login()
  File "/usr/local/lib/python3.12/site-packages/opower/opower.py", line 199, in async_login
    self.access_token = await self.utility.async_login(
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/opower/utilities/aepbase.py", line 158, in async_login
    await async_auth_saml(session, url)
  File "/usr/local/lib/python3.12/site-packages/opower/utilities/helpers.py", line 34, in async_auth_saml
    assert action_url.endswith(".opower.com/sp/ACS.saml2")
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

Discussion: How recent are your reads?

One thing I was hoping for in this integration was to get near real-time (maybe 15-30min ago) usage data to see how specific household behaviors affect energy usage. At least for Evergy, the most recent read I can get is from the end of the previous day. I'm curious if this is an overall Opower limitation or if Evergy is sending Opower the day's meter reads only at the end of the day. How recent are the reads for everyone else?

Feature Request: Implement near-realtime energy consumption

ConEd (#13) offers near-realtime (~4hr delay) energy consumption via a /usage endpoint. Request URL looks like this: https://cned.opower.com/ei/edge/apis/cws-real-time-ami-v1/cws/cned/accounts/$uuid/meters/$utilityAccountId2/usage and the request requires the regular Opower auth token. No other header trickery is required. The response body looks like this:

{
  "unit": "KWH",
  "reads": [
    {
      "startTime": "2023-08-05T03:15:00-04:00",
      "endTime": "2023-08-05T03:30:00-04:00",
      "value": 0.093
    },
    [...]
  ]
}

Not sure how applicable this is to other utilities, but might be interesting to find out if that exists. I'm a bit busy, so don't currently have plans to implement this myself. Feel free to pick this up.

PSE, does not retrieve/display dollar cost billing

PSE customer, demo can get electricity utilization, but not the actual dollar billing.

command:

python src/demo.py --verbose --utility pse --username murasakijou --password XXX --aggregate_type bill

returns (address and account#'s anonymized)

DEBUG:/home/admin/opower/src/opower/opower.py:Fetching: https://pse.opower.com/ei/edge/apis/multi-account-v1/cws/pse/customers?offset=0&batchSize=100&addressFilter=
DEBUG:/home/admin/opower/src/opower/opower.py:Fetched: {
"customers": [
{
"id": 5677219,
"uuid": "d5fdfc99-0000-11eb-b2b7-02001700b93c",
"legacyOpowerId": "43-1-56a0a3",
"accountNumber": "220024289999",
"accountName": "123 MAIN STREET; EVERYTOWN WA 98000",
"address": {
"uuid": "252009d4-d7ef-11ea-b2b7-02001700b93c",
"streetNumber": "123",
"streetName": "MAIN STREET",
"subpremise": null,
"postalCode": "98000",
"city": "EVERYTOWN",
"country": "US",
"state": "WA"
},
"type": "RESIDENTIAL",
"utilityAccounts": [
{
"id": 7902575,
"uuid": "d6df1266-0000-11eb-b2b7-02001700b93c",
"utilityAccountId": "4102839998",
"utilityAccountId2": null,
"servicePointId": 1933642,
"meterType": "ELEC",
"preferredUtilityAccountId": "4102839997",
"readResolution": "BILLING"
},
{
"id": 7902576,
"uuid": "d6df1229-0000-11eb-b2b7-02001700b93c",
"utilityAccountId": "4102839996",
"utilityAccountId2": null,
"servicePointId": 1933641,
"meterType": "ELEC",
"preferredUtilityAccountId": "4102839995",
"readResolution": "QUARTER_HOUR"
}
]
}
],
"offset": 0,
"batchSize": 100,
"total": 1
}
DEBUG:/home/admin/opower/src/opower/opower.py:Fetching: https://pse.opower.com/ei/edge/apis/bill-forecast-cws-v1/cws/pse/customers/d5fdfc99-0000-11eb-b2b7-02001700b93c/combined-forecast
DEBUG:/home/admin/opower/src/opower/opower.py:Fetched: {
"isValidUser": true,
"totalForecast": {
"meterType": "COMBINED",
"startDate": "2023-08-08",
"endDate": "2023-09-07",
"currentDate": "2023-08-18",
"daysInPeriod": 31,
"currentDay": 11,
"daysLeftInBill": 20,
"forecastedUsage": 2487,
"forecastedCost": 0,
"typicalUsage": 1749,
"typicalCost": 0,
"budgetBilling": false,
"costToDate": 0,
"usageToDate": 715,
"currencySymbol": "$"
},
"totalMetadata": [
"NO_FORECASTED_COST",
"ESTIMATED_PREVIOUS_BILL"
],
"accountForecasts": [
{
"unitOfMeasure": "KWH",
"meterType": "ELEC",
"startDate": "2023-08-08",
"endDate": "2023-09-07",
"currentDate": "2023-08-18",
"daysInPeriod": 31,
"currentDay": 11,
"daysLeftInBill": 20,
"forecastedUsage": 2487,
"typicalUsage": 1749,
"budgetBilling": false,
"usageToDate": 715,
"currencySymbol": "$",
"preferredUtilityAccountId": "4102839994",
"accountUuids": [
"d6df1229-0000-11eb-b2b7-02001700b93c"
],
"isSolar": false
}
]
}

Current bill forecast: Forecast(account=Account(customer=Customer(uuid='d5fdfc99-0000-11eb-b2b7-02001700b93c'), uuid='d6df1229-0000-11eb-b2b7-02001700b93c', utility_account_id='4102839993', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=None), start_date=datetime.date(2023, 8, 8), end_date=datetime.date(2023, 9, 7), current_date=datetime.date(2023, 8, 18), unit_of_measure=<UnitOfMeasure.KWH: 'KWH'>, usage_to_date=715.0, cost_to_date=0.0, forecasted_usage=2487.0, forecasted_cost=0.0, typical_usage=1749.0, typical_cost=0.0)

Getting historical data: account= Account(customer=Customer(uuid='d5fdfc99-0000-11eb-b2b7-02001700b93c'), uuid='d6df1266-0000-11eb-b2b7-02001700b93c', utility_account_id='4102839992', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=<ReadResolution.BILLING: 'BILLING'>) aggregate_type= bill start_date= 2023-08-11 20:50:26.813329 end_date= 2023-08-18 20:50:26.813356
DEBUG:/home/admin/opower/src/opower/opower.py:Fetching: https://pse.opower.com/ei/edge/apis/DataBrowser-v1/cws/cost/utilityAccount/d6df1266-0000-11eb-b2b7-02001700b93c?aggregateType=bill&startDate=2023-08-11T00%3A00%3A00-07%3A00&endDate=2023-08-19T00%3A00%3A00-07%3A00
DEBUG:/home/admin/opower/src/opower/opower.py:Fetched: {
"servicePointId": "6001359992",
"utilityAccountUuid": "d6df1266-0000-11eb-b2b7-02001700b93c",
"unit": "KWH",
"siteTimeZoneId": "America/Los_Angeles",
"reads": [],
"seriesComponents": null,
"ratePlans": null
}
start_time end_time consumption provided_cost start_minus_prev_end end_minus_prev_end

Getting historical data: account= Account(customer=Customer(uuid='d5fdfc99-0000-11eb-b2b7-02001700b93c'), uuid='d6df1229-0000-11eb-b2b7-02001700b93c', utility_account_id='4102839991', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=<ReadResolution.QUARTER_HOUR: 'QUARTER_HOUR'>) aggregate_type= bill start_date= 2023-08-11 20:50:26.813329 end_date= 2023-08-18 20:50:26.813356
DEBUG:/home/admin/opower/src/opower/opower.py:Fetching: https://pse.opower.com/ei/edge/apis/DataBrowser-v1/cws/cost/utilityAccount/d6df1229-0000-11eb-b2b7-02001700b93c?aggregateType=bill&startDate=2023-08-11T00%3A00%3A00-07%3A00&endDate=2023-08-19T00%3A00%3A00-07%3A00
DEBUG:/home/admin/opower/src/opower/opower.py:Fetched: {
"servicePointId": "6001359990",
"utilityAccountUuid": "d6df1229-0000-11eb-b2b7-02001700b93c",
"unit": "KWH",
"siteTimeZoneId": "America/Los_Angeles",
"reads": [],
"seriesComponents": null,
"ratePlans": null
}
start_time end_time consumption provided_cost start_minus_prev_end end_minus_prev_end

(.venv) admin@ip-172-30-3-187:~/opower$

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.