Skip to content

Commit

Permalink
Add support for reading bidirectionality from API
Browse files Browse the repository at this point in the history
  • Loading branch information
magico13 committed Jul 11, 2024
1 parent 70979a4 commit f22574f
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 71 deletions.
17 changes: 5 additions & 12 deletions pyemvue/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

# These provide AWS cognito authentication support
from pycognito import Cognito
from pycognito.exceptions import TokenVerificationException

CLIENT_ID = "4qte47jbstod8apnfic0bunmrq"
USER_POOL = "us-east-2_ghlOXVLi1"
Expand Down Expand Up @@ -62,17 +61,11 @@ def __init__(

def refresh_tokens(self) -> "dict[str, str]":
"""Refresh and return new tokens."""
try:
if self._password:
self.cognito.authenticate(password=self._password)

self.cognito.renew_access_token()
except TokenVerificationException as ex:
# ignore iat errors (until https://github.com/NabuCasa/pycognito/issues/225 is fixed)
if "The token is not yet valid (iat)" not in ex.args[0]:
raise
finally:
self._password = None
if self._password:
self.cognito.authenticate(password=self._password)

self.cognito.renew_access_token()
self._password = None

tokens = self._extract_tokens_from_cognito()
self.tokens = tokens
Expand Down
8 changes: 8 additions & 0 deletions pyemvue/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def __init__(self, gid=0, manId="", modelNum="", firmwareVersion=""):

# extra info
self.device_name = ""
self.display_name = ""
self.zip_code = "00000"
self.time_zone = ""
self.usage_cent_per_kw_hour = 0.0
Expand Down Expand Up @@ -84,6 +85,8 @@ def populate_location_properties_from_json(self, js: "dict[str, Any]"):
"""Adds the values from the get_device_properties method."""
if "deviceName" in js:
self.device_name = js["deviceName"]
if "displayName" in js:
self.display_name = js["displayName"]
if "zipCode" in js:
self.zip_code = js["zipCode"]
if "timeZone" in js:
Expand Down Expand Up @@ -138,6 +141,7 @@ def __init__(
self.channel_multiplier = channelMultiplier
self.channel_type_gid = channelTypeGid
self.nested_devices = {}
self.type = ""

def from_json_dictionary(self, js: "dict[str, Any]") -> Self:
"""Populate device channel data from a dictionary extracted from the response json."""
Expand All @@ -151,7 +155,11 @@ def from_json_dictionary(self, js: "dict[str, Any]") -> Self:
self.channel_multiplier = js["channelMultiplier"]
if "channelTypeGid" in js:
self.channel_type_gid = js["channelTypeGid"]
if "type" in js:
self.type = js["type"]
return self

# Known types: Main, FiftyAmp, FiftyAmpBidirectional

def as_dictionary(self) -> "dict[str, Any]":
"""Returns a dictionary of the device channel data."""
Expand Down
2 changes: 1 addition & 1 deletion pyemvue/pyemvue.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def get_chart_usage(
) -> "tuple[list[float], Optional[datetime.datetime]]":
"""Gets the usage over a given time period and the start of the measurement period. Note that you may need to scale this to get a rate (1MIN in kw = 60*result)"""
if channel.channel_num in ["MainsFromGrid", "MainsToGrid"]:
# These is not populated for the special Mains data as of right now
# This is not populated for the special Mains data as of right now
return [], start
if not start:
start = datetime.datetime.now(datetime.timezone.utc)
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pycognito>=2024.2.0
pycognito>=2024.5.0
python-dateutil>=2.8.2
requests>=2.26.0
typing_extensions>=4.0.1
136 changes: 81 additions & 55 deletions simulator/channel_types.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,131 +2,157 @@
{
"channelTypeGid": 1,
"description": "Air Conditioner",
"selectable": true
},
{
"channelTypeGid": 2,
"description": "Battery",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 3,
"description": "Boiler",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 4,
"description": "Clothes Dryer",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 5,
"description": "Clothes Washer",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 6,
"description": "Computer/Network",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 7,
"description": "Cooktop/Range/Oven/Stove",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 8,
"description": "Dishwasher",
"selectable": true
},
{
"channelTypeGid": 9,
"description": "Electric Vehicle/RV",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 10,
"description": "Fridge/Freezer",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 11,
"description": "Furnace",
"selectable": true
},
{
"channelTypeGid": 12,
"description": "Garage/Shop/Barn/Shed",
"selectable": true
},
{
"channelTypeGid": 24,
"description": "Heat Pump",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 14,
"description": "Hot Tub/Spa",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 15,
"description": "Humidifier/Dehumidifier",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 16,
"description": "Kitchen",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 17,
"description": "Microwave",
"selectable": true
},
{
"channelTypeGid": 18,
"description": "Other",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 19,
"description": "Pump",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 20,
"description": "Room/Multi-use Circuit",
"selectable": true
},
{
"channelTypeGid": 21,
"description": "Sub Panel",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 22,
"description": "Water Heater",
"selectable": true
},
{
"channelTypeGid": 13,
"description": "Solar/Generation",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 23,
"description": "Smart Outlet",
"selectable": false
"selectable": false,
"allowsBidirectional": false
},
{
"channelTypeGid": 25,
"description": "EV Charger",
"selectable": false
"selectable": false,
"allowsBidirectional": false
},
{
"channelTypeGid": 26,
"description": "Lights",
"selectable": true
"selectable": true,
"allowsBidirectional": false
},
{
"channelTypeGid": 2,
"description": "Battery",
"selectable": true,
"allowsBidirectional": true
},
{
"channelTypeGid": 9,
"description": "Electric Vehicle/RV",
"selectable": true,
"allowsBidirectional": true
},
{
"channelTypeGid": 12,
"description": "Garage/Shop/Barn/Shed",
"selectable": true,
"allowsBidirectional": true
},
{
"channelTypeGid": 24,
"description": "Heat Pump",
"selectable": true,
"allowsBidirectional": true
},
{
"channelTypeGid": 18,
"description": "Other",
"selectable": true,
"allowsBidirectional": true
},
{
"channelTypeGid": 21,
"description": "Sub Panel",
"selectable": true,
"allowsBidirectional": true
},
{
"channelTypeGid": 13,
"description": "Solar/Generation",
"selectable": true,
"allowsBidirectional": true
}
]
]
8 changes: 6 additions & 2 deletions simulator/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@
# plug 1 is pulling 5 amps at 120 volts
state.set_channel_1min_watts(1001, "1,2,3", 5 * 120)

