Skip to content

Commit 7a81cf2

Browse files
First pass updating for Powerwall API changes.
`tesla_powerwall` updates: * now wants username * powerwall class changes * meter class changes Weather needs lat/lon; zip deprecated. Allow `POLL_INTERVAL` to be specified.
1 parent 40a1f35 commit 7a81cf2

2 files changed

Lines changed: 62 additions & 51 deletions

File tree

env.list

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
INSIGHTS_API_KEY=<your new relic api key here>
2-
ZIP=<in the US, zip code for openweathermap.org>
2+
3+
WEATHER_LAT=<latitude for your location (e.g. xx.yy)>
4+
WEATHER_LON=<longitude for your location (e.g. xx.yy)>
35
WEATHER_KEY=<key for openweathermap.org
6+
7+
# powerwall address. IP address is acceptable as well
8+
PW_ADDR=powerwall
9+
PW_USER=<email address for your local Powerwall web UI>
410
PW_PASS=<password for your local Powerwall web UI>
11+
512
PYTHONUNBUFFERED=1 # so that stdout logs properly when run as a container
13+
POLL_INTERVAL=60 # seconds
614

715
# Running as a service? Set to 1 if it's a service.
816
# If it's not, define the variable but don't set it to anything ( so the whole line is AS_SERVICE=)

pwmon.py

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,41 +14,44 @@
1414
import requests
1515
import tenacity
1616
from dotenv import load_dotenv
17-
from tesla_powerwall import Powerwall, MeterType
17+
from tesla_powerwall.powerwall import Powerwall
18+
from tesla_powerwall.const import MeterType
1819

1920

2021
##### environment variables
2122
# load environment variables from a file if they're there
2223
load_dotenv('env.list', override=False)
2324

24-
# this script expects five environment variables to be set
25+
# this script expects these environment variables to be set
2526
# New Relic key
2627
INSIGHTS_API_KEY = os.environ.get('INSIGHTS_API_KEY')
2728

28-
# ZIPSTRING is for the wether API. In the US it takes the form '<ZIPCODE>, us'
29-
ZIPSTRING = f"{os.environ.get('ZIP')},us"
30-
WEATHER_KEY = os.environ.get("WEATHER_KEY")
29+
# Weather lat/long and key
30+
WEATHER_LAT = os.environ.get('WEATHER_LAT')
31+
WEATHER_LON = os.environ.get('WEATHER_LON')
32+
WEATHER_KEY = os.environ.get('WEATHER_KEY')
33+
34+
# powerwall username
35+
PW_USER = os.environ.get('PW_USER')
3136

3237
# powerwall password
33-
PW_PASS = os.environ.get("PW_PASS")
38+
PW_PASS = os.environ.get('PW_PASS')
3439

3540
# Am I running as a service? Part of a hack to let me run via CLI.
3641
AS_SERVICE = os.environ.get('AS_SERVICE')
37-
##### end environment variables
38-
3942

40-
41-
##### constants
4243
# How often does the script poll when run as a service?
43-
# this is not an environment variable
44-
POLL_INTERVAL = 60
44+
POLL_INTERVAL = int(os.environ.get('POLL_INTERVAL'))
4545

4646
# powerwall hostname or IP.
4747
# The powerwall's self-signed certificate only responds to
4848
# hostnamnes "powerwall", "teg", or "powerpack", and of course you have to have DNS set up properly.
4949
# IP addresses work, too.
50-
PW_ADDR = 'powerwall'
50+
PW_ADDR = os.environ.get("PW_ADDR")
5151

52+
##### end environment variables
53+
54+
##### constants
5255
# URL to post to
5356
URL = 'https://metric-api.newrelic.com/metric/v1'
5457

@@ -81,13 +84,12 @@ def post_metrics(data):
8184
# because the gateway is very slow to respond
8285
# and it has some absurdly low rate limit
8386

84-
8587
@tenacity.retry(stop=tenacity.stop_after_attempt(7),
8688
wait=tenacity.wait_random(min=3, max=7))
8789
def get_pw():
8890
"""Return a Powerwall connection object."""
8991
pw = Powerwall(PW_ADDR)
90-
pw.login(PW_PASS)
92+
loginResult = pw.login(PW_PASS, PW_USER)
9193
return pw
9294

9395

@@ -100,9 +102,10 @@ def connect():
100102
@tenacity.retry(stop=tenacity.stop_after_attempt(7),
101103
wait=tenacity.wait_random(min=3, max=7))
102104
def get_weather():
103-
"""Return weather for a given zipstring."""
105+
"""Return weather for a given lat/lon."""
104106
params = {
105-
'zip': ZIPSTRING,
107+
'lat': WEATHER_LAT,
108+
'lon': WEATHER_LON,
106109
'appid': WEATHER_KEY,
107110
'units': 'imperial',
108111
}
@@ -120,11 +123,11 @@ def get_data():
120123
# worth it.
121124
pw, m = connect()
122125

123-
# # WHY DO I NEED TO DO THIS? I SWEAR THIS USED TO WORK.
124-
m.battery = m.get_meter(MeterType.BATTERY)
125-
m.load = m.get_meter(MeterType.LOAD)
126-
m.site = m.get_meter(MeterType.SITE)
127-
m.solar = m.get_meter(MeterType.SOLAR)
126+
# Get a copy of each meter
127+
batteryMeter = m.get_meter(MeterType.BATTERY)
128+
loadMeter = m.get_meter(MeterType.LOAD)
129+
siteMeter = m.get_meter(MeterType.SITE)
130+
solarMeter = m.get_meter(MeterType.SOLAR)
128131

