-
Notifications
You must be signed in to change notification settings - Fork 13
How to Integrate Python based Electric Vehicle Models with OpenStudio Workflows
Electric vehicles (EVs) can be modeled within an OpenStudio Workflow (OSW) in Alfalfa. Using OSWs with Alfalfa is described in detail here. Since using an OpenStudio model already requires a workflow, adding electric vehicle modeling means inserting a measure into the workflow that encapsulates the electric vehicle model.
The electric vehicle model repository includes two Python classes:
- One that models the charging and discharging behavior of an electric vehicle (EV)
- One that emulates an electric vehicle supply equipment (EVSE) or the charging port
These classes can be instantiated with required parameter values to generate multiple EV and EVSE agents that can interact with each other through built-in methods.
To interact with the Python EV model above, an EnergyPlus-specific python script is needed. An example is shown below.
from pyenergyplus.plugin import EnergyPlusPlugin
from ElectricVehicles import ElectricVehicles
from evse_class import EVSE_class
import pandas as pd
import os
class EVSE(EnergyPlusPlugin):
def __init__(self):
super().__init__()
self.need_to_get_handles = True
self.ev_sch_handle = None
# define list for EVs
self.ev_list = []
self.ev_pmax = []
self.ev_inst = []
# read CSV
py_path = os.path.dirname(os.path.abspath(__file__))
csv_path = os.path.join(py_path, 'in.csv')
ev_sch = pd.read_csv(csv_path)
# iterate over CSV rows
for row in ev_sch.iterrows():
ev = ElectricVehicles(
departure_time = row[1]['departure_time'] * 60,
vehicle_type = row[1]['vehicle_type'],
arrival_time = row[1]['arrival_time'] * 60,
initial_soc = row[1]['initial_soc'],
target_soc = 0.9,
batterycapacity_kWh = row[1]['batterycapacity_kWh']
)
self.ev_list.append(ev)
self.ev_pmax.append(0)
# loop over EV list and create EVSE
for ev in self.ev_list:
evse = EVSE_class(
efficiency = 0.99,
Prated_kW = 6.6,
evse_id = self.ev_list.index(ev)
)
evse.server_setpoint = 10
self.ev_inst.append(evse)
# assign EVSE
for ev in self.ev_list:
ev.assign_evse(self.ev_inst[self.ev_list.index(ev)].evse_id)
def get_handles(self, state):
self.ev_sch_handle = self.api.exchange.get_actuator_handle(
state,
'Schedule:Constant',
'Schedule Value',
'EV Sch'
)
def on_begin_timestep_before_predictor(self, state) -> int:
if not self.api.exchange.api_data_fully_ready(state):
return 0
if self.need_to_get_handles:
self.get_handles(state)
self.need_to_get_handles = False
# timestep
zt = self.api.exchange.zone_time_step(state)
dt = zt * 60 * 60 # sec
dw = self.api.exchange.day_of_week(state)
ct = self.api.exchange.current_time(state)
# calc time of week (min)
if dw == 1:
tw = ((dw + 5) * 24 + ct) * 60
else:
tw = ((dw - 2) * 24 + ct) * 60
# charge vehicle
for ev in self.ev_list:
ev.chargevehicle(
tw * 60,
dt = dt,
evsePower_kW = self.ev_pmax[self.ev_list.index(ev)]
)
# reset SOC to initial SOC at departure time
pt = ev.departuretime / 60
if ((tw == pt) or ((tw + 60*24*7) == pt)):
ev.soc = ev.initialsoc
# EV -> EVSE
for evse in self.ev_inst:
evse.receive_from_ev(
self.ev_list[self.ev_inst.index(evse)].packvoltage,
self.ev_list[self.ev_inst.index(evse)].packpower,
self.ev_list[self.ev_inst.index(evse)].soc,
self.ev_list[self.ev_inst.index(evse)].pluggedin,
self.ev_list[self.ev_inst.index(evse)].readytocharge
)
# EVSE -> EV
for i in range(len(self.ev_pmax)):
if shed:
self.ev_pmax[i] = 0
else:
self.ev_pmax[i] = self.ev_inst[i].send_to_ev()
# calc charge power
tot_ev_pwr = 0
for evse in self.ev_inst:
tot_ev_pwr = tot_ev_pwr + (evse.ev_power / evse.efficiency)
# set total power actuator
self.api.exchange.set_actuator_value(
state,
self.ev_sch_handle,
tot_ev_pwr
)
return 0
The python script above reads a CSV file that defines each charging event. An example CSV file would be:
arrival_day_of_week | arrival_time | departure_time | initial_soc | batterycapacity_kWh | vehicle_type |
---|---|---|---|---|---|
Monday | 1200 | 1987 | 0.41 | 45 | BEV |
Tuesday | 2455 | 3404 | 0.26 | 45 | BEV |
Wednesday | 4165 | 4837 | 0.36 | 45 | BEV |
Thursday | 5100 | 6229 | 0.48 | 45 | BEV |
Friday | 6925 | 7851 | 0.15 | 45 | BEV |
Saturday | 8360 | 8870 | 0.53 | 45 | BEV |
Sunday | 9595 | 10153 | 0.60 | 45 | BEV |
The arrival and departure times are in minutes of the week.
The user will want to create a directory named resources
at the same level as their measure.rb
file. Inside that directory, the user will want to add:
- The electric vehicle class
- The electric vehicle supply equipment class
- The EnergyPlus python script above
- The electric vehicle parameter file above, in a comma-separated format
The OpenStudio (EnergyPlus) measure to would be:
# start the measure
class PythonEV < OpenStudio::Ruleset::WorkspaceUserScript
# human readable name
def name
return 'Python EV'
end
# human readable description
def description
return 'Add python EV to IDF'
end
# human readable description of modeling approach
def modeler_description
return 'Add python EV pieces to IDF'
end
# define the arguments that the user will input
def arguments(workspace)
args = OpenStudio::Ruleset::OSArgumentVector.new
# argument for python script name
py_name = OpenStudio::Ruleset::OSArgument.makeStringArgument(
'py_name',
true
)
py_name.setDisplayName('Python Script Name')
py_name.setDescription('Name of script with extension (e.g., in.py)')
args << py_name
return args
end
# define what happens when the measure is run
def run(ws, runner, usr_args)
# call the parent class method
super(ws, runner, usr_args)
# use the built-in error checking
return false unless runner.validateUserArguments(
arguments(ws),
usr_args
)
# assign the user inputs to variables
py_name = runner.getStringArgumentValue(
'py_name',
usr_args
)
# define python script dir
py_dir = "#{__dir__}/resources"
# make sure python script exists
unless File.exist?("#{py_dir}/#{py_name}")
runner.registerError("Could not find file at #{py_dir}/#{py_name}.")
return false
end
# change timestep to 1 min
ws.getObjectsByType('Timestep'.to_IddObjectType).each do |o|
o.setInt(0, 60)
end
# add python plugin search paths
n = OpenStudio::IdfObject.new('PythonPlugin_SearchPaths'.to_IddObjectType)
n.setString(0, 'Python Plugin Search Paths')
n.setString(1, 'Yes')
n.setString(2, 'Yes')
# set site packages location depending on operating system
if (RUBY_PLATFORM =~ /linux/) != nil
n.setString(3, '/usr/local/lib/python3.7/dist-packages')
elsif (RUBY_PLATFORM =~ /darwin/) != nil
n.setString(3, '/usr/local/lib/python3.7/site-packages')
elsif (RUBY_PLATFORM =~ /cygwin|mswin|mingw|bccwin|wince|emx/) != nil
h = ENV['USERPROFILE'].gsub('\\', '/')
n.setString(3, "#{h}/AppData/Local/Programs/Python/Python37/Lib/site-packages")
end
# add python dir
n.setString(4, py_dir)
ws.addObject(n)
# add python plugin instance
n = OpenStudio::IdfObject.new('PythonPlugin_Instance'.to_IddObjectType)
n.setString(0, 'EVSE Program')
n.setString(1, 'No')
n.setString(2, py_name.sub('.py', ''))
n.setString(3, 'EVSE')
ws.addObject(n)
# add EV schedule type limits
n = OpenStudio::IdfObject.new('ScheduleTypeLimits'.to_IddObjectType)
n.setString(0, 'EV Sch Type Limits')
ws.addObject(n)
# add EV schedule to actuate
n = OpenStudio::IdfObject.new('Schedule_Constant'.to_IddObjectType)
n.setString(0, 'EV Sch')
n.setString(1, 'EV Sch Type Limits')
n.setInt(2, 0)
ws.addObject(n)
# add EVSE
n = OpenStudio::IdfObject.new('Exterior_FuelEquipment'.to_IddObjectType)
n.setString(0, 'EVSE')
n.setString(1, 'Electricity')
n.setString(2, 'EV Sch')
n.setInt(3, 1)
n.setString(4, '')
ws.addObject(n)
end
end
# register the measure to be used by the application
PythonEV.new.registerWithApplication
Finally, insert this into your OSW:
{
"measure_dir_name" : "python_ev",
"name" : "Python EV",
"description" : "Add python EV to IDF",
"modeler_description" : "Add python EV pieces to IDF",
"arguments" : {
"py_name" : "in.py"
}
}
- Getting Started with Model Measures Part 1: Creating Inputs and Outputs
- Getting Started with Model Measures Part 2: Creating Actuators
- Getting Started with EnergyPlus Measures Part 1: Creating Inputs and Outputs
- Getting Started with EnergyPlus Measures Part 2: Creating Actuators
- How to Configure an OpenStudio Model
- How to Configure Measures for Use with Alfalfa Ruby Gem
- How to Create Inputs and Outputs With Measures
- How to Run URBANopt Output Models in Alfalfa
- How to Migrate EnergyPlus Python Plugins
- How to Integrate Python based Electric Vehicle Models with OpenStudio Workflows
- How to Locally Test OpenStudio Models
- Required Structure of OpenStudio Workflow
- List of Automatically Generated Energyplus Points
- Alfalfa EnergyPlus Mixin Methods
- Getting Started with Uploading and Running a Model Using Python
- Getting Started with Uploading and Running a Model Using the UI
- How to Install Alfalfa Client
- How to Preprocess and Upload a Model
- How to Step Through a Simulation
- How to View Historical Data in Grafana
- How to Configure an Alias
- How to Troubleshoot Models