tronikos / opower

A Python library for getting historical and forecasted usage/cost from utilities that use opower.com such as PG&E
Apache License 2.0
53 stars 49 forks source link

Added AEP Base Class and AEPO utility #61

Closed joewashear007 closed 6 months ago

joewashear007 commented 6 months ago

I have AEP as a utility provider. I added them to resolve #60 . AEP has many sub companies, but I can only test with AEP Ohio since that is where I live. I added an AEP base class since I am assuming that all the AEP customers will be the same logic, but let me know if this should change.

I can successfully run the demo.py script:

usage_to_date=13.0, cost_to_date=0.0, forecasted_usage=39.0, forecasted_cost=0.0, typical_usage=0.0, typical_cost=0.0)

Getting historical data: account= Account(customer=Customer(uuid='...'), uuid='...', utility_account_id='...', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=<ReadResolution.QUARTER_HOUR: 'QUARTER_HOUR'>) aggregate_type= day start_date= 2023-12-06 12:13:03.037768 end_date= 2023-12-13 12:13:03.037768
start_time      end_time        consumption     provided_cost   start_minus_prev_end    end_minus_prev_end
2023-12-06 00:00:00-05:00       2023-12-07 00:00:00-05:00       -4.638  0       None    None
2023-12-07 00:00:00-05:00       2023-12-08 00:00:00-05:00       -3.062  0       0:00:00 1 day, 0:00:00
2023-12-08 00:00:00-05:00       2023-12-09 00:00:00-05:00       -16.958 0       0:00:00 1 day, 0:00:00
2023-12-09 00:00:00-05:00       2023-12-10 00:00:00-05:00       5.544   0       0:00:00 1 day, 0:00:00
2023-12-10 00:00:00-05:00       2023-12-11 00:00:00-05:00       5.864   0       0:00:00 1 day, 0:00:00
2023-12-11 00:00:00-05:00       2023-12-12 00:00:00-05:00       18.968  0       0:00:00 1 day, 0:00:00
joewashear007 commented 6 months ago

Thanks for the review, I think I have implemented everything. I also tested with invalid credentials and ensured that it raises the opower.exceptions.InvalidAuth

tronikos commented 6 months ago

I made a few small code changes. I mostly extracted some duplicate code in helpers.py. Can you rebase and verify this still works before I merge?

To add support for all the other AEP utilities, is there any endpoint that returns the opower domain? For the exelon utilities it's https://secure.bge.com/api/Services/MyAccountService.svc/GetConfiguration so we were able to add support for all exelon utilities without having an account. It would be great if we could similarly add support for all AEP utilities.

tronikos commented 6 months ago

Can you also update the readme?

joewashear007 commented 6 months ago

I have rebased the code and added AEPOhio to the readme.

I am not aware of any endpoint that list the data like the one above. Funnily enough, I tried to login into the other utilities companies and surprising my credentials work on each of the sites except aep texas. I was able to see that they all use the apeo.opower.com domain. I am not sure if they all use the same server with different urls.

These all work:

