Class: Honeybee::TemplateHVAC

Inherits:
ModelObject show all
Defined in:
lib/honeybee/hvac/template.rb,
lib/to_openstudio/hvac/template.rb

Constant Summary collapse

@@all_air_types =
['VAV', 'PVAV', 'PSZ', 'PTAC', 'ForcedAirFurnace']
@@doas_types =
['FCUwithDOASAbridged', 'WSHPwithDOASAbridged',
'VRFwithDOASAbridged', 'RadiantwithDOASAbridged']
@@heat_cool_types =
['FCU', 'WSHP', 'VRF', 'Baseboard',  'EvaporativeCooler',
'Residential', 'WindowAC', 'GasUnitHeater', 'Radiant']
@@types =
@@all_air_types + @@doas_types + @@heat_cool_types
@@vintage_mapper =
{
  DOE_Ref_Pre_1980: 'DOE Ref Pre-1980',
  DOE_Ref_1980_2004: 'DOE Ref 1980-2004',
  ASHRAE_2004: '90.1-2004',
  ASHRAE_2007: '90.1-2007',
  ASHRAE_2010: '90.1-2010',
  ASHRAE_2013: '90.1-2013',
  ASHRAE_2016: '90.1-2016',
  ASHRAE_2019: '90.1-2019'
}

Instance Attribute Summary collapse

Attributes inherited from ModelObject

#openstudio_object

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ModelObject

clean_identifier, clean_name, #find_existing_openstudio_object, #initialize, #method_missing, read_from_disk, truncate

Constructor Details

This class inherits a constructor from Honeybee::ModelObject

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Honeybee::ModelObject

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors.



36
37
38
# File 'lib/honeybee/hvac/template.rb', line 36

def errors
  @errors
end

#warningsObject (readonly)

Returns the value of attribute warnings.



36
37
38
# File 'lib/honeybee/hvac/template.rb', line 36

def warnings
  @warnings
end

Class Method Details

.all_air_typesObject



54
55
56
57
# File 'lib/honeybee/hvac/template.rb', line 54

def self.all_air_types
  # array of the All Air HVAC types
  @@all_air_types
end

.doas_typesObject



59
60
61
62
# File 'lib/honeybee/hvac/template.rb', line 59

def self.doas_types
  # array of the DOAS HVAC types
  @@doas_types
end

.heat_cool_typesObject



64
65
66
67
# File 'lib/honeybee/hvac/template.rb', line 64

def self.heat_cool_types
  # array of the system types providing heating and cooling only
  @@heat_cool_types
end

.typesObject



49
50
51
52
# File 'lib/honeybee/hvac/template.rb', line 49

def self.types
  # array of all supported template HVAC systems
  @@types
end

Instance Method Details

#allowable_typesObject



45
46
47
# File 'lib/honeybee/hvac/template.rb', line 45

def allowable_types
  @@types
end

#defaults(system_type) ⇒ Object



69
70
71
# File 'lib/honeybee/hvac/template.rb', line 69

def defaults(system_type)
  @@schema[:components][:schemas][system_type.to_sym][:properties]
end

#to_openstudio(openstudio_model, room_ids) ⇒ Object



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
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
# File 'lib/to_openstudio/hvac/template.rb', line 49

