Skip to content

Commit

Permalink
Add grid control functions #108
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonacox committed Aug 25, 2024
1 parent fb3711d commit 8aba24f
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 12 deletions.
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ and call function to poll data. Here is an example:
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_grid_charging(mode) # Enable or disable grid charging (mode = True or False)
set_grid_export(mode) # Set grid export mode (mode = battery_ok, pv_only, never)
get_grid_charging() # Get the current grid charging mode
get_grid_export() # Get the current grid export mode
```

## Tools
Expand All @@ -243,16 +247,18 @@ Options:
Commands (run <command> -h to see usage information):
{setup,scan,set,get,version}
setup Setup Tesla Login for Cloud Mode access
scan Scan local network for Powerwall gateway
set Set Powerwall Mode and Reserve Level
get Get Powerwall Settings and Power Levels
version Print version information
setup Setup Tesla Login for Cloud Mode access
scan Scan local network for Powerwall gateway
set Set Powerwall Mode and Reserve Level
get Get Powerwall Settings and Power Levels
version Print version information
set options:
-mode MODE Powerwall Mode: self_consumption, backup, or autonomous
-reserve RESERVE Set Battery Reserve Level [Default=20]
-current Set Battery Reserve Level to Current Charge
-mode MODE Powerwall Mode: self_consumption, backup, or autonomous
-reserve RESERVE Set Battery Reserve Level [Default=20]
-current Set Battery Reserve Level to Current Charge
-gridcharging MODE Set Grid Charging (allow) Mode ("on" or "off")
-gridexport MODE Set Export to Grid Mode ("battery_ok", "pv_only", or "never")
get options:
-format FORMAT Output format: text, json, csv
Expand Down
49 changes: 49 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,54 @@
# RELEASE NOTES

## v0.10.10 - Add Grid Control

* Add a function and command line options to allow user to get and set grid charging and exporting modes (see https://github.com/jasonacox/pypowerwall/issues/108).
* Supports FleetAPI and Cloud modes only (not Local mode)

#### Command Line Examples

```bash
# Connect to Cloud
python3 -m pypowerwall setup # or fleetapi

# Get Current Settings
python3 -m pypowerwall get

# Turn on Grid charging
python3 -m pypowerwall set -gridcharging on

# Turn off Grid charging
python3 -m pypowerwall set -gridcharging off

# Set Grid Export to Solar (PV) energy only
python3 -m pypowerwall set -gridexport pv_only

# Set Grid Export to Battery and Solar energy
python3 -m pypowerwall set -gridexport battery_ok

# Disable export of all energy to grid
python3 -m pypowerwall set -gridexport never
```

#### Programming Examples

```python
import pypowerwall

# FleetAPI Mode
PW_HOST=""
PW_EMAIL="[email protected]"
pw = pypowerwall.Powerwall(host=PW_HOST, email=PW_EMAIL, fleetapi=True)

# Get modes
pw.get_grid_charging()
pw.get_grid_export()