129132
weather = get_weather()
130133

@@ -149,27 +152,27 @@ def get_data():
149152
weather['sys']['sunrise'] *= 1000
150153
weather['sys']['sunset'] *= 1000
151154
if now > weather['sys']['sunrise'] and now < weather['sys']['sunset']:
152-
is_sun_up = True
155+
is_daytime = True
153156
else:
154-
is_sun_up = False
157+
is_daytime = False
155158

156159
metric_data = {
157160
'solar': [
158161
('battery_charge_pct', round(pw.get_charge(), 1)),
159-
('battery.imported', m.battery.energy_imported),
160-
('battery.exported', m.battery.energy_exported),
161-
('house.imported', m.load.energy_imported),
162-
('house.exported', m.load.energy_exported),
163-
('grid.imported', m.site.energy_imported),
164-
('grid.exported', m.site.energy_exported),
165-
('solar.imported', m.solar.energy_imported),
166-
('solar.exported', m.solar.energy_exported),
162+
('battery.imported', batteryMeter.energy_imported),
163+
('battery.exported', batteryMeter.energy_exported),
164+
('house.imported', loadMeter.energy_imported),
165+
('house.exported', loadMeter.energy_exported),
166+
('grid.imported', siteMeter.energy_imported),
167+
('grid.exported', siteMeter.energy_exported),
168+
('solar.imported', solarMeter.energy_imported),
169+
('solar.exported', solarMeter.energy_exported),
167170
],
168171
'weather': [
169172
('cloud_coverage_pct', weather['clouds']['all']),
170173
('visibility', weather['visibility']),
171174
('temperature', weather['main']['temp']),
172-
('is_sun_up', is_sun_up),
175+
('is_daytime', is_daytime),
173176
]
174177
}
175178

@@ -195,45 +198,45 @@ def get_data():
195198
to_solar = make_gauge('solar.to_solar', 0)
196199
from_solar = make_gauge('solar.from_solar', 0)
197200

198-
if m.solar.instant_power > 0:
199-
from_solar = make_gauge('solar.from_solar', m.solar.instant_power)
200-
elif m.solar.instant_power < 0:
201-
to_solar = make_gauge('solar.to_solar', abs(m.solar.instant_power))
201+
if solarMeter.instant_power > 0:
202+
from_solar = make_gauge('solar.from_solar', solarMeter.instant_power)
203+
elif solarMeter.instant_power < 0:
204+
to_solar = make_gauge('solar.to_solar', abs(solarMeter.instant_power))
202205
data['metrics'].append(to_solar)
203206
data['metrics'].append(from_solar)
204207

205208
# 'to_grid',
206209
# 'from_grid',
207210
to_grid = make_gauge('solar.to_grid', 0)
208211
from_grid = make_gauge('solar.from_grid', 0)
209-
if m.site.instant_power > 0:
210-
from_grid = make_gauge('solar.from_grid', m.site.instant_power)
211-
elif m.site.instant_power < 0:
212-
to_grid = make_gauge('solar.to_grid', abs(m.site.instant_power))
212+
if siteMeter.instant_power > 0:
213+
from_grid = make_gauge('solar.from_grid', siteMeter.instant_power)
214+
elif siteMeter.instant_power < 0:
215+
to_grid = make_gauge('solar.to_grid', abs(siteMeter.instant_power))
213216
data['metrics'].append(to_grid)
214217
data['metrics'].append(from_grid)
215218

216219
# 'to_house',
217220
# 'from_house',
218221
to_house = make_gauge('solar.to_house', 0)
219222
from_house = make_gauge('solar.from_house', 0)
220-
if m.load.instant_power > 0:
221-
to_house = make_gauge('solar.to_house', m.load.instant_power)
222-
elif m.load.instant_power < 0:
223-
from_house = make_gauge('solar.from_house', abs(m.load.instant_power))
223+
if loadMeter.instant_power > 0:
224+
to_house = make_gauge('solar.to_house', loadMeter.instant_power)
225+
elif loadMeter.instant_power < 0:
226+
from_house = make_gauge('solar.from_house', abs(loadMeter.instant_power))
224227
data['metrics'].append(to_house)
225228
data['metrics'].append(from_house)
226229

227230
# 'to_battery',
228231
# 'from_battery',
229232
to_battery = make_gauge('solar.to_battery', 0)
230233
from_battery = make_gauge('solar.from_battery', 0)
231-
if m.battery.instant_power > 0:
234+
if batteryMeter.instant_power > 0:
232235
from_battery = make_gauge(
233-
'solar.from_battery', m.battery.instant_power)
234-
elif m.battery.instant_power < 0:
236+
'solar.from_battery', batteryMeter.instant_power)
237+
elif batteryMeter.instant_power < 0:
235238
to_battery = make_gauge(
236-
'solar.to_battery', abs(m.battery.instant_power))
239+
'solar.to_battery', abs(batteryMeter.instant_power))
237240
data['metrics'].append(to_battery)
238241
data['metrics'].append(from_battery)
239242

@@ -275,4 +278,4 @@ def run_from_cli():
275278
print('submitted at', dt.now(), "return code", ret)
276279
if not AS_SERVICE:
277280
run_from_cli()
278-
time.sleep(60)
281+
time.sleep(POLL_INTERVAL)

0 commit comments

Comments
 (0)