module OpenstudioStandards::ServiceWaterHeating
The ServiceWaterHeating
module provides methods to create, modify, and get information about service water heating
The ServiceWaterHeating
module provides methods to create, modify, and get information about service water heating
The ServiceWaterHeating
module provides methods to create, modify, and get information about service water heating
The ServiceWaterHeating
module provides methods to create, modify, and get information about service water heating
Public Class Methods
Creates a booster water heater on its own loop and attaches it to the main service water heating loop.
@param model [OpenStudio::Model::Model] OpenStudio model object @param water_heater_capacity [Double] water heater capacity, in W. Defaults to 8 kW / 27.283 kBtu/hr @param water_heater_volume [Double] water heater volume, in m^3. Defaults to 0.0227 m^3 / 6 gal @param water_heater_fuel [String] water heating fuel. Valid choices are ‘NaturalGas’, ‘Electricity’. @param on_cycle_parasitic_fuel_consumption_rate [Double] water heater on cycle parasitic fuel consumption rate, in W @param off_cycle_parasitic_fuel_consumption_rate [Double] water heater off cycle parasitic fuel consumption rate, in W @param service_water_temperature [Double] water heater temperature, in degrees C. Default is 82.2 C / 180 F. @param service_water_temperature_schedule [OpenStudio::Model::Schedule] the service water heating schedule.
If nil, will be defaulted to a constant temperature schedule based on the service_water_temperature
@param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] Thermal zone for ambient heat loss.
If nil, will assume 71.6 F / 22 C ambient air temperature.
@param service_water_loop [OpenStudio::Model::PlantLoop] if provided, add the water heater to this loop @return [OpenStudio::Model::PlantLoop] The booster water loop OpenStudio PlantLoop object
# File lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb, line 181 def self.create_booster_water_heating_loop(model, water_heater_capacity: 8000.0, water_heater_volume: OpenStudio.convert(6.0, 'gal', 'm^3').get, water_heater_fuel: 'Electricity', on_cycle_parasitic_fuel_consumption_rate: 0.0, off_cycle_parasitic_fuel_consumption_rate: 0.0, service_water_temperature: 82.2, service_water_temperature_schedule: nil, water_heater_thermal_zone: nil, service_water_loop: nil) if service_water_loop.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.ServiceWaterHeating', "#{_method_} requires the service_water_loop argument to couple the booster water heating loop with a heat exchanger.") return nil else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Adding booster water heater to #{service_water_loop.name}") end water_heater_volume_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get # Booster water heating loop booster_service_water_loop = OpenStudio::Model::PlantLoop.new(model) booster_service_water_loop.setName('Booster Service Water Loop') # create and add booster water heater to loop booster_water_heater = OpenstudioStandards::ServiceWaterHeating.create_water_heater(model, water_heater_capacity: water_heater_capacity, water_heater_volume: water_heater_volume, water_heater_fuel: water_heater_fuel, on_cycle_parasitic_fuel_consumption_rate: on_cycle_parasitic_fuel_consumption_rate, off_cycle_parasitic_fuel_consumption_rate: off_cycle_parasitic_fuel_consumption_rate, service_water_temperature: service_water_temperature, service_water_temperature_schedule: service_water_temperature_schedule, water_heater_thermal_zone: water_heater_thermal_zone, service_water_loop: booster_service_water_loop) booster_water_heater.setName("#{water_heater_volume_gal}gal #{water_heater_fuel} Booster Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr") booster_water_heater.setEndUseSubcategory('Booster') # Service water heating loop controls swh_temp_f = OpenStudio.convert(service_water_temperature, 'C', 'F').get swh_delta_t_r = 9.0 # 9F delta-T swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get swh_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, service_water_temperature, name: "Service Water Booster Temp - #{swh_temp_f.round}F", schedule_type_limit: 'Temperature') swh_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, swh_temp_sch) swh_stpt_manager.setName('Hot water booster setpoint manager') swh_stpt_manager.addToNode(booster_service_water_loop.supplyOutletNode) sizing_plant = booster_service_water_loop.sizingPlant sizing_plant.setLoopType('Heating') sizing_plant.setDesignLoopExitTemperature(service_water_temperature) sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k) # Booster water heating pump swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model) swh_pump.setName('Booster Water Loop Pump') swh_pump.setRatedPumpHead(0.0) # As if there is no circulation pump swh_pump.setRatedPowerConsumption(0.0) # As if there is no circulation pump swh_pump.setMotorEfficiency(1) swh_pump.setPumpControlType('Continuous') swh_pump.setMinimumFlowRate(0.0) swh_pump.addToNode(booster_service_water_loop.supplyInletNode) # Service water heating loop bypass pipes water_heater_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model) booster_service_water_loop.addSupplyBranchForComponent(water_heater_bypass_pipe) coil_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model) booster_service_water_loop.addDemandBranchForComponent(coil_bypass_pipe) supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model) supply_outlet_pipe.addToNode(booster_service_water_loop.supplyOutletNode) demand_inlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model) demand_inlet_pipe.addToNode(booster_service_water_loop.demandInletNode) demand_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model) demand_outlet_pipe.addToNode(booster_service_water_loop.demandOutletNode) # Heat exchanger to supply the booster water heater with normal hot water from the main service water loop hx = OpenStudio::Model::HeatExchangerFluidToFluid.new(model) hx.setName('Booster Water Heating Heat Exchanger') hx.setHeatExchangeModelType('Ideal') hx.setControlType('UncontrolledOn') hx.setHeatTransferMeteringEndUseType('LoopToLoop') # Add the HX to the supply side of the booster loop hx.addToNode(booster_service_water_loop.supplyInletNode) # Add the HX to the demand side of the main service water loop service_water_loop.addDemandBranchForComponent(hx) # Add a plant component temperature source to the demand outlet # of the HX to represent the fact that the water used by the booster # would in reality be at the mains temperature. mains_src = OpenStudio::Model::PlantComponentTemperatureSource.new(model) mains_src.setName('Mains Water Makeup for SWH Booster') mains_src.addToNode(hx.demandOutletModelObject.get.to_Node.get) # use the site water mains temperature schedule if available, # otherwise use the annual average outdoor air temperature site_water_mains = model.getSiteWaterMainsTemperature if site_water_mains.temperatureSchedule.is_initialized water_mains_temp_sch = site_water_mains.temperatureSchedule.get elsif site_water_mains.annualAverageOutdoorAirTemperature.is_initialized mains_src_temp_c = site_water_mains.annualAverageOutdoorAirTemperature.get mains_src.setSourceTemperature(mains_src_temp_c) water_mains_temp_sch = OpenStudio::Model::ScheduleConstant.new(model) water_mains_temp_sch.setName('Booster Water Makeup Temperature') water_mains_temp_sch.setValue(mains_src_temp_c) else # assume 50F mains_src_temp_c = OpenStudio.convert(50.0, 'F', 'C').get mains_src.setSourceTemperature(mains_src_temp_c) water_mains_temp_sch = OpenStudio::Model::ScheduleConstant.new(model) water_mains_temp_sch.setName('Booster Water Makeup Temperature') water_mains_temp_sch.setValue(mains_src_temp_c) end mains_src.setTemperatureSpecificationType('Scheduled') mains_src.setSourceTemperatureSchedule(water_mains_temp_sch) return booster_service_water_loop end
Creates a heatpump water heater and attaches it to the supplied service water heating loop.
@param model [OpenStudio::Model::Model] OpenStudio model object @param heat_pump_type [String] valid option are ‘WrappedCondenser’ or ‘PumpedCondenser’ (default).
The 'WrappedCondenser' uses a WaterHeaterStratified tank, 'PumpedCondenser' uses a WaterHeaterMixed tank.
@param water_heater_capacity [Double] water heater capacity, in W. Defaults to 500 W / 3.41 kBtu/hr @param water_heater_volume [Double] water heater volume, in m^3. Defaults to 0.303 m^3 / 80 gal @param coefficient_of_performance [Double] rated coefficient_of_performance @param electric_backup_capacity [Double] electric heating backup capacity, in W. Default is 4500 W. @param on_cycle_parasitic_fuel_consumption_rate [Double] water heater on cycle parasitic fuel consumption rate, in W @param off_cycle_parasitic_fuel_consumption_rate [Double] water heater off cycle parasitic fuel consumption rate, in W @param service_water_temperature [Double] water heater temperature, in degrees C. Default is 51.67 C / 125 F. @param service_water_temperature_schedule [OpenStudio::Model::Schedule] the service water heating schedule.
If nil, will be defaulted to a constant temperature schedule based on the service_water_temperature
@param set_peak_use_flowrate [Boolean] if true, the peak flow rate and flow rate schedule will be set. @param peak_flowrate [Double] peak flow rate in m^3/s @param flowrate_schedule [OpenStudio::Model::Schedule] the flow rate fraction schedule @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] Thermal zone for ambient heat loss.
If nil, will assume 71.6 F / 22 C ambient air temperature.
@param service_water_loop [OpenStudio::Model::PlantLoop] if provided, add the water heater to this loop @param use_ems_control [Boolean] if true, use ems control logic if using a ‘WrappedCondenser’ style HPWH. @return [OpenStudio::Model::WaterHeaterMixed] OpenStudio WaterHeaterMixed object
# File lib/openstudio-standards/service_water_heating/create_water_heater.rb, line 198 def self.create_heatpump_water_heater(model, heat_pump_type: 'PumpedCondenser', water_heater_capacity: 500.0, water_heater_volume: OpenStudio.convert(80.0, 'gal', 'm^3').get, coefficient_of_performance: 2.8, electric_backup_capacity: 4500.0, on_cycle_parasitic_fuel_consumption_rate: 0.0, off_cycle_parasitic_fuel_consumption_rate: 0.0, service_water_temperature: OpenStudio.convert(125.0, 'F', 'C').get, service_water_temperature_schedule: nil, set_peak_use_flowrate: false, peak_flowrate: nil, flowrate_schedule: nil, water_heater_thermal_zone: nil, service_water_loop: nil, use_ems_control: false) # create heat pump water heater if heat_pump_type == 'WrappedCondenser' hpwh = OpenStudio::Model::WaterHeaterHeatPumpWrappedCondenser.new(model) elsif heat_pump_type == 'PumpedCondenser' hpwh = OpenStudio::Model::WaterHeaterHeatPump.new(model) end # calculate tank height and radius water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get hpwh_vol_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get 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 tank_radius = (0.9 * water_heater_volume / (Math::PI * tank_height))**0.5 tank_surface_area = 2.0 * Math::PI * tank_radius * (tank_radius + tank_height) tank_ua = 3.9 # default ua assumption u_tank = (5.678 * tank_ua) / OpenStudio.convert(tank_surface_area, 'm^2', 'ft^2').get hpwh.setName("#{hpwh_vol_gal.round}gal Heat Pump Water Heater - #{water_heater_capacity_kbtu_per_hr.round(0)}kBtu/hr") # set min/max HPWH operating temperature limit hpwh_op_min_temp_c = OpenStudio.convert(45.0, 'F', 'C').get hpwh_op_max_temp_c = OpenStudio.convert(120.0, 'F', 'C').get if heat_pump_type == 'WrappedCondenser' hpwh.setMinimumInletAirTemperatureforCompressorOperation(hpwh_op_min_temp_c) hpwh.setMaximumInletAirTemperatureforCompressorOperation(hpwh_op_max_temp_c) # set sensor heights if hpwh_vol_gal <= 50.0 hpwh.setDeadBandTemperatureDifference(0.5) h_ue = (1 - (3.5 / 12.0)) * tank_height # in the 4th node of the tank (counting from top) h_le = (1 - (10.5 / 12.0)) * tank_height # in the 11th node of the tank (counting from top) h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top) h_condbot = (1 - (10.99 / 12.0)) * tank_height # in the 11th node of the tank h_hpctrl = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl) hpwh.setControlSensor1Weight(1.0) hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl) else hpwh.setDeadBandTemperatureDifference(3.89) h_ue = (1 - (3.5 / 12.0)) * tank_height # in the 3rd node of the tank (counting from top) h_le = (1 - (9.5 / 12.0)) * tank_height # in the 10th node of the tank (counting from top) h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top) h_condbot = 0.01 # bottom node h_hpctrl_up = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank h_hpctrl_low = (1 - (8.5 / 12.0)) * tank_height # in the 9th node of the tank hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl_up) hpwh.setControlSensor1Weight(0.75) hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl_low) end hpwh.setCondenserBottomLocation(h_condbot) hpwh.setCondenserTopLocation(h_condtop) hpwh.setTankElementControlLogic('MutuallyExclusive') hpwh.autocalculateEvaporatorAirFlowRate elsif heat_pump_type == 'PumpedCondenser' hpwh.setDeadBandTemperatureDifference(3.89) hpwh.autosizeEvaporatorAirFlowRate end # set heat pump water heater properties hpwh.setFanPlacement('DrawThrough') hpwh.setOnCycleParasiticElectricLoad(0.0) hpwh.setOffCycleParasiticElectricLoad(0.0) hpwh.setParasiticHeatRejectionLocation('Outdoors') # set temperature setpoint schedule if service_water_temperature_schedule.nil? # service water heating loop controls swh_temp_c = service_water_temperature swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get swh_delta_t_r = 9.0 # 9F delta-T swh_temp_c = OpenStudio.convert(swh_temp_f, 'F', 'C').get swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get service_water_temperature_schedule = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, swh_temp_c, name: "Heat Pump Water Heater Temp - #{swh_temp_f.round}F", schedule_type_limit: 'Temperature') end hpwh.setCompressorSetpointTemperatureSchedule(service_water_temperature_schedule) # coil curves hpwh_cap = OpenStudio::Model::CurveBiquadratic.new(model) hpwh_cap.setName('HPWH-Cap-fT') hpwh_cap.setCoefficient1Constant(0.563) hpwh_cap.setCoefficient2x(0.0437) hpwh_cap.setCoefficient3xPOW2(0.000039) hpwh_cap.setCoefficient4y(0.0055) hpwh_cap.setCoefficient5yPOW2(-0.000148) hpwh_cap.setCoefficient6xTIMESY(-0.000145) hpwh_cap.setMinimumValueofx(0.0) hpwh_cap.setMaximumValueofx(100.0) hpwh_cap.setMinimumValueofy(0.0) hpwh_cap.setMaximumValueofy(100.0) hpwh_cop = OpenStudio::Model::CurveBiquadratic.new(model) hpwh_cop.setName('HPWH-COP-fT') hpwh_cop.setCoefficient1Constant(1.1332) hpwh_cop.setCoefficient2x(0.063) hpwh_cop.setCoefficient3xPOW2(-0.0000979) hpwh_cop.setCoefficient4y(-0.00972) hpwh_cop.setCoefficient5yPOW2(-0.0000214) hpwh_cop.setCoefficient6xTIMESY(-0.000686) hpwh_cop.setMinimumValueofx(0.0) hpwh_cop.setMaximumValueofx(100.0) hpwh_cop.setMinimumValueofy(0.0) hpwh_cop.setMaximumValueofy(100.0) # create DX coil object if heat_pump_type == 'WrappedCondenser' coil = hpwh.dXCoil.to_CoilWaterHeatingAirToWaterHeatPumpWrapped.get coil.setRatedCondenserWaterTemperature(48.89) coil.autocalculateRatedEvaporatorAirFlowRate elsif heat_pump_type == 'PumpedCondenser' coil = hpwh.dXCoil.to_CoilWaterHeatingAirToWaterHeatPump.get coil.autosizeRatedEvaporatorAirFlowRate end # set coil properties coil.setName("#{hpwh.name} Coil") coil.setRatedHeatingCapacity(water_heater_capacity) coil.setRatedCOP(coefficient_of_performance) coil.setRatedSensibleHeatRatio(0.88) # default sensible_heat_ratio assumption coil.setRatedEvaporatorInletAirDryBulbTemperature(OpenStudio.convert(67.5, 'F', 'C').get) coil.setRatedEvaporatorInletAirWetBulbTemperature(OpenStudio.convert(56.426, 'F', 'C').get) coil.setEvaporatorFanPowerIncludedinRatedCOP(true) coil.setEvaporatorAirTemperatureTypeforCurveObjects('WetBulbTemperature') coil.setHeatingCapacityFunctionofTemperatureCurve(hpwh_cap) coil.setHeatingCOPFunctionofTemperatureCurve(hpwh_cop) coil.setMaximumAmbientTemperatureforCrankcaseHeaterOperation(0.0) # set tank properties if heat_pump_type == 'WrappedCondenser' tank = hpwh.tank.to_WaterHeaterStratified.get tank.setTankHeight(tank_height) tank.setHeaterPriorityControl('MasterSlave') if hpwh_vol_gal <= 50.0 tank.setHeater1DeadbandTemperatureDifference(25.0) tank.setHeater2DeadbandTemperatureDifference(30.0) else tank.setHeater1DeadbandTemperatureDifference(18.5) tank.setHeater2DeadbandTemperatureDifference(3.89) end hpwh_bottom_element_sp = OpenStudio::Model::ScheduleConstant.new(model) hpwh_bottom_element_sp.setName("#{hpwh.name} BottomElementSetpoint") hpwh_top_element_sp = OpenStudio::Model::ScheduleConstant.new(model) hpwh_top_element_sp.setName("#{hpwh.name} TopElementSetpoint") tank.setHeater1Capacity(electric_backup_capacity) tank.setHeater1Height(h_ue) tank.setHeater1SetpointTemperatureSchedule(hpwh_top_element_sp) # Overwritten later by EMS tank.setHeater2Capacity(electric_backup_capacity) tank.setHeater2Height(h_le) tank.setHeater2SetpointTemperatureSchedule(hpwh_bottom_element_sp) tank.setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(u_tank) tank.setNumberofNodes(12) tank.setAdditionalDestratificationConductivity(0) tank.setNode1AdditionalLossCoefficient(0) tank.setNode2AdditionalLossCoefficient(0) tank.setNode3AdditionalLossCoefficient(0) tank.setNode4AdditionalLossCoefficient(0) tank.setNode5AdditionalLossCoefficient(0) tank.setNode6AdditionalLossCoefficient(0) tank.setNode7AdditionalLossCoefficient(0) tank.setNode8AdditionalLossCoefficient(0) tank.setNode9AdditionalLossCoefficient(0) tank.setNode10AdditionalLossCoefficient(0) tank.setNode11AdditionalLossCoefficient(0) tank.setNode12AdditionalLossCoefficient(0) tank.setUseSideDesignFlowRate(0.9 * water_heater_volume / 60.1) tank.setSourceSideDesignFlowRate(0) tank.setSourceSideFlowControlMode('') tank.setSourceSideInletHeight(0) tank.setSourceSideOutletHeight(0) elsif heat_pump_type == 'PumpedCondenser' tank = hpwh.tank.to_WaterHeaterMixed.get tank.setDeadbandTemperatureDifference(3.89) tank.setHeaterControlType('Cycle') tank.setHeaterMaximumCapacity(electric_backup_capacity) end tank.setName("#{hpwh.name} Tank") tank.setEndUseSubcategory('Service Hot Water') tank.setTankVolume(0.9 * water_heater_volume) tank.setMaximumTemperatureLimit(90.0) tank.setHeaterFuelType('Electricity') tank.setHeaterThermalEfficiency(1.0) tank.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate) tank.setOffCycleParasiticFuelType('Electricity') tank.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate) tank.setOnCycleParasiticFuelType('Electricity') # set fan properties fan = hpwh.fan.to_FanOnOff.get fan.setName("#{hpwh.name} Fan") fan_power = 0.0462 # watts per cfm if hpwh_vol_gal <= 50.0 fan.setFanEfficiency(23.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get) fan.setPressureRise(23.0) else fan.setFanEfficiency(65.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get) fan.setPressureRise(65.0) end # determine maximum flow rate from water heater capacity # 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 fan_flow_rate_m3_per_s = water_heater_capacity * 5.035e-5 fan.setMaximumFlowRate(fan_flow_rate_m3_per_s) fan.setMotorEfficiency(1.0) fan.setMotorInAirstreamFraction(1.0) fan.setEndUseSubcategory('Service Hot Water') if water_heater_thermal_zone.nil? # add in schedules for Tamb, RHamb, and the compressor # assume the water heater is indoors at 71.6F / 22C default_water_heater_ambient_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, OpenStudio.convert(71.6, 'F', 'C').get, name: 'Water Heater Ambient Temp Schedule 70F', schedule_type_limit: 'Temperature') tank.setAmbientTemperatureIndicator('Schedule') tank.setAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch) tank.resetAmbientTemperatureThermalZone hpwh_rhamb = OpenStudio::Model::ScheduleConstant.new(model) hpwh_rhamb.setName("#{hpwh.name} Ambient Humidity Schedule") hpwh_rhamb.setValue(0.5) hpwh.setInletAirConfiguration('Schedule') hpwh.setInletAirTemperatureSchedule(default_water_heater_ambient_temp_sch) hpwh.setInletAirHumiditySchedule(hpwh_rhamb) hpwh.setCompressorLocation('Schedule') hpwh.setCompressorAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch) else hpwh.addToThermalZone(water_heater_thermal_zone) hpwh.setInletAirConfiguration('ZoneAirOnly') hpwh.setCompressorLocation('Zone') tank.setAmbientTemperatureIndicator('ThermalZone') tank.setAmbientTemperatureThermalZone(water_heater_thermal_zone) tank.resetAmbientTemperatureSchedule end if set_peak_use_flowrate rated_flow_rate_m3_per_s = peak_flowrate rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get tank.setPeakUseFlowRate(rated_flow_rate_m3_per_s) tank.setUseFlowRateFractionSchedule(flowrate_schedule) unless flowrate_schedule.nil? end # add EMS for overriding HPWH setpoints schedules (for upper/lower heating element in water tank and compressor in heat pump) if heat_pump_type == 'WrappedCondenser' && use_ems_control std = Standard.build('90.1-2013') hpwh_name_ems_friendly = std.ems_friendly_name(hpwh.name) # create an ambient temperature sensor for the air that blows through the HPWH evaporator if water_heater_thermal_zone.nil? # assume the condenser is outside amb_temp_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Outdoor Air Drybulb Temperature') amb_temp_sensor.setName("#{hpwh_name_ems_friendly}_amb_temp") amb_temp_sensor.setKeyName('Environment') else amb_temp_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Zone Mean Air Temperature') amb_temp_sensor.setName("#{hpwh_name_ems_friendly}_amb_temp") amb_temp_sensor.setKeyName(water_heater_thermal_zone.name.to_s) end # create actuator for heat pump compressor if service_water_temperature_schedule.to_ScheduleConstant.is_initialized service_water_temperature_schedule = service_water_temperature_schedule.to_ScheduleConstant.get schedule_type = 'Schedule:Constant' elsif service_water_temperature_schedule.to_ScheduleCompact.is_initialized service_water_temperature_schedule = service_water_temperature_schedule.to_ScheduleCompact.get schedule_type = 'Schedule:Compact' elsif service_water_temperature_schedule.to_ScheduleRuleset.is_initialized service_water_temperature_schedule = service_water_temperature_schedule.to_ScheduleRuleset.get schedule_type = 'Schedule:Year' else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.ServiceWaterHeating', "Unsupported schedule type for HPWH setpoint schedule #{service_water_temperature_schedule.name}.") return false end hpwhschedoverride_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(service_water_temperature_schedule, schedule_type, 'Schedule Value') hpwhschedoverride_actuator.setName("#{hpwh_name_ems_friendly}_HPWHSchedOverride") # create actuator for lower heating element in water tank leschedoverride_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(hpwh_bottom_element_sp, 'Schedule:Constant', 'Schedule Value') leschedoverride_actuator.setName("#{hpwh_name_ems_friendly}_LESchedOverride") # create actuator for upper heating element in water tank ueschedoverride_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(hpwh_top_element_sp, 'Schedule:Constant', 'Schedule Value') ueschedoverride_actuator.setName("#{hpwh_name_ems_friendly}_UESchedOverride") # create sensor for heat pump compressor t_set_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value') t_set_sensor.setName("#{hpwh_name_ems_friendly}_T_set") t_set_sensor.setKeyName(service_water_temperature_schedule.name.to_s) # define control configuration t_offset = 9.0 # deg-C # get tank specifications upper_element_db = tank.heater1DeadbandTemperatureDifference # define control logic hpwh_ctrl_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model) hpwh_ctrl_program.setName("#{hpwh_name_ems_friendly}_Control") hpwh_ctrl_program.addLine("SET #{hpwhschedoverride_actuator.name} = #{t_set_sensor.name}") # lockout hp when ambient temperature is either too high or too low hpwh_ctrl_program.addLine("IF (#{amb_temp_sensor.name}<#{hpwh_op_min_temp_c}) || (#{amb_temp_sensor.name}>#{hpwh_op_max_temp_c})") hpwh_ctrl_program.addLine("SET #{ueschedoverride_actuator.name} = #{t_set_sensor.name}") hpwh_ctrl_program.addLine("SET #{leschedoverride_actuator.name} = #{t_set_sensor.name}") hpwh_ctrl_program.addLine('ELSE') # upper element setpoint temperature hpwh_ctrl_program.addLine("SET #{ueschedoverride_actuator.name} = #{t_set_sensor.name} - #{t_offset}") # upper element cut-in temperature hpwh_ctrl_program.addLine("SET #{ueschedoverride_actuator.name}_cut_in = #{ueschedoverride_actuator.name} - #{upper_element_db}") # lower element disabled hpwh_ctrl_program.addLine("SET #{leschedoverride_actuator.name} = 0") # lower element disabled hpwh_ctrl_program.addLine("SET #{leschedoverride_actuator.name}_cut_in = 0") hpwh_ctrl_program.addLine('ENDIF') # create a program calling manager program_calling_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model) program_calling_manager.setName("#{hpwh_name_ems_friendly}_ProgramManager") program_calling_manager.setCallingPoint('InsideHVACSystemIterationLoop') program_calling_manager.addProgram(hpwh_ctrl_program) end # add the water heater to the service water loop if provided unless service_water_loop.nil? service_water_loop.addSupplyBranchForComponent(tank) end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added heat pump water heater called #{tank.name}") return hpwh end
Creates a service water heating loop.
@param model [OpenStudio::Model::Model] OpenStudio model object @param system_name [String] the name of the system. nil results in the default. @param service_water_temperature [Double] water heater temperature, in degrees C. Default is 60 C / 140 F. @param service_water_pump_head [Double] service water pump head, in Pa. Default is 29861 Pa / 10 ft. @param service_water_pump_motor_efficiency [Double] service water pump motor efficiency, as decimal. @param water_heater_capacity [Double] water heater capacity, in W. Defaults to 58.6 kW / 200 kBtu/hr @param water_heater_volume [Double] water heater volume, in m^3. Defaults to 0.378 m^3 / 100 gal @param water_heater_fuel [String] water heating fuel. Valid choices are ‘NaturalGas’, ‘Electricity’, ‘FuelOilNo2’, ‘SimpleHeatPump’, ‘HeatPump’, or ‘None’.
If 'None', no water heater will be added.
@param on_cycle_parasitic_fuel_consumption_rate [Double] water heater on cycle parasitic fuel consumption rate, in W @param off_cycle_parasitic_fuel_consumption_rate [Double] water heater off cycle parasitic fuel consumption rate, in W @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] Thermal zone for ambient heat loss.
If nil, will assume 71.6 F / 22 C ambient air temperature.
@param number_of_water_heaters [Integer] the number of water heaters represented by the capacity and volume inputs.
Used to modify efficiencies for water heaters based on individual component size while avoiding having to model lots of individual water heaters (for runtime sake).
@param add_piping_losses [Boolean] if true, add piping and associated heat losses to system. If false, add no pipe heat losses. @param pipe_insulation_thickness [Double] the thickness of the pipe insulation, in m. Default is 0.0127 m / 0.5 inches. @param floor_area [Double] the area of building served by the service water heating loop, in m^2
If nil, will use the total building floor area. Only used if piping losses is true and the system is circulating.
@param number_of_stories [Integer] the number of stories served by the service water heating loop
If nil, will use the total building number of stories. Only used if piping losses is true and the system is circulating.
@return [OpenStudio::Model::PlantLoop] OpenStudio PlantLoop object of the service water loop
# File lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb, line 31 def self.create_service_water_heating_loop(model, system_name: 'Service Water Loop', service_water_temperature: 60.0, service_water_pump_head: 29861.0, service_water_pump_motor_efficiency: 0.3, water_heater_capacity: nil, water_heater_volume: nil, water_heater_fuel: 'Electricity', on_cycle_parasitic_fuel_consumption_rate: 0.0, off_cycle_parasitic_fuel_consumption_rate: 0.0, water_heater_thermal_zone: nil, number_of_water_heaters: 1, add_piping_losses: false, pipe_insulation_thickness: 0.0127, floor_area: nil, number_of_stories: nil) # create service water heating loop service_water_loop = OpenStudio::Model::PlantLoop.new(model) service_water_loop.setMinimumLoopTemperature(10.0) if service_water_temperature > 60.0 service_water_loop.setMaximumLoopTemperature(service_water_temperature) else service_water_loop.setMaximumLoopTemperature(60.0) end if system_name.nil? system_name = 'Service Water Loop' end service_water_loop.setName(system_name) # service water heating loop controls swh_temp_f = OpenStudio.convert(service_water_temperature, 'C', 'F').get swh_delta_t_r = 9.0 # 9F delta-T swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get swh_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, service_water_temperature, name: "Service Water Loop Temp - #{swh_temp_f.round}F", schedule_type_limit: 'Temperature') swh_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, swh_temp_sch) swh_stpt_manager.setName('Service hot water setpoint manager') swh_stpt_manager.addToNode(service_water_loop.supplyOutletNode) sizing_plant = service_water_loop.sizingPlant sizing_plant.setLoopType('Heating') sizing_plant.setDesignLoopExitTemperature(service_water_temperature) sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k) # determine if circulating or non-circulating based on supplied head pressure if service_water_pump_head.nil? || service_water_pump_head <= 1 # set pump head pressure to near zero if there is no circulation pump service_water_pump_head = 0.001 service_water_pump_motor_efficiency = 1 circulating = false else circulating = true end # add pump if circulating swh_pump = OpenStudio::Model::PumpConstantSpeed.new(model) swh_pump.setName("#{service_water_loop.name} Circulator Pump") swh_pump.setPumpControlType('Intermittent') else swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model) swh_pump.setName("#{service_water_loop.name} Water Mains Pressure Driven") swh_pump.setPumpControlType('Continuous') end swh_pump.setRatedPumpHead(service_water_pump_head.to_f) swh_pump.setMotorEfficiency(service_water_pump_motor_efficiency) swh_pump.addToNode(service_water_loop.supplyInletNode) # add water heater case water_heater_fuel when 'None' # don't add a water heater when 'HeatPump' OpenstudioStandards::ServiceWaterHeating.create_heatpump_water_heater(model, water_heater_capacity: water_heater_capacity, water_heater_volume: water_heater_volume, on_cycle_parasitic_fuel_consumption_rate: on_cycle_parasitic_fuel_consumption_rate, off_cycle_parasitic_fuel_consumption_rate: off_cycle_parasitic_fuel_consumption_rate, service_water_temperature: service_water_temperature, service_water_temperature_schedule: swh_temp_sch, set_peak_use_flowrate: false, peak_flowrate: 0.0, flowrate_schedule: nil, water_heater_thermal_zone: water_heater_thermal_zone, service_water_loop: service_water_loop, use_ems_control: false) else OpenstudioStandards::ServiceWaterHeating.create_water_heater(model, water_heater_capacity: water_heater_capacity, water_heater_volume: water_heater_volume, water_heater_fuel: water_heater_fuel, on_cycle_parasitic_fuel_consumption_rate: on_cycle_parasitic_fuel_consumption_rate, off_cycle_parasitic_fuel_consumption_rate: off_cycle_parasitic_fuel_consumption_rate, service_water_temperature: service_water_temperature, service_water_temperature_schedule: swh_temp_sch, set_peak_use_flowrate: false, peak_flowrate: 0.0, flowrate_schedule: nil, water_heater_thermal_zone: water_heater_thermal_zone, number_of_water_heaters: number_of_water_heaters, service_water_loop: service_water_loop) end # add pipe losses if requested if add_piping_losses OpenstudioStandards::ServiceWaterHeating.create_service_water_heating_piping_losses(model, service_water_loop, circulating: circulating, pipe_insulation_thickness: pipe_insulation_thickness, floor_area: floor_area, number_of_stories: number_of_stories) end # service water heating loop bypass pipes water_heater_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model) service_water_loop.addSupplyBranchForComponent(water_heater_bypass_pipe) coil_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model) service_water_loop.addDemandBranchForComponent(coil_bypass_pipe) supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model) supply_outlet_pipe.addToNode(service_water_loop.supplyOutletNode) demand_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model) demand_outlet_pipe.addToNode(service_water_loop.demandOutletNode) if circulating OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added circulating SWH loop called #{service_water_loop.name}") else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added non-circulating SWH loop called #{service_water_loop.name}") end return service_water_loop end
Adds piping losses to a service water heating Loop. Assumes the piping system use insulated 0.75 inch copper piping. For circulating systems, assume length of piping is proportional to the building floor area and number of stories. For non-circulating systems, assume that the water heaters are close to the point of use.
@param model [OpenStudio::Model::Model] OpenStudio model object @param service_water_loop [OpenStudio::Model::PlantLoop] the service water heating loop @param circulating [Boolean] use true for circulating systems, false for non-circulating systems @param pipe_insulation_thickness [Double] the thickness of the pipe insulation, in m. Use 0 for no insulation @param floor_area [Double] the area of building served by the service water heating loop, in m^2
If nil, will use the total building floor area. Only used if circulating is true.
@param number_of_stories [Integer] the number of stories served by the service water heating loop
If nil, will use the total building number of stories. Only used if circulating is true.
@param pipe_length [Double] the length of the pipe in meters. Default is 6.1 m / 20 ft.
Only used if circulating is false.
@param air_temperature [Double] the temperature of the air surrounding the piping, in C. Default is 21.1 C / 70 F. @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/service_water_heating/create_piping_losses.rb, line 24 def self.create_service_water_heating_piping_losses(model, service_water_loop, circulating: true, pipe_insulation_thickness: 0.0, floor_area: nil, number_of_stories: nil, pipe_length: 6.1, air_temperature: 21.1) # Estimate pipe length if circulating # For circulating systems, get pipe length based on the size of the building. # Formula from A.3.1 PrototypeModelEnhancements_2014_0.pdf # get the floor area floor_area = model.getBuilding.floorArea if floor_area.nil? floor_area_ft2 = OpenStudio.convert(floor_area, 'm^2', 'ft^2').get # get the number of stories number_of_stories = model.getBuilding.buildingStories.size if number_of_stories.nil? # calculate the piping length pipe_length_ft = 2.0 * (Math.sqrt(floor_area_ft2 / number_of_stories) + (10.0 * (number_of_stories - 1.0))) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "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) ) )") else # For non-circulating systems, assume water heater is close to point of use # get pipe length pipe_length_m = pipe_length.nil? ? 6.1 : pipe_length pipe_length_ft = OpenStudio.convert(pipe_length_m, 'm', 'ft').get OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Pipe length #{pipe_length_ft.round}ft. For non-circulating systems, assume water heater is close to point of use.") end # For systems whose water heater object represents multiple pieces # of equipment, multiply the piping length by the number of pieces of equipment. service_water_loop.supplyComponents('OS_WaterHeater_Mixed'.to_IddObjectType).each do |sc| next unless sc.to_WaterHeaterMixed.is_initialized water_heater = sc.to_WaterHeaterMixed.get # get number of water heaters if water_heater.additionalProperties.getFeatureAsInteger('component_quantity').is_initialized comp_qty = water_heater.additionalProperties.getFeatureAsInteger('component_quantity').get else comp_qty = 1 end # if more than 1 water heater, multiply the pipe length by the number of water heaters, # unless the user has specified a pipe length if comp_qty > 1 && pipe_length.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Piping length has been multiplied by #{comp_qty}X because #{water_heater.name} represents #{comp_qty} pieces of equipment.") pipe_length_ft *= comp_qty break end end # Service water heating piping heat loss scheduled air temperature air_temperature_f = OpenStudio.convert(air_temperature, 'C', 'F').get swh_piping_air_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, air_temperature, name: "#{service_water_loop.name} Piping Air Temp - #{air_temperature_f.round}F", schedule_type_limit: 'Temperature') # Service water heating piping heat loss scheduled air velocity swh_piping_air_velocity_m_per_s = 0.3 swh_piping_air_velocity_mph = OpenStudio.convert(swh_piping_air_velocity_m_per_s, 'm/s', 'mile/hr').get swh_piping_air_velocity_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, swh_piping_air_velocity_m_per_s, name: "#{service_water_loop.name} Piping Air Velocity - #{swh_piping_air_velocity_mph.round(2)}mph") # Material for 3/4in type L (heavy duty) copper pipe copper_pipe = OpenStudio::Model::StandardOpaqueMaterial.new(model) copper_pipe.setName('Copper pipe 0.75in type L') copper_pipe.setRoughness('Smooth') copper_pipe.setThickness(OpenStudio.convert(0.045, 'in', 'm').get) copper_pipe.setThermalConductivity(386.0) copper_pipe.setDensity(OpenStudio.convert(556, 'lb/ft^3', 'kg/m^3').get) copper_pipe.setSpecificHeat(OpenStudio.convert(0.092, 'Btu/lb*R', 'J/kg*K').get) copper_pipe.setThermalAbsorptance(0.9) # @todo find reference for property copper_pipe.setSolarAbsorptance(0.7) # @todo find reference for property copper_pipe.setVisibleAbsorptance(0.7) # @todo find reference for property # Construction for pipe pipe_construction = OpenStudio::Model::Construction.new(model) # Add insulation material to insulated pipe if pipe_insulation_thickness > 0 # Material for fiberglass insulation # R-value from Owens-Corning 1/2in fiberglass pipe insulation # https://www.grainger.com/product/OWENS-CORNING-1-2-Thick-40PP22 # but modified until simulated heat loss = 17.7 Btu/hr/ft of pipe with 140F water and 70F air pipe_insulation_thickness_in = OpenStudio.convert(pipe_insulation_thickness, 'm', 'in').get insulation = OpenStudio::Model::StandardOpaqueMaterial.new(model) insulation.setName("Fiberglass batt #{pipe_insulation_thickness_in.round(2)}in") insulation.setRoughness('Smooth') insulation.setThickness(OpenStudio.convert(pipe_insulation_thickness_in, 'in', 'm').get) insulation.setThermalConductivity(OpenStudio.convert(0.46, 'Btu*in/hr*ft^2*R', 'W/m*K').get) insulation.setDensity(OpenStudio.convert(0.7, 'lb/ft^3', 'kg/m^3').get) insulation.setSpecificHeat(OpenStudio.convert(0.2, 'Btu/lb*R', 'J/kg*K').get) insulation.setThermalAbsorptance(0.9) # Irrelevant for Pipe:Indoor; no radiation model is used insulation.setSolarAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used insulation.setVisibleAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used pipe_construction.setName("Copper pipe 0.75in type L with #{pipe_insulation_thickness_in.round(2)}in fiberglass batt") pipe_construction.setLayers([insulation, copper_pipe]) else pipe_construction.setName('Uninsulated copper pipe 0.75in type L') pipe_construction.setLayers([copper_pipe]) end heat_loss_pipe = OpenStudio::Model::PipeIndoor.new(model) heat_loss_pipe.setName("#{service_water_loop.name} Pipe #{pipe_length_ft.round}ft") heat_loss_pipe.setEnvironmentType('Schedule') heat_loss_pipe.setAmbientTemperatureSchedule(swh_piping_air_temp_sch) heat_loss_pipe.setAmbientAirVelocitySchedule(swh_piping_air_velocity_sch) heat_loss_pipe.setConstruction(pipe_construction) heat_loss_pipe.setPipeInsideDiameter(OpenStudio.convert(0.785, 'in', 'm').get) heat_loss_pipe.setPipeLength(OpenStudio.convert(pipe_length_ft, 'ft', 'm').get) heat_loss_pipe.addToNode(service_water_loop.demandInletNode) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added #{pipe_length_ft.round}ft of #{pipe_construction.name} losing heat to #{air_temperature_f.round}F air to #{service_water_loop.name}.") return true end
Creates a water heater and attaches it to the supplied service water heating loop.
@param model [OpenStudio::Model::Model] OpenStudio model object @param water_heater_capacity [Double] water heater capacity, in W. Defaults to 58.6 kW / 200 kBtu/hr @param water_heater_volume [Double] water heater volume, in m^3. Defaults to 0.378 m^3 / 100 gal @param water_heater_fuel [String] water heating fuel. Valid choices are ‘NaturalGas’, ‘Electricity’, or ‘HeatPump’ @param on_cycle_parasitic_fuel_consumption_rate [Double] water heater on cycle parasitic fuel consumption rate, in W @param off_cycle_parasitic_fuel_consumption_rate [Double] water heater off cycle parasitic fuel consumption rate, in W @param service_water_temperature [Double] water heater temperature, in degrees C. Default is 60 C / 140 F. @param service_water_temperature_schedule [OpenStudio::Model::Schedule] the service water heating schedule.
If nil, will be defaulted to a constant temperature schedule based on the service_water_temperature
@param set_peak_use_flowrate [Boolean] if true, the peak flow rate and flow rate schedule will be set. @param peak_flowrate [Double] peak flow rate in m^3/s @param flowrate_schedule [OpenStudio::Model::Schedule] the flow rate fraction schedule @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] Thermal zone for ambient heat loss.
If nil, will assume 71.6 F / 22 C ambient air temperature.
@param number_of_water_heaters [Integer] the number of water heaters represented by the capacity and volume inputs.
Used to modify efficiencies for water heaters based on individual component size while avoiding having to model lots of individual water heaters (for runtime sake).
@param service_water_loop [OpenStudio::Model::PlantLoop] if provided, add the water heater to this loop @return [OpenStudio::Model::WaterHeaterMixed] OpenStudio WaterHeaterMixed object
# File lib/openstudio-standards/service_water_heating/create_water_heater.rb, line 28 def self.create_water_heater(model, water_heater_capacity: nil, water_heater_volume: nil, water_heater_fuel: 'Electricity', on_cycle_parasitic_fuel_consumption_rate: 0.0, off_cycle_parasitic_fuel_consumption_rate: 0.0, service_water_temperature: 60.0, service_water_temperature_schedule: nil, set_peak_use_flowrate: false, peak_flowrate: nil, flowrate_schedule: nil, water_heater_thermal_zone: nil, number_of_water_heaters: 1, service_water_loop: nil) # create water heater object # @todo Standards - Change water heater methodology to follow 'Model Enhancements Appendix A.' water_heater = OpenStudio::Model::WaterHeaterMixed.new(model) # default water heater capacity if nil if water_heater_capacity.nil? water_heater_capacity = OpenStudio.convert(200.0, 'kBtu/hr', 'W').get end water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get water_heater.setHeaterMaximumCapacity(water_heater_capacity) # default water heater volume if nil if water_heater_volume.nil? water_heater_volume = OpenStudio.convert(100.0, 'gal', 'm^3').get end water_heater_volume_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get water_heater.setTankVolume(water_heater_volume) # set the water heater fuel case water_heater_fuel when 'Natural Gas', 'NaturalGas', 'Gas' water_heater.setHeaterFuelType('Gas') water_heater.setHeaterThermalEfficiency(0.78) water_heater.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate) water_heater.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate) water_heater.setOnCycleParasiticFuelType('Gas') water_heater.setOffCycleParasiticFuelType('Gas') water_heater.setOffCycleLossCoefficienttoAmbientTemperature(6.0) water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0) when 'Electricity', 'Electric', 'Elec' water_heater.setHeaterFuelType('Electricity') water_heater.setHeaterThermalEfficiency(1.0) water_heater.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate) water_heater.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate) water_heater.setOnCycleParasiticFuelType('Electricity') water_heater.setOffCycleParasiticFuelType('Electricity') water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053) water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053) when 'FuelOilNo2' water_heater.setHeaterFuelType('FuelOilNo2') water_heater.setHeaterThermalEfficiency(0.78) water_heater.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate) water_heater.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate) water_heater.setOnCycleParasiticFuelType('FuelOilNo2') water_heater.setOffCycleParasiticFuelType('FuelOilNo2') water_heater.setOffCycleLossCoefficienttoAmbientTemperature(6.0) water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0) when 'HeatPump', 'SimpleHeatPump' OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ServiceWaterHeating', 'Simple workaround to represent heat pump water heaters without incurring significant runtime penalty associated with using correct objects.') # Make a part-load efficiency modifier curve with a value above 1, which is multiplied by the nominal efficiency of 100% to represent the COP of a HPWH. # @todo could make this workaround better by using EMS to modify this curve output in realtime based on the OA temperature. hpwh_cop = 2.8 water_heater.setHeaterFuelType('Electricity') water_heater.setHeaterThermalEfficiency(1.0) eff_f_of_plr = OpenStudio::Model::CurveCubic.new(model) eff_f_of_plr.setName("HPWH_COP_#{hpwh_cop}") eff_f_of_plr.setCoefficient1Constant(hpwh_cop) eff_f_of_plr.setCoefficient2x(0.0) eff_f_of_plr.setCoefficient3xPOW2(0.0) eff_f_of_plr.setCoefficient4xPOW3(0.0) eff_f_of_plr.setMinimumValueofx(0.0) eff_f_of_plr.setMaximumValueofx(1.0) water_heater.setPartLoadFactorCurve(eff_f_of_plr) water_heater.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate) water_heater.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate) water_heater.setOnCycleParasiticFuelType('Electricity') water_heater.setOffCycleParasiticFuelType('Electricity') water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053) water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053) else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.ServiceWaterHeating', "#{water_heater_fuel} is not a valid water heater fuel. Valid choices are NaturalGas, Electricity, and HeatPump.") end # set water temperature properties water_heater.setDeadbandTemperatureDifference(2.0) water_heater.setDeadbandTemperatureDifference(OpenStudio.convert(3.6, 'R', 'K').get) water_heater.setHeaterControlType('Cycle') water_heater.setOffCycleParasiticHeatFractiontoTank(0.8) water_heater.setIndirectWaterHeatingRecoveryTime(1.5) # 1.5hrs # create service water temperature schedule based on the service_water_temperature if none provided if service_water_temperature_schedule.nil? swh_temp_c = service_water_temperature swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get service_water_temperature_schedule = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, swh_temp_c, name: "Service Water Loop Temp - #{swh_temp_f.round}F", schedule_type_limit: 'Temperature') end water_heater.setMaximumTemperatureLimit(service_water_temperature) water_heater.setSetpointTemperatureSchedule(service_water_temperature_schedule) # set peak flow rate characteristics if set_peak_use_flowrate water_heater.setPeakUseFlowRate(peak_flowrate) unless peak_flowrate.nil? water_heater.setUseFlowRateFractionSchedule(flowrate_schedule) unless flowrate_schedule.nil? end # set the water heater ambient conditions if water_heater_thermal_zone.nil? # assume the water heater is indoors at 71.6F / 22C indoor_temp_f = 71.6 indoor_temp_c = OpenStudio.convert(indoor_temp_f, 'F', 'C').get default_water_heater_ambient_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, indoor_temp_c, name: "Water Heater Ambient Temp Schedule #{indoor_temp_f}F", schedule_type_limit: 'Temperature') water_heater.setAmbientTemperatureIndicator('Schedule') water_heater.setAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch) water_heater.resetAmbientTemperatureThermalZone else water_heater.setAmbientTemperatureIndicator('ThermalZone') water_heater.setAmbientTemperatureThermalZone(water_heater_thermal_zone) water_heater.resetAmbientTemperatureSchedule end # assign a quantity to the water heater if it represents multiple water heaters if number_of_water_heaters > 1 water_heater.setName("#{number_of_water_heaters}X #{(water_heater_volume_gal / number_of_water_heaters).round}gal #{water_heater_fuel} Water Heater - #{(water_heater_capacity_kbtu_per_hr / number_of_water_heaters).round}kBtu/hr") water_heater.additionalProperties.setFeature('component_quantity', number_of_water_heaters) else water_heater.setName("#{water_heater_volume_gal.round}gal #{water_heater_fuel} Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr") end # add the water heater to the service water loop if provided unless service_water_loop.nil? service_water_loop.addSupplyBranchForComponent(water_heater) end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added water heater called #{water_heater.name}") return water_heater end
Creates a water use and attaches it to a service water loop and a space, if provided
@param model [OpenStudio::Model::Model] OpenStudio model object @param name [String] Use name of the water use object, e.g. main or laundry @param flow_rate [Double] the peak flow rate of the water use in m^3/s @param flow_rate_fraction_schedule [OpenStudio::Model::Schedule] the flow rate fraction schedule @param water_use_temperature [Double] mixed water use temperature at the fixture, in degrees C. Default is 43.3 C / 110 F. @param water_use_temperature_schedule [OpenStudio::Model::Schedule] water use temperature schedule.
If nil, will be defaulted to a constant temperature schedule based on the water_use_temperature
@param sensible_fraction [Double] the water use equipment sensible fraction to the space @param latent_fraction [Double] the water use equipment latent fraction to the space @param service_water_loop [OpenStudio::Model::PlantLoop] if provided, add the water use fixture to this loop @param space [OpenStudio::Model::Space] OpenStudio Space
object @return [OpenStudio::Model::WaterUseEquipment] OpenStudio WaterUseEquipment object
# File lib/openstudio-standards/service_water_heating/create_water_use.rb, line 21 def self.create_water_use(model, name: 'Main Water Use', flow_rate: 0.0, flow_rate_fraction_schedule: nil, water_use_temperature: 43.3, water_use_temperature_schedule: nil, sensible_fraction: 0.2, latent_fraction: 0.05, service_water_loop: nil, space: nil) # IP conversions for naming flow_rate_gpm = OpenStudio.convert(flow_rate, 'm^3/s', 'gal/min').get water_use_temperature_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get # default name name = 'Main Water Use' if name.nil? # water use definition water_use_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model) # set sensible and latent fractions water_use_sensible_frac_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, sensible_fraction, name: "Fraction Sensible - #{sensible_fraction}", schedule_type_limit: 'Fractional') water_use_latent_frac_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, latent_fraction, name: "Fraction Latent - #{latent_fraction}", schedule_type_limit: 'Fractional') water_use_def.setSensibleFractionSchedule(water_use_sensible_frac_sch) water_use_def.setLatentFractionSchedule(water_use_latent_frac_sch) water_use_def.setPeakFlowRate(flow_rate) water_use_def.setName("#{name} #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F") # target mixed water temperature if water_use_temperature_schedule.nil? water_use_temperature_schedule = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model, water_use_temperature, name: "Mixed Water At Faucet Temp - #{water_use_temperature_f.round}F", schedule_type_limit: 'Temperature') end water_use_def.setTargetTemperatureSchedule(water_use_temperature_schedule) # create water use equipment water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_use_def) water_fixture.setFlowRateFractionSchedule(flow_rate_fraction_schedule) # create water use connection swh_connection = OpenStudio::Model::WaterUseConnections.new(model) swh_connection.addWaterUseEquipment(water_fixture) # add to the space if provided if space.nil? water_fixture.setName("#{name} Service Water Use #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F") swh_connection.setName("#{name} WUC #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F") else water_fixture.setName("#{space.name} Service Water Use #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F") swh_connection.setName("#{space.name} WUC #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F") water_fixture.setSpace(space) end # add to the service water loop if provided unless service_water_loop.nil? service_water_loop.addDemandBranchForComponent(swh_connection) OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Adding water fixture to #{service_water_loop.name}.") end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added #{water_fixture.name}.") return water_fixture end