Class: AddEMSToControlEVCharging

Inherits:
OpenStudio::Measure::ModelMeasure
  • Object
show all
Defined in:
lib/measures/add_ems_to_control_ev_charging/measure.rb

Overview

start the measure

Instance Method Summary collapse

Instance Method Details

#arguments(model) ⇒ Object

define the arguments that the user will input



24
25
26
27
28
29
30
31
32
33
# File 'lib/measures/add_ems_to_control_ev_charging/measure.rb', line 24

def arguments(model)
  args = OpenStudio::Measure::OSArgumentVector.new
  curtailment_frac = OpenStudio::Measure::OSArgument.makeDoubleArgument('curtailment_frac', true)
  curtailment_frac.setDisplayName('Fraction by Which to Curtail EV Charging During Load Shifting Events')
  curtailment_frac.setDefaultValue(0.5)
  curtailment_frac.setDescription('Number between 0 and 1 that denotes the fraction by which EV charging')
  args << curtailment_frac

  return args
end

#descriptionObject

human readable description



14
15
16
# File 'lib/measures/add_ems_to_control_ev_charging/measure.rb', line 14

def description
  return 'This measure implements a control system to curtail an electric vehicle (EV) charging load to better align EV charging with expected energy production from a solar PV system.'
end

#modeler_descriptionObject

human readable description of modeling approach



19
20
21
# File 'lib/measures/add_ems_to_control_ev_charging/measure.rb', line 19

def modeler_description
  return "This measure uses EnergyPlus' Energy Management System to control an electric vehicle (EV) charging load to better align charging power draw with expected energy production from solar PV. There must already be an EV charging load present in the model when this measure is applied, and the measure is configured based on the assumption of a typical office operating schedule."
end

#nameObject

human readable name



9
10
11
# File 'lib/measures/add_ems_to_control_ev_charging/measure.rb', line 9

def name
  return 'Add EMS to Control EV Charging'
end

#run(model, runner, user_arguments) ⇒ Object

define what happens when the measure is run



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/measures/add_ems_to_control_ev_charging/measure.rb', line 36

