openstudio-standards 0.6.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/standards/OpenStudio_Standards-ashrae_90_1.xlsx +0 -0
- data/data/standards/manage_OpenStudio_Standards.rb +2 -49
- data/data/standards/openstudio_standards_duplicates_log.csv +1 -7962
- data/data/standards/test_performance_expected_dd_results.csv +2005 -97
- data/lib/openstudio-standards/create_typical/space_type_ratios.rb +47 -57
- data/lib/openstudio-standards/geometry/create.rb +8 -2
- data/lib/openstudio-standards/geometry/create_bar.rb +6 -3
- data/lib/openstudio-standards/geometry/create_shape.rb +1 -1
- data/lib/openstudio-standards/geometry/group.rb +1 -1
- data/lib/openstudio-standards/geometry/information.rb +1 -1
- data/lib/openstudio-standards/geometry/modify.rb +53 -1
- data/lib/openstudio-standards/infiltration/nist_infiltration.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +11 -11
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +11 -11
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.hvac_systems.rb +2 -2
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SuperTallBuilding.rb +44 -47
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.TallBuilding.rb +43 -48
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.CentralAirSourceHeatPump.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +44 -24
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.refrigeration.rb +24 -24
- data/lib/openstudio-standards/schedules/information.rb +1 -1
- data/lib/openstudio-standards/schedules/parametric.rb +1 -1
- data/lib/openstudio-standards/service_water_heating/create_piping_losses.rb +152 -0
- data/lib/openstudio-standards/service_water_heating/create_water_heater.rb +544 -0
- data/lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb +303 -0
- data/lib/openstudio-standards/service_water_heating/create_water_use.rb +95 -0
- data/lib/openstudio-standards/space/space.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +65 -70
- data/lib/openstudio-standards/standards/Standards.CoilCoolingWaterToAirHeatPumpEquationFit.rb +12 -14
- data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +16 -5
- data/lib/openstudio-standards/standards/Standards.Model.rb +2 -2
- data/lib/openstudio-standards/standards/Standards.PlanarSurface.rb +10 -2
- data/lib/openstudio-standards/{prototypes/common/objects/Prototype.Model.swh.rb → standards/Standards.ServiceWaterHeating.rb} +209 -139
- data/lib/openstudio-standards/standards/Standards.Surface.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.ZoneHVACComponent.rb +4 -8
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.Model.rb +2 -2
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_properties.json +22251 -12963
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_sets.json +91 -91
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.construction_properties.json +8981 -5228
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.construction_properties.json +8935 -5182
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_properties.json +7281 -5391
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_sets.json +91 -91
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_properties.json +9005 -15215
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_sets.json +136 -136
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_properties.json +8717 -17168
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_sets.json +136 -136
- data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.constructions.json +1941 -651
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.construction_properties.json +135 -135
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.construction_properties.json +135 -135
- data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/data/nrel_zne_ready_2017.construction_properties.json +36 -36
- data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/data/ze_aedg_multifamily.construction_properties.json +36 -36
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb +377 -99
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb +2 -2
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.Model.rb +3 -3
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.construction_properties.json +6889 -4044
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_constructions.json +108 -108
- data/lib/openstudio-standards/standards/cbes/cbes_pre_1978/data/cbes_pre_1978.construction_properties.json +16 -16
- data/lib/openstudio-standards/standards/cbes/cbes_t24_1978/data/cbes_t24_1978.construction_properties.json +16 -16
- data/lib/openstudio-standards/standards/cbes/cbes_t24_1992/data/cbes_t24_1992.construction_properties.json +16 -16
- data/lib/openstudio-standards/standards/cbes/cbes_t24_2001/data/cbes_t24_2001.construction_properties.json +16 -16
- data/lib/openstudio-standards/standards/cbes/cbes_t24_2005/data/cbes_t24_2005.construction_properties.json +16 -16
- data/lib/openstudio-standards/standards/cbes/cbes_t24_2008/data/cbes_t24_2008.construction_properties.json +16 -16
- data/lib/openstudio-standards/standards/cbes/data/cbes.constructions.json +142 -142
- data/lib/openstudio-standards/standards/deer/data/deer.constructions.json +5 -1551
- data/lib/openstudio-standards/standards/deer/data/deer.materials.json +40 -0
- data/lib/openstudio-standards/standards/deer/deer_1985/data/deer_1985.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_1996/data/deer_1996.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2003/data/deer_2003.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2007/data/deer_2007.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2011/data/deer_2011.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2014/data/deer_2014.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2015/data/deer_2015.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2017/data/deer_2017.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2020/data/deer_2020.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2025/data/deer_2025.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2030/data/deer_2030.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2035/data/deer_2035.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2040/data/deer_2040.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2045/data/deer_2045.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2050/data/deer_2050.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2055/data/deer_2055.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2060/data/deer_2060.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2065/data/deer_2065.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2070/data/deer_2070.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_2075/data/deer_2075.motors.json +88 -8
- data/lib/openstudio-standards/standards/deer/deer_pre_1975/data/deer_pre_1975.motors.json +88 -8
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb +17 -0
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_systems.rb +2 -1
- data/lib/openstudio-standards/standards/necb/ECMS/ecms.rb +4 -4
- data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +61 -88
- data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +3 -2
- data/lib/openstudio-standards/standards/necb/NECB2011/data/boiler_fuel_type_sets.json +54 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPMidriseApartment.osm +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPMultiTower.osm +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPPointTower.osm +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPTownHouse.osm +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +4 -4
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +4 -4
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +32 -24
- data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +89 -15
- data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +5 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +22 -65
- data/lib/openstudio-standards/standards/necb/NECB2011/system_fuels.rb +19 -0
- data/lib/openstudio-standards/standards/necb/common/btap_data.rb +56 -2
- data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +3 -1
- data/lib/openstudio-standards/standards/necb/common/construction_defaults.osm +2 -2
- data/lib/openstudio-standards/standards/necb/docs/air_system_names_method.md +127 -0
- data/lib/openstudio-standards/thermal_zone/thermal_zone.rb +1 -1
- data/lib/openstudio-standards/utilities/template_measure/resources/BTAPMeasureHelper.rb +1 -1
- data/lib/openstudio-standards/version.rb +1 -1
- data/lib/openstudio-standards/weather/information.rb +61 -5
- data/lib/openstudio-standards/weather/modify.rb +1 -1
- data/lib/openstudio-standards.rb +5 -3
- metadata +12 -63
- data/data/standards/OpenStudio_Standards-deer.xlsx +0 -0
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +0 -1100
- data/lib/openstudio-standards/service_water_heating/component.rb +0 -189
@@ -1,1100 +0,0 @@
|
|
1
|
-
class Standard
|
2
|
-
# @!group ServiceWaterHeating
|
3
|
-
|
4
|
-
# Creates a service water heating loop.
|
5
|
-
#
|
6
|
-
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
7
|
-
# @param system_name [String] the name of the system, or nil in which case it will be defaulted
|
8
|
-
# @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
|
9
|
-
# zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
|
10
|
-
# @param service_water_temperature [Double] service water temperature, in C
|
11
|
-
# @param service_water_pump_head [Double] service water pump head, in Pa
|
12
|
-
# @param service_water_pump_motor_efficiency [Double] service water pump motor efficiency, as decimal.
|
13
|
-
# @param water_heater_capacity [Double] water heater heating capacity, in W
|
14
|
-
# @param water_heater_volume [Double] water heater volume, in m^3
|
15
|
-
# @param water_heater_fuel [String] water heater fuel. Valid choices are NaturalGas, Electricity
|
16
|
-
# @param parasitic_fuel_consumption_rate [Double] the parasitic fuel consumption rate of the water heater, in W
|
17
|
-
# @param add_pipe_losses [Boolean] if true, add piping and associated heat losses to system. If false, add no pipe heat losses
|
18
|
-
# @param floor_area_served [Double] area served by the SWH loop, in m^2. Used for pipe loss piping length estimation
|
19
|
-
# @param number_of_stories [Integer] number of stories served by the SWH loop. Used for pipe loss piping length estimation
|
20
|
-
# @param pipe_insulation_thickness [Double] thickness of the fiberglass batt pipe insulation, in m. Use 0 for uninsulated pipes
|
21
|
-
# @param number_water_heaters [Double] the number of water heaters represented by the capacity and volume inputs.
|
22
|
-
# Used to modify efficiencies for water heaters based on individual component size while avoiding having to model
|
23
|
-
# lots of individual water heaters (for runtime sake).
|
24
|
-
# @return [OpenStudio::Model::PlantLoop]
|
25
|
-
# the resulting service water loop.
|
26
|
-
def model_add_swh_loop(model,
|
27
|
-
system_name,
|
28
|
-
water_heater_thermal_zone,
|
29
|
-
service_water_temperature,
|
30
|
-
service_water_pump_head,
|
31
|
-
service_water_pump_motor_efficiency,
|
32
|
-
water_heater_capacity,
|
33
|
-
water_heater_volume,
|
34
|
-
water_heater_fuel,
|
35
|
-
parasitic_fuel_consumption_rate,
|
36
|
-
add_pipe_losses = false,
|
37
|
-
floor_area_served = 465,
|
38
|
-
number_of_stories = 1,
|
39
|
-
pipe_insulation_thickness = 0.0127, # 1/2in
|
40
|
-
number_water_heaters = 1)
|
41
|
-
# Service water heating loop
|
42
|
-
service_water_loop = OpenStudio::Model::PlantLoop.new(model)
|
43
|
-
service_water_loop.setMinimumLoopTemperature(10.0)
|
44
|
-
service_water_loop.setMaximumLoopTemperature(60.0)
|
45
|
-
|
46
|
-
if system_name.nil?
|
47
|
-
service_water_loop.setName('Service Water Loop')
|
48
|
-
else
|
49
|
-
service_water_loop.setName(system_name)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Temperature schedule type limits
|
53
|
-
temp_sch_type_limits = OpenstudioStandards::Schedules.create_schedule_type_limits(model,
|
54
|
-
name: 'Temperature Schedule Type Limits',
|
55
|
-
lower_limit_value: 0.0,
|
56
|
-
upper_limit_value: 100.0,
|
57
|
-
numeric_type: 'Continuous',
|
58
|
-
unit_type: 'Temperature')
|
59
|
-
|
60
|
-
# Service water heating loop controls
|
61
|
-
swh_temp_c = service_water_temperature
|
62
|
-
swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
|
63
|
-
swh_delta_t_r = 9.0 # 9F delta-T
|
64
|
-
swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
|
65
|
-
swh_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
66
|
-
swh_temp_c,
|
67
|
-
name: "Service Water Loop Temp - #{swh_temp_f.round}F",
|
68
|
-
schedule_type_limit: 'Temperature')
|
69
|
-
swh_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
|
70
|
-
swh_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, swh_temp_sch)
|
71
|
-
swh_stpt_manager.setName('Service hot water setpoint manager')
|
72
|
-
swh_stpt_manager.addToNode(service_water_loop.supplyOutletNode)
|
73
|
-
sizing_plant = service_water_loop.sizingPlant
|
74
|
-
sizing_plant.setLoopType('Heating')
|
75
|
-
sizing_plant.setDesignLoopExitTemperature(swh_temp_c)
|
76
|
-
sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)
|
77
|
-
|
78
|
-
# Determine if circulating or non-circulating based on supplied head pressure
|
79
|
-
swh_pump_head_press_pa = service_water_pump_head
|
80
|
-
circulating = true
|
81
|
-
if swh_pump_head_press_pa.nil? || swh_pump_head_press_pa <= 1
|
82
|
-
# As if there is no circulation pump
|
83
|
-
swh_pump_head_press_pa = 0.001
|
84
|
-
service_water_pump_motor_efficiency = 1
|
85
|
-
circulating = false
|
86
|
-
end
|
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
|
98
|
-
swh_pump.setRatedPumpHead(swh_pump_head_press_pa.to_f)
|
99
|
-
swh_pump.setMotorEfficiency(service_water_pump_motor_efficiency)
|
100
|
-
swh_pump.addToNode(service_water_loop.supplyInletNode)
|
101
|
-
|
102
|
-
water_heater = OpenstudioStandards::ServiceWaterHeating.model_add_water_heater(model,
|
103
|
-
water_heater_capacity: water_heater_capacity,
|
104
|
-
water_heater_volume: water_heater_volume,
|
105
|
-
water_heater_fuel: water_heater_fuel,
|
106
|
-
on_cycle_parasitic_fuel_consumption_rate: parasitic_fuel_consumption_rate,
|
107
|
-
off_cycle_parasitic_fuel_consumption_rate: parasitic_fuel_consumption_rate,
|
108
|
-
service_water_temperature: service_water_temperature,
|
109
|
-
service_water_temperature_schedule: swh_temp_sch,
|
110
|
-
set_peak_use_flowrate: false,
|
111
|
-
peak_flowrate: 0.0,
|
112
|
-
flowrate_schedule: nil,
|
113
|
-
water_heater_thermal_zone: water_heater_thermal_zone,
|
114
|
-
number_water_heaters: number_water_heaters)
|
115
|
-
service_water_loop.addSupplyBranchForComponent(water_heater)
|
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
|
-
|
127
|
-
# Service water heating loop bypass pipes
|
128
|
-
water_heater_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
129
|
-
service_water_loop.addSupplyBranchForComponent(water_heater_bypass_pipe)
|
130
|
-
coil_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
131
|
-
service_water_loop.addDemandBranchForComponent(coil_bypass_pipe)
|
132
|
-
supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
133
|
-
supply_outlet_pipe.addToNode(service_water_loop.supplyOutletNode)
|
134
|
-
demand_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
135
|
-
demand_outlet_pipe.addToNode(service_water_loop.demandOutletNode)
|
136
|
-
|
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
|
142
|
-
|
143
|
-
return service_water_loop
|
144
|
-
end
|
145
|
-
|
146
|
-
# Creates a heatpump water heater and attaches it to the supplied service water heating loop.
|
147
|
-
#
|
148
|
-
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
149
|
-
# @param type [String] valid option are 'WrappedCondenser' or 'PumpedCondenser' (default).
|
150
|
-
# The 'WrappedCondenser' uses a WaterHeaterStratified tank, 'PumpedCondenser' uses a WaterHeaterMixed tank.
|
151
|
-
# @param water_heater_capacity [Double] water heater capacity, in W
|
152
|
-
# @param water_heater_volume [Double] water heater volume, in m^3
|
153
|
-
# @param service_water_temperature [Double] water heater temperature, in C
|
154
|
-
# @param parasitic_fuel_consumption_rate [Double] water heater parasitic fuel consumption rate, in W
|
155
|
-
# @param swh_temp_sch [OpenStudio::Model::Schedule] the service water heating schedule. If nil, will be defaulted.
|
156
|
-
# @param set_peak_use_flowrate [Boolean] if true, the peak flow rate and flow rate schedule will be set.
|
157
|
-
# @param peak_flowrate [Double] in m^3/s
|
158
|
-
# @param flowrate_schedule [String] name of the flow rate schedule
|
159
|
-
# @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] zone to place water heater in.
|
160
|
-
# If nil, will be assumed in 70F air for heat loss.
|
161
|
-
# @param use_ems_control [Boolean] if true, use ems control logic if using a 'WrappedCondenser' style HPWH.
|
162
|
-
# @return [OpenStudio::Model::WaterHeaterMixed] the resulting water heater
|
163
|
-
def model_add_heatpump_water_heater(model,
|
164
|
-
type: 'PumpedCondenser',
|
165
|
-
water_heater_capacity: 500,
|
166
|
-
electric_backup_capacity: 4500,
|
167
|
-
water_heater_volume: OpenStudio.convert(80.0, 'gal', 'm^3').get,
|
168
|
-
service_water_temperature: OpenStudio.convert(125.0, 'F', 'C').get,
|
169
|
-
parasitic_fuel_consumption_rate: 3.0,
|
170
|
-
swh_temp_sch: nil,
|
171
|
-
cop: 2.8,
|
172
|
-
shr: 0.88,
|
173
|
-
tank_ua: 3.9,
|
174
|
-
set_peak_use_flowrate: false,
|
175
|
-
peak_flowrate: 0.0,
|
176
|
-
flowrate_schedule: nil,
|
177
|
-
water_heater_thermal_zone: nil,
|
178
|
-
use_ems_control: false)
|
179
|
-
|
180
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', 'Adding heat pump water heater')
|
181
|
-
|
182
|
-
# create heat pump water heater
|
183
|
-
if type == 'WrappedCondenser'
|
184
|
-
hpwh = OpenStudio::Model::WaterHeaterHeatPumpWrappedCondenser.new(model)
|
185
|
-
elsif type == 'PumpedCondenser'
|
186
|
-
hpwh = OpenStudio::Model::WaterHeaterHeatPump.new(model)
|
187
|
-
end
|
188
|
-
|
189
|
-
# calculate tank height and radius
|
190
|
-
water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get
|
191
|
-
hpwh_vol_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get
|
192
|
-
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
|
193
|
-
tank_radius = (0.9 * water_heater_volume / (Math::PI * tank_height))**0.5
|
194
|
-
tank_surface_area = 2.0 * Math::PI * tank_radius * (tank_radius + tank_height)
|
195
|
-
u_tank = (5.678 * tank_ua) / OpenStudio.convert(tank_surface_area, 'm^2', 'ft^2').get
|
196
|
-
hpwh.setName("#{hpwh_vol_gal.round}gal Heat Pump Water Heater - #{water_heater_capacity_kbtu_per_hr.round(0)}kBtu/hr")
|
197
|
-
|
198
|
-
# set min/max HPWH operating temperature limit
|
199
|
-
hpwh_op_min_temp_c = OpenStudio.convert(45.0, 'F', 'C').get
|
200
|
-
hpwh_op_max_temp_c = OpenStudio.convert(120.0, 'F', 'C').get
|
201
|
-
|
202
|
-
if type == 'WrappedCondenser'
|
203
|
-
hpwh.setMinimumInletAirTemperatureforCompressorOperation(hpwh_op_min_temp_c)
|
204
|
-
hpwh.setMaximumInletAirTemperatureforCompressorOperation(hpwh_op_max_temp_c)
|
205
|
-
# set sensor heights
|
206
|
-
if hpwh_vol_gal <= 50.0
|
207
|
-
hpwh.setDeadBandTemperatureDifference(0.5)
|
208
|
-
h_ue = (1 - (3.5 / 12.0)) * tank_height # in the 4th node of the tank (counting from top)
|
209
|
-
h_le = (1 - (10.5 / 12.0)) * tank_height # in the 11th node of the tank (counting from top)
|
210
|
-
h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
|
211
|
-
h_condbot = (1 - (10.99 / 12.0)) * tank_height # in the 11th node of the tank
|
212
|
-
h_hpctrl = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
|
213
|
-
hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl)
|
214
|
-
hpwh.setControlSensor1Weight(1.0)
|
215
|
-
hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl)
|
216
|
-
else
|
217
|
-
hpwh.setDeadBandTemperatureDifference(3.89)
|
218
|
-
h_ue = (1 - (3.5 / 12.0)) * tank_height # in the 3rd node of the tank (counting from top)
|
219
|
-
h_le = (1 - (9.5 / 12.0)) * tank_height # in the 10th node of the tank (counting from top)
|
220
|
-
h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
|
221
|
-
h_condbot = 0.01 # bottom node
|
222
|
-
h_hpctrl_up = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
|
223
|
-
h_hpctrl_low = (1 - (8.5 / 12.0)) * tank_height # in the 9th node of the tank
|
224
|
-
hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl_up)
|
225
|
-
hpwh.setControlSensor1Weight(0.75)
|
226
|
-
hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl_low)
|
227
|
-
end
|
228
|
-
hpwh.setCondenserBottomLocation(h_condbot)
|
229
|
-
hpwh.setCondenserTopLocation(h_condtop)
|
230
|
-
hpwh.setTankElementControlLogic('MutuallyExclusive')
|
231
|
-
hpwh.autocalculateEvaporatorAirFlowRate
|
232
|
-
elsif type == 'PumpedCondenser'
|
233
|
-
hpwh.setDeadBandTemperatureDifference(3.89)
|
234
|
-
hpwh.autosizeEvaporatorAirFlowRate
|
235
|
-
end
|
236
|
-
|
237
|
-
# set heat pump water heater properties
|
238
|
-
hpwh.setFanPlacement('DrawThrough')
|
239
|
-
hpwh.setOnCycleParasiticElectricLoad(0.0)
|
240
|
-
hpwh.setOffCycleParasiticElectricLoad(0.0)
|
241
|
-
hpwh.setParasiticHeatRejectionLocation('Outdoors')
|
242
|
-
|
243
|
-
# set temperature setpoint schedule
|
244
|
-
if swh_temp_sch.nil?
|
245
|
-
# temperature schedule type limits
|
246
|
-
temp_sch_type_limits = OpenstudioStandards::Schedules.create_schedule_type_limits(model,
|
247
|
-
name: 'Temperature Schedule Type Limits',
|
248
|
-
lower_limit_value: 0.0,
|
249
|
-
upper_limit_value: 100.0,
|
250
|
-
numeric_type: 'Continuous',
|
251
|
-
unit_type: 'Temperature')
|
252
|
-
# service water heating loop controls
|
253
|
-
swh_temp_c = service_water_temperature
|
254
|
-
swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
|
255
|
-
swh_delta_t_r = 9.0 # 9F delta-T
|
256
|
-
swh_temp_c = OpenStudio.convert(swh_temp_f, 'F', 'C').get
|
257
|
-
swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
|
258
|
-
swh_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
259
|
-
swh_temp_c,
|
260
|
-
name: "Heat Pump Water Heater Temp - #{swh_temp_f.round}F",
|
261
|
-
schedule_type_limit: 'Temperature')
|
262
|
-
swh_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
|
263
|
-
end
|
264
|
-
hpwh.setCompressorSetpointTemperatureSchedule(swh_temp_sch)
|
265
|
-
|
266
|
-
# coil curves
|
267
|
-
hpwh_cap = OpenStudio::Model::CurveBiquadratic.new(model)
|
268
|
-
hpwh_cap.setName('HPWH-Cap-fT')
|
269
|
-
hpwh_cap.setCoefficient1Constant(0.563)
|
270
|
-
hpwh_cap.setCoefficient2x(0.0437)
|
271
|
-
hpwh_cap.setCoefficient3xPOW2(0.000039)
|
272
|
-
hpwh_cap.setCoefficient4y(0.0055)
|
273
|
-
hpwh_cap.setCoefficient5yPOW2(-0.000148)
|
274
|
-
hpwh_cap.setCoefficient6xTIMESY(-0.000145)
|
275
|
-
hpwh_cap.setMinimumValueofx(0.0)
|
276
|
-
hpwh_cap.setMaximumValueofx(100.0)
|
277
|
-
hpwh_cap.setMinimumValueofy(0.0)
|
278
|
-
hpwh_cap.setMaximumValueofy(100.0)
|
279
|
-
|
280
|
-
hpwh_cop = OpenStudio::Model::CurveBiquadratic.new(model)
|
281
|
-
hpwh_cop.setName('HPWH-COP-fT')
|
282
|
-
hpwh_cop.setCoefficient1Constant(1.1332)
|
283
|
-
hpwh_cop.setCoefficient2x(0.063)
|
284
|
-
hpwh_cop.setCoefficient3xPOW2(-0.0000979)
|
285
|
-
hpwh_cop.setCoefficient4y(-0.00972)
|
286
|
-
hpwh_cop.setCoefficient5yPOW2(-0.0000214)
|
287
|
-
hpwh_cop.setCoefficient6xTIMESY(-0.000686)
|
288
|
-
hpwh_cop.setMinimumValueofx(0.0)
|
289
|
-
hpwh_cop.setMaximumValueofx(100.0)
|
290
|
-
hpwh_cop.setMinimumValueofy(0.0)
|
291
|
-
hpwh_cop.setMaximumValueofy(100.0)
|
292
|
-
|
293
|
-
# create DX coil object
|
294
|
-
if type == 'WrappedCondenser'
|
295
|
-
coil = hpwh.dXCoil.to_CoilWaterHeatingAirToWaterHeatPumpWrapped.get
|
296
|
-
coil.setRatedCondenserWaterTemperature(48.89)
|
297
|
-
coil.autocalculateRatedEvaporatorAirFlowRate
|
298
|
-
elsif type == 'PumpedCondenser'
|
299
|
-
coil = hpwh.dXCoil.to_CoilWaterHeatingAirToWaterHeatPump.get
|
300
|
-
coil.autosizeRatedEvaporatorAirFlowRate
|
301
|
-
end
|
302
|
-
|
303
|
-
# set coil properties
|
304
|
-
coil.setName("#{hpwh.name} Coil")
|
305
|
-
coil.setRatedHeatingCapacity(water_heater_capacity)
|
306
|
-
coil.setRatedCOP(cop)
|
307
|
-
coil.setRatedSensibleHeatRatio(shr)
|
308
|
-
coil.setRatedEvaporatorInletAirDryBulbTemperature(OpenStudio.convert(67.5, 'F', 'C').get)
|
309
|
-
coil.setRatedEvaporatorInletAirWetBulbTemperature(OpenStudio.convert(56.426, 'F', 'C').get)
|
310
|
-
coil.setEvaporatorFanPowerIncludedinRatedCOP(true)
|
311
|
-
coil.setEvaporatorAirTemperatureTypeforCurveObjects('WetBulbTemperature')
|
312
|
-
coil.setHeatingCapacityFunctionofTemperatureCurve(hpwh_cap)
|
313
|
-
coil.setHeatingCOPFunctionofTemperatureCurve(hpwh_cop)
|
314
|
-
coil.setMaximumAmbientTemperatureforCrankcaseHeaterOperation(0.0)
|
315
|
-
|
316
|
-
# set tank properties
|
317
|
-
if type == 'WrappedCondenser'
|
318
|
-
tank = hpwh.tank.to_WaterHeaterStratified.get
|
319
|
-
tank.setTankHeight(tank_height)
|
320
|
-
tank.setHeaterPriorityControl('MasterSlave')
|
321
|
-
if hpwh_vol_gal <= 50.0
|
322
|
-
tank.setHeater1DeadbandTemperatureDifference(25.0)
|
323
|
-
tank.setHeater2DeadbandTemperatureDifference(30.0)
|
324
|
-
else
|
325
|
-
tank.setHeater1DeadbandTemperatureDifference(18.5)
|
326
|
-
tank.setHeater2DeadbandTemperatureDifference(3.89)
|
327
|
-
end
|
328
|
-
hpwh_bottom_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
|
329
|
-
hpwh_bottom_element_sp.setName("#{hpwh.name} BottomElementSetpoint")
|
330
|
-
hpwh_top_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
|
331
|
-
hpwh_top_element_sp.setName("#{hpwh.name} TopElementSetpoint")
|
332
|
-
tank.setHeater1Capacity(electric_backup_capacity)
|
333
|
-
tank.setHeater1Height(h_ue)
|
334
|
-
tank.setHeater1SetpointTemperatureSchedule(hpwh_top_element_sp) # Overwritten later by EMS
|
335
|
-
tank.setHeater2Capacity(electric_backup_capacity)
|
336
|
-
tank.setHeater2Height(h_le)
|
337
|
-
tank.setHeater2SetpointTemperatureSchedule(hpwh_bottom_element_sp)
|
338
|
-
tank.setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(u_tank)
|
339
|
-
tank.setNumberofNodes(12)
|
340
|
-
tank.setAdditionalDestratificationConductivity(0)
|
341
|
-
tank.setNode1AdditionalLossCoefficient(0)
|
342
|
-
tank.setNode2AdditionalLossCoefficient(0)
|
343
|
-
tank.setNode3AdditionalLossCoefficient(0)
|
344
|
-
tank.setNode4AdditionalLossCoefficient(0)
|
345
|
-
tank.setNode5AdditionalLossCoefficient(0)
|
346
|
-
tank.setNode6AdditionalLossCoefficient(0)
|
347
|
-
tank.setNode7AdditionalLossCoefficient(0)
|
348
|
-
tank.setNode8AdditionalLossCoefficient(0)
|
349
|
-
tank.setNode9AdditionalLossCoefficient(0)
|
350
|
-
tank.setNode10AdditionalLossCoefficient(0)
|
351
|
-
tank.setNode11AdditionalLossCoefficient(0)
|
352
|
-
tank.setNode12AdditionalLossCoefficient(0)
|
353
|
-
tank.setUseSideDesignFlowRate(0.9 * water_heater_volume / 60.1)
|
354
|
-
tank.setSourceSideDesignFlowRate(0)
|
355
|
-
tank.setSourceSideFlowControlMode('')
|
356
|
-
tank.setSourceSideInletHeight(0)
|
357
|
-
tank.setSourceSideOutletHeight(0)
|
358
|
-
elsif type == 'PumpedCondenser'
|
359
|
-
tank = hpwh.tank.to_WaterHeaterMixed.get
|
360
|
-
tank.setDeadbandTemperatureDifference(3.89)
|
361
|
-
tank.setHeaterControlType('Cycle')
|
362
|
-
tank.setHeaterMaximumCapacity(electric_backup_capacity)
|
363
|
-
end
|
364
|
-
tank.setName("#{hpwh.name} Tank")
|
365
|
-
tank.setEndUseSubcategory('Service Hot Water')
|
366
|
-
tank.setTankVolume(0.9 * water_heater_volume)
|
367
|
-
tank.setMaximumTemperatureLimit(90.0)
|
368
|
-
tank.setHeaterFuelType('Electricity')
|
369
|
-
tank.setHeaterThermalEfficiency(1.0)
|
370
|
-
tank.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
371
|
-
tank.setOffCycleParasiticFuelType('Electricity')
|
372
|
-
tank.setOnCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
373
|
-
tank.setOnCycleParasiticFuelType('Electricity')
|
374
|
-
|
375
|
-
# set fan properties
|
376
|
-
fan = hpwh.fan.to_FanOnOff.get
|
377
|
-
fan.setName("#{hpwh.name} Fan")
|
378
|
-
fan_power = 0.0462 # watts per cfm
|
379
|
-
if hpwh_vol_gal <= 50.0
|
380
|
-
fan.setFanEfficiency(23.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
|
381
|
-
fan.setPressureRise(23.0)
|
382
|
-
else
|
383
|
-
fan.setFanEfficiency(65.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
|
384
|
-
fan.setPressureRise(65.0)
|
385
|
-
end
|
386
|
-
# determine maximum flow rate from water heater capacity
|
387
|
-
# use 5.035E-5 m^3/s/W from EnergyPlus used to autocalculate the evaporator air flow rate in WaterHeater:HeatPump:PumpedCondenser and Coil:WaterHeating:AirToWaterHeatPump:Pumped
|
388
|
-
fan_flow_rate_m3_per_s = water_heater_capacity * 5.035e-5
|
389
|
-
fan.setMaximumFlowRate(fan_flow_rate_m3_per_s)
|
390
|
-
fan.setMotorEfficiency(1.0)
|
391
|
-
fan.setMotorInAirstreamFraction(1.0)
|
392
|
-
fan.setEndUseSubcategory('Service Hot Water')
|
393
|
-
|
394
|
-
if water_heater_thermal_zone.nil?
|
395
|
-
# add in schedules for Tamb, RHamb, and the compressor
|
396
|
-
# assume the water heater is indoors at 70F for now
|
397
|
-
default_water_heater_ambient_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
398
|
-
OpenStudio.convert(70.0, 'F', 'C').get,
|
399
|
-
name: 'Water Heater Ambient Temp Schedule 70F',
|
400
|
-
schedule_type_limit: 'Temperature')
|
401
|
-
if temp_sch_type_limits.nil?
|
402
|
-
temp_sch_type_limits = OpenstudioStandards::Schedules.create_schedule_type_limits(model,
|
403
|
-
name: 'Temperature Schedule Type Limits',
|
404
|
-
lower_limit_value: 0.0,
|
405
|
-
upper_limit_value: 100.0,
|
406
|
-
numeric_type: 'Continuous',
|
407
|
-
unit_type: 'Temperature')
|
408
|
-
end
|
409
|
-
default_water_heater_ambient_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
|
410
|
-
tank.setAmbientTemperatureIndicator('Schedule')
|
411
|
-
tank.setAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
|
412
|
-
tank.resetAmbientTemperatureThermalZone
|
413
|
-
hpwh_rhamb = OpenStudio::Model::ScheduleConstant.new(model)
|
414
|
-
hpwh_rhamb.setName("#{hpwh.name} Ambient Humidity Schedule")
|
415
|
-
hpwh_rhamb.setValue(0.5)
|
416
|
-
hpwh.setInletAirConfiguration('Schedule')
|
417
|
-
hpwh.setInletAirTemperatureSchedule(default_water_heater_ambient_temp_sch)
|
418
|
-
hpwh.setInletAirHumiditySchedule(hpwh_rhamb)
|
419
|
-
hpwh.setCompressorLocation('Schedule')
|
420
|
-
hpwh.setCompressorAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
|
421
|
-
else
|
422
|
-
hpwh.addToThermalZone(water_heater_thermal_zone)
|
423
|
-
hpwh.setInletAirConfiguration('ZoneAirOnly')
|
424
|
-
hpwh.setCompressorLocation('Zone')
|
425
|
-
tank.setAmbientTemperatureIndicator('ThermalZone')
|
426
|
-
tank.setAmbientTemperatureThermalZone(water_heater_thermal_zone)
|
427
|
-
tank.resetAmbientTemperatureSchedule
|
428
|
-
end
|
429
|
-
|
430
|
-
if set_peak_use_flowrate
|
431
|
-
rated_flow_rate_m3_per_s = peak_flowrate
|
432
|
-
rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
|
433
|
-
tank.setPeakUseFlowRate(rated_flow_rate_m3_per_s)
|
434
|
-
schedule = model_add_schedule(model, flowrate_schedule)
|
435
|
-
tank.setUseFlowRateFractionSchedule(schedule)
|
436
|
-
end
|
437
|
-
|
438
|
-
# add EMS for overriding HPWH setpoints schedules (for upper/lower heating element in water tank and compressor in heat pump)
|
439
|
-
if type == 'WrappedCondenser' && use_ems_control
|
440
|
-
hpwh_name_ems_friendly = ems_friendly_name(hpwh.name)
|
441
|
-
|
442
|
-
# create an ambient temperature sensor for the air that blows through the HPWH evaporator
|
443
|
-
if water_heater_thermal_zone.nil?
|
444
|
-
# assume the condenser is outside
|
445
|
-
amb_temp_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Outdoor Air Drybulb Temperature')
|
446
|
-
amb_temp_sensor.setName("#{hpwh_name_ems_friendly}_amb_temp")
|
447
|
-
amb_temp_sensor.setKeyName('Environment')
|
448
|
-
else
|
449
|
-
amb_temp_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Zone Mean Air Temperature')
|
450
|
-
amb_temp_sensor.setName("#{hpwh_name_ems_friendly}_amb_temp")
|
451
|
-
amb_temp_sensor.setKeyName(water_heater_thermal_zone.name.to_s)
|
452
|
-
end
|
453
|
-
|
454
|
-
# create actuator for heat pump compressor
|
455
|
-
if swh_temp_sch.to_ScheduleConstant.is_initialized
|
456
|
-
swh_temp_sch = swh_temp_sch.to_ScheduleConstant.get
|
457
|
-
schedule_type = 'Schedule:Constant'
|
458
|
-
elsif swh_temp_sch.to_ScheduleCompact.is_initialized
|
459
|
-
swh_temp_sch = swh_temp_sch.to_ScheduleCompact.get
|
460
|
-
schedule_type = 'Schedule:Compact'
|
461
|
-
elsif swh_temp_sch.to_ScheduleRuleset.is_initialized
|
462
|
-
swh_temp_sch = swh_temp_sch.to_ScheduleRuleset.get
|
463
|
-
schedule_type = 'Schedule:Year'
|
464
|
-
else
|
465
|
-
OpenStudio.logFree(OpenStudio::Error, 'openstudio.Prototype.ServiceWaterHeating', "Unsupported schedule type for HPWH setpoint schedule #{swh_temp_sch.name}.")
|
466
|
-
return false
|
467
|
-
end
|
468
|
-
hpwhschedoverride_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(swh_temp_sch, schedule_type, 'Schedule Value')
|
469
|
-
hpwhschedoverride_actuator.setName("#{hpwh_name_ems_friendly}_HPWHSchedOverride")
|
470
|
-
|
471
|
-
# create actuator for lower heating element in water tank
|
472
|
-
leschedoverride_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(hpwh_bottom_element_sp, 'Schedule:Constant', 'Schedule Value')
|
473
|
-
leschedoverride_actuator.setName("#{hpwh_name_ems_friendly}_LESchedOverride")
|
474
|
-
|
475
|
-
# create actuator for upper heating element in water tank
|
476
|
-
ueschedoverride_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(hpwh_top_element_sp, 'Schedule:Constant', 'Schedule Value')
|
477
|
-
ueschedoverride_actuator.setName("#{hpwh_name_ems_friendly}_UESchedOverride")
|
478
|
-
|
479
|
-
# create sensor for heat pump compressor
|
480
|
-
t_set_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
|
481
|
-
t_set_sensor.setName("#{hpwh_name_ems_friendly}_T_set")
|
482
|
-
t_set_sensor.setKeyName(swh_temp_sch.name.to_s)
|
483
|
-
|
484
|
-
# define control configuration
|
485
|
-
t_offset = 9.0 # deg-C
|
486
|
-
|
487
|
-
# get tank specifications
|
488
|
-
upper_element_db = tank.heater1DeadbandTemperatureDifference
|
489
|
-
|
490
|
-
# define control logic
|
491
|
-
hpwh_ctrl_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
|
492
|
-
hpwh_ctrl_program.setName("#{hpwh_name_ems_friendly}_Control")
|
493
|
-
hpwh_ctrl_program.addLine("SET #{hpwhschedoverride_actuator.name} = #{t_set_sensor.name}")
|
494
|
-
# lockout hp when ambient temperature is either too high or too low
|
495
|
-
hpwh_ctrl_program.addLine("IF (#{amb_temp_sensor.name}<#{hpwh_op_min_temp_c}) || (#{amb_temp_sensor.name}>#{hpwh_op_max_temp_c})")
|
496
|
-
hpwh_ctrl_program.addLine("SET #{ueschedoverride_actuator.name} = #{t_set_sensor.name}")
|
497
|
-
hpwh_ctrl_program.addLine("SET #{leschedoverride_actuator.name} = #{t_set_sensor.name}")
|
498
|
-
hpwh_ctrl_program.addLine('ELSE')
|
499
|
-
# upper element setpoint temperature
|
500
|
-
hpwh_ctrl_program.addLine("SET #{ueschedoverride_actuator.name} = #{t_set_sensor.name} - #{t_offset}")
|
501
|
-
# upper element cut-in temperature
|
502
|
-
hpwh_ctrl_program.addLine("SET #{ueschedoverride_actuator.name}_cut_in = #{ueschedoverride_actuator.name} - #{upper_element_db}")
|
503
|
-
# lower element disabled
|
504
|
-
hpwh_ctrl_program.addLine("SET #{leschedoverride_actuator.name} = 0")
|
505
|
-
# lower element disabled
|
506
|
-
hpwh_ctrl_program.addLine("SET #{leschedoverride_actuator.name}_cut_in = 0")
|
507
|
-
hpwh_ctrl_program.addLine('ENDIF')
|
508
|
-
|
509
|
-
# create a program calling manager
|
510
|
-
program_calling_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
|
511
|
-
program_calling_manager.setName("#{hpwh_name_ems_friendly}_ProgramManager")
|
512
|
-
program_calling_manager.setCallingPoint('InsideHVACSystemIterationLoop')
|
513
|
-
program_calling_manager.addProgram(hpwh_ctrl_program)
|
514
|
-
end
|
515
|
-
|
516
|
-
return hpwh
|
517
|
-
end
|
518
|
-
|
519
|
-
# Creates a booster water heater and attaches it
|
520
|
-
# to the supplied service water heating loop.
|
521
|
-
#
|
522
|
-
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
523
|
-
# @param main_service_water_loop [OpenStudio::Model::PlantLoop]
|
524
|
-
# the main service water loop that this booster assists.
|
525
|
-
# @param water_heater_capacity [Double] water heater capacity, in W
|
526
|
-
# @param water_heater_volume [Double] water heater volume, in m^3
|
527
|
-
# @param water_heater_fuel [Double] valid choices are
|
528
|
-
# Gas, Electric
|
529
|
-
# @param booster_water_temperature [Double] water heater temperature, in C
|
530
|
-
# @param parasitic_fuel_consumption_rate [Double] water heater parasitic
|
531
|
-
# fuel consumption rate, in W
|
532
|
-
# @param booster_water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
|
533
|
-
# zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
|
534
|
-
# @return [OpenStudio::Model::PlantLoop]
|
535
|
-
# the resulting booster water loop.
|
536
|
-
def model_add_swh_booster(model,
|
537
|
-
main_service_water_loop,
|
538
|
-
water_heater_capacity,
|
539
|
-
water_heater_volume,
|
540
|
-
water_heater_fuel,
|
541
|
-
booster_water_temperature,
|
542
|
-
parasitic_fuel_consumption_rate,
|
543
|
-
booster_water_heater_thermal_zone)
|
544
|
-
|
545
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding booster water heater to #{main_service_water_loop.name}")
|
546
|
-
|
547
|
-
# Booster water heating loop
|
548
|
-
booster_service_water_loop = OpenStudio::Model::PlantLoop.new(model)
|
549
|
-
booster_service_water_loop.setName('Service Water Loop')
|
550
|
-
|
551
|
-
# Temperature schedule type limits
|
552
|
-
temp_sch_type_limits = OpenstudioStandards::Schedules.create_schedule_type_limits(model,
|
553
|
-
name: 'Temperature Schedule Type Limits',
|
554
|
-
lower_limit_value: 0.0,
|
555
|
-
upper_limit_value: 100.0,
|
556
|
-
numeric_type: 'Continuous',
|
557
|
-
unit_type: 'Temperature')
|
558
|
-
|
559
|
-
# Service water heating loop controls
|
560
|
-
swh_temp_c = booster_water_temperature
|
561
|
-
swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
|
562
|
-
swh_delta_t_r = 9 # 9F delta-T
|
563
|
-
swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
|
564
|
-
swh_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
565
|
-
swh_temp_c,
|
566
|
-
name: "Service Water Booster Temp - #{swh_temp_f}F",
|
567
|
-
schedule_type_limit: 'Temperature')
|
568
|
-
swh_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
|
569
|
-
swh_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, swh_temp_sch)
|
570
|
-
swh_stpt_manager.setName('Hot water booster setpoint manager')
|
571
|
-
swh_stpt_manager.addToNode(booster_service_water_loop.supplyOutletNode)
|
572
|
-
sizing_plant = booster_service_water_loop.sizingPlant
|
573
|
-
sizing_plant.setLoopType('Heating')
|
574
|
-
sizing_plant.setDesignLoopExitTemperature(swh_temp_c)
|
575
|
-
sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)
|
576
|
-
|
577
|
-
# Booster water heating pump
|
578
|
-
swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
|
579
|
-
swh_pump.setName('Booster Water Loop Pump')
|
580
|
-
swh_pump.setRatedPumpHead(0.0) # As if there is no circulation pump
|
581
|
-
swh_pump.setRatedPowerConsumption(0.0) # As if there is no circulation pump
|
582
|
-
swh_pump.setMotorEfficiency(1)
|
583
|
-
swh_pump.setPumpControlType('Continuous')
|
584
|
-
swh_pump.setMinimumFlowRate(0.0)
|
585
|
-
swh_pump.addToNode(booster_service_water_loop.supplyInletNode)
|
586
|
-
|
587
|
-
# Water heater
|
588
|
-
# @todo Standards - Change water heater methodology to follow
|
589
|
-
# 'Model Enhancements Appendix A.'
|
590
|
-
water_heater_capacity_btu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'Btu/hr').get
|
591
|
-
water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'kBtu/hr').get
|
592
|
-
water_heater_vol_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get
|
593
|
-
|
594
|
-
# Water heater depends on the fuel type
|
595
|
-
water_heater = OpenStudio::Model::WaterHeaterMixed.new(model)
|
596
|
-
water_heater.setName("#{water_heater_vol_gal}gal #{water_heater_fuel} Booster Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
|
597
|
-
water_heater.setTankVolume(OpenStudio.convert(water_heater_vol_gal, 'gal', 'm^3').get)
|
598
|
-
water_heater.setSetpointTemperatureSchedule(swh_temp_sch)
|
599
|
-
water_heater.setDeadbandTemperatureDifference(2.0)
|
600
|
-
water_heater.setEndUseSubcategory('Booster')
|
601
|
-
|
602
|
-
if booster_water_heater_thermal_zone.nil?
|
603
|
-
# Assume the water heater is indoors at 70F or 72F
|
604
|
-
case template
|
605
|
-
when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013', '90.1-2016', '90.1-2019'
|
606
|
-
indoor_temp = 71.6
|
607
|
-
else
|
608
|
-
indoor_temp = 70.0
|
609
|
-
end
|
610
|
-
default_water_heater_ambient_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
611
|
-
OpenStudio.convert(indoor_temp, 'F', 'C').get,
|
612
|
-
name: "Water Heater Ambient Temp Schedule #{indoor_temp}F",
|
613
|
-
schedule_type_limit: 'Temperature')
|
614
|
-
default_water_heater_ambient_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
|
615
|
-
water_heater.setAmbientTemperatureIndicator('Schedule')
|
616
|
-
water_heater.setAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
|
617
|
-
water_heater.resetAmbientTemperatureThermalZone
|
618
|
-
else
|
619
|
-
water_heater.setAmbientTemperatureIndicator('ThermalZone')
|
620
|
-
water_heater.setAmbientTemperatureThermalZone(booster_water_heater_thermal_zone)
|
621
|
-
water_heater.resetAmbientTemperatureSchedule
|
622
|
-
end
|
623
|
-
|
624
|
-
water_heater.setMaximumTemperatureLimit(swh_temp_c)
|
625
|
-
water_heater.setDeadbandTemperatureDifference(OpenStudio.convert(3.6, 'R', 'K').get)
|
626
|
-
water_heater.setHeaterControlType('Cycle')
|
627
|
-
water_heater.setHeaterMaximumCapacity(OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'W').get)
|
628
|
-
water_heater.setOffCycleParasiticHeatFractiontoTank(0.8)
|
629
|
-
water_heater.setIndirectWaterHeatingRecoveryTime(1.5) # 1.5hrs
|
630
|
-
if water_heater_fuel == 'Electricity'
|
631
|
-
water_heater.setHeaterFuelType('Electricity')
|
632
|
-
water_heater.setHeaterThermalEfficiency(1.0)
|
633
|
-
water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
634
|
-
water_heater.setOnCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
635
|
-
water_heater.setOffCycleParasiticFuelType('Electricity')
|
636
|
-
water_heater.setOnCycleParasiticFuelType('Electricity')
|
637
|
-
water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
|
638
|
-
water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
|
639
|
-
elsif water_heater_fuel == 'Natural Gas' || water_heater_fuel == 'NaturalGas'
|
640
|
-
water_heater.setHeaterFuelType('Gas')
|
641
|
-
water_heater.setHeaterThermalEfficiency(0.8)
|
642
|
-
water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
643
|
-
water_heater.setOnCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
|
644
|
-
water_heater.setOffCycleParasiticFuelType('Gas')
|
645
|
-
water_heater.setOnCycleParasiticFuelType('Gas')
|
646
|
-
water_heater.setOffCycleLossCoefficienttoAmbientTemperature(6.0)
|
647
|
-
water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0)
|
648
|
-
end
|
649
|
-
|
650
|
-
booster_service_water_loop.addSupplyBranchForComponent(water_heater)
|
651
|
-
|
652
|
-
# Service water heating loop bypass pipes
|
653
|
-
water_heater_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
654
|
-
booster_service_water_loop.addSupplyBranchForComponent(water_heater_bypass_pipe)
|
655
|
-
coil_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
656
|
-
booster_service_water_loop.addDemandBranchForComponent(coil_bypass_pipe)
|
657
|
-
supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
658
|
-
supply_outlet_pipe.addToNode(booster_service_water_loop.supplyOutletNode)
|
659
|
-
demand_inlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
660
|
-
demand_inlet_pipe.addToNode(booster_service_water_loop.demandInletNode)
|
661
|
-
demand_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
|
662
|
-
demand_outlet_pipe.addToNode(booster_service_water_loop.demandOutletNode)
|
663
|
-
|
664
|
-
# Heat exchanger to supply the booster water heater
|
665
|
-
# with normal hot water from the main service water loop.
|
666
|
-
hx = OpenStudio::Model::HeatExchangerFluidToFluid.new(model)
|
667
|
-
hx.setName('HX for Booster Water Heating')
|
668
|
-
hx.setHeatExchangeModelType('Ideal')
|
669
|
-
hx.setControlType('UncontrolledOn')
|
670
|
-
hx.setHeatTransferMeteringEndUseType('LoopToLoop')
|
671
|
-
|
672
|
-
# Add the HX to the supply side of the booster loop
|
673
|
-
hx.addToNode(booster_service_water_loop.supplyInletNode)
|
674
|
-
|
675
|
-
# Add the HX to the demand side of
|
676
|
-
# the main service water loop.
|
677
|
-
main_service_water_loop.addDemandBranchForComponent(hx)
|
678
|
-
|
679
|
-
# Add a plant component temperature source to the demand outlet
|
680
|
-
# of the HX to represent the fact that the water used by the booster
|
681
|
-
# would in reality be at the mains temperature.
|
682
|
-
mains_src = OpenStudio::Model::PlantComponentTemperatureSource.new(model)
|
683
|
-
mains_src.setName('Mains Water Makeup for SWH Booster')
|
684
|
-
mains_src.addToNode(hx.demandOutletModelObject.get.to_Node.get)
|
685
|
-
|
686
|
-
# Mains water temperature sensor
|
687
|
-
mains_water_temp_sen = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Mains Water Temperature')
|
688
|
-
mains_water_temp_sen.setName('Mains_Water_Temp_Sen')
|
689
|
-
mains_water_temp_sen.setKeyName('Environment')
|
690
|
-
|
691
|
-
# Schedule to actuate
|
692
|
-
water_mains_temp_sch = OpenStudio::Model::ScheduleConstant.new(model)
|
693
|
-
water_mains_temp_sch.setName('Mains Water Temperature')
|
694
|
-
water_mains_temp_sch.setValue(OpenStudio.convert(50, 'F', 'C').get)
|
695
|
-
|
696
|
-
# Actuator for mains water temperature schedule
|
697
|
-
mains_water_temp_sch_act = OpenStudio::Model::EnergyManagementSystemActuator.new(water_mains_temp_sch, 'Schedule:Constant', 'Schedule Value')
|
698
|
-
mains_water_temp_sch_act.setName('Mains_Water_Temp_Act')
|
699
|
-
|
700
|
-
# Program
|
701
|
-
mains_prg = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
|
702
|
-
mains_prg.setName('Mains_Water_Prg')
|
703
|
-
mains_prg_body = "SET #{mains_water_temp_sch_act.handle} = #{mains_water_temp_sen.handle}"
|
704
|
-
mains_prg.setBody(mains_prg_body)
|
705
|
-
|
706
|
-
# Program Calling Manager
|
707
|
-
mains_mgr = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
|
708
|
-
mains_mgr.setName('Mains_Water_Prg_Mgr')
|
709
|
-
mains_mgr.setCallingPoint('BeginTimestepBeforePredictor')
|
710
|
-
mains_mgr.addProgram(mains_prg)
|
711
|
-
|
712
|
-
# Make the plant component use the actuated schedule
|
713
|
-
mains_src.setTemperatureSpecificationType('Scheduled')
|
714
|
-
mains_src.setSourceTemperatureSchedule(water_mains_temp_sch)
|
715
|
-
|
716
|
-
return booster_service_water_loop
|
717
|
-
end
|
718
|
-
|
719
|
-
# Creates water fixtures and attaches them
|
720
|
-
# to the supplied service water loop.
|
721
|
-
#
|
722
|
-
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
723
|
-
# @param use_name [String] The name that will be assigned
|
724
|
-
# to the newly created fixture.
|
725
|
-
# @param swh_loop [OpenStudio::Model::PlantLoop]
|
726
|
-
# the main service water loop to add water fixtures to.
|
727
|
-
# @param peak_flowrate [Double] in m^3/s
|
728
|
-
# @param flowrate_schedule [String] name of the flow rate schedule
|
729
|
-
# @param water_use_temperature [Double] mixed water use temperature, in C
|
730
|
-
# @param space_name [String] the name of the space to add the water fixture to,
|
731
|
-
# or nil, in which case it will not be assigned to any particular space.
|
732
|
-
# @return [OpenStudio::Model::WaterUseEquipment]
|
733
|
-
# the resulting water fixture.
|
734
|
-
def model_add_swh_end_uses(model,
|
735
|
-
use_name,
|
736
|
-
swh_loop,
|
737
|
-
peak_flowrate,
|
738
|
-
flowrate_schedule,
|
739
|
-
water_use_temperature,
|
740
|
-
space_name,
|
741
|
-
frac_sensible: 0.2,
|
742
|
-
frac_latent: 0.05)
|
743
|
-
# Water use connection
|
744
|
-
swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
|
745
|
-
|
746
|
-
# Water fixture definition
|
747
|
-
water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
|
748
|
-
rated_flow_rate_m3_per_s = peak_flowrate
|
749
|
-
rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
|
750
|
-
|
751
|
-
water_use_sensible_frac_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
752
|
-
frac_sensible,
|
753
|
-
name: "Fraction Sensible - #{frac_sensible}",
|
754
|
-
schedule_type_limit: 'Fractional')
|
755
|
-
water_use_latent_frac_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
756
|
-
frac_latent,
|
757
|
-
name: "Fraction Latent - #{frac_latent}",
|
758
|
-
schedule_type_limit: 'Fractional')
|
759
|
-
water_fixture_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
|
760
|
-
water_fixture_def.setLatentFractionSchedule(water_use_latent_frac_sch)
|
761
|
-
water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
|
762
|
-
water_fixture_def.setName("#{use_name} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gpm")
|
763
|
-
# Target mixed water temperature
|
764
|
-
mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get
|
765
|
-
mixed_water_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
766
|
-
OpenStudio.convert(mixed_water_temp_f, 'F', 'C').get,
|
767
|
-
name: "Mixed Water At Faucet Temp - #{mixed_water_temp_f.round}F",
|
768
|
-
schedule_type_limit: 'Temperature')
|
769
|
-
water_fixture_def.setTargetTemperatureSchedule(mixed_water_temp_sch)
|
770
|
-
|
771
|
-
# Water use equipment
|
772
|
-
water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
|
773
|
-
schedule = model_add_schedule(model, flowrate_schedule)
|
774
|
-
water_fixture.setFlowRateFractionSchedule(schedule)
|
775
|
-
|
776
|
-
if space_name.nil?
|
777
|
-
water_fixture.setName("#{use_name} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
|
778
|
-
swh_connection.setName("#{use_name} WUC #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
|
779
|
-
else
|
780
|
-
water_fixture.setName("#{space_name} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
|
781
|
-
swh_connection.setName("#{space_name} WUC #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
|
782
|
-
end
|
783
|
-
|
784
|
-
unless space_name.nil?
|
785
|
-
space = model.getSpaceByName(space_name)
|
786
|
-
space = space.get
|
787
|
-
water_fixture.setSpace(space)
|
788
|
-
end
|
789
|
-
|
790
|
-
swh_connection.addWaterUseEquipment(water_fixture)
|
791
|
-
|
792
|
-
# Connect the water use connection to the SWH loop
|
793
|
-
unless swh_loop.nil?
|
794
|
-
swh_loop.addDemandBranchForComponent(swh_connection)
|
795
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_loop.name}.")
|
796
|
-
end
|
797
|
-
|
798
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Added #{water_fixture.name}.")
|
799
|
-
|
800
|
-
return water_fixture
|
801
|
-
end
|
802
|
-
|
803
|
-
# This method will add a swh water fixture to the model for the space.
|
804
|
-
# It will return a water fixture object, or NIL if there is no water load at all.
|
805
|
-
#
|
806
|
-
# Adds a WaterUseEquipment object representing the SWH loads of the supplied Space.
|
807
|
-
# Attaches this WaterUseEquipment to the supplied PlantLoop via a new WaterUseConnections object.
|
808
|
-
#
|
809
|
-
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
810
|
-
# @param swh_loop [OpenStudio::Model::PlantLoop] the SWH loop to connect the WaterUseEquipment to
|
811
|
-
# @param space [OpenStudio::Model::Space] the Space to add a WaterUseEquipment for
|
812
|
-
# @param space_multiplier [Double] the multiplier to use if the supplied Space actually represents
|
813
|
-
# more area than is shown in the model.
|
814
|
-
# @param is_flow_per_area [Boolean] if true, use the value in the 'service_water_heating_peak_flow_per_area'
|
815
|
-
# field of the space_types JSON. If false, use the value in the 'service_water_heating_peak_flow_rate' field.
|
816
|
-
# @return [OpenStudio::Model::WaterUseEquipment] the WaterUseEquipment for the
|
817
|
-
def model_add_swh_end_uses_by_space(model,
|
818
|
-
swh_loop,
|
819
|
-
space,
|
820
|
-
space_multiplier = 1.0,
|
821
|
-
is_flow_per_area = true)
|
822
|
-
# SpaceType
|
823
|
-
if space.spaceType.empty?
|
824
|
-
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name} does not have a Space Type assigned, cannot add SWH end uses.")
|
825
|
-
return nil
|
826
|
-
end
|
827
|
-
space_type = space.spaceType.get
|
828
|
-
|
829
|
-
# Standards Building Type
|
830
|
-
if space_type.standardsBuildingType.empty?
|
831
|
-
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.")
|
832
|
-
return nil
|
833
|
-
end
|
834
|
-
stds_bldg_type = space_type.standardsBuildingType.get
|
835
|
-
building_type = model_get_lookup_name(stds_bldg_type)
|
836
|
-
|
837
|
-
# Standards Space Type
|
838
|
-
if space_type.standardsSpaceType.empty?
|
839
|
-
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.")
|
840
|
-
return nil
|
841
|
-
end
|
842
|
-
stds_spc_type = space_type.standardsSpaceType.get
|
843
|
-
|
844
|
-
# find the specific space_type properties from standard.json
|
845
|
-
search_criteria = {
|
846
|
-
'template' => template,
|
847
|
-
'building_type' => building_type,
|
848
|
-
'space_type' => stds_spc_type
|
849
|
-
}
|
850
|
-
data = standards_lookup_table_first(table_name: 'space_types', search_criteria: search_criteria)
|
851
|
-
if data.nil?
|
852
|
-
OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', "Could not find space type for: #{search_criteria}.")
|
853
|
-
return nil
|
854
|
-
end
|
855
|
-
space_area = OpenStudio.convert(space.floorArea, 'm^2', 'ft^2').get # ft2
|
856
|
-
|
857
|
-
# If there is no service hot water load.. Don't bother adding anything.
|
858
|
-
if data['service_water_heating_peak_flow_per_area'].to_f < 0.00001 && data['service_water_heating_peak_flow_rate'].to_f < 0.00001
|
859
|
-
return nil
|
860
|
-
end
|
861
|
-
|
862
|
-
# Water use connection
|
863
|
-
swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
|
864
|
-
|
865
|
-
# Water fixture definition
|
866
|
-
water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
|
867
|
-
rated_flow_rate_per_area = data['service_water_heating_peak_flow_per_area'].to_f # gal/h.ft2
|
868
|
-
rated_flow_rate_gal_per_hour = if is_flow_per_area
|
869
|
-
rated_flow_rate_per_area * space_area * space_multiplier # gal/h
|
870
|
-
else
|
871
|
-
data['service_water_heating_peak_flow_rate'].to_f
|
872
|
-
end
|
873
|
-
rated_flow_rate_gal_per_min = rated_flow_rate_gal_per_hour / 60 # gal/h to gal/min
|
874
|
-
rated_flow_rate_m3_per_s = OpenStudio.convert(rated_flow_rate_gal_per_min, 'gal/min', 'm^3/s').get
|
875
|
-
water_use_sensible_frac_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
876
|
-
0.2,
|
877
|
-
name: 'Fraction Sensible - 0.2',
|
878
|
-
schedule_type_limit: 'Fractional')
|
879
|
-
water_use_latent_frac_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
880
|
-
0.05,
|
881
|
-
name: 'Fraction Latent - 0.05',
|
882
|
-
schedule_type_limit: 'Fractional')
|
883
|
-
water_fixture_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
|
884
|
-
water_fixture_def.setLatentFractionSchedule(water_use_latent_frac_sch)
|
885
|
-
water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
|
886
|
-
water_fixture_def.setName("#{space.name.get} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gpm")
|
887
|
-
# Target mixed water temperature
|
888
|
-
mixed_water_temp_f = data['service_water_heating_target_temperature']
|
889
|
-
mixed_water_temp_c = OpenStudio.convert(mixed_water_temp_f, 'F', 'C').get
|
890
|
-
mixed_water_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
891
|
-
mixed_water_temp_c,
|
892
|
-
name: "Mixed Water At Faucet Temp - #{mixed_water_temp_f.round}F",
|
893
|
-
schedule_type_limit: 'Temperature')
|
894
|
-
water_fixture_def.setTargetTemperatureSchedule(mixed_water_temp_sch)
|
895
|
-
|
896
|
-
# Water use equipment
|
897
|
-
water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
|
898
|
-
schedule = model_add_schedule(model, data['service_water_heating_schedule'])
|
899
|
-
water_fixture.setFlowRateFractionSchedule(schedule)
|
900
|
-
water_fixture.setName("#{space.name.get} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm")
|
901
|
-
swh_connection.addWaterUseEquipment(water_fixture)
|
902
|
-
# Assign water fixture to a space
|
903
|
-
water_fixture.setSpace(space) if model_attach_water_fixtures_to_spaces?(model)
|
904
|
-
|
905
|
-
# Connect the water use connection to the SWH loop
|
906
|
-
swh_loop.addDemandBranchForComponent(swh_connection)
|
907
|
-
return water_fixture
|
908
|
-
end
|
909
|
-
|
910
|
-
# Determine whether or not water fixtures are attached to spaces
|
911
|
-
# @todo For hotels and apartments, add the water fixture at the space level
|
912
|
-
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
913
|
-
# @return [Boolean] returns true if successful, false if not
|
914
|
-
def model_attach_water_fixtures_to_spaces?(model)
|
915
|
-
# if building_type!=nil && ((building_type.downcase.include?"hotel") || (building_type.downcase.include?"apartment"))
|
916
|
-
# return true
|
917
|
-
# end
|
918
|
-
return false
|
919
|
-
end
|
920
|
-
|
921
|
-
# Creates water fixtures and attaches them to the supplied booster water loop.
|
922
|
-
#
|
923
|
-
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
924
|
-
# @param swh_booster_loop [OpenStudio::Model::PlantLoop]
|
925
|
-
# the booster water loop to add water fixtures to.
|
926
|
-
# @param peak_flowrate [Double] in m^3/s
|
927
|
-
# @param flowrate_schedule [String] name of the flow rate schedule
|
928
|
-
# @param water_use_temperature [Double] mixed water use temperature, in C
|
929
|
-
# @return [OpenStudio::Model::WaterUseEquipment] the resulting water fixture
|
930
|
-
def model_add_booster_swh_end_uses(model,
|
931
|
-
swh_booster_loop,
|
932
|
-
peak_flowrate,
|
933
|
-
flowrate_schedule,
|
934
|
-
water_use_temperature)
|
935
|
-
|
936
|
-
# Water use connection
|
937
|
-
swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
|
938
|
-
|
939
|
-
# Water fixture definition
|
940
|
-
water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
|
941
|
-
rated_flow_rate_m3_per_s = peak_flowrate
|
942
|
-
rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
|
943
|
-
water_fixture_def.setName("Booster Water Fixture Def - #{rated_flow_rate_gal_per_min.round(2)} gpm")
|
944
|
-
water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
|
945
|
-
# Target mixed water temperature
|
946
|
-
mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get
|
947
|
-
mixed_water_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
948
|
-
OpenStudio.convert(mixed_water_temp_f, 'F', 'C').get,
|
949
|
-
name: "Mixed Water At Faucet Temp - #{mixed_water_temp_f.round}F",
|
950
|
-
schedule_type_limit: 'Temperature')
|
951
|
-
water_fixture_def.setTargetTemperatureSchedule(mixed_water_temp_sch)
|
952
|
-
|
953
|
-
# Water use equipment
|
954
|
-
water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
|
955
|
-
water_fixture.setName("Booster Water Fixture - #{rated_flow_rate_gal_per_min.round(2)} gpm at #{mixed_water_temp_f.round}F")
|
956
|
-
schedule = model_add_schedule(model, flowrate_schedule)
|
957
|
-
water_fixture.setFlowRateFractionSchedule(schedule)
|
958
|
-
swh_connection.addWaterUseEquipment(water_fixture)
|
959
|
-
|
960
|
-
# Connect the water use connection to the SWH loop
|
961
|
-
unless swh_booster_loop.nil?
|
962
|
-
swh_booster_loop.addDemandBranchForComponent(swh_connection)
|
963
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_booster_loop.name}.")
|
964
|
-
end
|
965
|
-
|
966
|
-
return water_fixture
|
967
|
-
end
|
968
|
-
|
969
|
-
# Adds insulated 0.75in copper piping to the model.
|
970
|
-
# For circulating systems, assume length of piping is proportional
|
971
|
-
# to the area and number of stories in the building.
|
972
|
-
# For non-circulating systems, assume that the water heaters
|
973
|
-
# are close to the point of use.
|
974
|
-
# Assume that piping is located in a zone
|
975
|
-
#
|
976
|
-
# @param model [OpenStudio::Model::Model] OpenStudio model object
|
977
|
-
# @param swh_loop [OpenStudio::Model::PlantLoop] the service water heating loop
|
978
|
-
# @param floor_area_served [Double] the area of building served by the service water heating loop, in m^2
|
979
|
-
# @param number_of_stories [Integer] the number of stories served by the service water heating loop
|
980
|
-
# @param pipe_insulation_thickness [Double] the thickness of the pipe insulation, in m. Use 0 for no insulation
|
981
|
-
# @param circulating [Boolean] use true for circulating systems, false for non-circulating systems
|
982
|
-
# @param air_temp_surrounding_piping [Double] the temperature of the air surrounding the piping, in C.
|
983
|
-
# @return [Boolean] returns true if successful, false if not
|
984
|
-
def model_add_piping_losses_to_swh_system(model,
|
985
|
-
swh_loop,
|
986
|
-
circulating,
|
987
|
-
pipe_insulation_thickness: 0,
|
988
|
-
floor_area_served: 465,
|
989
|
-
number_of_stories: 1,
|
990
|
-
air_temp_surrounding_piping: 21.1111)
|
991
|
-
|
992
|
-
# Estimate pipe length
|
993
|
-
if circulating
|
994
|
-
# For circulating systems, get pipe length based on the size of the building.
|
995
|
-
# Formula from A.3.1 PrototypeModelEnhancements_2014_0.pdf
|
996
|
-
floor_area_ft2 = OpenStudio.convert(floor_area_served, 'm^2', 'ft^2').get
|
997
|
-
pipe_length_ft = 2.0 * (Math.sqrt(floor_area_ft2 / number_of_stories) + (10.0 * (number_of_stories - 1.0)))
|
998
|
-
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) ) )")
|
999
|
-
else
|
1000
|
-
# For non-circulating systems, assume water heater is close to point of use
|
1001
|
-
pipe_length_ft = 20.0
|
1002
|
-
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.")
|
1003
|
-
end
|
1004
|
-
|
1005
|
-
# For systems whose water heater object represents multiple pieces
|
1006
|
-
# of equipment, multiply the piping length by the number of pieces of equipment.
|
1007
|
-
swh_loop.supplyComponents('OS_WaterHeater_Mixed'.to_IddObjectType).each do |sc|
|
1008
|
-
next unless sc.to_WaterHeaterMixed.is_initialized
|
1009
|
-
|
1010
|
-
water_heater = sc.to_WaterHeaterMixed.get
|
1011
|
-
|
1012
|
-
# get number of water heaters
|
1013
|
-
if water_heater.additionalProperties.getFeatureAsInteger('component_quantity').is_initialized
|
1014
|
-
comp_qty = water_heater.additionalProperties.getFeatureAsInteger('component_quantity').get
|
1015
|
-
else
|
1016
|
-
comp_qty = 1
|
1017
|
-
end
|
1018
|
-
|
1019
|
-
if comp_qty > 1
|
1020
|
-
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.")
|
1021
|
-
pipe_length_ft *= comp_qty
|
1022
|
-
break
|
1023
|
-
end
|
1024
|
-
end
|
1025
|
-
|
1026
|
-
# Service water heating piping heat loss scheduled air temperature
|
1027
|
-
swh_piping_air_temp_c = air_temp_surrounding_piping
|
1028
|
-
swh_piping_air_temp_f = OpenStudio.convert(swh_piping_air_temp_c, 'C', 'F').get
|
1029
|
-
swh_piping_air_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
1030
|
-
swh_piping_air_temp_c,
|
1031
|
-
name: "#{swh_loop.name} Piping Air Temp - #{swh_piping_air_temp_f.round}F",
|
1032
|
-
schedule_type_limit: 'Temperature')
|
1033
|
-
|
1034
|
-
# Service water heating piping heat loss scheduled air velocity
|
1035
|
-
swh_piping_air_velocity_m_per_s = 0.3
|
1036
|
-
swh_piping_air_velocity_mph = OpenStudio.convert(swh_piping_air_velocity_m_per_s, 'm/s', 'mile/hr').get
|
1037
|
-
swh_piping_air_velocity_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
|
1038
|
-
swh_piping_air_velocity_m_per_s,
|
1039
|
-
name: "#{swh_loop.name} Piping Air Velocity - #{swh_piping_air_velocity_mph.round(2)}mph",
|
1040
|
-
schedule_type_limit: 'Dimensionless')
|
1041
|
-
|
1042
|
-
# Material for 3/4in type L (heavy duty) copper pipe
|
1043
|
-
copper_pipe = OpenStudio::Model::StandardOpaqueMaterial.new(model)
|
1044
|
-
copper_pipe.setName('Copper pipe 0.75in type L')
|
1045
|
-
copper_pipe.setRoughness('Smooth')
|
1046
|
-
copper_pipe.setThickness(OpenStudio.convert(0.045, 'in', 'm').get)
|
1047
|
-
copper_pipe.setThermalConductivity(386.0)
|
1048
|
-
copper_pipe.setDensity(OpenStudio.convert(556, 'lb/ft^3', 'kg/m^3').get)
|
1049
|
-
copper_pipe.setSpecificHeat(OpenStudio.convert(0.092, 'Btu/lb*R', 'J/kg*K').get)
|
1050
|
-
copper_pipe.setThermalAbsorptance(0.9) # @todo find reference for property
|
1051
|
-
copper_pipe.setSolarAbsorptance(0.7) # @todo find reference for property
|
1052
|
-
copper_pipe.setVisibleAbsorptance(0.7) # @todo find reference for property
|
1053
|
-
|
1054
|
-
# Construction for pipe
|
1055
|
-
pipe_construction = OpenStudio::Model::Construction.new(model)
|
1056
|
-
|
1057
|
-
# Add insulation material to insulated pipe
|
1058
|
-
if pipe_insulation_thickness > 0
|
1059
|
-
# Material for fiberglass insulation
|
1060
|
-
# R-value from Owens-Corning 1/2in fiberglass pipe insulation
|
1061
|
-
# https://www.grainger.com/product/OWENS-CORNING-1-2-Thick-40PP22
|
1062
|
-
# but modified until simulated heat loss = 17.7 Btu/hr/ft of pipe with 140F water and 70F air
|
1063
|
-
pipe_insulation_thickness_in = OpenStudio.convert(pipe_insulation_thickness, 'm', 'in').get
|
1064
|
-
insulation = OpenStudio::Model::StandardOpaqueMaterial.new(model)
|
1065
|
-
insulation.setName("Fiberglass batt #{pipe_insulation_thickness_in.round(2)}in")
|
1066
|
-
insulation.setRoughness('Smooth')
|
1067
|
-
insulation.setThickness(OpenStudio.convert(pipe_insulation_thickness_in, 'in', 'm').get)
|
1068
|
-
insulation.setThermalConductivity(OpenStudio.convert(0.46, 'Btu*in/hr*ft^2*R', 'W/m*K').get)
|
1069
|
-
insulation.setDensity(OpenStudio.convert(0.7, 'lb/ft^3', 'kg/m^3').get)
|
1070
|
-
insulation.setSpecificHeat(OpenStudio.convert(0.2, 'Btu/lb*R', 'J/kg*K').get)
|
1071
|
-
insulation.setThermalAbsorptance(0.9) # Irrelevant for Pipe:Indoor; no radiation model is used
|
1072
|
-
insulation.setSolarAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used
|
1073
|
-
insulation.setVisibleAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used
|
1074
|
-
|
1075
|
-
pipe_construction.setName("Copper pipe 0.75in type L with #{pipe_insulation_thickness_in.round(2)}in fiberglass batt")
|
1076
|
-
pipe_construction.setLayers([insulation, copper_pipe])
|
1077
|
-
else
|
1078
|
-
pipe_construction.setName('Uninsulated copper pipe 0.75in type L')
|
1079
|
-
pipe_construction.setLayers([copper_pipe])
|
1080
|
-
end
|
1081
|
-
|
1082
|
-
heat_loss_pipe = OpenStudio::Model::PipeIndoor.new(model)
|
1083
|
-
heat_loss_pipe.setName("#{swh_loop.name} Pipe #{pipe_length_ft}ft")
|
1084
|
-
heat_loss_pipe.setEnvironmentType('Schedule')
|
1085
|
-
# @todoschedule type registry error for this setter
|
1086
|
-
# heat_loss_pipe.setAmbientTemperatureSchedule(swh_piping_air_temp_sch)
|
1087
|
-
heat_loss_pipe.setPointer(7, swh_piping_air_temp_sch.handle)
|
1088
|
-
# @todo schedule type registry error for this setter
|
1089
|
-
# heat_loss_pipe.setAmbientAirVelocitySchedule(model.alwaysOffDiscreteSchedule)
|
1090
|
-
heat_loss_pipe.setPointer(8, swh_piping_air_velocity_sch.handle)
|
1091
|
-
heat_loss_pipe.setConstruction(pipe_construction)
|
1092
|
-
heat_loss_pipe.setPipeInsideDiameter(OpenStudio.convert(0.785, 'in', 'm').get)
|
1093
|
-
heat_loss_pipe.setPipeLength(OpenStudio.convert(pipe_length_ft, 'ft', 'm').get)
|
1094
|
-
|
1095
|
-
heat_loss_pipe.addToNode(swh_loop.demandInletNode)
|
1096
|
-
|
1097
|
-
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}.")
|
1098
|
-
return true
|
1099
|
-
end
|
1100
|
-
end
|