Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.9.0 - FleetAPI #91

Merged
merged 24 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fc4c86d
Add FleetAPI class, exceptions, decorators, and stubs
jasonacox May 12, 2024
818d1d9
Update pypowerwall version to 0.9.0 and add support for FleetAPI mode
jasonacox May 12, 2024
d0d99d8
Add auto-select mode and fix backup reserve percent calculation
jasonacox May 13, 2024
e611126
Update proxy for FleetAPI
jasonacox May 13, 2024
02d32be
Fix config file path issues
jasonacox May 13, 2024
f827a12
Update documentation
jasonacox May 14, 2024
89c52db
Documentation update
jasonacox May 14, 2024
516937a
Fix bugs
jasonacox May 14, 2024
58df9f9
Fix setup bugs for fleetapi
jasonacox May 14, 2024
0f100f4
Remove configfile argument
jasonacox May 14, 2024
60a2361
Rename aux to regex and update fleetapi token refresh error checking
jasonacox May 15, 2024
e2b9464
Fix token refresh error handling in FleetAPI
jasonacox May 15, 2024
0d94f57
Added auto select retry logic and fix bugs
jasonacox May 15, 2024
092c57b
Fix bug in handling null site_name
jasonacox May 15, 2024
326c2c4
Fix default mode to local
jasonacox May 15, 2024
c6db863
Backport 0.8.5 site name handling in PyPowerwallCloud class fix
jasonacox May 15, 2024
37d1966
Git ignore
jasonacox May 15, 2024
93651b6
Merge branch 'main' into fleetapi
jasonacox May 15, 2024
74d44ca
Update proxy to t57
jasonacox May 15, 2024
8f42d8e
Release notes
jasonacox May 16, 2024
bf3c008
Add timeout param for FleetAPI
jasonacox May 18, 2024
74c219c
Force token generation during setup
jasonacox May 20, 2024
a7c6d0d
Fix auto select logic
jasonacox May 21, 2024
e818e5d
Isolate timeout feature to polling requests only
jasonacox May 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 57 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,11 @@ poll API endpoints on the Gateway.
pyPowerwall will cache the authentication headers and API call responses to help reduce the number of calls made to the Gateway (useful if you are polling the Powerwall frequently for trending data).

* Works with Tesla Energy Gateways - Powerwall and Powerwall+
* Simple access through easy to use functions using customer credentials
* Access provided via Local Gateway API, Tesla FleetAPI (official), and Tesla Owners API (unofficial).
* Will cache authentication to reduce load on Powerwall Gateway
* Will cache responses to limit number of calls to Powerwall Gateway (optional/user definable)
* Will cache responses to limit number of calls to Powerwall Gateway or Cloud (optional/user definable)
* Will re-use http connections to Powerwall Gateway for reduced load and faster response times
* Easy access to decoded binary device vitals (/api/devices/vitals in JSON format)
* Provides solar string data for Powerwall+ systems

NOTE: This module requires that you (or your installer) have set up *Customer Login* credentials
on your Powerwall Gateway.
* Provides solar string data for Powerwall+ systems.

## Setup

Expand All @@ -35,13 +31,41 @@ You can clone this repo or install the package with pip. Once installed, pyPowe
# Install pyPowerwall
python3 -m pip install pypowerwall

# Scan Network for Powerwalls
# Option 1 - LOCAL MODE - Scan Network for Powerwalls
python3 -m pypowerwall scan

# (optional) Setup to use Tesla Owners cloud API
# Option 2 - FLEETAPI MODE - Setup to use the official Tesla FleetAPI - See notes below.
python3 -m pypowerwal fleetapi