def run(model, runner, user_arguments)
  super(model, runner, user_arguments)

  # use the built-in error checking
  if !runner.validateUserArguments(arguments(model), user_arguments)
    return false
  end

  curtailment_frac = runner.getDoubleArgumentValue('curtailment_frac', user_arguments)
  if curtailment_frac < 0 || curtailment_frac > 1
    runner.registerError('Curtailment fraction must be between 0 and 1')
    return false
  end

  # Initialize handles
  bldg_handle = nil
  bldg_handle = model.getBuilding.handle

  # Create object for end-of-year-date.
  eoy = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(12), 31, 2006)

  # Find the EV charger object, which will be passed in to the actuator.
  ext_fuel_equip = model.getFacility.exteriorFuelEquipments
  ext_equip_sched = []
  ext_fuel_equip.each do |equip|
    if equip.exteriorFuelEquipmentDefinition.name.to_s.include?('EV') || equip.exteriorFuelEquipmentDefinition.name.to_s.include?('vehicle') || equip.exteriorFuelEquipmentDefinition.name.to_s.include?('Vehicle')
      ext_equip_sched << equip.schedule
    end
  end
  ev_sched = ext_equip_sched [0]
  if ext_equip_sched.empty?
    runner.registerError('No EV charging schedule found. Schedule must include the string "EV", "vehicle", or "Vehicle" in its name.')
    return false
  end
  if ext_equip_sched.length > 1
    runner.registerError('More than one EV charging schedule found. Currently, this measure is capable of handling only one EV charging schedule.')
    return false
  end
  ev_sched_copy = ext_equip_sched [0].clone
  ev_sched_copy.setName('EV Charging Power Draw Copy')

  equip_sched_day = ext_equip_sched [0].to_ScheduleRuleset.get.defaultDaySchedule
  puts(ext_equip_sched [0].to_ScheduleRuleset.get)
  equip_sched_day_default_val = equip_sched_day.values
  puts equip_sched_day_default_val

  # Create a new schedule, scaled by the curtailment fraction
  ev_curtailed_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  ev_curtailed_sch.setName('EV Charging Curtailed Schedule')

  # Default day (use this for weekdays)
  ev_curtailed_sch.defaultDaySchedule.setName('EV Charging Curtailed Default')
  # Loop through all the values and add to schedule
  equip_sched_day_default_val.each_with_index do |value, i|
    time = OpenStudio::Time.new(0, 0, (i + 1) * 15, 0)
    ev_curtailed_sch.defaultDaySchedule.addValue(time, (value * curtailment_frac))
  end
  # Create needed sensors.
  site_solar_rad_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Direct Solar Radiation Rate per Area')
  site_solar_rad_sensor.setName('Direct_Solar_Radiation_Rate')

  ev_load_curtailed_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
  ev_load_curtailed_sensor.setKeyName('EV Charging Curtailed Schedule')
  ev_load_curtailed_sensor.setName('EV_Load_Curtailed_Sensor')

  ev_load_sched_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
  ev_load_sched_sensor.setKeyName('EV Charging Power Draw Copy')
  ev_load_sched_sensor.setName('EV_reg_sched_power')

  # Make the needed actuator.
  ev_schedule_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(ev_sched, 'Schedule:Year', 'Schedule Value')
  ev_schedule_actuator.setName('EVChargeSchedule_Actuator')
  ev_schedule_actuator_handle = ev_schedule_actuator.handle

  # Make the global variables.
  solar_trend = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'solartrend')
  arg_dr_state = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'argdrstate')
  curtailed_energy_sum = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'curtailed_energy_sum')
  curtail_frac = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'curtail_frac')
  ev_charge_energy = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'EV_charge_energy')
  ev_sched_charge_energy = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'EV_sched_charge_energy')

  # Make the trend variables.
  solar_rad_trend = OpenStudio::Model::EnergyManagementSystemTrendVariable.new(model, site_solar_rad_sensor)
  solar_rad_trend.setName('Solar_Radiation_Trend')
  solar_rad_trend.setEMSVariableName(site_solar_rad_sensor.name.to_s)
  solar_rad_trend.setNumberOfTimestepsToBeLogged(144)

  dr_state_trend = OpenStudio::Model::EnergyManagementSystemTrendVariable.new(model, arg_dr_state)
  dr_state_trend.setName('DR_State_Trend')
  dr_state_trend.setEMSVariableName(arg_dr_state.name.to_s)

  solar_rad_slope_trend = OpenStudio::Model::EnergyManagementSystemTrendVariable.new(model, solar_trend)
  solar_rad_slope_trend.setName('Solar_Radiation_Slope_Trend')
  solar_rad_slope_trend.setEMSVariableName(solar_trend.name.to_s)

  # Make the needed output variables. Below is using the model and an acutator, or the model and a global variable, which is allowable.
  ev_charge_eff_sched = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, ev_schedule_actuator)
  ev_charge_eff_sched.setName('EV Charging Effective Schedule')
  ev_charge_eff_sched.setEMSVariableName(ev_schedule_actuator.name.to_s)

  ev_sched_load = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, ev_load_sched_sensor)
  ev_sched_load.setName('EV Charging Regularly Sched Load')
  ev_sched_load.setEMSVariableName(ev_load_sched_sensor.name.to_s)

  dr_state = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, arg_dr_state)
  dr_state.setName('dr_state')
  dr_state.setEMSVariableName(arg_dr_state.name.to_s)

  solar_rad_slope = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, solar_trend)
  solar_rad_slope.setName('solar_radiation_slope_trend')
  solar_rad_slope.setEMSVariableName(solar_trend.name.to_s)

  curtailed_energy_total = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, curtailed_energy_sum)
  curtailed_energy_total.setName('EV Curtailed Energy Total')
  curtailed_energy_total.setEMSVariableName(curtailed_energy_sum.name.to_s)

  ev_charge_energy_output = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, ev_charge_energy)
  ev_charge_energy_output.setName('EV Charging Energy')
  ev_charge_energy_output.setEMSVariableName(ev_charge_energy.name.to_s)

  ev_sched_charge_energy_output = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, ev_sched_charge_energy)
  ev_sched_charge_energy_output.setName('Scheduled EV Charging Energy')
  ev_sched_charge_energy_output.setEMSVariableName(ev_sched_charge_energy.name.to_s)

  # Make sub-routines.
  # Reset the counter of curtailed energy.
  reset_curtailment_subroutine = OpenStudio::Model::EnergyManagementSystemSubroutine.new(model)
  reset_curtailment_subroutine.setName('Initalize_EV_Curtailment')
  reset_curtailment_subroutine.addLine('SET curtailed_energy_sum=0') # Reset this counter to zero.

  # Set the actuator for curtailment.
  curtail_ev_sched_subroutine = OpenStudio::Model::EnergyManagementSystemSubroutine.new(model)
  curtail_ev_sched_subroutine.setName('Set_EV_Sched_Curtailed_Value')
  curtail_ev_sched_subroutine.addLine('SET EVChargeSchedule_Actuator  =EV_Load_Curtailed_Sensor')
  curtail_ev_sched_subroutine.addLine('SET argdrstate = 1')
  curtail_ev_sched_subroutine.addLine('SET curtailed_energy_sum=curtailed_energy_sum + (EV_reg_sched_power-EV_Load_Curtailed_Sensor)')
  curtail_ev_sched_subroutine.addLine('SET ev_charge_energy=ev_charge_energy + EV_Load_Curtailed_Sensor*SystemTimeStep')
  curtail_ev_sched_subroutine.addLine('SET ev_sched_charge_energy=ev_sched_charge_energy + EV_reg_sched_power*SystemTimeStep')

  # Make program.
  execute_ev_curtailment_prgrm = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
  execute_ev_curtailment_prgrm.setName('Execute_EV_Curtailment')
  execute_ev_curtailment_prgrm.addLine('IF (CurrentTime==1)')
  execute_ev_curtailment_prgrm.addLine('SET ev_charge_energy=0')
  execute_ev_curtailment_prgrm.addLine('SET ev_sched_charge_energy=0')
  execute_ev_curtailment_prgrm.addLine('ELSEIF (solartrend <0 ) && (CurrentTime<=16) && (CurrentTime>=6) &&(DayofWeek<>1) && (DayofWeek<>7)')
  # execute_ev_curtailment_prgrm.addLine('RUN Initalize_EV_Curtailment')
  execute_ev_curtailment_prgrm.addLine('RUN Set_EV_Sched_Curtailed_Value')
  execute_ev_curtailment_prgrm.addLine('ELSEIF (CurrentTime>16) && (CurrentTime<18.50) && (DayofWeek<>1) && (DayofWeek<>7) ')
  execute_ev_curtailment_prgrm.addLine('SET EVChargeSchedule_Actuator =EV_reg_sched_power + curtailed_energy_sum/((18.75-CurrentTime-SystemTimeStep)/SystemTimeStep)')
  execute_ev_curtailment_prgrm.addLine('SET curtailed_energy_sum =curtailed_energy_sum- curtailed_energy_sum/((18.75-CurrentTime-SystemTimeStep)/SystemTimeStep)')
  execute_ev_curtailment_prgrm.addLine('SET ev_charge_energy =ev_charge_energy + (EV_reg_sched_power + curtailed_energy_sum/((18.75-CurrentTime-SystemTimeStep)/SystemTimeStep))*SystemTimeStep')
  execute_ev_curtailment_prgrm.addLine('SET ev_sched_charge_energy =ev_sched_charge_energy + EV_reg_sched_power*SystemTimeStep')
  execute_ev_curtailment_prgrm.addLine('SET argdrstate = 0')
  execute_ev_curtailment_prgrm.addLine('ELSEIF (CurrentTime==18.50)')
  execute_ev_curtailment_prgrm.addLine('SET curtailed_energy_sum=0')
  execute_ev_curtailment_prgrm.addLine('SET ev_charge_energy =ev_charge_energy + EV_reg_sched_power*SystemTimeStep')
  execute_ev_curtailment_prgrm.addLine('SET ev_sched_charge_energy =ev_sched_charge_energy + EV_reg_sched_power*SystemTimeStep')
  execute_ev_curtailment_prgrm.addLine('ELSEIF (solartrend>=0) && (CurrentTime<18.75)')
  execute_ev_curtailment_prgrm.addLine('SET EVChargeSchedule_Actuator =EV_reg_sched_power + curtailed_energy_sum/((18.75-CurrentTime-SystemTimeStep)/SystemTimeStep)') # Compensate for curtailment
  execute_ev_curtailment_prgrm.addLine('SET curtailed_energy_sum =curtailed_energy_sum- curtailed_energy_sum/((18.75-CurrentTime-SystemTimeStep)/SystemTimeStep)') # Update curtailment counter
  execute_ev_curtailment_prgrm.addLine('SET ev_charge_energy =ev_charge_energy + (EV_reg_sched_power + curtailed_energy_sum/((18.75-CurrentTime-SystemTimeStep)/SystemTimeStep))*SystemTimeStep')
  execute_ev_curtailment_prgrm.addLine('SET ev_sched_charge_energy =ev_sched_charge_energy + EV_reg_sched_power*SystemTimeStep')
  execute_ev_curtailment_prgrm.addLine('SET argdrstate = 0')
  execute_ev_curtailment_prgrm.addLine('ELSE')
  execute_ev_curtailment_prgrm.addLine('SET EVChargeSchedule_Actuator  = Null')
  execute_ev_curtailment_prgrm.addLine('SET ev_sched_charge_energy =ev_sched_charge_energy + EV_reg_sched_power*SystemTimeStep')
  execute_ev_curtailment_prgrm.addLine('SET ev_charge_energy =ev_charge_energy + EV_reg_sched_power*SystemTimeStep')
  execute_ev_curtailment_prgrm.addLine('SET argdrstate = 0')
  execute_ev_curtailment_prgrm.addLine('ENDIF')

  solar_trend_prgrm = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
  solar_trend_prgrm.setName('Set_Solar_Trend')
  solar_trend_prgrm.addLine('SET solartrend  =@TrendDirection Solar_Radiation_Trend 2')

  set_demand_threshold_prgrm = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
  set_demand_threshold_prgrm.setName('Set_Demand_Threshold')
  set_demand_threshold_prgrm.addLine('SET curtailed_energy_sum =0')
  set_demand_threshold_prgrm.addLine('SET ev_charge_energy =0')
  set_demand_threshold_prgrm.addLine('SET ev_sched_charge_energy =0')

  # Add program calling managers.
  ev_curtailment_main_calling_mgr = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
  ev_curtailment_main_calling_mgr.setName('EV_Curtailment')
  ev_curtailment_main_calling_mgr.setCallingPoint('BeginTimestepBeforePredictor')

  ev_curtailment_main_calling_mgr.setProgram(solar_trend_prgrm, 0)
  ev_curtailment_main_calling_mgr.setProgram(execute_ev_curtailment_prgrm, 1)

  set_input_vars_mgr = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
  set_input_vars_mgr.setName('Set_input_vars')
  set_input_vars_mgr.setCallingPoint('BeginNewEnvironment')
  set_input_vars_mgr.setProgram(set_demand_threshold_prgrm, 0)

  # Add output variables
  outputVariable = OpenStudio::Model::OutputVariable.new('dr_state', model)
  outputVariable.setReportingFrequency('timestep')

  outputVariable = OpenStudio::Model::OutputVariable.new('Schedule Value', model)
  outputVariable.setReportingFrequency('timestep')
  outputVariable.setKeyValue('EV Charging Power Draw')

  outputVariable = OpenStudio::Model::OutputVariable.new('Schedule Value', model)
  outputVariable.setReportingFrequency('timestep')
  outputVariable.setKeyValue('EV Charging Power Draw Copy')

  outputVariable = OpenStudio::Model::OutputVariable.new('EV Curtailed Energy Total', model)
  outputVariable.setReportingFrequency('timestep')

  outputVariable = OpenStudio::Model::OutputVariable.new('Scheduled EV Charging Energy', model)
  outputVariable.setReportingFrequency('timestep')

  outputVariable = OpenStudio::Model::OutputVariable.new('EV Charging Energy', model)
  outputVariable.setReportingFrequency('timestep')

  return true
end