Skip to content

Commit

Permalink
Merge pull request #91 from jasonacox/fleetapi
Browse files Browse the repository at this point in the history
v0.9.0 - FleetAPI
  • Loading branch information
jasonacox authored May 21, 2024
2 parents de4e9bf + e818e5d commit 4dcc36f
Show file tree
Hide file tree
Showing 17 changed files with 2,324 additions and 80 deletions.
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

0 comments on commit 4dcc36f

Please sign in to comment.