# Option 3 - CLOUD MODE - Setup to use Tesla Owners cloud API
python3 -m pypowerwall setup
```

### Local Setup - Option 1

The Tesla Powerwall, Powerwall 2 and Powerwall+ have a local LAN based API that you can use to monitor your Powerwall. It requires that you (or your installer) have the IP address (see scan above) and set up *Customer Login* credentials on your Powerwall Gateway. That is all that is needed to connect. Unfortunately, Powerwall 3 does not have a local API but you can access it via the cloud (options 2 and 3).

### FleetAPI Setup - Option 2

FleetAPI is the official Tesla API for accessing your Tesla products. This setup has some additional setup requirements that you will be prompted to do:

Step 1 - Tesla Partner Account - Sign in to Tesla Developer Portal and make an App Access Request: See [Tesla App Access Request](https://developer.tesla.com/request) - During this process, you will need to set up and remember the following account settings:

* CLIENT_ID - This will be provided to you by Tesla when your request is approved.
* CLIENT_SECRET - Same as above.
* DOMAIN - The domain name of a website your own and control.
* REDIRECT_URI - This is the URL that Tesla will direct you to after you authenticate. This landing URL (on your website) will extract the GET variable `code`, which is a one-time use authorization code needed during the pyPowerwall setup. You can use [index.html](./tools/fleetapi/index.html) on your site and update REDIRECT_URI with that url. Alternatively, you can just copy the URL from the 404 page during the authorization process (the code is in the URL).

Step 2 - Run the [create_pem_key.py](./tools/fleetapi/create_pem_key.py) script and place the **public** key on your website at the URL: https://{DOMAIN}/.well-known/appspecific/com.tesla.3p.public-key.pem

Step 3 - Run `python3 -m pypowerwal fleetapi` - The credentials and tokens will be stored in the `.pypowerwall.fleetapi` file.

### Cloud Mode - Option 3

The unofficial Tesla Owners API allows FleetAPI access (option 2) without having to set up a website and PEM key. Follow the directions given to you by running `python3 -m pypowerwall setup`. The credentials and site_id will be stored in `.pypowerwall.auth` and `.pypowerwall.site`.

### FreeBSD Install

FreeBSD users can install from ports or pkg [FreshPorts](https://www.freshports.org/net-mgmt/py-pypowerwall):

Via pkg:
Expand All @@ -67,20 +91,23 @@ and call function to poll data. Here is an example:
# Optional: Turn on Debug Mode
# pypowerwall.set_debug(True)

# Local Mode - Credentials for your Powerwall - Customer Login
password='password'
email='[email protected]'
# Option 1 - LOCAL MODE - Credentials for your Powerwall - Customer Login
password="password"
email="[email protected]"
host = "10.0.1.123" # Address of your Powerwall Gateway
timezone = "America/Los_Angeles" # Your local timezone

# (Optional) Cloud Mode - Requires Setup
password = ""
# Option 2 - FLEETAPI MODE - Requires Setup
host = password = email = ""
timezone = "America/Los_Angeles"

# Option 3 - CLOUD MODE - Requires Setup
host = password = ""
email='[email protected]'
host = ""
timezone = "America/Los_Angeles" # Your local timezone
timezone = "America/Los_Angeles"

# Connect to Powerwall
pw = pypowerwall.Powerwall(host,password,email,timezone)
# Connect to Powerwall - auto_select mode (local, fleetapi, cloud)
pw = pypowerwall.Powerwall(host,password,email,timezone,auto_select=True)

# Some System Info
print("Site Name: %s - Firmware: %s - DIN: %s" % (pw.site_name(), pw.version(), pw.din()))
Expand Down Expand Up @@ -117,12 +144,13 @@ and call function to poll data. Here is an example:
```

### pyPowerwall Module Class and Functions

