2020from tesla_powerwall .responses import Meter , Battery
2121
2222
23- ##### environment variables
23+ # environment variables
2424# load environment variables from a file if they're there
2525load_dotenv ('env.list' , override = False )
2626
4747POLL_INTERVAL = int (os .environ .get ('POLL_INTERVAL' , 60 ))
4848
4949# powerwall hostname or IP.
50- # The powerwall's self-signed certificate only responds to
50+ # The powerwall's self-signed certificate only responds to
5151# hostnamnes "powerwall", "teg", or "powerpack", and of course you have to have DNS set up properly.
5252# IP addresses work, too.
53- PW_ADDR = os .environ .get ("PW_ADDR" , 'powerwall' )
53+ PW_ADDR = os .environ .get ("PW_ADDR" , 'powerwall' )
5454
5555# Optional Metrics
5656# Reserve Percent (enabled by default)
6464OPT_BATTERY_CAPACITY_WH = os .environ .get ('OPT_BATTERY_CAPACITY_WH' , False )
6565OPT_GRID_STATUS_GAUGE = os .environ .get ('OPT_GRID_STATUS_GAUGE' , False )
6666
67- ##### end environment variables
67+ # end environment variables
6868
69- ##### constants
69+ # constants
7070# URL to post to
7171URL = 'https://metric-api.newrelic.com/metric/v1'
7272
7575 'Content-Type' : 'application/json' ,
7676 'Api-Key' : INSIGHTS_API_KEY ,
7777}
78- ##### end constants
78+ # end constants
79+
80+ # Grid Status Enum for OPT_GRID_STATUS_GAUGE
81+
7982
80- ##### Grid Status Enum for OPT_GRID_STATUS_GAUGE
8183class GridStatus (enum .IntEnum ):
8284 UNKNOWN = 0
8385 CONNECTED = 1
@@ -88,7 +90,8 @@ class GridStatus(enum.IntEnum):
8890
8991 def _missing (self , value ):
9092 return self .UNKNOWN
91- ##### end Grid Status Enum
93+ # end Grid Status Enum
94+
9295
9396def get_now ():
9497 """Return the current Unix timestamp in msec."""
@@ -111,6 +114,7 @@ def post_metrics(data):
111114# because the gateway is very slow to respond
112115# and it has some absurdly low rate limit
113116
117+
114118@tenacity .retry (stop = tenacity .stop_after_attempt (7 ),
115119 wait = tenacity .wait_random (min = 3 , max = 7 ))
116120def get_pw ():
@@ -230,7 +234,7 @@ def get_data():
230234
231235 if OPT_RESERVE_PCT :
232236 reserve = make_gauge ('solar.reserve_pct' ,
233- pw .get_backup_reserve_percentage ())
237+ pw .get_backup_reserve_percentage ())
234238 data ['metrics' ].append (reserve )
235239
236240 if OPT_RESERVE_PCT_AVAIL :
@@ -243,7 +247,7 @@ def get_data():
243247 batteries : list [Battery ] = []
244248 if OPT_BATTERY_CHARGE_WH or OPT_BATTERY_CAPACITY_WH :
245249 batteries = pw .get_batteries ()
246-
250+
247251 if OPT_BATTERY_CHARGE_WH :
248252 tmp = 0
249253 for battery in batteries :
@@ -261,11 +265,13 @@ def get_data():
261265 data ['metrics' ].append (capacity )
262266
263267 if OPT_GRID_STATUS_GAUGE :
264- grid_status = make_gauge ('solar.grid_status' , GridStatus [pw .get_grid_status ().name ].value )
268+ grid_status = make_gauge (
269+ 'solar.grid_status' , GridStatus [pw .get_grid_status ().name ].value )
265270 data ['metrics' ].append (grid_status )
266271
267272 return data
268273
274+
269275def make_meter_gauges (name : str , meter : Meter , invertDirection : bool = False , type : str = 'gauge' ) -> list [dict ]:
270276 """Return a list of gauges for a supplied Meter"""
271277 gauges = [
@@ -297,11 +303,27 @@ def run_from_cli():
297303
298304
299305if __name__ == "__main__" :
306+ # If POLL_INTERVAL is a multiple of a minute, try to start at the beginning of the next minute
307+ if POLL_INTERVAL % 60 == 0 and AS_SERVICE :
308+ wait_time = 60 - time .localtime ().tm_sec
309+ print ('Found minute intervals, delaying first iteration' ,
310+ wait_time , 'seconds until the start of the next minute' )
311+ print ()
312+ time .sleep (wait_time )
313+
300314 while True :
315+ start = time .time ()
301316 data = get_data ()
302317 ret = post_metrics (data )
303318
304- print ('submitted at' , dt .now (), "return code" , ret )
319+ print ('Submitted at' , dt .now ())
320+
305321 if not AS_SERVICE :
306322 run_from_cli ()
307- time .sleep (POLL_INTERVAL )
323+
324+ # Try to position each loop exactly POLL_INTERVAL seconds apart.
325+ # This is most useful when POLL_INTERVAL is an even division of a minute
326+ elapsed = time .time () - start
327+ if elapsed < 0 or elapsed > POLL_INTERVAL :
328+ elapsed = 0
329+ time .sleep (POLL_INTERVAL - elapsed )
0 commit comments