Class: AddElectricVehicleChargingLoad
- Inherits:
-
OpenStudio::Measure::ModelMeasure
- Object
- OpenStudio::Measure::ModelMeasure
- AddElectricVehicleChargingLoad
- Defined in:
- lib/measures/AddElectricVehicleChargingLoad/measure.rb
Overview
******************************************************************************* OpenStudio®, Copyright © Alliance for Sustainable Energy, LLC. See also openstudio.net/license *******************************************************************************
Defined Under Namespace
Classes: EVcharger, ElectricVehicle
Instance Method Summary collapse
-
#arguments(model) ⇒ Object
define the arguments that the user will input.
- #create_ev_sch(model, ev_chargers, max_charging_power, charge_on_sat, charge_on_sun) ⇒ Object
- #create_ev_sch_for_home(model, ev_chargers, max_charging_power, num_evs, start_charge_time, avg_charge_hours, charge_on_sat, charge_on_sun) ⇒ Object
-
#create_ev_sch_for_workplace(model, ev_chargers, max_charging_power, num_evs, avg_arrival_time, avg_leave_time, avg_charge_hours, charge_on_sat, charge_on_sun) ⇒ Object
********************************************* for workplace waitlist is only applicable to workplace.
-
#description ⇒ Object
human readable description.
-
#modeler_description ⇒ Object
human readable description of modeling approach.
-
#name ⇒ Object
human readable name.
-
#run(model, runner, user_arguments) ⇒ Object
define what happens when the measure is run.
Instance Method Details
#arguments(model) ⇒ Object
define the arguments that the user will input
26 27 28 29 30 31 32 33 34 35 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 |
# File 'lib/measures/AddElectricVehicleChargingLoad/measure.rb', line 26 def arguments(model) args = OpenStudio::Measure::OSArgumentVector.new # building use type, choice argument, 'home' or 'workplace' bldg_use_type_chs = OpenStudio::StringVector.new bldg_use_type_chs << 'home' bldg_use_type_chs << 'workplace' bldg_use_type = OpenStudio::Measure::OSArgument.makeChoiceArgument('bldg_use_type', bldg_use_type_chs, true) bldg_use_type.setDisplayName('Building Use Type') bldg_use_type.setDefaultValue('home') args << bldg_use_type # Number of EV chargers num_ev_chargers = OpenStudio::Measure::OSArgument.makeIntegerArgument('num_ev_chargers', true) num_ev_chargers.setDisplayName('Number of EV Chargers') num_ev_chargers.setDefaultValue(1) args << num_ev_chargers # Number of Electric Vehicles num_evs = OpenStudio::Measure::OSArgument.makeIntegerArgument('num_evs', true) num_evs.setDisplayName('Number of Electric Vehicles') num_evs.setDefaultValue(1) args << num_evs # EV charger level, choice argument charger_level_chs = OpenStudio::StringVector.new charger_level_chs << 'Level 1' charger_level_chs << 'Level 2' charger_level_chs << 'DC charger' charger_level = OpenStudio::Measure::OSArgument.makeChoiceArgument('charger_level', charger_level_chs, true) charger_level.setDisplayName('EV Charger Level') charger_level.setDefaultValue('Level 2') args << charger_level # average arrival time, applicable for workplace only avg_arrival_time = OpenStudio::Measure::OSArgument.makeStringArgument('avg_arrival_time', false) avg_arrival_time.setDisplayName('Average arrival time, applicable for workplace only') avg_arrival_time.setDefaultValue('08:30') args << avg_arrival_time # average arrival time, applicable for workplace only avg_leave_time = OpenStudio::Measure::OSArgument.makeStringArgument('avg_leave_time', false) avg_leave_time.setDisplayName('Average leave time, applicable for workplace only') avg_leave_time.setDefaultValue('17:30') args << avg_leave_time # start charging time, required for home start_charge_time = OpenStudio::Measure::OSArgument.makeStringArgument('start_charge_time', false) start_charge_time.setDisplayName('Start charging time, required for home only') start_charge_time.setDefaultValue('21:00') args << start_charge_time # average needed hours to charge to full. This should vary with the charger level. avg_charge_hours = OpenStudio::Measure::OSArgument.makeDoubleArgument('avg_charge_hours', true) avg_charge_hours.setDisplayName('Average needed hours to charge to full (should vary with charger level)') avg_charge_hours.setDefaultValue(4) args << avg_charge_hours # if EVs are charged on Saturday charge_on_sat = OpenStudio::Measure::OSArgument.makeBoolArgument('charge_on_sat', false) charge_on_sat.setDisplayName('EVs are charged on Saturday') charge_on_sat.setDefaultValue(true) args << charge_on_sat # if EVs are charged on Sunday charge_on_sun = OpenStudio::Measure::OSArgument.makeBoolArgument('charge_on_sun', false) charge_on_sun.setDisplayName('EVs are charged on Sunday') charge_on_sun.setDefaultValue(true) args << charge_on_sun return args end |
#create_ev_sch(model, ev_chargers, max_charging_power, charge_on_sat, charge_on_sun) ⇒ Object
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 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/measures/AddElectricVehicleChargingLoad/measure.rb', line 228 def create_ev_sch(model, ev_chargers, max_charging_power, charge_on_sat, charge_on_sun) # create the schedule # Creating a schedule:ruleset ev_sch = OpenStudio::Model::ScheduleRuleset.new(model) ev_sch.setName('EV Charging Power Draw') ev_sch.defaultDaySchedule.setName('EV Charging Default') day_start_time = Time.strptime("00:00", '%H:%M') # initial EV load depends on if each charger charges overnight ev_load = 0 # kW ev_chargers.each do |ev_charger| puts "#{ev_charger.name}" puts "ev_charger.occupied_until_time - day_start_time - 24*60*60)/60: #{(ev_charger.occupied_until_time - day_start_time - 24*60*60)/60}" puts "ev_charger.occupied_start_time - day_start_time)/60: #{(ev_charger.occupied_start_time - day_start_time)/60}" puts "ev_charger.occupied_start_time: #{ev_charger.occupied_start_time}" puts "ev_charger.occupied_until_time: #{ev_charger.occupied_until_time}" puts "ev_charger.occupied_start_time.day: #{ev_charger.occupied_start_time.day}" puts "ev_charger.occupied_until_time.day: #{ev_charger.occupied_until_time.day}" ev_load += ev_charger.charging_power if ev_charger.occupied_start_time.day != ev_charger.occupied_until_time.day end ev_load_new = ev_load # kW puts "******Initial******" puts "ev_load: #{ev_load}" puts "ev_load_new: #{ev_load_new}" # iterate through 1440 minutes in one day for min in 1..24*60 ev_chargers.each do |ev_charger| # charging on the same day if ev_charger.occupied_start_time.day == ev_charger.occupied_until_time.day if ((ev_charger.occupied_start_time - day_start_time)/60).to_i == min if ev_load_new == ev_load ev_load_new = ev_load + ev_charger.charging_power else # if more than one chargers change status at this time point ev_load_new += ev_charger.charging_power end elsif ((ev_charger.occupied_until_time - day_start_time)/60).to_i == min if ev_load_new == ev_load ev_load_new = ev_load - ev_charger.charging_power else # if more than one chargers change status at this time point ev_load_new -= ev_charger.charging_power end end else # charging overnight if ((ev_charger.occupied_until_time - day_start_time - 24*60*60)/60).to_i == min if ev_load_new == ev_load ev_load_new = ev_load - ev_charger.charging_power else # if more than one chargers change status at this time point ev_load_new -= ev_charger.charging_power end elsif ((ev_charger.occupied_start_time - day_start_time)/60).to_i == min if ev_load_new == ev_load ev_load_new = ev_load + ev_charger.charging_power else # if more than one chargers change status at this time point ev_load_new += ev_charger.charging_power end end end end # if any change, add to schedule if ev_load_new != ev_load || min == 24*60 puts "****after****" puts "ev_load_new: #{ev_load_new}" puts "ev_load: #{ev_load}" time = OpenStudio::Time.new(0, 0, min) # OpenStudio::Time.new(day,hr of day, minute of hr, seconds of hr?) ev_sch.defaultDaySchedule.addValue(time, ev_load/max_charging_power) ev_load = ev_load_new end end if charge_on_sat ev_sch_sat = OpenStudio::Model::ScheduleRule.new(ev_sch, ev_sch.defaultDaySchedule) ev_sch_sat.setName('EV Charging Power Saturday') ev_sch_sat.setApplySaturday(true) else ev_sch_sat_rule = OpenStudio::Model::ScheduleRule.new(ev_sch) ev_sch_sat_rule.setName('EV Charging Power Saturday') ev_sch_sat_rule.setApplySaturday(true) ev_sch_sat = ev_sch_sat_rule.daySchedule ev_sch_sat.setName('EV Charging Saturday') ev_sch_sat.addValue(OpenStudio::Time.new(0,24,0), 0) end if charge_on_sun ev_sch_sun = OpenStudio::Model::ScheduleRule.new(ev_sch, ev_sch.defaultDaySchedule) ev_sch_sun.setName('EV Charging Power Sunday') ev_sch_sun.setApplySunday(true) else ev_sch_sun_rule = OpenStudio::Model::ScheduleRule.new(ev_sch) ev_sch_sun_rule.setName('EV Charging Power Sunday') ev_sch_sun_rule.setApplySunday(true) ev_sch_sun = ev_sch_sun_rule.daySchedule ev_sch_sun.setName('EV Charging Sunday') ev_sch_sun.addValue(OpenStudio::Time.new(0,24,0), 0) end return ev_sch end |
#create_ev_sch_for_home(model, ev_chargers, max_charging_power, num_evs, start_charge_time, avg_charge_hours, charge_on_sat, charge_on_sun) ⇒ Object
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'lib/measures/AddElectricVehicleChargingLoad/measure.rb', line 413 def create_ev_sch_for_home(model, ev_chargers, max_charging_power, num_evs, start_charge_time, avg_charge_hours, charge_on_sat, charge_on_sun) ev_list = [] for j in 1..num_evs ev = ElectricVehicle.new("ev_#{j.to_s}") ev.needed_charge_hours = avg_charge_hours + rand(-60...60) / 60.0 # +- 1 hour ev_list << ev end # for homes, EV charging could go overnight, so iterate 48 hours for hour in 0..47 if hour <= 23 current_time = Time.strptime("#{hour}:00", '%H:%M') + 3600 # %H: 00..23, 23 should represent the period 23:00-24:00, so add 1 hour to be the check point else current_time = Time.strptime("#{hour-24}:00", '%H:%M') + 24*60*60 + 3600 # %H: the second day (for overnight). still 00..23, 23 should represent the period 23:00-24:00, so add 1 hour to be the check point end next if start_charge_time > current_time ev_chargers.each do |ev_charger| if ev_charger.occupied if ev_charger.connected_ev.class.to_s != 'AddElectricVehicleChargingLoad::ElectricVehicle' runner.registerError("EV charger #{ev_charger.name.to_s} shows occupied, but no EV is connected.") return false end # Time addition uses seconds, so needs to multiple 3600 if ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 <= current_time ev_charger.connected_ev.end_charge_time = ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 ev_charger.occupied_until_time = ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 ev_charger.occupied = false ev_charger.connected_ev.has_been_charged = true ev_charger.connected_ev.connected_to_charger = false ev_charger.connected_ev = nil end end # continue to check if charger not occupied, then connect to an EV unless ev_charger.occupied # no need of waitlist, just connect to whichever EV that hasn't been charged next_ev_to_charge = nil ev_list.each do |this_ev| # skip this EV if it is being charged or is being charged next if this_ev.has_been_charged next if this_ev.connected_to_charger next_ev_to_charge = this_ev break end # skip if no EV is on the wait list next if next_ev_to_charge.nil? if ev_charger.charged_ev_list.empty? ev_charger.occupied_start_time = start_charge_time next_ev_to_charge.start_charge_time = start_charge_time else next_ev_to_charge.start_charge_time = ev_charger.occupied_until_time end ev_charger.occupied = true next_ev_to_charge.connected_to_charger = true ev_charger.connected_ev = next_ev_to_charge ev_charger.charged_ev_list << next_ev_to_charge end end end ev_sch = create_ev_sch(model, ev_chargers, max_charging_power, charge_on_sat, charge_on_sun) return ev_sch end |
#create_ev_sch_for_workplace(model, ev_chargers, max_charging_power, num_evs, avg_arrival_time, avg_leave_time, avg_charge_hours, charge_on_sat, charge_on_sun) ⇒ Object
********************************************* for workplace waitlist is only applicable to workplace. For homes, charging is scheduled with start_charge_time create all EV chargers
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'lib/measures/AddElectricVehicleChargingLoad/measure.rb', line 330 def create_ev_sch_for_workplace(model, ev_chargers, max_charging_power, num_evs, avg_arrival_time, avg_leave_time, avg_charge_hours, charge_on_sat, charge_on_sun) ev_list = [] for j in 1..num_evs ev = ElectricVehicle.new("ev_#{j.to_s}") ev.arrival_time = avg_arrival_time + rand(-30...30) * 60 # TODO make sure time format is working correctly, Ruby Times "+" adopts seconds ev.leave_time = avg_leave_time + rand(-30...30) * 60 # TODO make sure time format is working correctly, Ruby Times "+" adopts seconds ev.leave_time = Time.strptime("23:00", '%H:%M') + 3600 if ev.leave_time > Time.strptime("23:00", '%H:%M') + 3600 # fix leave time at 24:00 if later than 24:00 ev.needed_charge_hours = avg_charge_hours + rand(-60...60) / 60.0 # +- 1 hour ev_list << ev end # find the earliest arrival time arrival_time_earliest = Time.strptime("23:00", '%H:%M') + 3600 # initial: 24:00 ev_list.each do |this_ev| if this_ev.arrival_time < arrival_time_earliest arrival_time_earliest = this_ev.arrival_time end end # For workplace: iterate through time, check status of each charger, if vacant, find the EV that has the earliest arrival time within uncharged EVs. # if this EV's leaving time is later than the current time, start charging until fully charged or leaving time, whichever comes first # when no EV is found any more, charging on this day ends, conclude the charging profile # 23 represent 23:00-24:00, corresponding to E+ schedule Until: 24:00 for hour in 0..23 current_time = Time.strptime("#{hour}:00", '%H:%M') + 3600 # %H: 00..23, 23 should represent the period 23:00-24:00, so add 1 hour to be the check point next if arrival_time_earliest > current_time ev_chargers.each do |ev_charger| if ev_charger.occupied if ev_charger.connected_ev.class.to_s != 'AddElectricVehicleChargingLoad::ElectricVehicle' runner.registerError("EV charger #{ev_charger.name.to_s} shows occupied, but no EV is connected.") return false end # disconnect EV if charged to full or till leave time. Only check if expected end time or leave is earlier than current time, otherwise check in next iteration. # Time addition uses seconds, so needs to multiple 3600 if ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 <= current_time || ev_charger.connected_ev.leave_time <= current_time if ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 > ev_charger.connected_ev.leave_time ev_charger.occupied_until_time = ev_charger.connected_ev.leave_time ev_charger.connected_ev.end_charge_time = ev_charger.connected_ev.leave_time else ev_charger.occupied_until_time = ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 ev_charger.connected_ev.end_charge_time = ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 end ev_charger.occupied = false ev_charger.connected_ev.has_been_charged = true ev_charger.connected_ev.connected_to_charger = false ev_charger.connected_ev = nil end end # continue to check if charger not occupied, then connect to an EV unless ev_charger.occupied next_ev_to_charge = nil wait_list_time_earliest = Time.strptime("23:00", '%H:%M') + 3600 # initial: 24:00 ev_list.each do |this_ev| # skip this EV if it is being charged or is being charged or already left next if this_ev.has_been_charged next if this_ev.connected_to_charger next if this_ev.leave_time <= current_time # get the uncharged, earliest arrival EV (so front in wait list) if this_ev.arrival_time < wait_list_time_earliest wait_list_time_earliest = this_ev.arrival_time next_ev_to_charge = this_ev end end # skip if no EV is on the wait list next if next_ev_to_charge.nil? if ev_charger.charged_ev_list.empty? ev_charger.occupied_start_time = wait_list_time_earliest next_ev_to_charge.start_charge_time = wait_list_time_earliest else next_ev_to_charge.start_charge_time = ev_charger.occupied_until_time end ev_charger.occupied = true next_ev_to_charge.connected_to_charger = true ev_charger.connected_ev = next_ev_to_charge ev_charger.charged_ev_list << next_ev_to_charge end end end ev_sch = create_ev_sch(model, ev_chargers, max_charging_power, charge_on_sat, charge_on_sun) return ev_sch end |
#description ⇒ Object
human readable description
16 17 18 |
# File 'lib/measures/AddElectricVehicleChargingLoad/measure.rb', line 16 def description return 'This measure adds electric vehicle charging load to the building. The user can specify the level of charger, number of chargers, number of EVs charging daily, start time, average number of hours to fully charge. ' end |
#modeler_description ⇒ Object
human readable description of modeling approach
21 22 23 |
# File 'lib/measures/AddElectricVehicleChargingLoad/measure.rb', line 21 def modeler_description return 'This measure will add electric vehicle charging load as exterior electric equipment. The user inputs of level of chargers, number of chargers, and number of EVs charging daily will be used to determine the load level, and the inputs of start time and average number of hours to fully charge will be used to determine load schedule.' end |
#name ⇒ Object
human readable name
10 11 12 13 |
# File 'lib/measures/AddElectricVehicleChargingLoad/measure.rb', line 10 def name # Measure name should be the title case of the class name. return 'AddElectricVehicleChargingLoad' end |
#run(model, runner, user_arguments) ⇒ Object
define what happens when the measure is run
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 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 |
# File 'lib/measures/AddElectricVehicleChargingLoad/measure.rb', line 147 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 # assign the user inputs to variables bldg_use_type = runner.getStringArgumentValue('bldg_use_type', user_arguments) num_ev_chargers = runner.getIntegerArgumentValue('num_ev_chargers', user_arguments) num_evs = runner.getIntegerArgumentValue('num_evs', user_arguments) charger_level = runner.getStringArgumentValue('charger_level', user_arguments) avg_arrival_time = runner.getStringArgumentValue('avg_arrival_time', user_arguments) avg_leave_time = runner.getStringArgumentValue('avg_leave_time', user_arguments) start_charge_time = runner.getStringArgumentValue('start_charge_time', user_arguments) avg_charge_hours = runner.getDoubleArgumentValue('avg_charge_hours', user_arguments) charge_on_sat = runner.getBoolArgumentValue('charge_on_sat', user_arguments) charge_on_sun = runner.getBoolArgumentValue('charge_on_sun', user_arguments) puts "num_ev_chargers: #{num_ev_chargers.inspect}" puts "avg_arrival_time: #{avg_arrival_time.inspect}" puts "avg_charge_hours: #{avg_charge_hours.inspect}" puts "charge_on_sat: #{charge_on_sat.inspect}" puts "charge_on_sun: #{charge_on_sun.inspect}" if bldg_use_type == 'workplace' # check avg_arrival_time and avg_leave_time should be in correct Time format begin avg_arrival_time = Time.strptime(avg_arrival_time, '%H:%M') avg_leave_time = Time.strptime(avg_leave_time, '%H:%M') rescue ArgumentError runner.registerError('For workplaces, average arrival and leave time are required, and should be in format of %H:%M, e.g., 16:00.') return false end # check avg_leave_time should be later than avg_arrival_time if avg_leave_time <= avg_arrival_time runner.registerError('For workplaces, average arrival time should be earlier than average leave time.') return false end elsif bldg_use_type == 'home' # check start_charge_time should be in correct Time format begin start_charge_time = Time.strptime(start_charge_time, '%H:%M') rescue ArgumentError runner.registerError('For homes, start charging time is required, and should be in format of %H:%M, e.g., 16:00.') return false end else runner.registerError("Wrong building use type, available options: 'workplace' and 'home'.") return false end # report initial condition of model runner.registerInitialCondition("Starting to add electric vehicle to the building.") # Initialize the EV chargers ev_chargers = [] max_charging_power = 0 # initial for i in 1..num_ev_chargers charger = EVcharger.new("EVcharger_#{i.to_s}") charger.level = charger_level # charging power references: # https://calevip.org/electric-vehicle-charging-101 # https://rmi.org/electric-vehicle-charging-for-dummies/ # https://freewiretech.com/difference-between-ev-charging-levels/#:~:text=Level%201%20Charging&text=L1%20chargers%20plug%20directly%20into,is%20sufficient%20for%20many%20commuters. case charger_level when 'Level 1' charger.charging_power = 1.5 when 'Level 2' charger.charging_power = 7.0 when 'DC charger' charger.charging_power = 50.0 else runner.registerError("Wrong EV charging level, available options: 'Level 1', 'Level 2', 'DC charger'.") return false end max_charging_power += charger.charging_power ev_chargers << charger end def create_ev_sch(model, ev_chargers, max_charging_power, charge_on_sat, charge_on_sun) # create the schedule # Creating a schedule:ruleset ev_sch = OpenStudio::Model::ScheduleRuleset.new(model) ev_sch.setName('EV Charging Power Draw') ev_sch.defaultDaySchedule.setName('EV Charging Default') day_start_time = Time.strptime("00:00", '%H:%M') # initial EV load depends on if each charger charges overnight ev_load = 0 # kW ev_chargers.each do |ev_charger| puts "#{ev_charger.name}" puts "ev_charger.occupied_until_time - day_start_time - 24*60*60)/60: #{(ev_charger.occupied_until_time - day_start_time - 24*60*60)/60}" puts "ev_charger.occupied_start_time - day_start_time)/60: #{(ev_charger.occupied_start_time - day_start_time)/60}" puts "ev_charger.occupied_start_time: #{ev_charger.occupied_start_time}" puts "ev_charger.occupied_until_time: #{ev_charger.occupied_until_time}" puts "ev_charger.occupied_start_time.day: #{ev_charger.occupied_start_time.day}" puts "ev_charger.occupied_until_time.day: #{ev_charger.occupied_until_time.day}" ev_load += ev_charger.charging_power if ev_charger.occupied_start_time.day != ev_charger.occupied_until_time.day end ev_load_new = ev_load # kW puts "******Initial******" puts "ev_load: #{ev_load}" puts "ev_load_new: #{ev_load_new}" # iterate through 1440 minutes in one day for min in 1..24*60 ev_chargers.each do |ev_charger| # charging on the same day if ev_charger.occupied_start_time.day == ev_charger.occupied_until_time.day if ((ev_charger.occupied_start_time - day_start_time)/60).to_i == min if ev_load_new == ev_load ev_load_new = ev_load + ev_charger.charging_power else # if more than one chargers change status at this time point ev_load_new += ev_charger.charging_power end elsif ((ev_charger.occupied_until_time - day_start_time)/60).to_i == min if ev_load_new == ev_load ev_load_new = ev_load - ev_charger.charging_power else # if more than one chargers change status at this time point ev_load_new -= ev_charger.charging_power end end else # charging overnight if ((ev_charger.occupied_until_time - day_start_time - 24*60*60)/60).to_i == min if ev_load_new == ev_load ev_load_new = ev_load - ev_charger.charging_power else # if more than one chargers change status at this time point ev_load_new -= ev_charger.charging_power end elsif ((ev_charger.occupied_start_time - day_start_time)/60).to_i == min if ev_load_new == ev_load ev_load_new = ev_load + ev_charger.charging_power else # if more than one chargers change status at this time point ev_load_new += ev_charger.charging_power end end end end # if any change, add to schedule if ev_load_new != ev_load || min == 24*60 puts "****after****" puts "ev_load_new: #{ev_load_new}" puts "ev_load: #{ev_load}" time = OpenStudio::Time.new(0, 0, min) # OpenStudio::Time.new(day,hr of day, minute of hr, seconds of hr?) ev_sch.defaultDaySchedule.addValue(time, ev_load/max_charging_power) ev_load = ev_load_new end end if charge_on_sat ev_sch_sat = OpenStudio::Model::ScheduleRule.new(ev_sch, ev_sch.defaultDaySchedule) ev_sch_sat.setName('EV Charging Power Saturday') ev_sch_sat.setApplySaturday(true) else ev_sch_sat_rule = OpenStudio::Model::ScheduleRule.new(ev_sch) ev_sch_sat_rule.setName('EV Charging Power Saturday') ev_sch_sat_rule.setApplySaturday(true) ev_sch_sat = ev_sch_sat_rule.daySchedule ev_sch_sat.setName('EV Charging Saturday') ev_sch_sat.addValue(OpenStudio::Time.new(0,24,0), 0) end if charge_on_sun ev_sch_sun = OpenStudio::Model::ScheduleRule.new(ev_sch, ev_sch.defaultDaySchedule) ev_sch_sun.setName('EV Charging Power Sunday') ev_sch_sun.setApplySunday(true) else ev_sch_sun_rule = OpenStudio::Model::ScheduleRule.new(ev_sch) ev_sch_sun_rule.setName('EV Charging Power Sunday') ev_sch_sun_rule.setApplySunday(true) ev_sch_sun = ev_sch_sun_rule.daySchedule ev_sch_sun.setName('EV Charging Sunday') ev_sch_sun.addValue(OpenStudio::Time.new(0,24,0), 0) end return ev_sch end # ********************************************* # for workplace # waitlist is only applicable to workplace. For homes, charging is scheduled with start_charge_time # create all EV chargers def create_ev_sch_for_workplace(model, ev_chargers, max_charging_power, num_evs, avg_arrival_time, avg_leave_time, avg_charge_hours, charge_on_sat, charge_on_sun) ev_list = [] for j in 1..num_evs ev = ElectricVehicle.new("ev_#{j.to_s}") ev.arrival_time = avg_arrival_time + rand(-30...30) * 60 # TODO make sure time format is working correctly, Ruby Times "+" adopts seconds ev.leave_time = avg_leave_time + rand(-30...30) * 60 # TODO make sure time format is working correctly, Ruby Times "+" adopts seconds ev.leave_time = Time.strptime("23:00", '%H:%M') + 3600 if ev.leave_time > Time.strptime("23:00", '%H:%M') + 3600 # fix leave time at 24:00 if later than 24:00 ev.needed_charge_hours = avg_charge_hours + rand(-60...60) / 60.0 # +- 1 hour ev_list << ev end # find the earliest arrival time arrival_time_earliest = Time.strptime("23:00", '%H:%M') + 3600 # initial: 24:00 ev_list.each do |this_ev| if this_ev.arrival_time < arrival_time_earliest arrival_time_earliest = this_ev.arrival_time end end # For workplace: iterate through time, check status of each charger, if vacant, find the EV that has the earliest arrival time within uncharged EVs. # if this EV's leaving time is later than the current time, start charging until fully charged or leaving time, whichever comes first # when no EV is found any more, charging on this day ends, conclude the charging profile # 23 represent 23:00-24:00, corresponding to E+ schedule Until: 24:00 for hour in 0..23 current_time = Time.strptime("#{hour}:00", '%H:%M') + 3600 # %H: 00..23, 23 should represent the period 23:00-24:00, so add 1 hour to be the check point next if arrival_time_earliest > current_time ev_chargers.each do |ev_charger| if ev_charger.occupied if ev_charger.connected_ev.class.to_s != 'AddElectricVehicleChargingLoad::ElectricVehicle' runner.registerError("EV charger #{ev_charger.name.to_s} shows occupied, but no EV is connected.") return false end # disconnect EV if charged to full or till leave time. Only check if expected end time or leave is earlier than current time, otherwise check in next iteration. # Time addition uses seconds, so needs to multiple 3600 if ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 <= current_time || ev_charger.connected_ev.leave_time <= current_time if ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 > ev_charger.connected_ev.leave_time ev_charger.occupied_until_time = ev_charger.connected_ev.leave_time ev_charger.connected_ev.end_charge_time = ev_charger.connected_ev.leave_time else ev_charger.occupied_until_time = ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 ev_charger.connected_ev.end_charge_time = ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 end ev_charger.occupied = false ev_charger.connected_ev.has_been_charged = true ev_charger.connected_ev.connected_to_charger = false ev_charger.connected_ev = nil end end # continue to check if charger not occupied, then connect to an EV unless ev_charger.occupied next_ev_to_charge = nil wait_list_time_earliest = Time.strptime("23:00", '%H:%M') + 3600 # initial: 24:00 ev_list.each do |this_ev| # skip this EV if it is being charged or is being charged or already left next if this_ev.has_been_charged next if this_ev.connected_to_charger next if this_ev.leave_time <= current_time # get the uncharged, earliest arrival EV (so front in wait list) if this_ev.arrival_time < wait_list_time_earliest wait_list_time_earliest = this_ev.arrival_time next_ev_to_charge = this_ev end end # skip if no EV is on the wait list next if next_ev_to_charge.nil? if ev_charger.charged_ev_list.empty? ev_charger.occupied_start_time = wait_list_time_earliest next_ev_to_charge.start_charge_time = wait_list_time_earliest else next_ev_to_charge.start_charge_time = ev_charger.occupied_until_time end ev_charger.occupied = true next_ev_to_charge.connected_to_charger = true ev_charger.connected_ev = next_ev_to_charge ev_charger.charged_ev_list << next_ev_to_charge end end end ev_sch = create_ev_sch(model, ev_chargers, max_charging_power, charge_on_sat, charge_on_sun) return ev_sch end def create_ev_sch_for_home(model, ev_chargers, max_charging_power, num_evs, start_charge_time, avg_charge_hours, charge_on_sat, charge_on_sun) ev_list = [] for j in 1..num_evs ev = ElectricVehicle.new("ev_#{j.to_s}") ev.needed_charge_hours = avg_charge_hours + rand(-60...60) / 60.0 # +- 1 hour ev_list << ev end # for homes, EV charging could go overnight, so iterate 48 hours for hour in 0..47 if hour <= 23 current_time = Time.strptime("#{hour}:00", '%H:%M') + 3600 # %H: 00..23, 23 should represent the period 23:00-24:00, so add 1 hour to be the check point else current_time = Time.strptime("#{hour-24}:00", '%H:%M') + 24*60*60 + 3600 # %H: the second day (for overnight). still 00..23, 23 should represent the period 23:00-24:00, so add 1 hour to be the check point end next if start_charge_time > current_time ev_chargers.each do |ev_charger| if ev_charger.occupied if ev_charger.connected_ev.class.to_s != 'AddElectricVehicleChargingLoad::ElectricVehicle' runner.registerError("EV charger #{ev_charger.name.to_s} shows occupied, but no EV is connected.") return false end # Time addition uses seconds, so needs to multiple 3600 if ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 <= current_time ev_charger.connected_ev.end_charge_time = ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 ev_charger.occupied_until_time = ev_charger.connected_ev.start_charge_time + ev_charger.connected_ev.needed_charge_hours * 3600 ev_charger.occupied = false ev_charger.connected_ev.has_been_charged = true ev_charger.connected_ev.connected_to_charger = false ev_charger.connected_ev = nil end end # continue to check if charger not occupied, then connect to an EV unless ev_charger.occupied # no need of waitlist, just connect to whichever EV that hasn't been charged next_ev_to_charge = nil ev_list.each do |this_ev| # skip this EV if it is being charged or is being charged next if this_ev.has_been_charged next if this_ev.connected_to_charger next_ev_to_charge = this_ev break end # skip if no EV is on the wait list next if next_ev_to_charge.nil? if ev_charger.charged_ev_list.empty? ev_charger.occupied_start_time = start_charge_time next_ev_to_charge.start_charge_time = start_charge_time else next_ev_to_charge.start_charge_time = ev_charger.occupied_until_time end ev_charger.occupied = true next_ev_to_charge.connected_to_charger = true ev_charger.connected_ev = next_ev_to_charge ev_charger.charged_ev_list << next_ev_to_charge end end end ev_sch = create_ev_sch(model, ev_chargers, max_charging_power, charge_on_sat, charge_on_sun) return ev_sch end # create EV load schedule (normalized) case bldg_use_type when 'workplace' ev_sch = create_ev_sch_for_workplace(model, ev_chargers, max_charging_power, num_evs, avg_arrival_time, avg_leave_time, avg_charge_hours, charge_on_sat, charge_on_sun) when 'home' ev_sch = create_ev_sch_for_home(model, ev_chargers, max_charging_power, num_evs, start_charge_time, avg_charge_hours, charge_on_sat, charge_on_sun) end # Adding an EV charger definition and instance for the regular EV charging. ev_charger_def = OpenStudio::Model::ExteriorFuelEquipmentDefinition.new(model) ev_charger_level = max_charging_power * 1000 # Converting from kW to watts ev_charger_def.setName("#{ev_charger_level} w EV Charging Definition") ev_charger_def.setDesignLevel(ev_charger_level) # creating EV charger object for the regular EV charging. ev_charger = OpenStudio::Model::ExteriorFuelEquipment.new(ev_charger_def, ev_sch) ev_charger.setName("#{ev_charger_level} w EV Charger") ev_charger.setFuelType('Electricity') ev_charger.setEndUseSubcategory('Electric Vehicles') runner.registerInfo("multiplier (kW) = #{max_charging_power}}") # echo the new space's name back to the user runner.registerInfo("EV load with #{num_ev_chargers} EV chargers and #{num_evs} EVs was added.") # report final condition of model runner.registerFinalCondition("The building completed adding EV load.") return true end |