Skip to content

Commit

Permalink
v0.10.2 #97
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonacox committed Jun 9, 2024
1 parent 2a6e971 commit cc967e9
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 71 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,23 @@ 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).
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, the Powerwall 3 does not have a local API but you can access it via the cloud (see options 2 and 3).

Extended Device Vitals Metrics: With version v0.10.0+, pypowerwall can be set to access the TEDAPI on the Gateway to pull additional metrics. However, you will need the Gateway Password (often found on the QR sticker on the Powerwall Gateway). Additionally, your computer will need network access to the Gateway IP (192.168.91.1). You can have your computer join the Gateway local WiFi or you can add a route:

```bash
# Example - Change 192.168.0.100 to the IP address of Powerwall Gateway on your LAN

# Linux Ubuntu and RPi - Can add to /etc/rc.local for persistence
sudo ip route add 192.168.91.1 via 192.168.0.100

# MacOS
sudo route add -host 192.168.91.1 192.168.0.100 # Temporary
networksetup -setadditionalroutes Wi-Fi 192.168.91.1 255.255.255.255 192.168.0.100 # Persistent

# Windows - Using persistence flag - Administrator Shell
route -p add 192.168.91.1 mask 255.255.255.255 192.168.0.100
```

### FleetAPI Setup - Option 2

Expand Down
16 changes: 16 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# RELEASE NOTES

## v0.10.3 - TEDAPI Connect

* Update `setup.py` to include dependencies on `protobuf>=3.20.0`.
* Add TEDAPI `connect()` logic to better validate Gateway endpoint access.
* Add documentation for TEDAPI setup.
* Update CLI to support TEDAPI calls.

```bash
# Connect to TEDAPI and pull data
python3 -m pypowerwall tedapi

# Direct call to TEDAPI class test function (optional password)
python3 -m pypowerwall.tedapi GWPASSWORD
python3 -m pypowerwall.tedapi --debug
```

## v0.10.2 - FleetAPI Hotfix

* Fix FleetAPI setup script as raised in https://github.com/jasonacox/pypowerwall/issues/98.
Expand Down
4 changes: 4 additions & 0 deletions proxy/RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## pyPowerwall Proxy Release Notes

### Proxy t60 (9 Jun 2024)

* Add error handling for `/csv` API to accommodate `None` data points.

### Proxy t59 (8 Jun 2024)

* Minor fix to send less ambiguous debug information during client disconnects.
Expand Down
4 changes: 2 additions & 2 deletions proxy/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
from transform import get_static, inject_js
from urllib.parse import urlparse, parse_qs