For AEP Texas (https://www.aeptexas.com/) I get this: image

Sadly I don't have any other ways to test beyond this. but i am curious if a lot of these will just work for people of other companies.

tronikos commented 6 months ago

I pumped the version in home assistant. Can you please create a virtual integration in core?

systematicallyrandom commented 6 months ago

I'm an Indian Michigan Power customer and would love to help troubleshoot this to get it to work with that site. If I try my IMP password with the AEPOhio utility I get the following error (although, interestingly I can manually log into aepohio.com/account/usage/ with my IMP password in a web browser and it works fine).

raise InvalidAuth(err)
opower.exceptions.InvalidAuth: 403, message='Forbidden', url=URL('https://aepo.opower.com/ei/app/403.html')

I tried adding an aepim.py file and changed the opower subdomain to the correct one for Indiana Michigan Power ("aepi"), updated the login url ("indianamichiganpower.com"), and then I get the following error:

python : Traceback (most recent call last):
At line:1 char:1
+ python src/demo.py --utility aepim --verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Traceback (most recent call last)::String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

  File "C:\python\opower-main\src\demo.py", line 170, in <module>
    asyncio.run(_main())
  File "C:\Users\ssalzman\AppData\Local\Programs\Python\Python310\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Users\ssalzman\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 646, in 
run_until_complete
    return future.result()
  File "C:\python\opower-main\src\demo.py", line 86, in _main
    await opower.async_login()
  File "C:\python\opower-main\src\opower\opower.py", line 193, in async_login
    self.access_token = await self.utility.async_login(
  File "C:\python\opower-main\src\opower\utilities\aepbase.py", line 155, in async_login
    await async_auth_saml(session, url)
  File "C:\python\opower-main\src\opower\utilities\helpers.py", line 30, in async_auth_saml
    assert action_url.endswith(".opower.com/sp/ACS.saml2")
AssertionError

It appears my username or password is correct because if I enter an invalid password I get the invalid auth error instead of the SAML related error above:

  File "C:\python\opower-main\src\opower\utilities\aepbase.py", line 126, in async_login
    raise InvalidAuth("Username/Password are invalid")
opower.exceptions.InvalidAuth: Username/Password are invalid

I'm not super technical, but I'd be glad to help troubleshoot it any way I can.

tronikos commented 6 months ago

Can you try removing this line https://github.com/tronikos/opower/blob/1f585cf7014e63893ce83d0a23de8bcebc29ad30/src/opower/utilities/helpers.py#L30 and try again? Alternatively, could you set a temporary password and privately share it with me to take a look? You can find me at home assistant discord, home assistant community forum or gmail. My username is tronikos in all of them.

systematicallyrandom commented 6 months ago

I removed that line and now I'm getting:

  File "C:\python\opower-main\src\opower\utilities\helpers.py", line 31, in async_auth_saml
    assert set(hidden_inputs.keys()) == {"RelayState", "SAMLResponse"}
AssertionError

I emailed you my details for troubleshooting.

tronikos commented 6 months ago

@systematicallyrandom @joewashear007 please check the latest release. It should work for all AEP utilities. I only tested it with Indiana Michigan Power.

joewashear007 commented 6 months ago

I pulled the latest and successfully ran the demo.py script. I will try the HA integration later today (hopefully)

systematicallyrandom commented 6 months ago

The demo.py is working for me now! @tronikos Thanks for your work on this. What is the timeline for this update to be included in HA? Should it be in the January release?

joewashear007 commented 6 months ago

For testing the update in HA, I manually edit the manifest.json for opower component to reference version 0.1.0 of opower. I restart and I was able to login in, but the component says it fails to initialize,

hass  | 2023-12-18 13:03:43.240 DEBUG (MainThread) [homeassistant.components.opower.coordinator] Updating sensor data with: [Forecast(account=Account(customer=Customer(uuid='<REMOVED>'), uuid='<REMOVED>', utility_account_id='107-037-462-0-5', meter_type=<MeterType.ELEC: 'ELEC'>, read_resolution=None), start_date=datetime.date(2023, 12, 8), end_date=datetime.date(2024, 1, 5), current_date=datetime.date(2023, 12, 18), unit_of_measure=<UnitOfMeasure.KWH: 'KWH'>, usage_to_date=-56.0, cost_to_date=0.0, forecasted_usage=-181.0, forecasted_cost=0.0, typical_usage=0.0, typical_cost=0.0)]
hass  | 2023-12-18 13:03:43.240 DEBUG (MainThread) [homeassistant.components.opower.coordinator] Updating Statistics for opower:aepo_elec_107-037-462-0-5_energy_cost and opower:aepo_elec_107-037-462-0-5_energy_consumption
hass  | 2023-12-18 13:03:43.242 DEBUG (MainThread) [homeassistant.components.opower.coordinator] Updating statistic for the first time
hass  | 2023-12-18 13:03:47.393 ERROR (MainThread) [homeassistant.components.opower.coordinator] Unexpected error fetching Opower data: Invalid statistic_id
hass  | Traceback (most recent call last):
hass  |   File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 300, in _async_refresh
hass  |     self.data = await self._async_update_data()
hass  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
hass  |   File "/usr/src/homeassistant/homeassistant/components/opower/coordinator.py", line 86, in _async_update_data
hass  |     await self._insert_statistics()
hass  |   File "/usr/src/homeassistant/homeassistant/components/opower/coordinator.py", line 185, in _insert_statistics
hass  |     async_add_external_statistics(self.hass, cost_metadata, cost_statistics)
hass  |   File "/usr/src/homeassistant/homeassistant/components/recorder/statistics.py", line 2225, in async_add_external_statistics
hass  |     raise HomeAssistantError("Invalid statistic_id")
hass  | homeassistant.exceptions.HomeAssistantError: Invalid statistic_id

Looking that those logs, is there an issue with the utility_account_id='107-037-462-0-5' converting into the stat: aepo_elec_107-037-462-0-5_energy_cost? Does the mix of underscore and dashes make an issue?

I run the HA in docker, Version: Core 2023.12.3 Frontend 20231208.2

joewashear007 commented 6 months ago

In my local HA instance, I changed this line: https://github.com/home-assistant/core/blob/dev/homeassistant/components/opower/coordinator.py#L96 to be account.utility_account_id.replace("-","_"), and it allowed the integration to work

image