```
set_debug(True, color=True)

Classes
Powerwall(host, password, email, timezone, pwcacheexpire, timeout, poolmaxsize,
cloudmode, siteid, authpath, authmode, cachefile)
cloudmode, siteid, authpath, authmode, cachefile, fleetapi, auto_select)

Parameters
host # Hostname or IP of the Tesla gateway
Expand All @@ -134,10 +162,12 @@ and call function to poll data. Here is an example:
poolmaxsize = 10 # Pool max size for http connection re-use (persistent
connections disabled if zero)
cloudmode = False # If True, use Tesla cloud for data (default is False)
siteid # If cloudmode is True, use this siteid (default is None)
authpath # Path to cloud auth and site cache files (default is "")
siteid = None # If cloudmode is True, use this siteid (default is None)
authpath = "" # Path to cloud auth and site files (default current directory)
authmode = "cookie" # "cookie" (default) or "token" - use cookie or bearer token for auth
cachefile = ".powerwall" # Path to cache file (default current directory)
fleetapi = False # If True, use Tesla FleetAPI for data (default is False)
auto_select = False # If True, select the best available mode to connect (default is False)

Functions
poll(api, json, force) # Return data from Powerwall api (dict if json=True, bypass cache force=True)
Expand All @@ -160,21 +190,17 @@ and call function to poll data. Here is an example:
temps() # Return Powerwall Temperatures
alerts() # Return array of Alerts from devices
system_status(json) # Returns the system status
battery_blocks(json) # Returns battery specific information merged from
# system_status() and vitals()
grid_status(type) # Return the power grid status, type ="string" (default),
# "json", or "numeric":
battery_blocks(json) # Returns battery specific information merged from system_status() and vitals()
grid_status(type) # Return the power grid status, type ="string" (default), "json", or "numeric"
# - "string": "UP", "DOWN", "SYNCING"
# - "numeric": -1 (Syncing), 0 (DOWN), 1 (UP)
is_connected() # Returns True if able to connect to Powerwall
is_connected() # Returns True if able to connect and login to Powerwall
get_reserve(scale) # Get Battery Reserve Percentage
get_mode() # Get Current Battery Operation Mode
set_reserve(level) # Set Battery Reserve Percentage (only cloud mode)
set_mode(mode) # Set Current Battery Operation Mode (only cloud mode)
set_reserve(level) # Set Battery Reserve Percentage
set_mode(mode) # Set Current Battery Operation Mode
get_time_remaining() # Get the backup time remaining on the battery

set_operation(level, mode, json) # Set Battery Reserve Percentage and/or Operation Mode

set_operation(level, mode, json) # Set Battery Reserve Percentage and/or Operation Mode
```

## Tools
Expand Down
8 changes: 8 additions & 0 deletions proxy/RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## pyPowerwall Proxy Release Notes

### Proxy t57 (15 May 2024)

* Add pypowerwall v0.9.0 capabilities, specifically supporting Tesla FleetAPI for cloud connections (main data and control).

### Proxy t56 (14 May 2024)

* Fix error with site_name on Solar Only systems.

### Proxy t55 (4 May 2024)

* Fix `/pod` API to add `time_remaining_hours` and `backup_reserve_percent` for cloud mode.
Expand Down
2 changes: 1 addition & 1 deletion proxy/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pypowerwall==0.8.5
pypowerwall==0.9.0
bs4==0.0.2
33 changes: 23 additions & 10 deletions proxy/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@

import pypowerwall
from pypowerwall import parse_version
from pypowerwall.fleetapi.fleetapi import CONFIGFILE
from transform import get_static, inject_js
from urllib.parse import urlparse, parse_qs

BUILD = "t56"
BUILD = "t57"
ALLOWLIST = [
'/api/status', '/api/site_info/site_name', '/api/meters/site',
'/api/meters/solar', '/api/sitemaster', '/api/powerwalls',
Expand Down Expand Up @@ -97,6 +98,7 @@
# Global Stats
proxystats = {
'pypowerwall': "%s Proxy %s" % (pypowerwall.version, BUILD),
'mode': "Unknown",
'gets': 0,
'posts': 0,
'errors': 0,
Expand All @@ -109,6 +111,7 @@
'mem': 0,
'site_name': "",
'cloudmode': False,
'fleetapi': False,
'siteid': None,
'counter': 0
}
Expand Down Expand Up @@ -166,7 +169,8 @@ def get_value(a, key):
pw = pypowerwall.Powerwall(host, password, email, timezone, cache_expire,
timeout, pool_maxsize, siteid=siteid,
authpath=authpath, authmode=authmode,
cachefile=cachefile)
cachefile=cachefile, auto_select=True,
retry_modes=True)
except Exception as e:
log.error(e)
log.error("Fatal Error: Unable to connect. Please fix config and restart.")
Expand All @@ -175,9 +179,15 @@ def get_value(a, key):
time.sleep(5) # Infinite loop to keep container running
except (KeyboardInterrupt, SystemExit):
sys.exit(0)