def to_openstudio(openstudio_model, room_ids)

  # only load openstudio-standards when needed
  require 'openstudio-standards'
  require_relative 'Model.hvac'
  require_relative 'radiant'

  # get the defaults for the specific system type
  hvac_defaults = defaults(@hash[:type])

  # make the standard applier
  if @hash[:vintage]
    standard_id = @@vintage_mapper[@hash[:vintage].to_sym]
  else
    standard_id = @@vintage_mapper[hvac_defaults[:vintage][:default].to_sym]
  end
  standard = Standard.build(standard_id)

  # get the default equipment type
  if @hash[:equipment_type]
    equipment_type = @hash[:equipment_type]
  else
    equipment_type = hvac_defaults[:equipment_type][:default]
  end

  # get all of the thermal zones from the Model using the room identifiers
  zones = []
  room_ids.each do |room_id|
    zone_get = openstudio_model.getThermalZoneByName(room_id)
    unless zone_get.empty?
      os_thermal_zone = zone_get.get
      zones << os_thermal_zone
    end
  end

  # create the HVAC system
  if equipment_type.to_s.include? 'Radiant'
    os_hvac = openstudio_model.add_radiant_hvac_system(standard, equipment_type.to_s, zones, @hash)
  else
    os_hvac = openstudio_model.add_cbecs_hvac_system(standard, equipment_type, zones)
  end

  # Get the air loops and assign the display name to the air loop name if it exists
  os_air_loops = []
  unless equipment_type.to_s.include? 'Furnace'
    air_loops = openstudio_model.getAirLoopHVACs
    unless air_loops.length == $air_loop_count
      $air_loop_count = air_loops.length
      zones.each do |zon|
        os_air_terminal = zon.airLoopHVACTerminal
        unless os_air_terminal.empty?
          os_air_terminal = os_air_terminal.get
          os_air_loop_opt = os_air_terminal.airLoopHVAC
          unless os_air_loop_opt.empty?
            os_air_loop = os_air_loop_opt.get
            os_air_loops << os_air_loop
            loop_name = os_air_loop.name
            unless loop_name.empty?
              # set the name of the air loop to align with the HVAC name
              if @hash[:display_name]
                clean_name = @hash[:display_name].to_s.gsub(/[^.A-Za-z0-9_-] /, " ")
                os_air_loop.setName(clean_name + ' - ' + loop_name.get)
              end
            end
            break if !equipment_type.include? 'PSZ'  # multiple air loops have been added
          end
        end
      end
    end
  end

  # set the efficiencies of fans to be reasonable for DOAS vs. All-Air loops
  if !os_air_loops.empty?
    if equipment_type.to_s.include? 'DOAS'
      fan_size = 0.2
    else
      fan_size = 2
    end
    os_air_loops.each do |os_air_loop|
      # set the supply fan efficiency
      unless os_air_loop.supplyFan.empty?
        s_fan = os_air_loop.supplyFan.get
        s_fan = fan_from_component(s_fan)
        unless s_fan.nil?
          s_fan.setMaximumFlowRate(fan_size)  # set to a typical value
          standard.fan_apply_standard_minimum_motor_efficiency(
            s_fan, standard.fan_brake_horsepower(s_fan))
          s_fan.autosizeMaximumFlowRate()  # set it back to autosize
        end
      end
      # set the return fan efficiency
      unless os_air_loop.returnFan.empty?
        ex_fan = os_air_loop.returnFan.get
        ex_fan = fan_from_component(ex_fan)
        unless ex_fan.nil?
          ex_fan.setMaximumFlowRate(fan_size)  # set to a typical value
          standard.fan_apply_standard_minimum_motor_efficiency(
            ex_fan, standard.fan_brake_horsepower(ex_fan))
          ex_fan.autosizeMaximumFlowRate()  # set it back to autosize
        end
      end
    end
  end

  # get the boilers and assign a reasonable efficiency (assuming 40kW boilers)
  if equipment_type.to_s.include? 'Boiler'
    openstudio_model.getBoilerHotWaters.sort.each do |obj|
      obj.setNominalCapacity(40000)  # set to a typical value
      standard.boiler_hot_water_apply_efficiency_and_curves(obj)
      obj.autosizeNominalCapacity()  # set it back to autosize
      obj.setName(standard_id + ' Boiler')
    end
  end

  # get the chillers and assign a reasonable COP (assuming 2000kW water-cooled; 600kW air-cooled)
  if equipment_type.to_s.include? 'Chiller'
    if equipment_type.to_s.include? 'ACChiller'
      chiller_size = 600000
    else
      chiller_size = 2000000
    end
    clg_tower_objs = openstudio_model.getCoolingTowerSingleSpeeds
    openstudio_model.getChillerElectricEIRs.sort.each do |obj|
      obj.setReferenceCapacity(chiller_size)  # set to a typical value
      if obj.name.empty?
        obj_name = standard_id + ' Chiller'
      else
        obj_name = obj.name.get
      end
      standard.chiller_electric_eir_apply_efficiency_and_curves(obj, clg_tower_objs)
      obj.autosizeReferenceCapacity()  # set it back to autosize
      obj.setName(obj_name)
    end
  end

  # set the efficiency of any gas heaters (assuming 10kW heaters)
  if equipment_type.to_s.include? 'GasHeaters'
    zones.each do |zon|
      zon.equipment.each do |equp|
        if !equp.to_ZoneHVACUnitHeater.empty?
          unit_heater = equp.to_ZoneHVACUnitHeater.get
          coil = unit_heater.heatingCoil
          unless coil.to_CoilHeatingGas.empty?
            coil = coil.to_CoilHeatingGas.get
            coil.setNominalCapacity(10000)  # set to a typical value
            standard.coil_heating_gas_apply_efficiency_and_curves(coil)
            coil.autosizeNominalCapacity()  # set it back to autosize
          end
        end
      end
    end
  end

  # change furnace to electric if specified
  if equipment_type.to_s.include? 'Furnace_Electric'
    openstudio_model.getAirLoopHVACUnitarySystems.sort.each do |obj|
      unless obj.heatingCoil.empty?
        old_coil = obj.heatingCoil.get
        heat_coil = OpenStudio::Model::CoilHeatingElectric.new(openstudio_model)
        unless old_coil.name.empty?
          heat_coil.setName(old_coil.name.get)
        end
        obj.setHeatingCoil(heat_coil)
        old_coil.remove()
      end
    end
  end

  # assign the economizer type if there's an air loop and the economizer is specified
  if @hash[:economizer_type] && !os_air_loops.empty?
    os_air_loops.each do |os_air_loop|
      oasys = os_air_loop.airLoopHVACOutdoorAirSystem
      unless oasys.empty?
        os_oasys = oasys.get
        oactrl = os_oasys.getControllerOutdoorAir
        oactrl.setEconomizerControlType(@hash[:economizer_type])
      end
    end
  end

  # set the sensible heat recovery if there's an air loop and the heat recovery is specified
  if @hash[:sensible_heat_recovery] && @hash[:sensible_heat_recovery] != 0 && !os_air_loops.empty?
    os_air_loops.each do |os_air_loop|
      erv = get_existing_erv(os_air_loop)
      unless erv
        erv = create_erv(openstudio_model, os_air_loop)
      end
      eff_at_max = @hash[:sensible_heat_recovery] * (0.76 / 0.81)  # taken from OpenStudio defaults
      erv.setSensibleEffectivenessat100CoolingAirFlow(eff_at_max)
      erv.setSensibleEffectivenessat100HeatingAirFlow(eff_at_max)
      erv.setSensibleEffectivenessat75CoolingAirFlow(@hash[:sensible_heat_recovery])
      erv.setSensibleEffectivenessat75HeatingAirFlow(@hash[:sensible_heat_recovery])
    end
  end

  # set the latent heat recovery if there's an air loop and the heat recovery is specified
  if @hash[:latent_heat_recovery] && @hash[:latent_heat_recovery] != 0 && !os_air_loops.empty?
    os_air_loops.each do |os_air_loop|
      erv = get_existing_erv(os_air_loop)
      unless erv
        erv = create_erv(openstudio_model, os_air_loop)
      end
      eff_at_max = @hash[:latent_heat_recovery] * (0.68 / 0.73)  # taken from OpenStudio defaults
      erv.setLatentEffectivenessat100CoolingAirFlow(eff_at_max)
      erv.setLatentEffectivenessat100HeatingAirFlow(eff_at_max)
      erv.setLatentEffectivenessat75CoolingAirFlow(@hash[:latent_heat_recovery])
      erv.setLatentEffectivenessat75HeatingAirFlow(@hash[:latent_heat_recovery])
    end
  end

  # assign demand controlled ventilation if there's an air loop
  if @hash[:demand_controlled_ventilation] && !os_air_loops.empty?
    os_air_loops.each do |os_air_loop|
      oasys = os_air_loop.airLoopHVACOutdoorAirSystem
      unless oasys.empty?
        os_oasys = oasys.get
        oactrl = os_oasys.getControllerOutdoorAir
        vent_ctrl = oactrl.controllerMechanicalVentilation
        vent_ctrl.setDemandControlledVentilationNoFail(true)
        oactrl.resetMinimumFractionofOutdoorAirSchedule
      end
    end
  end

  # assign the DOAS availability schedule if there's an air loop and it is specified
  if @hash[:doas_availability_schedule] && !os_air_loops.empty?
    os_air_loops.each do |os_air_loop|
      schedule = openstudio_model.getScheduleByName(@hash[:doas_availability_schedule])
      unless schedule.empty?
        avail_sch = schedule.get
        os_air_loop.setAvailabilitySchedule(avail_sch)
      end
    end
  end

  # set the outdoor air controller to respect room-level ventilation schedules if they exist
  if !os_air_loops.empty?
    oa_sch, oa_sch_name = nil, nil
    zones.each do |zone|
      oa_spec = zone.spaces[0].designSpecificationOutdoorAir
      unless oa_spec.empty?
        oa_spec = oa_spec.get
        space_oa_sch = oa_spec.outdoorAirFlowRateFractionSchedule
        unless space_oa_sch.empty?
          space_oa_sch = space_oa_sch.get
          space_oa_sch_name = space_oa_sch.name
          unless space_oa_sch_name.empty?
            space_oa_sch_name = space_oa_sch_name.get
            if oa_sch_name.nil? || space_oa_sch_name == oa_sch_name
              oa_sch, oa_sch_name = space_oa_sch, space_oa_sch_name
            else
              oa_sch = nil
            end
          end
        end
      end
    end

    if oa_sch
      os_air_loops.each do |os_air_loop|
        oasys = os_air_loop.airLoopHVACOutdoorAirSystem
        unless oasys.empty?
          os_oasys = oasys.get
          oactrl = os_oasys.getControllerOutdoorAir
          oactrl.resetMinimumFractionofOutdoorAirSchedule
          oactrl.setMinimumOutdoorAirSchedule(oa_sch)
        end
      end
    end
  end

  # if the systems are PTAC and there is ventilation, ensure the system includes it
  if equipment_type.include?('PTAC') || equipment_type.include?('PTHP')
    always_on = openstudio_model.getScheduleByName('Always On').get
    zones.each do |zone|
      # check if the space type has ventilation assigned to it
      out_air = zone.spaces[0].designSpecificationOutdoorAir
      unless out_air.empty?
        # get any ventilation schedules
        vent_sched = always_on
        out_air = out_air.get
        air_sch = out_air.outdoorAirFlowRateFractionSchedule
        unless air_sch.empty?
          vent_sched = air_sch.get
        end
        # get the PTAC object
        ptac = nil
        zone.equipment.each do |equip|
          e_name = equip.name
          unless e_name.empty?
            e_name = e_name.get
            if e_name.include? 'PTAC'
              ptac = openstudio_model.getZoneHVACPackagedTerminalAirConditioner(equip.handle)
            elsif e_name.include? 'PTHP'
              ptac = openstudio_model.getZoneHVACPackagedTerminalHeatPump(equip.handle)
            end
          end
        end
        # assign the schedule to the PTAC object
        unless ptac.nil? || ptac.empty?
          ptac = ptac.get
          ptac.setSupplyAirFanOperatingModeSchedule(vent_sched)
        end
      end
    end
  end

  # assign an electric humidifier if there's an air loop and the zones have a humidistat
  if !os_air_loops.empty?
    humidistat_exists = false
    zones.each do |zone|
      h_stat = zone.zoneControlHumidistat
      unless h_stat.empty?
        humidistat_exists = true
        if equipment_type.to_s.include? 'DOAS'
          z_sizing = zone.sizingZone
          z_sizing.setDedicatedOutdoorAirSystemControlStrategy('NeutralDehumidifiedSupplyAir')
        end
      end
    end
    if humidistat_exists
      os_air_loops.each do |os_air_loop|
        humidifier = create_humidifier(openstudio_model, os_air_loop)
      end
    end
  end

end