openstudio-standards 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/data/geometry/ASHRAEPrimarySchool.osm +36 -2
- data/data/geometry/ASHRAESecondarySchool.osm +19 -2
- data/data/standards/OpenStudio_Standards_elevators.json +10756 -0
- data/lib/openstudio-standards.rb +0 -2
- data/lib/openstudio-standards/hvac_sizing/Siz.HVACComponent.rb +36 -0
- data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +3 -0
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.elevators.rb +175 -164
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.swh.rb +268 -476
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +625 -116
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +4 -0
- data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +2 -19
- data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +112 -68
- data/lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb +10 -2
- data/lib/openstudio-standards/standards/necb/necb_2011/data/space_types.json +224 -224
- data/lib/openstudio-standards/standards/necb/necb_2011/service_water_heating.rb +8 -16
- data/lib/openstudio-standards/standards/necb/necb_2015/data/space_types.json +318 -318
- data/lib/openstudio-standards/standards/standard.rb +1 -0
- data/lib/openstudio-standards/version.rb +1 -1
- metadata +4 -4
- data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.hvac_systems.rb +0 -15
- data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.hvac_systems.rb +0 -15
@@ -5,18 +5,21 @@ class Standard
|
|
5
5
|
#
|
6
6
|
# @param system_name [String] the name of the system, or nil in which case it will be defaulted
|
7
7
|
# @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
|
8
|
-
#
|
8
|
+
# zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
|
9
9
|
# @param service_water_temperature [Double] service water temperature, in C
|
10
10
|
# @param service_water_pump_head [Double] service water pump head, in Pa
|
11
|
-
# @param service_water_pump_motor_efficiency [Double]
|
12
|
-
# service water pump motor efficiency, as decimal.
|
11
|
+
# @param service_water_pump_motor_efficiency [Double] service water pump motor efficiency, as decimal.
|
13
12
|
# @param water_heater_capacity [Double] water heater heating capacity, in W
|
14
13
|
# @param water_heater_volume [Double] water heater volume, in m^3
|
15
|
-
# @param water_heater_fuel [String] water heater fuel.
|
16
|
-
#
|
17
|
-
# @param
|
18
|
-
#
|
19
|
-
# @param
|
14
|
+
# @param water_heater_fuel [String] water heater fuel. Valid choices are NaturalGas, Electricity
|
15
|
+
# @param parasitic_fuel_consumption_rate [Double] the parasitic fuel consumption rate of the water heater, in W
|
16
|
+
# @param add_pipe_losses [Bool] if true, add piping and associated heat losses to system. If false, add no pipe heat losses
|
17
|
+
# @param floor_area_served [Double] area served by the SWH loop, in m^2. Used for pipe loss piping length estimation
|
18
|
+
# @param number_of_stories [Integer] number of stories served by the SWH loop. Used for pipe loss piping length estimation
|
19
|
+
# @param pipe_insulation_thickness [Double] thickness of the fiberglass batt pipe insulation, in m. Use 0 for uninsulated pipes
|
20
|
+
# @param number_water_heaters [Double] the number of water heaters represented by the capacity and volume inputs.
|
21
|
+
# Used to modify efficiencies for water heaters based on individual component size while avoiding having to model
|
22
|
+
# lots of individual water heaters (for runtime sake).
|
20
23
|
# @return [OpenStudio::Model::PlantLoop]
|
21
24
|
# the resulting service water loop.
|
22
25
|
def model_add_swh_loop(model,
|
@@ -29,14 +32,17 @@ class Standard
|
|
29
32
|
water_heater_volume,
|
30
33
|
water_heater_fuel,
|
31
34
|
parasitic_fuel_consumption_rate,
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
+
add_pipe_losses = false,
|
36
|
+
floor_area_served = 465,
|
37
|
+
number_of_stories = 1,
|
38
|
+
pipe_insulation_thickness = 0.0127, # 1/2in
|
39
|
+
number_water_heaters = 1)
|
40
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', "In model_add_swh_loop, number_water_heaters = #{number_water_heaters}")
|
35
41
|
|
36
42
|
# Service water heating loop
|
37
43
|
service_water_loop = OpenStudio::Model::PlantLoop.new(model)
|
38
|
-
service_water_loop.setMinimumLoopTemperature(10)
|
39
|
-
service_water_loop.setMaximumLoopTemperature(60)
|
44
|
+
service_water_loop.setMinimumLoopTemperature(10.0)
|
45
|
+
service_water_loop.setMaximumLoopTemperature(60.0)
|
40
46
|
|
41
47
|
if system_name.nil?
|
42
48
|
service_water_loop.setName('Service Water Loop')
|
@@ -45,17 +51,17 @@ class Standard
|
|
45
51
|
end
|
46
52
|
|
47
53
|
# Temperature schedule type limits
|
48
|
-
temp_sch_type_limits =
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
+
temp_sch_type_limits = model_add_schedule_type_limits(model,
|
55
|
+
name: 'Temperature Schedule Type Limits',
|
56
|
+
lower_limit_value: 0.0,
|
57
|
+
upper_limit_value: 100.0,
|
58
|
+
numeric_type: 'Continuous',
|
59
|
+
unit_type: 'Temperature')
|
54
60
|
|
55
61
|
# Service water heating loop controls
|
56
62
|
swh_temp_c = service_water_temperature
|
57
63
|
swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
|
58
|
-
swh_delta_t_r = 9 # 9F delta-T
|
64
|
+
swh_delta_t_r = 9.0 # 9F delta-T
|
59
65
|
swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
|
60
66
|
swh_temp_sch = model_add_constant_schedule_ruleset(model,
|
61
67
|
swh_temp_c,
|
@@ -69,25 +75,28 @@ class Standard
|
|
69
75
|
sizing_plant.setDesignLoopExitTemperature(swh_temp_c)
|
70
76
|
sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)
|
71
77
|
|
72
|
-
#
|
78
|
+
# Determine if circulating or non-circulating based on supplied head pressure
|
73
79
|
swh_pump_head_press_pa = service_water_pump_head
|
74
|
-
|
75
|
-
if swh_pump_head_press_pa.nil?
|
80
|
+
circulating = true
|
81
|
+
if swh_pump_head_press_pa.nil? || swh_pump_head_press_pa <= 1
|
76
82
|
# As if there is no circulation pump
|
77
83
|
swh_pump_head_press_pa = 0.001
|
78
|
-
|
84
|
+
service_water_pump_motor_efficiency = 1
|
85
|
+
circulating = false
|
79
86
|
end
|
80
87
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
+
# Service water heating pump
|
89
|
+
if circulating
|
90
|
+
swh_pump = OpenStudio::Model::PumpConstantSpeed.new(model)
|
91
|
+
swh_pump.setName("#{service_water_loop.name} Circulator Pump")
|
92
|
+
swh_pump.setPumpControlType('Intermittent')
|
93
|
+
else
|
94
|
+
swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
|
95
|
+
swh_pump.setName("#{service_water_loop.name} Water Mains Pressure Driven")
|
96
|
+
swh_pump.setPumpControlType('Continuous')
|
97
|
+
end
|
88
98
|
swh_pump.setRatedPumpHead(swh_pump_head_press_pa.to_f)
|
89
|
-
swh_pump.setMotorEfficiency(
|
90
|
-
swh_pump.setPumpControlType('Intermittent')
|
99
|
+
swh_pump.setMotorEfficiency(service_water_pump_motor_efficiency)
|
91
100
|
swh_pump.addToNode(service_water_loop.supplyInletNode)
|
92
101
|
|
93
102
|
water_heater = model_add_water_heater(model,
|
@@ -100,10 +109,21 @@ class Standard
|
|
100
109
|
false,
|
101
110
|
0.0,
|
102
111
|
nil,
|
103
|
-
water_heater_thermal_zone
|
112
|
+
water_heater_thermal_zone,
|
113
|
+
number_water_heaters)
|
104
114
|
|
105
115
|
service_water_loop.addSupplyBranchForComponent(water_heater)
|
106
116
|
|
117
|
+
# Pipe losses
|
118
|
+
if add_pipe_losses
|
119
|
+
model_add_piping_losses_to_swh_system(model,
|
120
|
+
service_water_loop,
|
121
|
+
circulating,
|
122
|
+
pipe_insulation_thickness: pipe_insulation_thickness,
|
123
|
+
floor_area_served: floor_area_served,
|
124
|
+
number_of_stories: number_of_stories)
|
125
|
+
end
|
126
|
+
|
107
127
|
# Service water heating loop bypass pipes
|
108
128
|
water_heater_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
109
129
|
service_water_loop.addSupplyBranchForComponent(water_heater_bypass_pipe)
|
@@ -111,40 +131,35 @@ class Standard
|
|
111
131
|
service_water_loop.addDemandBranchForComponent(coil_bypass_pipe)
|
112
132
|
supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
113
133
|
supply_outlet_pipe.addToNode(service_water_loop.supplyOutletNode)
|
114
|
-
demand_inlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
115
|
-
demand_inlet_pipe.addToNode(service_water_loop.demandInletNode)
|
116
134
|
demand_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
117
135
|
demand_outlet_pipe.addToNode(service_water_loop.demandOutletNode)
|
118
136
|
|
119
|
-
|
120
|
-
|
137
|
+
if circulating
|
138
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Added circulating SWH loop called #{service_water_loop.name}")
|
139
|
+
else
|
140
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Added non-circulating SWH loop called #{service_water_loop.name}")
|
141
|
+
end
|
121
142
|
|
122
|
-
|
123
|
-
# @return [String] the SWH pump type: ConstantSpeed, VariableSpeed
|
124
|
-
def model_swh_pump_type(model, building_type)
|
125
|
-
swh_pump_type = 'ConstantSpeed'
|
126
|
-
return swh_pump_type
|
143
|
+
return service_water_loop
|
127
144
|
end
|
128
145
|
|
129
146
|
# Creates a water heater and attaches it to the supplied service water heating loop.
|
130
147
|
#
|
131
148
|
# @param water_heater_capacity [Double] water heater capacity, in W
|
132
149
|
# @param water_heater_volume [Double] water heater volume, in m^3
|
133
|
-
# @param water_heater_fuel [Double] valid choices are
|
134
|
-
# Natural Gas, Electricity
|
150
|
+
# @param water_heater_fuel [Double] valid choices are NaturalGas, Electricity
|
135
151
|
# @param service_water_temperature [Double] water heater temperature, in C
|
136
|
-
# @param parasitic_fuel_consumption_rate [Double] water heater parasitic
|
137
|
-
#
|
138
|
-
# @param
|
139
|
-
# schedule. If nil, will be defaulted.
|
140
|
-
# @param set_peak_use_flowrate [Bool] if true, the peak flow rate
|
141
|
-
# and flow rate schedule will be set.
|
152
|
+
# @param parasitic_fuel_consumption_rate [Double] water heater parasitic fuel consumption rate, in W
|
153
|
+
# @param swh_temp_sch [OpenStudio::Model::Schedule] the service water heating schedule. If nil, will be defaulted.
|
154
|
+
# @param set_peak_use_flowrate [Bool] if true, the peak flow rate and flow rate schedule will be set.
|
142
155
|
# @param peak_flowrate [Double] in m^3/s
|
143
156
|
# @param flowrate_schedule [String] name of the flow rate schedule
|
144
|
-
# @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
|
145
|
-
#
|
146
|
-
# @
|
147
|
-
#
|
157
|
+
# @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] zone to place water heater in.
|
158
|
+
# If nil, will be assumed in 70F air for heat loss.
|
159
|
+
# @param number_water_heaters [Double] the number of water heaters represented by the capacity and volume inputs.
|
160
|
+
# Used to modify efficiencies for water heaters based on individual component size while avoiding having to model
|
161
|
+
# lots of individual water heaters (for runtime sake).
|
162
|
+
# @return [OpenStudio::Model::WaterHeaterMixed] the resulting water heater
|
148
163
|
def model_add_water_heater(model,
|
149
164
|
water_heater_capacity,
|
150
165
|
water_heater_volume,
|
@@ -155,10 +170,9 @@ class Standard
|
|
155
170
|
set_peak_use_flowrate,
|
156
171
|
peak_flowrate,
|
157
172
|
flowrate_schedule,
|
158
|
-
water_heater_thermal_zone
|
159
|
-
|
160
|
-
OpenStudio.logFree(OpenStudio::
|
161
|
-
|
173
|
+
water_heater_thermal_zone,
|
174
|
+
number_water_heaters)
|
175
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', "In model_add_water_heater, number_water_heaters = #{number_water_heaters}")
|
162
176
|
# Water heater
|
163
177
|
# TODO Standards - Change water heater methodology to follow
|
164
178
|
# 'Model Enhancements Appendix A.'
|
@@ -168,11 +182,11 @@ class Standard
|
|
168
182
|
|
169
183
|
# Temperature schedule type limits
|
170
184
|
temp_sch_type_limits = model_add_schedule_type_limits(model,
|
171
|
-
name:
|
185
|
+
name: 'Temperature Schedule Type Limits',
|
172
186
|
lower_limit_value: 0.0,
|
173
187
|
upper_limit_value: 100.0,
|
174
|
-
numeric_type:
|
175
|
-
unit_type:
|
188
|
+
numeric_type: 'Continuous',
|
189
|
+
unit_type: 'Temperature')
|
176
190
|
|
177
191
|
if swh_temp_sch.nil?
|
178
192
|
# Service water heating loop controls
|
@@ -189,9 +203,18 @@ class Standard
|
|
189
203
|
|
190
204
|
# Water heater depends on the fuel type
|
191
205
|
water_heater = OpenStudio::Model::WaterHeaterMixed.new(model)
|
192
|
-
|
206
|
+
|
207
|
+
# Assign a quantity to the water heater if it represents multiple water heaters
|
208
|
+
if number_water_heaters > 1
|
209
|
+
water_heater.setName("#{number_water_heaters}X #{(water_heater_vol_gal/number_water_heaters).round}gal #{water_heater_fuel} Water Heater - #{(water_heater_capacity_kbtu_per_hr/number_water_heaters).round}kBtu/hr")
|
210
|
+
water_heater.set_component_quantity(number_water_heaters)
|
211
|
+
else
|
212
|
+
water_heater.setName("#{water_heater_vol_gal.round}gal #{water_heater_fuel} Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
|
213
|
+
end
|
214
|
+
|
193
215
|
water_heater.setTankVolume(OpenStudio.convert(water_heater_vol_gal, 'gal', 'm^3').get)
|
194
216
|
water_heater.setSetpointTemperatureSchedule(swh_temp_sch)
|
217
|
+
water_heater.setDeadbandTemperatureDifference(2.0)
|
195
218
|
|
196
219
|
if water_heater_thermal_zone.nil?
|
197
220
|
# Assume the water heater is indoors at 70F or 72F
|
@@ -214,7 +237,7 @@ class Standard
|
|
214
237
|
water_heater.resetAmbientTemperatureSchedule
|
215
238
|
end
|
216
239
|
|
217
|
-
water_heater.setMaximumTemperatureLimit(
|
240
|
+
water_heater.setMaximumTemperatureLimit(service_water_temperature)
|
218
241
|
water_heater.setDeadbandTemperatureDifference(OpenStudio.convert(3.6, 'R', 'K').get)
|
219
242
|
water_heater.setHeaterControlType('Cycle')
|
220
243
|
water_heater.setHeaterMaximumCapacity(OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'W').get)
|
@@ -229,7 +252,7 @@ class Standard
|
|
229
252
|
water_heater.setOnCycleParasiticFuelType('Electricity')
|
230
253
|
water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
|
231
254
|
water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
|
232
|
-
elsif water_heater_fuel == 'Natural Gas'
|
255
|
+
elsif water_heater_fuel == 'Natural Gas' || water_heater_fuel == 'NaturalGas'
|
233
256
|
water_heater.setHeaterFuelType('Gas')
|
234
257
|
water_heater.setHeaterThermalEfficiency(0.78)
|
235
258
|
water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
@@ -238,6 +261,34 @@ class Standard
|
|
238
261
|
water_heater.setOnCycleParasiticFuelType('Gas')
|
239
262
|
water_heater.setOffCycleLossCoefficienttoAmbientTemperature(6.0)
|
240
263
|
water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0)
|
264
|
+
elsif water_heater_fuel == 'HeatPump'
|
265
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', 'Simple but crappy workaround to represent heat pump water heaters without incurring significant runtime penalty associated with using correct objects.')
|
266
|
+
# Make a part-load efficiency modifier curve with a value above 1, which
|
267
|
+
# is multiplied by the nominal efficiency of 100% to represent
|
268
|
+
# the COP of a HPWH.
|
269
|
+
# TODO could make this workaround better by using EMS
|
270
|
+
# to modify this curve output in realtime based on
|
271
|
+
# the OA temperature.
|
272
|
+
hpwh_cop = 2.8
|
273
|
+
eff_f_of_plr = OpenStudio::Model::CurveCubic.new(model)
|
274
|
+
eff_f_of_plr.setName("HPWH_COP_#{hpwh_cop}")
|
275
|
+
eff_f_of_plr.setCoefficient1Constant(hpwh_cop)
|
276
|
+
eff_f_of_plr.setCoefficient2x(0.0)
|
277
|
+
eff_f_of_plr.setCoefficient3xPOW2(0.0)
|
278
|
+
eff_f_of_plr.setCoefficient4xPOW3(0.0)
|
279
|
+
eff_f_of_plr.setMinimumValueofx(0.0)
|
280
|
+
eff_f_of_plr.setMaximumValueofx(1.0)
|
281
|
+
water_heater.setHeaterFuelType('Electricity')
|
282
|
+
water_heater.setHeaterThermalEfficiency(1.0)
|
283
|
+
water_heater.setPartLoadFactorCurve(eff_f_of_plr)
|
284
|
+
water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
285
|
+
water_heater.setOnCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
286
|
+
water_heater.setOffCycleParasiticFuelType('Electricity')
|
287
|
+
water_heater.setOnCycleParasiticFuelType('Electricity')
|
288
|
+
water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
|
289
|
+
water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
|
290
|
+
else
|
291
|
+
OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', "#{water_heater_fuel} is not a valid water heater fuel. Valid choices are Electricity, NaturalGas, and HeatPump.")
|
241
292
|
end
|
242
293
|
|
243
294
|
if set_peak_use_flowrate
|
@@ -249,9 +300,284 @@ class Standard
|
|
249
300
|
water_heater.setUseFlowRateFractionSchedule(schedule)
|
250
301
|
end
|
251
302
|
|
303
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Added water heater called #{water_heater.name}")
|
304
|
+
|
252
305
|
return water_heater
|
253
306
|
end
|
254
307
|
|
308
|
+
# Creates a heatpump water heater and attaches it to the supplied service water heating loop.
|
309
|
+
#
|
310
|
+
# @param water_heater_capacity [Double] water heater capacity, in W
|
311
|
+
# @param water_heater_volume [Double] water heater volume, in m^3
|
312
|
+
# @param service_water_temperature [Double] water heater temperature, in C
|
313
|
+
# @param parasitic_fuel_consumption_rate [Double] water heater parasitic fuel consumption rate, in W
|
314
|
+
# @param swh_temp_sch [OpenStudio::Model::Schedule] the service water heating schedule. If nil, will be defaulted.
|
315
|
+
# @param set_peak_use_flowrate [Bool] if true, the peak flow rate and flow rate schedule will be set.
|
316
|
+
# @param peak_flowrate [Double] in m^3/s
|
317
|
+
# @param flowrate_schedule [String] name of the flow rate schedule
|
318
|
+
# @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] zone to place water heater in.
|
319
|
+
# If nil, will be assumed in 70F air for heat loss.
|
320
|
+
# @return [OpenStudio::Model::WaterHeaterMixed] the resulting water heater
|
321
|
+
def model_add_heatpump_water_heater(model,
|
322
|
+
type: 'PumpedCondenser',
|
323
|
+
water_heater_capacity: 500,
|
324
|
+
electric_backup_capacity: 4500,
|
325
|
+
water_heater_volume: OpenStudio.convert(80.0, 'gal', 'm^3').get,
|
326
|
+
service_water_temperature: OpenStudio.convert(125.0, 'F', 'C').get,
|
327
|
+
parasitic_fuel_consumption_rate: 3.0,
|
328
|
+
swh_temp_sch: nil,
|
329
|
+
cop: 2.8,
|
330
|
+
shr: 0.88,
|
331
|
+
tank_ua: 3.9,
|
332
|
+
set_peak_use_flowrate: false,
|
333
|
+
peak_flowrate: 0.0,
|
334
|
+
flowrate_schedule: nil,
|
335
|
+
water_heater_thermal_zone: nil)
|
336
|
+
|
337
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', 'Adding heat pump water heater')
|
338
|
+
|
339
|
+
# create heat pump water heater
|
340
|
+
if type == 'WrappedCondenser'
|
341
|
+
hpwh = OpenStudio::Model::WaterHeaterHeatPumpWrappedCondenser.new(model)
|
342
|
+
elsif type == 'PumpedCondenser'
|
343
|
+
hpwh = OpenStudio::Model::WaterHeaterHeatPump.new(model)
|
344
|
+
end
|
345
|
+
|
346
|
+
# calculate tank height and radius
|
347
|
+
water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get
|
348
|
+
hpwh_vol_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get
|
349
|
+
tank_height = 0.0188 * hpwh_vol_gal + 0.0935 # linear relationship that gets GE height at 50 gal and AO Smith height at 80 gal
|
350
|
+
tank_radius = (0.9 * water_heater_volume / (Math::PI * tank_height))**0.5
|
351
|
+
tank_surface_area = 2.0 * Math::PI * tank_radius * (tank_radius + tank_height)
|
352
|
+
u_tank = (5.678 * tank_ua) / OpenStudio.convert(tank_surface_area, 'm^2', 'ft^2').get
|
353
|
+
hpwh.setName("#{hpwh_vol_gal.round}gal Heat Pump Water Heater - #{water_heater_capacity_kbtu_per_hr.round(0)}kBtu/hr")
|
354
|
+
|
355
|
+
if type == 'WrappedCondenser'
|
356
|
+
hpwh.setMinimumInletAirTemperatureforCompressorOperation(OpenStudio.convert(45.0, 'F', 'C').get)
|
357
|
+
hpwh.setMaximumInletAirTemperatureforCompressorOperation(OpenStudio.convert(120.0, 'F', 'C').get)
|
358
|
+
# set sensor heights
|
359
|
+
if hpwh_vol_gal <= 50.0
|
360
|
+
hpwh.setDeadBandTemperatureDifference(0.5)
|
361
|
+
h_UE = (1 - (3.5 / 12.0)) * tank_height # in the 4th node of the tank (counting from top)
|
362
|
+
h_LE = (1 - (10.5 / 12.0)) * tank_height # in the 11th node of the tank (counting from top)
|
363
|
+
h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
|
364
|
+
h_condbot = (1 - (10.99 / 12.0)) * tank_height # in the 11th node of the tank
|
365
|
+
h_hpctrl = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
|
366
|
+
hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl)
|
367
|
+
hpwh.setControlSensor1Weight(1.0)
|
368
|
+
hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl)
|
369
|
+
else
|
370
|
+
hpwh.setDeadBandTemperatureDifference(3.89)
|
371
|
+
h_UE = (1 - (3.5 / 12.0)) * tank_height # in the 3rd node of the tank (counting from top)
|
372
|
+
h_LE = (1 - (9.5 / 12.0)) * tank_height # in the 10th node of the tank (counting from top)
|
373
|
+
h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
|
374
|
+
h_condbot = 0.01 # bottom node
|
375
|
+
h_hpctrl_up = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
|
376
|
+
h_hpctrl_low = (1 - (8.5 / 12.0)) * tank_height # in the 9th node of the tank
|
377
|
+
hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl_up)
|
378
|
+
hpwh.setControlSensor1Weight(0.75)
|
379
|
+
hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl_low)
|
380
|
+
end
|
381
|
+
hpwh.setCondenserBottomLocation(h_condbot)
|
382
|
+
hpwh.setCondenserTopLocation(h_condtop)
|
383
|
+
hpwh.setTankElementControlLogic('MutuallyExclusive')
|
384
|
+
elsif type == 'PumpedCondenser'
|
385
|
+
hpwh.setDeadBandTemperatureDifference(3.89)
|
386
|
+
end
|
387
|
+
|
388
|
+
# set heat pump water heater properties
|
389
|
+
hpwh.setEvaporatorAirFlowRate(OpenStudio.convert(181.0, 'ft^3/min', 'm^3/s').get)
|
390
|
+
hpwh.setFanPlacement('DrawThrough')
|
391
|
+
hpwh.setOnCycleParasiticElectricLoad(0.0)
|
392
|
+
hpwh.setOffCycleParasiticElectricLoad(0.0)
|
393
|
+
hpwh.setParasiticHeatRejectionLocation('Outdoors')
|
394
|
+
|
395
|
+
# set temperature setpoint schedule
|
396
|
+
if swh_temp_sch.nil?
|
397
|
+
# temperature schedule type limits
|
398
|
+
temp_sch_type_limits = model_add_schedule_type_limits(model,
|
399
|
+
name: 'Temperature Schedule Type Limits',
|
400
|
+
lower_limit_value: 0.0,
|
401
|
+
upper_limit_value: 100.0,
|
402
|
+
numeric_type: 'Continuous',
|
403
|
+
unit_type: 'Temperature')
|
404
|
+
# service water heating loop controls
|
405
|
+
swh_temp_c = service_water_temperature
|
406
|
+
swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
|
407
|
+
swh_delta_t_r = 9.0 # 9F delta-T
|
408
|
+
swh_temp_c = OpenStudio.convert(swh_temp_f, 'F', 'C').get
|
409
|
+
swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
|
410
|
+
swh_temp_sch = model_add_constant_schedule_ruleset(model,
|
411
|
+
swh_temp_c,
|
412
|
+
name = "Heat Pump Water Heater Temp - #{swh_temp_f.round}F")
|
413
|
+
swh_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
|
414
|
+
end
|
415
|
+
hpwh.setCompressorSetpointTemperatureSchedule(swh_temp_sch)
|
416
|
+
|
417
|
+
# coil curves
|
418
|
+
hpwh_cap = OpenStudio::Model::CurveBiquadratic.new(model)
|
419
|
+
hpwh_cap.setName('HPWH-Cap-fT')
|
420
|
+
hpwh_cap.setCoefficient1Constant(0.563)
|
421
|
+
hpwh_cap.setCoefficient2x(0.0437)
|
422
|
+
hpwh_cap.setCoefficient3xPOW2(0.000039)
|
423
|
+
hpwh_cap.setCoefficient4y(0.0055)
|
424
|
+
hpwh_cap.setCoefficient5yPOW2(-0.000148)
|
425
|
+
hpwh_cap.setCoefficient6xTIMESY(-0.000145)
|
426
|
+
hpwh_cap.setMinimumValueofx(0.0)
|
427
|
+
hpwh_cap.setMaximumValueofx(100.0)
|
428
|
+
hpwh_cap.setMinimumValueofy(0.0)
|
429
|
+
hpwh_cap.setMaximumValueofy(100.0)
|
430
|
+
|
431
|
+
hpwh_cop = OpenStudio::Model::CurveBiquadratic.new(model)
|
432
|
+
hpwh_cop.setName('HPWH-COP-fT')
|
433
|
+
hpwh_cop.setCoefficient1Constant(1.1332)
|
434
|
+
hpwh_cop.setCoefficient2x(0.063)
|
435
|
+
hpwh_cop.setCoefficient3xPOW2(-0.0000979)
|
436
|
+
hpwh_cop.setCoefficient4y(-0.00972)
|
437
|
+
hpwh_cop.setCoefficient5yPOW2(-0.0000214)
|
438
|
+
hpwh_cop.setCoefficient6xTIMESY(-0.000686)
|
439
|
+
hpwh_cop.setMinimumValueofx(0.0)
|
440
|
+
hpwh_cop.setMaximumValueofx(100.0)
|
441
|
+
hpwh_cop.setMinimumValueofy(0.0)
|
442
|
+
hpwh_cop.setMaximumValueofy(100.0)
|
443
|
+
|
444
|
+
# create DX coil object
|
445
|
+
if type == 'WrappedCondenser'
|
446
|
+
coil = hpwh.dXCoil.to_CoilWaterHeatingAirToWaterHeatPumpWrapped.get
|
447
|
+
coil.setRatedCondenserWaterTemperature(48.89)
|
448
|
+
elsif type == 'PumpedCondenser'
|
449
|
+
coil = OpenStudio::Model::CoilWaterHeatingAirToWaterHeatPump.new(model)
|
450
|
+
hpwh.setDXCoil(coil)
|
451
|
+
end
|
452
|
+
|
453
|
+
# set coil properties
|
454
|
+
coil.setName("#{hpwh.name} Coil")
|
455
|
+
coil.setRatedHeatingCapacity(water_heater_capacity * cop)
|
456
|
+
coil.setRatedCOP(cop)
|
457
|
+
coil.setRatedSensibleHeatRatio(shr)
|
458
|
+
coil.setRatedEvaporatorInletAirDryBulbTemperature(OpenStudio.convert(67.5, 'F', 'C').get)
|
459
|
+
coil.setRatedEvaporatorInletAirWetBulbTemperature(OpenStudio.convert(56.426, 'F', 'C').get)
|
460
|
+
coil.setRatedEvaporatorAirFlowRate(OpenStudio.convert(181.0, 'ft^3/min', 'm^3/s').get)
|
461
|
+
coil.setEvaporatorFanPowerIncludedinRatedCOP(true)
|
462
|
+
coil.setEvaporatorAirTemperatureTypeforCurveObjects('WetBulbTemperature')
|
463
|
+
coil.setHeatingCapacityFunctionofTemperatureCurve(hpwh_cap)
|
464
|
+
coil.setHeatingCOPFunctionofTemperatureCurve(hpwh_cop)
|
465
|
+
coil.setMaximumAmbientTemperatureforCrankcaseHeaterOperation(0.0)
|
466
|
+
|
467
|
+
# set tank properties
|
468
|
+
if type == 'WrappedCondenser'
|
469
|
+
tank = hpwh.tank.to_WaterHeaterStratified.get
|
470
|
+
tank.setTankHeight(tank_height)
|
471
|
+
tank.setHeaterPriorityControl('MasterSlave')
|
472
|
+
if hpwh_vol_gal <= 50.0
|
473
|
+
tank.setHeater1DeadbandTemperatureDifference(25.0)
|
474
|
+
tank.setHeater2DeadbandTemperatureDifference(30.0)
|
475
|
+
else
|
476
|
+
tank.setHeater1DeadbandTemperatureDifference(18.5)
|
477
|
+
tank.setHeater2DeadbandTemperatureDifference(3.89)
|
478
|
+
end
|
479
|
+
hpwh_bottom_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
|
480
|
+
hpwh_bottom_element_sp.setName("#{hpwh.name} BottomElementSetpoint")
|
481
|
+
hpwh_top_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
|
482
|
+
hpwh_top_element_sp.setName("#{hpwh.name} TopElementSetpoint")
|
483
|
+
tank.setHeater1Capacity(electric_backup_capacity)
|
484
|
+
tank.setHeater1Height(h_UE)
|
485
|
+
tank.setHeater1SetpointTemperatureSchedule(hpwh_top_element_sp) # Overwritten later by EMS
|
486
|
+
tank.setHeater2Capacity(electric_backup_capacity)
|
487
|
+
tank.setHeater2Height(h_LE)
|
488
|
+
tank.setHeater2SetpointTemperatureSchedule(hpwh_bottom_element_sp)
|
489
|
+
tank.setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(u_tank)
|
490
|
+
tank.setNumberofNodes(12)
|
491
|
+
tank.setAdditionalDestratificationConductivity(0)
|
492
|
+
tank.setNode1AdditionalLossCoefficient(0)
|
493
|
+
tank.setNode2AdditionalLossCoefficient(0)
|
494
|
+
tank.setNode3AdditionalLossCoefficient(0)
|
495
|
+
tank.setNode4AdditionalLossCoefficient(0)
|
496
|
+
tank.setNode5AdditionalLossCoefficient(0)
|
497
|
+
tank.setNode6AdditionalLossCoefficient(0)
|
498
|
+
tank.setNode7AdditionalLossCoefficient(0)
|
499
|
+
tank.setNode8AdditionalLossCoefficient(0)
|
500
|
+
tank.setNode9AdditionalLossCoefficient(0)
|
501
|
+
tank.setNode10AdditionalLossCoefficient(0)
|
502
|
+
tank.setNode11AdditionalLossCoefficient(0)
|
503
|
+
tank.setNode12AdditionalLossCoefficient(0)
|
504
|
+
tank.setUseSideDesignFlowRate(0.9 * water_heater_volume / 60.1)
|
505
|
+
tank.setSourceSideDesignFlowRate(0)
|
506
|
+
tank.setSourceSideFlowControlMode('')
|
507
|
+
tank.setSourceSideInletHeight(0)
|
508
|
+
tank.setSourceSideOutletHeight(0)
|
509
|
+
elsif type == 'PumpedCondenser'
|
510
|
+
tank = OpenStudio::Model::WaterHeaterMixed.new(model)
|
511
|
+
hpwh.setTank(tank)
|
512
|
+
tank.setDeadbandTemperatureDifference(3.89)
|
513
|
+
tank.setHeaterControlType('Cycle')
|
514
|
+
tank.setHeaterMaximumCapacity(electric_backup_capacity)
|
515
|
+
end
|
516
|
+
tank.setName("#{hpwh.name} Tank")
|
517
|
+
tank.setEndUseSubcategory('Service Hot Water')
|
518
|
+
tank.setTankVolume(0.9 * water_heater_volume)
|
519
|
+
tank.setMaximumTemperatureLimit(90.0)
|
520
|
+
tank.setHeaterFuelType('Electricity')
|
521
|
+
tank.setHeaterThermalEfficiency(1.0)
|
522
|
+
tank.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
523
|
+
tank.setOffCycleParasiticFuelType('Electricity')
|
524
|
+
tank.setOnCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
525
|
+
tank.setOnCycleParasiticFuelType('Electricity')
|
526
|
+
|
527
|
+
# set fan properties
|
528
|
+
fan = hpwh.fan.to_FanOnOff.get
|
529
|
+
fan.setName("#{hpwh.name} Fan")
|
530
|
+
fan_power = 0.0462 # watts per cfm
|
531
|
+
if hpwh_vol_gal <= 50.0
|
532
|
+
fan.setFanEfficiency(23.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
|
533
|
+
fan.setPressureRise(23.0)
|
534
|
+
else
|
535
|
+
fan.setFanEfficiency(65.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
|
536
|
+
fan.setPressureRise(65.0)
|
537
|
+
end
|
538
|
+
fan.setMaximumFlowRate(OpenStudio.convert(181.0, 'ft^3/min', 'm^3/s').get)
|
539
|
+
fan.setMotorEfficiency(1.0)
|
540
|
+
fan.setMotorInAirstreamFraction(1.0)
|
541
|
+
fan.setEndUseSubcategory('Service Hot Water')
|
542
|
+
|
543
|
+
if water_heater_thermal_zone.nil?
|
544
|
+
# add in schedules for Tamb, RHamb, and the compressor
|
545
|
+
# assume the water heater is indoors at 70F for now
|
546
|
+
default_water_heater_ambient_temp_sch = model_add_constant_schedule_ruleset(model,
|
547
|
+
OpenStudio.convert(70.0, 'F', 'C').get,
|
548
|
+
name = 'Water Heater Ambient Temp Schedule - 70F')
|
549
|
+
default_water_heater_ambient_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
|
550
|
+
tank.setAmbientTemperatureIndicator('Schedule')
|
551
|
+
tank.setAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
|
552
|
+
tank.resetAmbientTemperatureThermalZone
|
553
|
+
hpwh_rhamb = OpenStudio::Model::ScheduleConstant.new(model)
|
554
|
+
hpwh_rhamb.setName("#{hpwh.name} Ambient Humidity Schedule")
|
555
|
+
hpwh_rhamb.setValue(0.5)
|
556
|
+
hpwh.setInletAirConfiguration('Schedule')
|
557
|
+
hpwh.setInletAirTemperatureSchedule(default_water_heater_ambient_temp_sch)
|
558
|
+
hpwh.setInletAirHumiditySchedule(hpwh_rhamb)
|
559
|
+
hpwh.setCompressorLocation('Schedule')
|
560
|
+
hpwh.setCompressorAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
|
561
|
+
else
|
562
|
+
hpwh.addToThermalZone(water_heater_thermal_zone)
|
563
|
+
hpwh.setInletAirConfiguration('ZoneAirOnly')
|
564
|
+
hpwh.setCompressorLocation('Zone')
|
565
|
+
tank.setAmbientTemperatureIndicator('ThermalZone')
|
566
|
+
tank.setAmbientTemperatureThermalZone(water_heater_thermal_zone)
|
567
|
+
tank.resetAmbientTemperatureSchedule
|
568
|
+
end
|
569
|
+
|
570
|
+
if set_peak_use_flowrate
|
571
|
+
rated_flow_rate_m3_per_s = peak_flowrate
|
572
|
+
rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
|
573
|
+
tank.setPeakUseFlowRate(rated_flow_rate_m3_per_s)
|
574
|
+
schedule = model_add_schedule(model, flowrate_schedule)
|
575
|
+
tank.setUseFlowRateFractionSchedule(schedule)
|
576
|
+
end
|
577
|
+
|
578
|
+
return hpwh
|
579
|
+
end
|
580
|
+
|
255
581
|
# Creates a booster water heater and attaches it
|
256
582
|
# to the supplied service water heating loop.
|
257
583
|
#
|
@@ -266,7 +592,6 @@ class Standard
|
|
266
592
|
# fuel consumption rate, in W
|
267
593
|
# @param booster_water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
|
268
594
|
# zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
|
269
|
-
# @param building_type [String] the building type
|
270
595
|
# @return [OpenStudio::Model::PlantLoop]
|
271
596
|
# the resulting booster water loop.
|
272
597
|
def model_add_swh_booster(model,
|
@@ -276,8 +601,7 @@ class Standard
|
|
276
601
|
water_heater_fuel,
|
277
602
|
booster_water_temperature,
|
278
603
|
parasitic_fuel_consumption_rate,
|
279
|
-
booster_water_heater_thermal_zone
|
280
|
-
building_type = nil)
|
604
|
+
booster_water_heater_thermal_zone)
|
281
605
|
|
282
606
|
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding booster water heater to #{main_service_water_loop.name}")
|
283
607
|
|
@@ -311,12 +635,13 @@ class Standard
|
|
311
635
|
sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)
|
312
636
|
|
313
637
|
# Booster water heating pump
|
314
|
-
swh_pump = OpenStudio::Model::
|
638
|
+
swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
|
315
639
|
swh_pump.setName('Booster Water Loop Pump')
|
316
|
-
|
317
|
-
swh_pump.
|
640
|
+
swh_pump.setRatedPumpHead(0.0) # As if there is no circulation pump
|
641
|
+
swh_pump.setRatedPowerConsumption(0.0) # As if there is no circulation pump
|
318
642
|
swh_pump.setMotorEfficiency(1)
|
319
|
-
swh_pump.setPumpControlType('
|
643
|
+
swh_pump.setPumpControlType('Continuous')
|
644
|
+
swh_pump.setMinimumFlowRate(0.0)
|
320
645
|
swh_pump.addToNode(booster_service_water_loop.supplyInletNode)
|
321
646
|
|
322
647
|
# Water heater
|
@@ -331,6 +656,8 @@ class Standard
|
|
331
656
|
water_heater.setName("#{water_heater_vol_gal}gal #{water_heater_fuel} Booster Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
|
332
657
|
water_heater.setTankVolume(OpenStudio.convert(water_heater_vol_gal, 'gal', 'm^3').get)
|
333
658
|
water_heater.setSetpointTemperatureSchedule(swh_temp_sch)
|
659
|
+
water_heater.setDeadbandTemperatureDifference(2.0)
|
660
|
+
water_heater.setEndUseSubcategory('Booster')
|
334
661
|
|
335
662
|
if booster_water_heater_thermal_zone.nil?
|
336
663
|
# Assume the water heater is indoors at 70F or 72F
|
@@ -353,7 +680,7 @@ class Standard
|
|
353
680
|
water_heater.resetAmbientTemperatureSchedule
|
354
681
|
end
|
355
682
|
|
356
|
-
water_heater.setMaximumTemperatureLimit(
|
683
|
+
water_heater.setMaximumTemperatureLimit(swh_temp_c)
|
357
684
|
water_heater.setDeadbandTemperatureDifference(OpenStudio.convert(3.6, 'R', 'K').get)
|
358
685
|
water_heater.setHeaterControlType('Cycle')
|
359
686
|
water_heater.setHeaterMaximumCapacity(OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'W').get)
|
@@ -368,7 +695,7 @@ class Standard
|
|
368
695
|
water_heater.setOnCycleParasiticFuelType('Electricity')
|
369
696
|
water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
|
370
697
|
water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
|
371
|
-
elsif water_heater_fuel == 'Natural Gas'
|
698
|
+
elsif water_heater_fuel == 'Natural Gas' || water_heater_fuel == 'NaturalGas'
|
372
699
|
water_heater.setHeaterFuelType('Gas')
|
373
700
|
water_heater.setHeaterThermalEfficiency(0.8)
|
374
701
|
water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
@@ -379,15 +706,6 @@ class Standard
|
|
379
706
|
water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0)
|
380
707
|
end
|
381
708
|
|
382
|
-
if water_heater_fuel == 'Electricity'
|
383
|
-
water_heater.setHeaterFuelType('Electricity')
|
384
|
-
water_heater.setOffCycleParasiticFuelType('Electricity')
|
385
|
-
water_heater.setOnCycleParasiticFuelType('Electricity')
|
386
|
-
elsif water_heater_fuel == 'Natural Gas'
|
387
|
-
water_heater.setHeaterFuelType('Gas')
|
388
|
-
water_heater.setOffCycleParasiticFuelType('Gas')
|
389
|
-
water_heater.setOnCycleParasiticFuelType('Gas')
|
390
|
-
end
|
391
709
|
booster_service_water_loop.addSupplyBranchForComponent(water_heater)
|
392
710
|
|
393
711
|
# Service water heating loop bypass pipes
|
@@ -417,6 +735,43 @@ class Standard
|
|
417
735
|
# the main service water loop.
|
418
736
|
main_service_water_loop.addDemandBranchForComponent(hx)
|
419
737
|
|
738
|
+
# Add a plant component temperature source to the demand outlet
|
739
|
+
# of the HX to represent the fact that the water used by the booster
|
740
|
+
# would in reality be at the mains temperature.
|
741
|
+
mains_src = OpenStudio::Model::PlantComponentTemperatureSource.new(model)
|
742
|
+
mains_src.setName('Mains Water Makeup for SWH Booster')
|
743
|
+
mains_src.addToNode(hx.demandOutletModelObject.get.to_Node.get)
|
744
|
+
|
745
|
+
# Mains water temperature sensor
|
746
|
+
mains_water_temp_sen = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Mains Water Temperature')
|
747
|
+
mains_water_temp_sen.setName('Mains_Water_Temp_Sen')
|
748
|
+
mains_water_temp_sen.setKeyName('Environment')
|
749
|
+
|
750
|
+
# Schedule to actuate
|
751
|
+
water_mains_temp_sch = OpenStudio::Model::ScheduleConstant.new(model)
|
752
|
+
water_mains_temp_sch.setName('Mains Water Temperature')
|
753
|
+
water_mains_temp_sch.setValue(OpenStudio.convert(50, 'F', 'C').get)
|
754
|
+
|
755
|
+
# Actuator for mains water temperature schedule
|
756
|
+
mains_water_temp_sch_act = OpenStudio::Model::EnergyManagementSystemActuator.new(water_mains_temp_sch, 'Schedule:Constant', 'Schedule Value')
|
757
|
+
mains_water_temp_sch_act.setName('Mains_Water_Temp_Act')
|
758
|
+
|
759
|
+
# Program
|
760
|
+
mains_prg = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
|
761
|
+
mains_prg.setName('Mains_Water_Prg')
|
762
|
+
mains_prg_body = "SET #{mains_water_temp_sch_act.handle} = #{mains_water_temp_sen.handle}"
|
763
|
+
mains_prg.setBody(mains_prg_body)
|
764
|
+
|
765
|
+
# Program Calling Manager
|
766
|
+
mains_mgr = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
|
767
|
+
mains_mgr.setName("Mains_Water_Prg_Mgr")
|
768
|
+
mains_mgr.setCallingPoint('BeginTimestepBeforePredictor')
|
769
|
+
mains_mgr.addProgram(mains_prg)
|
770
|
+
|
771
|
+
# Make the plant component use the actuated schedule
|
772
|
+
mains_src.setTemperatureSpecificationType('Scheduled')
|
773
|
+
mains_src.setSourceTemperatureSchedule(water_mains_temp_sch)
|
774
|
+
|
420
775
|
return booster_service_water_loop
|
421
776
|
end
|
422
777
|
|
@@ -432,7 +787,6 @@ class Standard
|
|
432
787
|
# @param water_use_temperature [Double] mixed water use temperature, in C
|
433
788
|
# @param space_name [String] the name of the space to add the water fixture to,
|
434
789
|
# or nil, in which case it will not be assigned to any particular space.
|
435
|
-
# @param building_type [String] the building type
|
436
790
|
# @return [OpenStudio::Model::WaterUseEquipment]
|
437
791
|
# the resulting water fixture.
|
438
792
|
def model_add_swh_end_uses(model,
|
@@ -442,10 +796,8 @@ class Standard
|
|
442
796
|
flowrate_schedule,
|
443
797
|
water_use_temperature,
|
444
798
|
space_name,
|
445
|
-
|
446
|
-
|
447
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_loop.name}.")
|
448
|
-
|
799
|
+
frac_sensible: 0.2,
|
800
|
+
frac_latent: 0.05)
|
449
801
|
# Water use connection
|
450
802
|
swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
|
451
803
|
|
@@ -453,8 +805,7 @@ class Standard
|
|
453
805
|
water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
|
454
806
|
rated_flow_rate_m3_per_s = peak_flowrate
|
455
807
|
rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
|
456
|
-
|
457
|
-
frac_latent = 0.05
|
808
|
+
|
458
809
|
water_use_sensible_frac_sch = model_add_constant_schedule_ruleset(model,
|
459
810
|
frac_sensible,
|
460
811
|
name = "Fraction Sensible - #{frac_sensible}",
|
@@ -466,7 +817,7 @@ class Standard
|
|
466
817
|
water_fixture_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
|
467
818
|
water_fixture_def.setLatentFractionSchedule(water_use_latent_frac_sch)
|
468
819
|
water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
|
469
|
-
water_fixture_def.setName("#{use_name
|
820
|
+
water_fixture_def.setName("#{use_name} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gpm")
|
470
821
|
# Target mixed water temperature
|
471
822
|
mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get
|
472
823
|
mixed_water_temp_sch = model_add_constant_schedule_ruleset(model,
|
@@ -480,9 +831,11 @@ class Standard
|
|
480
831
|
water_fixture.setFlowRateFractionSchedule(schedule)
|
481
832
|
|
482
833
|
if space_name.nil?
|
483
|
-
water_fixture.setName("#{use_name
|
834
|
+
water_fixture.setName("#{use_name} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
|
835
|
+
swh_connection.setName("#{use_name} WUC #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
|
484
836
|
else
|
485
|
-
water_fixture.setName("#{space_name
|
837
|
+
water_fixture.setName("#{space_name} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
|
838
|
+
swh_connection.setName("#{space_name} WUC #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
|
486
839
|
end
|
487
840
|
|
488
841
|
unless space_name.nil?
|
@@ -494,31 +847,69 @@ class Standard
|
|
494
847
|
swh_connection.addWaterUseEquipment(water_fixture)
|
495
848
|
|
496
849
|
# Connect the water use connection to the SWH loop
|
497
|
-
swh_loop.
|
850
|
+
unless swh_loop.nil?
|
851
|
+
swh_loop.addDemandBranchForComponent(swh_connection)
|
852
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_loop.name}.")
|
853
|
+
end
|
854
|
+
|
855
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Added #{water_fixture.name}.")
|
498
856
|
|
499
857
|
return water_fixture
|
500
858
|
end
|
501
859
|
|
502
|
-
# This method will add
|
503
|
-
#
|
504
|
-
|
860
|
+
# This method will add a swh water fixture to the model for the space.
|
861
|
+
# It will return a water fixture object, or NIL if there is no water load at all.
|
862
|
+
#
|
863
|
+
# Adds a WaterUseEquipment object representing the SWH loads of the supplied Space.
|
864
|
+
# Attaches this WaterUseEquipment to the supplied PlantLoop via a new WaterUseConnections object.
|
865
|
+
#
|
866
|
+
# @param model [OpenStudio::Model::Model] the model
|
867
|
+
# @param swh_loop [OpenStudio::Model::PlantLoop] the SWH loop to connect the WaterUseEquipment to
|
868
|
+
# @space [OpenStudio::Model::Space] the Space to add a WaterUseEquipment for
|
869
|
+
# @space_multiplier [Double] the multiplier to use if the supplied Space actually represents
|
870
|
+
# more area than is shown in the model.
|
871
|
+
# @param is_flow_per_area [Bool] if true, use the value in the 'service_water_heating_peak_flow_per_area'
|
872
|
+
# field of the space_types JSON. If false, use the value in the 'service_water_heating_peak_flow_rate' field.
|
873
|
+
# @return [OpenStudio::Model::WaterUseEquipment] the WaterUseEquipment for the
|
874
|
+
def model_add_swh_end_uses_by_space(model,
|
875
|
+
swh_loop,
|
876
|
+
space,
|
877
|
+
space_multiplier = 1.0,
|
878
|
+
is_flow_per_area = true)
|
879
|
+
# SpaceType
|
880
|
+
if space.spaceType.empty?
|
881
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name} does not have a Space Type assigned, cannot add SWH end uses.")
|
882
|
+
return nil
|
883
|
+
end
|
884
|
+
space_type = space.spaceType.get
|
885
|
+
|
886
|
+
# Standards Building Type
|
887
|
+
if space_type.standardsBuildingType.empty?
|
888
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name}'s Space Type does not have a Standards Building Type assigned, cannot add SWH end uses.")
|
889
|
+
return nil
|
890
|
+
end
|
891
|
+
stds_bldg_type = space_type.standardsBuildingType.get
|
892
|
+
building_type = model_get_lookup_name(stds_bldg_type)
|
893
|
+
|
894
|
+
# Standards Space Type
|
895
|
+
if space_type.standardsSpaceType.empty?
|
896
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name}'s Space Type does not have a Standards Space Type assigned, cannot add SWH end uses.")
|
897
|
+
return nil
|
898
|
+
end
|
899
|
+
stds_spc_type = space_type.standardsSpaceType.get
|
900
|
+
|
505
901
|
# find the specific space_type properties from standard.json
|
506
902
|
search_criteria = {
|
507
903
|
'template' => template,
|
508
904
|
'building_type' => building_type,
|
509
|
-
'space_type' =>
|
905
|
+
'space_type' => stds_spc_type
|
510
906
|
}
|
511
907
|
data = model_find_object(standards_data['space_types'], search_criteria)
|
512
908
|
if data.nil?
|
513
909
|
OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', "Could not find space type for: #{search_criteria}.")
|
514
910
|
return nil
|
515
911
|
end
|
516
|
-
space = model.getSpaceByName(space_name)
|
517
|
-
space = space.get
|
518
912
|
space_area = OpenStudio.convert(space.floorArea, 'm^2', 'ft^2').get # ft2
|
519
|
-
if space_multiplier.nil?
|
520
|
-
space_multiplier = 1
|
521
|
-
end
|
522
913
|
|
523
914
|
# If there is no service hot water load.. Don't bother adding anything.
|
524
915
|
if data['service_water_heating_peak_flow_per_area'].to_f == 0.0 &&
|
@@ -550,10 +941,10 @@ class Standard
|
|
550
941
|
water_fixture_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
|
551
942
|
water_fixture_def.setLatentFractionSchedule(water_use_latent_frac_sch)
|
552
943
|
water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
|
553
|
-
water_fixture_def.setName("#{
|
944
|
+
water_fixture_def.setName("#{space.name.get} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gpm")
|
554
945
|
# Target mixed water temperature
|
555
|
-
|
556
|
-
|
946
|
+
mixed_water_temp_f = data['service_water_heating_target_temperature']
|
947
|
+
mixed_water_temp_c = OpenStudio.convert(mixed_water_temp_f, 'F', 'C').get
|
557
948
|
mixed_water_temp_sch = model_add_constant_schedule_ruleset(model,
|
558
949
|
mixed_water_temp_c,
|
559
950
|
name = "Mixed Water At Faucet Temp - #{mixed_water_temp_f.round}F")
|
@@ -563,7 +954,7 @@ class Standard
|
|
563
954
|
water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
|
564
955
|
schedule = model_add_schedule(model, data['service_water_heating_schedule'])
|
565
956
|
water_fixture.setFlowRateFractionSchedule(schedule)
|
566
|
-
water_fixture.setName("#{
|
957
|
+
water_fixture.setName("#{space.name.get} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm")
|
567
958
|
swh_connection.addWaterUseEquipment(water_fixture)
|
568
959
|
# Assign water fixture to a space
|
569
960
|
water_fixture.setSpace(space) if model_attach_water_fixtures_to_spaces?(model)
|
@@ -586,17 +977,13 @@ class Standard
|
|
586
977
|
# @param peak_flowrate [Double] in m^3/s
|
587
978
|
# @param flowrate_schedule [String] name of the flow rate schedule
|
588
979
|
# @param water_use_temperature [Double] mixed water use temperature, in C
|
589
|
-
# @param building_type [String] the building type
|
590
980
|
# @return [OpenStudio::Model::WaterUseEquipment]
|
591
981
|
# the resulting water fixture.
|
592
982
|
def model_add_booster_swh_end_uses(model,
|
593
983
|
swh_booster_loop,
|
594
984
|
peak_flowrate,
|
595
985
|
flowrate_schedule,
|
596
|
-
water_use_temperature
|
597
|
-
building_type = nil)
|
598
|
-
|
599
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_booster_loop.name}.")
|
986
|
+
water_use_temperature)
|
600
987
|
|
601
988
|
# Water use connection
|
602
989
|
swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
|
@@ -605,10 +992,10 @@ class Standard
|
|
605
992
|
water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
|
606
993
|
rated_flow_rate_m3_per_s = peak_flowrate
|
607
994
|
rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
|
608
|
-
water_fixture_def.setName("Water Fixture Def - #{rated_flow_rate_gal_per_min}
|
995
|
+
water_fixture_def.setName("Booster Water Fixture Def - #{rated_flow_rate_gal_per_min.round(2)} gpm")
|
609
996
|
water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
|
610
997
|
# Target mixed water temperature
|
611
|
-
mixed_water_temp_f = OpenStudio.convert(water_use_temperature, '
|
998
|
+
mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get
|
612
999
|
mixed_water_temp_sch = model_add_constant_schedule_ruleset(model,
|
613
1000
|
OpenStudio.convert(mixed_water_temp_f, 'F', 'C').get,
|
614
1001
|
name = "Mixed Water At Faucet Temp - #{mixed_water_temp_f.round}F")
|
@@ -616,15 +1003,137 @@ class Standard
|
|
616
1003
|
|
617
1004
|
# Water use equipment
|
618
1005
|
water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
|
619
|
-
water_fixture.setName("Booster Water Fixture - #{rated_flow_rate_gal_per_min}
|
1006
|
+
water_fixture.setName("Booster Water Fixture - #{rated_flow_rate_gal_per_min.round(2)} gpm at #{mixed_water_temp_f.round}F")
|
620
1007
|
schedule = model_add_schedule(model, flowrate_schedule)
|
621
1008
|
water_fixture.setFlowRateFractionSchedule(schedule)
|
622
1009
|
swh_connection.addWaterUseEquipment(water_fixture)
|
623
1010
|
|
624
1011
|
# Connect the water use connection to the SWH loop
|
625
|
-
swh_booster_loop.
|
1012
|
+
unless swh_booster_loop.nil?
|
1013
|
+
swh_booster_loop.addDemandBranchForComponent(swh_connection)
|
1014
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_booster_loop.name}.")
|
1015
|
+
end
|
626
1016
|
|
627
1017
|
return water_fixture
|
628
1018
|
end
|
629
1019
|
|
1020
|
+
# Adds insulated 0.75in copper piping to the model.
|
1021
|
+
# For circulating systems, assume length of piping is proportional
|
1022
|
+
# to the area and number of stories in the building.
|
1023
|
+
# For non-circulating systems, assume that the water heaters
|
1024
|
+
# are close to the point of use.
|
1025
|
+
# Assume that piping is located in a zone
|
1026
|
+
#
|
1027
|
+
# @param model [OpenStudio::Model::Model] the model
|
1028
|
+
# @param swh_loop [OpenStudio::Model::PlantLoop] the service water heating loop
|
1029
|
+
# @param floor_area_served [Double] the area of building served by the service water heating loop, in m^2
|
1030
|
+
# @param number_of_stories [Integer] the number of stories served by the service water heating loop
|
1031
|
+
# @param pipe_insulation_thickness [Double] the thickness of the pipe insulation, in m. Use 0 for no insulation
|
1032
|
+
# @param circulating [Bool] use true for circulating systems, false for non-circulating systems
|
1033
|
+
# @param air_temp_surrounding_piping [Double] the temperature of the air surrounding the piping, in C.
|
1034
|
+
def model_add_piping_losses_to_swh_system(model,
|
1035
|
+
swh_loop,
|
1036
|
+
circulating,
|
1037
|
+
pipe_insulation_thickness: 0,
|
1038
|
+
floor_area_served: 465,
|
1039
|
+
number_of_stories: 1,
|
1040
|
+
air_temp_surrounding_piping: 21.1111)
|
1041
|
+
|
1042
|
+
# Estimate pipe length
|
1043
|
+
if circulating
|
1044
|
+
# For circulating systems, get pipe length based on the size of the building.
|
1045
|
+
# Formula from A.3.1 PrototypeModelEnhancements_2014_0.pdf
|
1046
|
+
floor_area_ft2 = OpenStudio.convert(floor_area_served, 'm^2', 'ft^2').get
|
1047
|
+
pipe_length_ft = 2.0 * (Math.sqrt(floor_area_ft2 / number_of_stories) + (10.0 * (number_of_stories - 1.0)))
|
1048
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Pipe length #{pipe_length_ft.round}ft = 2.0 * ( (#{floor_area_ft2.round}ft2 / #{number_of_stories} stories)^0.5 + (10.0ft * (#{number_of_stories} stories - 1.0) ) )")
|
1049
|
+
else
|
1050
|
+
# For non-circulating systems, assume water heater is close to point of use
|
1051
|
+
pipe_length_ft = 20.0
|
1052
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Pipe length #{pipe_length_ft.round}ft. For non-circulating systems, assume water heater is close to point of use.")
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
# For systems whose water heater object represents multiple pieces
|
1056
|
+
# of equipment, multiply the piping length by the number of pieces of equipment.
|
1057
|
+
swh_loop.supplyComponents('OS_WaterHeater_Mixed'.to_IddObjectType).each do |sc|
|
1058
|
+
next unless sc.to_WaterHeaterMixed.is_initialized
|
1059
|
+
water_heater = sc.to_WaterHeaterMixed.get
|
1060
|
+
comp_qty = water_heater.component_quantity
|
1061
|
+
if comp_qty > 1
|
1062
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Piping length has been multiplied by #{comp_qty}X because #{water_heater.name} represents #{comp_qty} pieces of equipment.")
|
1063
|
+
pipe_length_ft *= comp_qty
|
1064
|
+
break
|
1065
|
+
end
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
# Service water heating piping heat loss scheduled air temperature
|
1069
|
+
swh_piping_air_temp_c = air_temp_surrounding_piping
|
1070
|
+
swh_piping_air_temp_f = OpenStudio.convert(swh_piping_air_temp_c, 'C', 'F').get
|
1071
|
+
swh_piping_air_temp_sch = model_add_constant_schedule_ruleset(model,
|
1072
|
+
swh_piping_air_temp_c,
|
1073
|
+
name = "#{swh_loop.name} Piping Air Temp - #{swh_piping_air_temp_f.round}F")
|
1074
|
+
|
1075
|
+
# Service water heating piping heat loss scheduled air velocity
|
1076
|
+
swh_piping_air_velocity_m_per_s = 0.3
|
1077
|
+
swh_piping_air_velocity_mph = OpenStudio.convert(swh_piping_air_velocity_m_per_s, 'm/s', 'mile/hr').get
|
1078
|
+
swh_piping_air_velocity_sch = model_add_constant_schedule_ruleset(model,
|
1079
|
+
swh_piping_air_velocity_m_per_s,
|
1080
|
+
name = "#{swh_loop.name} Piping Air Velocity - #{swh_piping_air_velocity_mph.round(2)}mph")
|
1081
|
+
|
1082
|
+
# Material for 3/4in type L (heavy duty) copper pipe
|
1083
|
+
copper_pipe = OpenStudio::Model::StandardOpaqueMaterial.new(model)
|
1084
|
+
copper_pipe.setName("Copper pipe 0.75in type L")
|
1085
|
+
copper_pipe.setRoughness('Smooth')
|
1086
|
+
copper_pipe.setThickness(OpenStudio.convert(0.045, 'in', 'm').get)
|
1087
|
+
copper_pipe.setConductivity(386.0)
|
1088
|
+
copper_pipe.setDensity(OpenStudio.convert(556, 'lb/ft^3', 'kg/m^3').get)
|
1089
|
+
copper_pipe.setSpecificHeat(OpenStudio.convert(0.092, 'Btu/lb*R', 'J/kg*K').get)
|
1090
|
+
copper_pipe.setThermalAbsorptance(0.9) # TODO: find reference for property
|
1091
|
+
copper_pipe.setSolarAbsorptance(0.7) # TODO: find reference for property
|
1092
|
+
copper_pipe.setVisibleAbsorptance(0.7) # TODO: find reference for property
|
1093
|
+
|
1094
|
+
# Construction for pipe
|
1095
|
+
pipe_construction = OpenStudio::Model::Construction.new(model)
|
1096
|
+
|
1097
|
+
# Add insulation material to insulated pipe
|
1098
|
+
if pipe_insulation_thickness > 0
|
1099
|
+
# Material for fiberglass insulation
|
1100
|
+
# R-value from Owens-Corning 1/2in fiberglass pipe insulation
|
1101
|
+
# https://www.grainger.com/product/OWENS-CORNING-1-2-Thick-40PP22
|
1102
|
+
# but modified until simulated heat loss = 17.7 Btu/hr/ft of pipe with 140F water and 70F air
|
1103
|
+
pipe_insulation_thickness_in = OpenStudio.convert(pipe_insulation_thickness, 'm', 'in').get
|
1104
|
+
insulation = OpenStudio::Model::StandardOpaqueMaterial.new(model)
|
1105
|
+
insulation.setName("Fiberglass batt #{pipe_insulation_thickness_in.round(2)}in")
|
1106
|
+
insulation.setRoughness('Smooth')
|
1107
|
+
insulation.setThickness(OpenStudio.convert(pipe_insulation_thickness_in, 'in', 'm').get)
|
1108
|
+
insulation.setConductivity(OpenStudio.convert(0.46, 'Btu*in/hr*ft^2*R', 'W/m*K').get)
|
1109
|
+
insulation.setDensity(OpenStudio.convert(0.7, 'lb/ft^3', 'kg/m^3').get)
|
1110
|
+
insulation.setSpecificHeat(OpenStudio.convert(0.2, 'Btu/lb*R', 'J/kg*K').get)
|
1111
|
+
insulation.setThermalAbsorptance(0.9) # Irrelevant for Pipe:Indoor; no radiation model is used
|
1112
|
+
insulation.setSolarAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used
|
1113
|
+
insulation.setVisibleAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used
|
1114
|
+
|
1115
|
+
pipe_construction.setName("Copper pipe 0.75in type L with #{pipe_insulation_thickness_in.round(2)}in fiberglass batt")
|
1116
|
+
pipe_construction.setLayers([insulation, copper_pipe])
|
1117
|
+
else
|
1118
|
+
pipe_construction.setName("Uninsulated copper pipe 0.75in type L")
|
1119
|
+
pipe_construction.setLayers([copper_pipe])
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
heat_loss_pipe = OpenStudio::Model::PipeIndoor.new(model)
|
1123
|
+
heat_loss_pipe.setName("#{swh_loop.name} Pipe #{pipe_length_ft}ft")
|
1124
|
+
heat_loss_pipe.setEnvironmentType('Schedule')
|
1125
|
+
# heat_loss_pipe.setAmbientTemperatureSchedule(swh_piping_air_temp_sch) # TODO: schedule type registry error for this setter
|
1126
|
+
heat_loss_pipe.setPointer(7, swh_piping_air_temp_sch.handle)
|
1127
|
+
# heat_loss_pipe.setAmbientAirVelocitySchedule(model.alwaysOffDiscreteSchedule) # TODO: schedule type registry error for this setter
|
1128
|
+
heat_loss_pipe.setPointer(8, swh_piping_air_velocity_sch.handle)
|
1129
|
+
heat_loss_pipe.setConstruction(pipe_construction)
|
1130
|
+
heat_loss_pipe.setPipeInsideDiameter(OpenStudio.convert(0.785, 'in', 'm').get)
|
1131
|
+
heat_loss_pipe.setPipeLength(OpenStudio.convert(pipe_length_ft, 'ft', 'm').get)
|
1132
|
+
|
1133
|
+
heat_loss_pipe.addToNode(swh_loop.demandInletNode)
|
1134
|
+
|
1135
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Added #{pipe_length_ft.round}ft of #{pipe_construction.name} losing heat to #{swh_piping_air_temp_f.round}F air to #{swh_loop.name}.")
|
1136
|
+
return true
|
1137
|
+
end
|
1138
|
+
|
630
1139
|
end
|