site_name = pw.site_name() or "Unknown"
if pw.cloudmode:
log.info("pyPowerwall Proxy Server - Cloud Mode")
if pw.cloudmode or pw.fleetapi:
if pw.fleetapi:
proxystats['mode'] = "FleetAPI"
log.info("pyPowerwall Proxy Server - FleetAPI Mode")
else:
proxystats['mode'] = "Cloud"
log.info("pyPowerwall Proxy Server - Cloud Mode")
log.info("Connected to Site ID %s (%s)" % (pw.client.siteid, site_name.strip()))
if siteid is not None and siteid != str(pw.client.siteid):
log.info("Switch to Site ID %s" % siteid)
Expand All @@ -189,24 +199,25 @@ def get_value(a, key):
except (KeyboardInterrupt, SystemExit):
sys.exit(0)
else:
proxystats['mode'] = "Local"
log.info("pyPowerwall Proxy Server - Local Mode")
log.info("Connected to Energy Gateway %s (%s)" % (host, site_name.strip()))

pw_control = None
if control_secret:
log.info("Control Commands Activating - WARNING: Use with caution!")
try:
if pw.cloudmode:
if pw.cloudmode or pw.fleetapi:
pw_control = pw
else:
pw_control = pypowerwall.Powerwall("", password, email, siteid=siteid,
authpath=authpath, authmode=authmode,
cachefile=cachefile)
cachefile=cachefile, auto_select=True)
except Exception as e:
log.error("Control Mode Failed: Unable to connect to cloud - Run Setup")
control_secret = ""
if pw_control:
log.info("Control Mode Enabled: Cloud Mode Connected")
log.info(f"Control Mode Enabled: Cloud Mode ({pw_control.mode}) Connected")
else:
log.error("Control Mode Failed: Unable to connect to cloud - Run Setup")
control_secret = None
Expand Down Expand Up @@ -335,7 +346,8 @@ def do_GET(self):
proxystats['mem'] = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
proxystats['site_name'] = pw.site_name()
proxystats['cloudmode'] = pw.cloudmode
if pw.cloudmode and pw.client is not None:
proxystats['fleetapi'] = pw.fleetapi
if (pw.cloudmode or pw.fleetapi) and pw.client is not None:
proxystats['siteid'] = pw.client.siteid
proxystats['counter'] = pw.client.counter
proxystats['authmode'] = pw.authmode
Expand Down Expand Up @@ -505,7 +517,8 @@ def do_GET(self):
proxystats['mem'] = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
proxystats['site_name'] = pw.site_name()
proxystats['cloudmode'] = pw.cloudmode
if pw.cloudmode and pw.client is not None:
proxystats['fleetapi'] = pw.fleetapi
if (pw.cloudmode or pw.fleetapi) and pw.client is not None:
proxystats['siteid'] = pw.client.siteid
proxystats['counter'] = pw.client.counter
proxystats['authmode'] = pw.authmode
Expand Down Expand Up @@ -574,7 +587,7 @@ def do_GET(self):
if fcontent:
log.debug("Served from local web root: {} type {}".format(self.path, ftype))
# If not found, serve from Powerwall web server
elif pw.cloudmode:
elif pw.cloudmode or pw.fleetapi:
log.debug("Cloud Mode - File not found: {}".format(self.path))
fcontent = bytes("Not Found", 'utf-8')
ftype = "text/plain"
Expand Down
Loading
Loading