# the overall house is pulling 95 amps at 240 volts
state.set_channel_1min_watts(1000, "1,2,3", 95 * 240)
# channel 2 is generating 10 amps and is bidirectional
state.set_channel_bidirectionality(1000, "2", True)
state.set_channel_1min_watts(1000, "2", -10 * 120)

# the overall house is pulling 85 amps at 240 volts
state.set_channel_1min_watts(1000, "1,2,3", 85 * 240)

# the balance is effectively 40 amps
state.set_channel_1min_watts(1000, "Balance", 40 * 240)
Expand Down
3 changes: 3 additions & 0 deletions simulator/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class SimulatorChannel(SimulatorBase):
channelNum: str
channelMultiplier: float = 1.0
channelTypeGid: Optional[int]
type: str


class SimulatorLatitudeLongitude(SimulatorBase):
Expand All @@ -109,6 +110,7 @@ class SimulatorLocationProperties(SimulatorBase):
latitudeLongitude: SimulatorLatitudeLongitude
utilityRateGid: Optional[int]
deviceName: Optional[str]
displayName: Optional[str]
deviceGid: int
zipCode: str
billingCycleStartDay: int
Expand Down Expand Up @@ -154,6 +156,7 @@ class ChannelType(SimulatorBase):
channelTypeGid: int
description: str
selectable: bool
allowsBidirectional: bool


class SimulatorLoad(SimulatorBase):
Expand Down
24 changes: 24 additions & 0 deletions simulator/simulator_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ def add_outlet(

outlet_device.locationProperties.deviceGid = gid
outlet_device.locationProperties.deviceName = name
outlet_device.locationProperties.displayName = name
self.devices.append(outlet_device)
return outlet

Expand Down Expand Up @@ -271,6 +272,7 @@ def add_charger(

charger_device.locationProperties.deviceGid = gid
charger_device.locationProperties.deviceName = name
charger_device.locationProperties.displayName = name
self.devices.append(charger_device)
return charger

Expand Down Expand Up @@ -312,6 +314,7 @@ def add_vue(
channelNum="1,2,3",
channelMultiplier=1.0,
channelTypeGid=None,
type="Main",
)
)
# Create the other channels as children of the "WAT001" device
Expand All @@ -332,11 +335,13 @@ def add_vue(
channelNum=str(i),
channelMultiplier=1.0,
channelTypeGid=18,
type="FiftyAmp"
)
)
# Add the device to the list
vue.locationProperties.deviceGid = gid
vue.locationProperties.deviceName = name
vue.locationProperties.displayName = name
self.devices.append(vue)
return vue

Expand All @@ -362,3 +367,22 @@ def set_channel_1min_usage(
self, deviceGid: int, channelNum: str, usage: Optional[float]
):
self.usage_dict_1min[f"{deviceGid}_{channelNum}"] = usage

def set_channel_bidirectionality(
self, deviceGid: int, channelNum: str, bidirectional: bool
):
# this should technically check if a channel type is allowed to be bidirectional
# but that won't really affect the simulation, even if it results in invalid real states
new_type = "FiftyAmpBidirectional" if bidirectional else "FiftyAmp"
for device in self.devices:
if device.deviceGid == deviceGid:
for channel in device.channels:
if channel.channelNum == channelNum:
channel.type = new_type
return
for sub_device in device.devices:
for channel in sub_device.channels:
if channel.channelNum == channelNum:
channel.type = new_type
return
raise Exception(f"Device {deviceGid} or channel {channelNum} not found")

0 comments on commit f22574f

Please sign in to comment.