# Set modes
pw.set_grid_charging("on") # set grid charging mode (on or off)
pw.set_grid_export("pv_only") # set grid export mode (battery_ok, pv_only, or never)
```

## v0.10.9 - TEDAPI Voltage & Current

* Add computed voltage and current to `/api/meters/aggregates` from TEDAPI status data.
Expand Down
49 changes: 49 additions & 0 deletions pypowerwall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
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_grid_charging(mode) # Enable or disable grid charging (mode = True or False)
set_grid_export(mode) # Set grid export mode (mode = battery_ok, pv_only, never)
get_grid_charging() # Get the current grid charging mode
get_grid_export() # Get the current grid export mode
Requirements
This module requires the following modules: requests, protobuf, teslapy
Expand Down Expand Up @@ -835,6 +839,51 @@ def get_time_remaining(self) -> Optional[float]:
"""
return self.client.get_time_remaining()

def set_grid_charging(self, mode) -> Optional[dict]:
"""
Enable or disable grid charging
Args:
enabled: Set to True to enable grid charging, False to disable it
Returns:
Dictionary with operation results.
"""
return self.client.set_grid_charging(mode)

def get_grid_charging(self) -> Optional[bool]:
"""
Get the current grid charging mode
Returns:
True if grid charging is enabled, False if it is disabled
"""
return self.client.get_grid_charging()

def set_grid_export(self, mode: str) -> Optional[dict]:
"""
Set grid export mode
Args:
mode: Set grid export mode (battery_ok, pv_only, or never)
Returns:
Dictionary with operation results.
"""
# Check for valid mode
if mode not in ['battery_ok', 'pv_only', 'never']:
raise ValueError(f"Invalid value for parameter 'mode': {mode}")
return self.client.set_grid_export(mode)

def get_grid_export(self) -> Optional[str]:
"""
Get the current grid export mode
Returns:
The current grid export mode
"""
return self.client.get_grid_export()

def _validate_init_configuration(self):

# Basic user input validation for starters. Can be expanded to limit other parameters such as cache
Expand Down
27 changes: 24 additions & 3 deletions pypowerwall/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
help="Set Battery Reserve Level [Default=20]")
set_mode_args.add_argument("-current", action="store_true", default=False,
help="Set Battery Reserve Level to Current Charge")
set_mode_args.add_argument("-gridcharging", type=str, default=None,
help="Enable Grid Charging Mode: on or off")
set_mode_args.add_argument("-gridexport", type=str, default=None,
help="Grid Export Mode: battery_ok, pv_only, or never")

get_mode_args = subparsers.add_parser("get", help='Get Powerwall Settings and Power Levels')
get_mode_args.add_argument("-format", type=str, default="text",
Expand Down Expand Up @@ -129,8 +133,8 @@
# Set Powerwall Mode
elif command == 'set':
# If no arguments, print usage
if not args.mode and not args.reserve and not args.current:
print("usage: pypowerwall set [-h] [-mode MODE] [-reserve RESERVE] [-current]")
if not args.mode and not args.reserve and not args.current and not args.gridcharging and not args.gridexport:
print("usage: pypowerwall set [-h] [-mode MODE] [-reserve RESERVE] [-current] [-gridcharging MODE] [-gridexport MODE]")
sys.exit(1)
import pypowerwall
# Determine which cloud mode to use
Expand All @@ -154,6 +158,20 @@
current = float(pw.level())
print("Setting Powerwall Reserve to Current Charge Level %s" % current)
pw.set_reserve(current)
if args.gridcharging:
gridcharging = args.gridcharging.lower()
if gridcharging not in ['on', 'off']:
print("ERROR: Invalid Grid Charging Mode [%s] - must be on or off" % gridcharging)
sys.exit(1)
print("Setting Grid Charging Mode to %s" % gridcharging)
pw.set_grid_charging(gridcharging)
if args.gridexport:
gridexport = args.gridexport.lower()
if gridexport not in ['battery_ok', 'pv_only', 'never']:
print("ERROR: Invalid Grid Export Mode [%s] - must be battery_ok, pv_only, or never" % gridexport)
sys.exit(1)
print("Setting Grid Export Mode to %s" % gridexport)
pw.set_grid_export(gridexport)

# Get Powerwall Mode
elif command == 'get':
Expand All @@ -177,6 +195,8 @@
'home': pw.home(),
'battery': pw.battery(),
'solar': pw.solar(),
'grid_charging': pw.get_grid_charging(),
'grid_export_mode': pw.get_grid_export(),
}
if args.format == 'json':
print(json.dumps(output, indent=2))
Expand All @@ -190,7 +210,8 @@
# Table Output
for item in output:
name = item.replace("_", " ").title()
print(" {:<15}{}".format(name, output[item]))
print(" {:<18}{}".format(name, output[item]))
print("")

# Print Version
elif command == 'version':
Expand Down
58 changes: 57 additions & 1 deletion pypowerwall/cloud/pypowerwall_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,6 @@ def get_site_config(self, force: bool = False):
"vpp_backup_reserve_percent": 80
}
}
"""
# GET api/1/energy_sites/{site_id}/site_info
(response, _) = self._site_api("SITE_CONFIG", SITE_CONFIG_TTL, language="en", force=force)
Expand Down Expand Up @@ -756,6 +755,63 @@ def get_api_system_status(self, **kwargs) -> Optional[Union[dict, list, str, byt

return data

def set_grid_charging(self, mode: str) -> bool:
"""
Enable/Disable grid charging mode (mode: "on" or "off")
"""
if mode in ["on", "yes"] or mode is True:
mode = False
elif mode in ["off", "no"] or mode is False:
mode = True
else:
log.debug(f"Invalid mode: {mode}")
return False
response = self._site_api("ENERGY_SITE_IMPORT_EXPORT_CONFIG", ttl=SITE_CONFIG_TTL, force=True,
disallow_charge_from_grid_with_solar_installed = mode)
# invalidate cache
super()._invalidate_cache("SITE_CONFIG")
self.pwcachetime["SITE_CONFIG"] = 0
return response

def set_grid_export(self, mode: str) -> bool:
"""
Set grid export mode (battery_ok, pv_only, or never)
Mode will show up in get_site_info() under components:
* never
"non_export_configured": true,
"customer_preferred_export_rule": "never",
* pv_only
"customer_preferred_export_rule": "pv_only"
* battery_ok
"customer_preferred_export_rule": "battery_ok"
or not set
"""
if mode not in ["battery_ok", "pv_only", "never"]:
log.debug(f"Invalid mode: {mode} - must be battery_ok, pv_only, or never")
# POST api/1/energy_sites/{site_id}/grid_import_export
response = self._site_api("ENERGY_SITE_IMPORT_EXPORT_CONFIG", ttl=SITE_CONFIG_TTL, force=True,
customer_preferred_export_rule = mode)
# invalidate cache
super()._invalidate_cache("SITE_CONFIG")
self.pwcachetime["SITE_CONFIG"] = 0
return response

def get_grid_charging(self, force=False):
""" Get allow grid charging allowed mode (True or False) """
components = self.get_site_config(force=force).get("response").get("components") or {}
state = components.get("disallow_charge_from_grid_with_solar_installed")
return not state

def get_grid_export(self, force=False):
""" Get grid export mode (battery_ok, pv_only, or never) """
components = self.get_site_config(force=force).get("response").get("components") or {}
# Check to see if non_export_configured - pre-PTO setting
if components.get("non_export_configured"):
return "never"
mode = components.get("customer_preferred_export_rule") or "battery_ok"
return mode

# noinspection PyUnusedLocal
@not_implemented_mock_data
def api_logout(self, **kwargs) -> Optional[Union[dict, list, str, bytes]]:
Expand Down
67 changes: 67 additions & 0 deletions pypowerwall/fleetapi/fleetapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
get_operating_mode() - get operating mode
get_history() - get energy history
get_calendar_history() - get calendar history
get_grid_charging() - get allow grid charging mode
get_grid_export() - get grid export mode
solar_power() - get solar power
grid_power() - get grid power
battery_power() - get battery power
Expand All @@ -34,6 +36,8 @@
... Set
set_battery_reserve(reserve) - set battery reserve level (percent)
set_operating_mode(mode) - set operating mode (self_consumption or autonomous)
set_grid_charging(mode) - set grid charging mode (on or off)
set_grid_export(mode) - set grid export mode (battery_ok, pv_only, or never)
Author: Jason A. Cox
Date: 18 Feb 2024
Expand Down Expand Up @@ -496,7 +500,23 @@ def get_history(self, kind=None, duration=None, time_zone=None,
h = self.poll(f"api/1/energy_sites/{self.site_id}/{history}?{arg_kind}{arg_duration}{arg_time_zone}{arg_start}{arg_end}")
return self.keyval(h, "response")

def get_grid_charging(self, force=False):
""" Get allow grid charging allowed mode (True or False) """
components = self.get_site_info(force=force).get("components") or {}
state = self.keyval(components, "disallow_charge_from_grid_with_solar_installed") or False
return not state

def get_grid_export(self, force=False):
""" Get grid export mode (battery_ok, pv_only, or never) """
components = self.get_site_info(force=force).get("components") or {}
# Check to see if non_export_configured - pre-PTO setting
if self.keyval(components, "non_export_configured"):
return "never"
mode = self.keyval(components, "customer_preferred_export_rule") or "battery_ok"
return mode

def set_battery_reserve(self, reserve: int):
""" Set battery reserve level (percent) """
if reserve < 0 or reserve > 100:
log.debug(f"Invalid reserve level: {reserve}")
return False
Expand All @@ -508,6 +528,7 @@ def set_battery_reserve(self, reserve: int):
return payload

def set_operating_mode(self, mode: str):
""" Set operating mode (self_consumption or autonomous) """
data = {"default_real_mode": mode}
if mode not in ["self_consumption", "autonomous"]:
log.debug(f"Invalid mode: {mode}")
Expand All @@ -518,6 +539,52 @@ def set_operating_mode(self, mode: str):
self.pwcachetime.pop(f"api/1/energy_sites/{self.site_id}/site_info", None)
return payload

def set_grid_charging(self, mode: str):
""" Set allow grid charging mode (True or False)
Mode will show up in get_site_info() under components:
* False
"disallow_charge_from_grid_with_solar_installed": true,
* True
No entry
"""
if mode in ["on", "yes"] or mode is True:
mode = False
elif mode in ["off", "no"] or mode is False:
mode = True
else:
log.debug(f"Invalid mode: {mode}")
return False
data = {"disallow_charge_from_grid_with_solar_installed": mode}
# 'https://fleet-api.prd.na.vn.cloud.tesla.com/api/1/energy_sites/{energy_site_id}/grid_import_export'
payload = self.poll(f"api/1/energy_sites/{self.site_id}/grid_import_export", "POST", data)
# Invalidate cache
self.pwcachetime.pop(f"api/1/energy_sites/{self.site_id}/site_info", None)
return payload

def set_grid_export(self, mode: str):
""" Set grid export mode (battery_ok, pv_only, or never)
Mode will show up in get_site_info() under components:
* never
"non_export_configured": true,
"customer_preferred_export_rule": "never",
* pv_only
"customer_preferred_export_rule": "pv_only"
* battery_ok
"customer_preferred_export_rule": "battery_ok"
or not set
"""
if mode not in ["battery_ok", "pv_only", "never"]:
log.debug(f"Invalid mode: {mode} - must be battery_ok, pv_only, or never")
return False
data = {"customer_preferred_export_rule": mode}
# 'https://fleet-api.prd.na.vn.cloud.tesla.com/api/1/energy_sites/{energy_site_id}/grid_import_export'
payload = self.poll(f"api/1/energy_sites/{self.site_id}/grid_import_export", "POST", data)
# Invalidate cache
self.pwcachetime.pop(f"api/1/energy_sites/{self.site_id}/site_info", None)
return payload

def get_operating_mode(self, force=False):
return self.keyval(self.get_site_info(force=force), "default_real_mode")

Expand Down
Loading

0 comments on commit 8aba24f

Please sign in to comment.