BUILD = "t59"
BUILD = "t60"
ALLOWLIST = [
'/api/status', '/api/site_info/site_name', '/api/meters/site',
'/api/meters/solar', '/api/sitemaster', '/api/powerwalls',
Expand Down Expand Up @@ -330,7 +330,7 @@ def do_GET(self):
elif self.path == '/csv':
# Grid,Home,Solar,Battery,Level - CSV
contenttype = 'text/plain; charset=utf-8'
batterylevel = pw.level()
batterylevel = pw.level() or 0
grid = pw.grid() or 0
solar = pw.solar() or 0
battery = pw.battery() or 0
Expand Down
11 changes: 11 additions & 0 deletions pypowerwall/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@

setup_args = subparsers.add_parser("fleetapi", help='Setup Tesla FleetAPI for Cloud Mode access')

setup_args = subparsers.add_parser("tedapi", help='Test TEDAPI connection to Powerwall Gateway')

scan_args = subparsers.add_parser("scan", help='Scan local network for Powerwall gateway')
scan_args.add_argument("-timeout", type=float, default=timeout,
help=f"Seconds to wait per host [Default={timeout:.1f}]")
Expand Down Expand Up @@ -96,6 +98,7 @@
else:
print("ERROR: Failed to setup Tesla Cloud Mode")
exit(1)

# FleetAPI Mode Setup
elif command == 'fleetapi':
from pypowerwall import PyPowerwallFleetAPI
Expand All @@ -108,6 +111,12 @@
else:
print("Setup Aborted.")
exit(1)

# TEDAPI Test
elif command == 'tedapi':
from pypowerwall.tedapi.__main__ import run_tedapi_test
run_tedapi_test(auto=True, debug=args.debug)

# Run Scan
elif command == 'scan':
from pypowerwall import scan
Expand All @@ -118,6 +127,7 @@
hosts = args.hosts
timeout = args.timeout
scan.scan(color, timeout, hosts, ip)

# Set Powerwall Mode
elif command == 'set':
# If no arguments, print usage
Expand Down Expand Up @@ -146,6 +156,7 @@
current = float(pw.level())
print("Setting Powerwall Reserve to Current Charge Level %s" % current)
pw.set_reserve(current)

# Get Powerwall Mode
elif command == 'get':
import pypowerwall
Expand Down
53 changes: 38 additions & 15 deletions pypowerwall/tedapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import time
from pypowerwall import __version__
import math
import sys

# TEDAPI Fixed Gateway IP Address
GW_IP = "192.168.91.1"
Expand All @@ -48,6 +49,8 @@

# Setup Logging
log = logging.getLogger(__name__)
log.debug('%s version %s', __name__, __version__)
log.debug('Python %s on %s', sys.version, sys.platform)

# Utility Functions
def lookup(data, keylist):
Expand All @@ -64,7 +67,6 @@ def lookup(data, keylist):
return data

# TEDAPI Class

class TEDAPI:
def __init__(self, gw_pwd, debug=False, pwcacheexpire: int = 5, timeout: int = 5, pwconfigexpire: int = 300) -> None:
self.debug = debug
Expand All @@ -78,15 +80,26 @@ def __init__(self, gw_pwd, debug=False, pwcacheexpire: int = 5, timeout: int = 5
if not gw_pwd:
raise ValueError("Missing gw_pwd")
if self.debug:
log.setLevel(logging.DEBUG)
self.set_debug(True)
self.gw_pwd = gw_pwd
# Connect to Powerwall Gateway
if not self.connect():
log.error("Failed to connect to Powerwall Gateway")
raise ValueError("Failed to connect to Powerwall Gateway")


# TEDAPI Functions

def set_debug(toggle=True, color=True):
"""Enable verbose logging"""
if toggle:
if color:
logging.basicConfig(format='\x1b[31;1m%(levelname)s:%(message)s\x1b[0m', level=logging.DEBUG)
else:
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
log.setLevel(logging.DEBUG)
log.debug("%s [%s]\n" % (__name__, __version__))
else:
log.setLevel(logging.NOTSET)

def get_din(self, force=False):
"""
Get the DIN from the Powerwall Gateway
Expand All @@ -109,6 +122,9 @@ def get_din(self, force=False):
self.pwcooldown = time.perf_counter() + 300
log.error('Possible Rate limited by Powerwall at - Activating 5 minute cooldown')
return None
if r.status_code == 403:
log.error("Access Denied: Check your Gateway Password")
return None
if r.status_code != 200:
log.error(f"Error fetching DIN: {r.status_code}")
return None
Expand Down Expand Up @@ -158,6 +174,11 @@ def get_config(self,force=False):
# Rate limited - return None
log.debug('Rate limit cooldown period - Pausing API calls')
return None
# Check Connection
if not self.din:
if not self.connect():
log.error("Not Connected - Unable to get configuration")
return None
# Fetch Configuration from Powerwall
log.debug("Get Configuration from Powerwall")
# Build Protobuf to fetch config
Expand Down Expand Up @@ -245,6 +266,11 @@ def get_status(self, force=False):
# Rate limited - return None
log.debug('Rate limit cooldown period - Pausing API calls')
return None
# Check Connection
if not self.din:
if not self.connect():
log.error("Not Connected - Unable to get status")
return None
# Fetch Current Status from Powerwall
log.debug("Get Status from Powerwall")
# Build Protobuf to fetch status
Expand Down Expand Up @@ -292,18 +318,15 @@ def connect(self):
# Test IP Connection to Powerwall Gateway
log.debug(f"Testing Connection to Powerwall Gateway: {GW_IP}")
url = f'https://{GW_IP}'
try:
r = requests.get(url, verify=False, timeout=5)
except requests.exceptions.RequestException as e:
r = False
log.error("ERROR: Powerwall not Found",
f"Try: sudo route add -host <Powerwall_IP> {GW_IP}")
if r:
# Attempt to fetch DIN from Powerwall
self.din = self.get_din()
return True
self.din = None
return False
try:
_ = requests.get(url, verify=False, timeout=5)
self.din = self.get_din()
except Exception as e:
log.error(f"Unable to connect to Powerwall Gateway {GW_IP}")
log.error("Please verify your your host has a route to the Gateway.")
log.error(f"Error Details: {e}")
return self.din

# Handy Function to access Powerwall Status

Expand Down
Loading

0 comments on commit cc967e9

Please sign in to comment.