Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8758181
Add new sample files for heat pump with hvac seasons, power outage.
joseph-robertson Jan 21, 2026
aae60b1
Add crankcase ems program, and use hvac seasons and unavailability pe…
joseph-robertson Jan 21, 2026
e1c4950
Update tests and docs.
joseph-robertson Jan 21, 2026
0ff6650
Merge branch 'master' into address-crankcase-defrost-pan-warning
joseph-robertson Jan 22, 2026
1a40627
Crankcase into its own program, and remove season from pan and crankc…
joseph-robertson Jan 22, 2026
554afd1
Update defrost/pan/crankcase ems test for crankcase changes.
joseph-robertson Jan 22, 2026
2873982
Remove old crankcase unit test.
joseph-robertson Jan 22, 2026
e526e25
Deal with crankcase allocation to heating or cooling.
joseph-robertson Jan 22, 2026
6eeaa29
Latest results.
Jan 23, 2026
3316eab
Crankcase allows zero capacity; slight program fix.
joseph-robertson Jan 26, 2026
51bb6a7
Latest results.
Jan 26, 2026
897e276
Merge branch 'master' into address-crankcase-defrost-pan-warning
joseph-robertson Jan 26, 2026
a215af2
Use heating and/or cooling availability sensors for crankcase operation.
joseph-robertson Jan 26, 2026
8ba115f
Latest results.
Jan 26, 2026
3a05c70
Use heating coil runtime fraction for crankcase calculation.
joseph-robertson Jan 27, 2026
8c263cb
Revise and simplify ems logic for unavailable periods.
joseph-robertson Jan 27, 2026
61a3792
Revert clg_coil addition in hvac test.
joseph-robertson Jan 27, 2026
c1c8046
Latest results.
Jan 27, 2026
9345759
Set additional properties for hp heating tout and rtf sensors.
joseph-robertson Feb 2, 2026
2abcd8e
Update measure xml.
joseph-robertson Feb 2, 2026
cd0d7ab
Merge branch 'master' into address-crankcase-defrost-pan-warning
joseph-robertson Feb 2, 2026
641094c
Put hp heating tout and rtf sensors upstream of defrost and crankcase.
joseph-robertson Feb 2, 2026
5e0541d
Introduce a new sensor for unitary system.
joseph-robertson Feb 2, 2026
ecdc565
Bring cycling ratio into the calculation for multispeed objects.
joseph-robertson Feb 2, 2026
c8dd252
Update measure xml file.
joseph-robertson Feb 2, 2026
f1da956
Organize ems sensors.
joseph-robertson Feb 3, 2026
b85e3c4
Introduce the cooling coil runtime fraction sensor.
joseph-robertson Feb 3, 2026
5147828
Latest results.
Feb 3, 2026
ba58f27
Merge branch 'master' into address-crankcase-defrost-pan-warning
joseph-robertson Feb 4, 2026
a4f9f01
Latest results.
Feb 5, 2026
13fab78
Merge branch 'master' into address-crankcase-defrost-pan-warning
joseph-robertson Feb 9, 2026
fd4513d
Latest results.
Feb 9, 2026
63dd002
Merge branch 'master' into address-crankcase-defrost-pan-warning
joseph-robertson Mar 6, 2026
439a213
Remove model from being passed into get_object_outputs_by_key.
joseph-robertson Mar 6, 2026
76cddff
Latest results.
Mar 7, 2026
5c2f362
Merge branch 'master' into address-crankcase-defrost-pan-warning
joseph-robertson Mar 12, 2026
54238a3
Latest results.
Mar 12, 2026
2afbc95
Change calling point InsideHVACSystemIterationLoop -> EndOfSystemTime…
joseph-robertson Mar 13, 2026
44ea2e1
Latest results.
Mar 13, 2026
5e820e6
Replace a few remaining TODO comments. [ci skip]
joseph-robertson Mar 16, 2026
c585f32
Update the changelog. [ci skip]
joseph-robertson Mar 16, 2026
5e8f794
Merge branch 'master' into address-crankcase-defrost-pan-warning
joseph-robertson Mar 31, 2026
69e7703
Remove passing model into get_object_outputs_by_key.
joseph-robertson Mar 31, 2026
a6d7be1
Latest results.
Mar 31, 2026
546ddbb
Merge branch 'master' of https://github.com/NatLabRockies/OpenStudio-…
shorowit Apr 6, 2026
9c4b80b
Latest results.
Apr 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions HPXMLtoOpenStudio/measure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<schema_version>3.1</schema_version>
<name>hpxm_lto_openstudio</name>
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
<version_id>977c1cef-1979-4589-8fc0-ca07c4a548f0</version_id>
<version_modified>2026-01-21T17:43:00Z</version_modified>
<version_id>5d9254c5-7bae-4c77-97f4-870ab1e7c136</version_id>
<version_modified>2026-01-26T17:09:00Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -216,7 +216,7 @@
<filename>constants.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>0DE5B248</checksum>
<checksum>00A62229</checksum>
</file>
<file>
<filename>constructions.rb</filename>
Expand Down Expand Up @@ -414,7 +414,7 @@
<filename>hvac.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>846457FE</checksum>
<checksum>E4720C92</checksum>
</file>
<file>
<filename>hvac_sizing.rb</filename>
Expand Down Expand Up @@ -654,7 +654,7 @@
<filename>schedules.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>662E62CE</checksum>
<checksum>D064707B</checksum>
</file>
<file>
<filename>simcontrols.rb</filename>
Expand Down Expand Up @@ -762,7 +762,7 @@
<filename>test_hvac.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>E70D77B1</checksum>
<checksum>68E916FC</checksum>
</file>
<file>
<filename>test_hvac_sizing.rb</filename>
Expand Down Expand Up @@ -810,7 +810,7 @@
<filename>test_validation.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>1A14E141</checksum>
<checksum>4ACBE620</checksum>
</file>
<file>
<filename>test_vehicle.rb</filename>
Expand Down
1 change: 1 addition & 0 deletions HPXMLtoOpenStudio/resources/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ module Constants
ObjectTypeNaturalVentilation = 'natural vent'
ObjectTypeNeighbors = 'neighbors'
ObjectTypeOccupants = 'occupants'
ObjectTypeCrankcaseHeater = 'crankcase heater'
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new "end use" for actuated other equipment objects.

ObjectTypePanHeater = 'pan heater'
ObjectTypePhotovoltaics = 'photovoltaics'
ObjectTypePTAC = 'packaged terminal air conditioner'
Expand Down
97 changes: 87 additions & 10 deletions HPXMLtoOpenStudio/resources/hvac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,12 @@ def self.apply_air_source_hvac_systems(runner, model, weather, hpxml_bldg, hpxml

add_variable_speed_power_ems_program(runner, model, air_loop_unitary, control_zone, heating_system, cooling_system, htg_supp_coil, clg_coil, htg_coil, schedules_file)

if is_heatpump
ems_program = apply_defrost_ems_program(model, htg_coil, control_zone.spaces[0], cooling_system, hpxml_bldg.building_construction.number_of_units)
if cooling_system.pan_heater_watts.to_f > 0
if not cooling_system.nil?
if is_heatpump
ems_program = apply_defrost_ems_program(model, htg_coil, control_zone.spaces[0], cooling_system, hpxml_bldg.building_construction.number_of_units)
apply_pan_heater_ems_program(model, ems_program, htg_coil, control_zone.spaces[0], cooling_system, htg_ap.hp_min_temp, hvac_unavailable_periods[:htg])
end
apply_crankcase_heater_ems_program(model, clg_coil, control_zone.spaces[0], cooling_system)
end
return air_loop
end
Expand Down Expand Up @@ -1557,9 +1558,6 @@ def self.apply_setpoints(runner, model, weather, spaces, hpxml_bldg, hpxml_heade
hvac_control.seasons_heating_end_month, hvac_control.seasons_heating_end_day)
hvac_season_days[:clg] = Calendar.get_daily_season(hpxml_header.sim_calendar_year, hvac_control.seasons_cooling_begin_month, hvac_control.seasons_cooling_begin_day,
hvac_control.seasons_cooling_end_month, hvac_control.seasons_cooling_end_day)
if hvac_season_days[:htg].include?(0) || hvac_season_days[:clg].include?(0)
runner.registerWarning('It is not possible to eliminate all HVAC energy use (e.g. crankcase/defrost energy) in EnergyPlus outside of an HVAC season.')
end

heating_sch = nil
cooling_sch = nil
Expand Down Expand Up @@ -3078,7 +3076,7 @@ def self.create_dx_cooling_coil(model, obj_name, cooling_system, weather_max_dry

clg_coil.setName(coil_name)
clg_coil.setCondenserType('AirCooled')
clg_coil.setCrankcaseHeaterCapacity(cooling_system.crankcase_heater_watts)
clg_coil.setCrankcaseHeaterCapacity(0) # We model crankcase heater via EMS.
clg_coil.additionalProperties.setFeature('HPXML_ID', cooling_system.id) # Used by reporting measure
if has_deadband_control
# Apply startup capacity degradation
Expand Down Expand Up @@ -3246,7 +3244,7 @@ def self.create_dx_heating_coil(model, obj_name, heating_system, weather_min_dry

# Per E+ documentation, if an air-to-air heat pump, the crankcase heater defined for the DX cooling coil is ignored and the crankcase heater power defined for the DX heating coil is used
htg_coil.setMaximumOutdoorDryBulbTemperatureforCrankcaseHeaterOperation(UnitConversions.convert(CrankcaseHeaterTemp, 'F', 'C'))
htg_coil.setCrankcaseHeaterCapacity(heating_system.crankcase_heater_watts)
htg_coil.setCrankcaseHeaterCapacity(0) # We model crankcase heater via EMS.
htg_coil.additionalProperties.setFeature('HPXML_ID', heating_system.id) # Used by reporting measure
htg_coil.additionalProperties.setFeature('FractionHeatLoadServed', heating_system.fraction_heat_load_served) # Used by reporting measure
if has_deadband_control
Expand Down Expand Up @@ -4653,6 +4651,84 @@ def self.apply_installation_quality_ems_program(model, heating_system, cooling_s
)
end

# Creates an EMS program to add crankcase heater energy use for a heat pump.
# A crankcase heater ...
#
# @param model [OpenStudio::Model::Model] OpenStudio Model object
# @param clg_coil [OpenStudio::Model::CoilCoolingDXSingleSpeed or OpenStudio::Model::CoilCoolingDXMultiSpeed] OpenStudio Cooling Coil object
# @param conditioned_space [OpenStudio::Model::Space] OpenStudio Space object for conditioned zone
# @param heat_pump [HPXML::HeatPump] The HPXML heat pump of interest
# @return [nil]
def self.apply_crankcase_heater_ems_program(model, clg_coil, conditioned_space, heat_pump)
return unless heat_pump.crankcase_heater_watts.to_f > 0

# Other equipment/actuator
cnt = model.getOtherEquipments.count { |e| e.endUseSubcategory.start_with? Constants::ObjectTypeCrankcaseHeater } # Ensure unique meter for each heat pump
crankcase_heater_energy_oe = Model.add_other_equipment(
model,
name: "#{clg_coil.name} crankcase heater energy",
end_use: "#{Constants::ObjectTypeCrankcaseHeater}#{cnt + 1}",
space: conditioned_space,
design_level: 0,
frac_radiant: 0,
frac_latent: 0,
frac_lost: 1,
schedule: model.alwaysOnDiscreteSchedule,
fuel_type: HPXML::FuelTypeElectricity
)
crankcase_heater_energy_oe.additionalProperties.setFeature('HPXML_ID', heat_pump.id) # Used by reporting measure
if heat_pump.is_a? HPXML::CoolingSystem
crankcase_heater_energy_oe.additionalProperties.setFeature('FractionHeatLoadServed', 0.0) # Used by reporting measure
elsif heat_pump.is_a? HPXML::HeatPump
crankcase_heater_energy_oe.additionalProperties.setFeature('FractionHeatLoadServed', heat_pump.fraction_heat_load_served) # Used by reporting measure
end
Comment on lines +4751 to +4755
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used by the reporting measure for allocating crankcase either to heating or cooling end use.


crankcase_heater_energy_oe_act = Model.add_ems_actuator(
name: "#{crankcase_heater_energy_oe.name} act",
model_object: crankcase_heater_energy_oe,
comp_type_and_control: EPlus::EMSActuatorOtherEquipmentPower
)

# Sensors
tout_db_sensor = Model.add_ems_sensor(
model,
name: "#{clg_coil.name} tout s",
output_var_or_meter_name: 'Site Outdoor Air Drybulb Temperature',
key_name: 'Environment'
)

hvac_avail_sensor = model.getEnergyManagementSystemSensors.find { |s| s.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeHVACAvailabilitySensor }

# EMS program
if clg_coil.is_a? OpenStudio::Model::CoilCoolingDXSingleSpeed
max_oat_crankcase = clg_coil.maximumOutdoorDryBulbTemperatureForCrankcaseHeaterOperation
elsif clg_coil.is_a? OpenStudio::Model::CoilCoolingDXMultiSpeed
max_oat_crankcase = clg_coil.maximumOutdoorDryBulbTemperatureforCrankcaseHeaterOperation
end
program = Model.add_ems_program(
model,
name: "#{clg_coil.name} crankcase program"
)
program.addLine("Set T_out = #{tout_db_sensor.name}")
temp_criteria = "If (T_out < #{max_oat_crankcase})"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crankcase heater only operates when the compressor is not running -- how do we incorporate that here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 minus the coil runtime fraction?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g., crankcase_power * (1 - coil_rtf)

You don't incorporate it in the criteria, you incorporate it in the energy use calculation.

if not hvac_avail_sensor.nil?
# Don't run crankcase heater during HVAC unavailable period either
temp_criteria += " && (#{hvac_avail_sensor.name} == 1)"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this only run when (A) AC and cooling available, and (B) HP and either heating/cooling available?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

end
program.addLine(temp_criteria)
program.addLine(" Set #{crankcase_heater_energy_oe_act.name} = #{heat_pump.crankcase_heater_watts}")
program.addLine('Else')
program.addLine(" Set #{crankcase_heater_energy_oe_act.name} = 0.0")
program.addLine('EndIf')

Model.add_ems_program_calling_manager(
model,
name: "#{program.name} calling manager",
calling_point: 'InsideHVACSystemIterationLoop',
ems_programs: [program]
)
end

# Creates an EMS program to add pan heater energy use for a heat pump.
# A pan heater ensures that water melted during the defrost cycle does not refreeze into ice and
# result in fan obstruction or coil damage.
Expand All @@ -4666,6 +4742,8 @@ def self.apply_installation_quality_ems_program(model, heating_system, cooling_s
# @param heating_unavailable_periods [HPXML::UnavailablePeriods] Unavailable periods for heating
# @return [nil]
def self.apply_pan_heater_ems_program(model, ems_program, htg_coil, conditioned_space, heat_pump, hp_min_temp, heating_unavailable_periods)
return unless heat_pump.pan_heater_watts.to_f > 0

# Other equipment/actuator
cnt = model.getOtherEquipments.count { |e| e.endUseSubcategory.start_with? Constants::ObjectTypePanHeater } # Ensure unique meter for each heat pump
pan_heater_energy_oe = Model.add_other_equipment(
Expand Down Expand Up @@ -4717,6 +4795,7 @@ def self.apply_pan_heater_ems_program(model, ems_program, htg_coil, conditioned_
ems_program.addLine('Else')
ems_program.addLine(" Set #{pan_heater_energy_oe_act.name} = 0.0")
ems_program.addLine('EndIf')
return ems_program
end

# Create EMS program and Other equipment objects to account for delivered cooling load and supplemental heating energy during defrost.
Expand Down Expand Up @@ -5011,7 +5090,6 @@ def self.apply_unit_multiplier(hpxml_bldg, hpxml_header)
clg_sys.cooling_capacity *= unit_multiplier
clg_sys.cooling_design_airflow_cfm *= unit_multiplier
clg_ap.cooling_actual_airflow_cfm *= unit_multiplier
clg_sys.crankcase_heater_watts *= unit_multiplier unless clg_sys.crankcase_heater_watts.nil?
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meters incoporate thermal zone multipliers.

clg_sys.integrated_heating_system_capacity *= unit_multiplier unless clg_sys.integrated_heating_system_capacity.nil?
clg_sys.integrated_heating_system_airflow_cfm *= unit_multiplier unless clg_sys.integrated_heating_system_airflow_cfm.nil?
clg_sys.cooling_detailed_performance_data.each do |dp|
Expand All @@ -5028,7 +5106,6 @@ def self.apply_unit_multiplier(hpxml_bldg, hpxml_header)
hp_ap.heating_actual_airflow_cfm *= unit_multiplier
hp_sys.heating_capacity_17F *= unit_multiplier unless hp_sys.heating_capacity_17F.nil?
hp_sys.backup_heating_capacity *= unit_multiplier unless hp_sys.backup_heating_capacity.nil?
hp_sys.crankcase_heater_watts *= unit_multiplier unless hp_sys.crankcase_heater_watts.nil?
hpxml_header.heat_pump_backup_heating_capacity_increment *= unit_multiplier unless hpxml_header.heat_pump_backup_heating_capacity_increment.nil?
hp_sys.heating_detailed_performance_data.each do |dp|
dp.capacity *= unit_multiplier unless dp.capacity.nil?
Expand Down
4 changes: 1 addition & 3 deletions HPXMLtoOpenStudio/resources/schedules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -822,9 +822,7 @@ def self.unavailable_period_applies(runner, schedule_name, col_name)
end
if applies == 1
if not runner.nil?
if [SchedulesFile::Columns[:SpaceHeating].name, SchedulesFile::Columns[:SpaceCooling].name].include?(schedule_name)
runner.registerWarning('It is not possible to eliminate all HVAC energy use (e.g. crankcase/defrost energy) in EnergyPlus during an unavailable period.')
elsif schedule_name == SchedulesFile::Columns[:WaterHeater].name
if schedule_name == SchedulesFile::Columns[:WaterHeater].name
runner.registerWarning('It is not possible to eliminate all DHW energy use (e.g. water heater parasitics) in EnergyPlus during an unavailable period.')
end
end
Expand Down
52 changes: 25 additions & 27 deletions HPXMLtoOpenStudio/tests/test_hvac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1240,22 +1240,25 @@ def test_air_to_air_heat_pump_1_speed_onoff_thermostat
_check_onoff_thermostat_EMS(model, clg_coil, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
end

def test_heat_pump_defrost_and_pan_heater
def test_heat_pump_defrost_and_pan_heater_and_crankcase_heater
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the existing crankcase test into here since we're now, like defrost and pan heater, using EMS programs.

# Single Speed heat pump test
args_hash = {}
args_hash['hpxml_path'] = @tmp_hpxml_path
hpxml, hpxml_bldg = _create_hpxml('base-hvac-air-to-air-heat-pump-1-speed.xml')
hpxml_bldg.heat_pumps[0].pan_heater_watts = 60.0
hpxml_bldg.heat_pumps[0].crankcase_heater_watts = 20.0
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
model, _hpxml, hpxml_bldg = _test_measure(args_hash)

# Get HPXML values
backup_fuel = EPlus.fuel_type(hpxml_bldg.heat_pumps[0].backup_heating_fuel)
pan_heater_watts = hpxml_bldg.heat_pumps[0].pan_heater_watts
crankcase_heater_watts = hpxml_bldg.heat_pumps[0].crankcase_heater_watts

assert_equal(1, model.getCoilHeatingDXSingleSpeeds.size)
htg_coil = model.getCoilHeatingDXSingleSpeeds[0]
_check_defrost_and_pan_heater(model, htg_coil, 10000, 1.0, backup_fuel, 0.1, 0.0, pan_heater_watts)
clg_coil = model.getCoilCoolingDXSingleSpeeds[0]
_check_defrost_and_pan_heater_and_crankcase_heater(model, htg_coil, clg_coil, 10000, 1.0, backup_fuel, 0.1, 0.0, pan_heater_watts, crankcase_heater_watts)

# Ductless heat pump test
args_hash = {}
Expand All @@ -1267,7 +1270,8 @@ def test_heat_pump_defrost_and_pan_heater

assert_equal(1, model.getCoilHeatingDXMultiSpeeds.size)
htg_coil = model.getCoilHeatingDXMultiSpeeds[0]
_check_defrost_and_pan_heater(model, htg_coil, 0.0, 0.0, backup_fuel, 0.06667, 0.0)
clg_coil = model.getCoilCoolingDXMultiSpeeds[0]
_check_defrost_and_pan_heater_and_crankcase_heater(model, htg_coil, clg_coil, 0.0, 0.0, backup_fuel, 0.06667, 0.0)

# Ductless heat pump w/ backup heat during defrost test
args_hash = {}
Expand All @@ -1279,7 +1283,8 @@ def test_heat_pump_defrost_and_pan_heater

assert_equal(1, model.getCoilHeatingDXMultiSpeeds.size)
htg_coil = model.getCoilHeatingDXMultiSpeeds[0]
_check_defrost_and_pan_heater(model, htg_coil, 10000, 1.0, backup_fuel, 0.06667, 0.0)
clg_coil = model.getCoilCoolingDXMultiSpeeds[0]
_check_defrost_and_pan_heater_and_crankcase_heater(model, htg_coil, clg_coil, 10000, 1.0, backup_fuel, 0.06667, 0.0)

# Dual fuel heat pump test
args_hash = {}
Expand All @@ -1291,7 +1296,8 @@ def test_heat_pump_defrost_and_pan_heater

assert_equal(1, model.getCoilHeatingDXMultiSpeeds.size)
htg_coil = model.getCoilHeatingDXMultiSpeeds[0]
_check_defrost_and_pan_heater(model, htg_coil, 10000, 0.95, backup_fuel, 0.06667, 0.0)
clg_coil = model.getCoilCoolingDXMultiSpeeds[0]
_check_defrost_and_pan_heater_and_crankcase_heater(model, htg_coil, clg_coil, 10000, 0.95, backup_fuel, 0.06667, 0.0)

# Two heat pump test
args_hash = {}
Expand All @@ -1304,10 +1310,12 @@ def test_heat_pump_defrost_and_pan_heater

assert_equal(2, model.getCoilHeatingDXMultiSpeeds.size)
htg_coil = model.getCoilHeatingDXMultiSpeeds[0]
_check_defrost_and_pan_heater(model, htg_coil, 10000, 1.0, backup_fuel, 0.06667, 0.0, 150.0, 2)
clg_coil = model.getCoilCoolingDXMultiSpeeds[0]
_check_defrost_and_pan_heater_and_crankcase_heater(model, htg_coil, clg_coil, 10000, 1.0, backup_fuel, 0.06667, 0.0, 150.0, 30.0, 2)

htg_coil = model.getCoilHeatingDXMultiSpeeds[1]
_check_defrost_and_pan_heater(model, htg_coil, 10000, 1.0, backup_fuel, 0.06667, 0.0, 150.0, 2)
clg_coil = model.getCoilCoolingDXMultiSpeeds[0]
_check_defrost_and_pan_heater_and_crankcase_heater(model, htg_coil, clg_coil, 10000, 1.0, backup_fuel, 0.06667, 0.0, 150.0, 30.0, 2)

# Separate backup heat pump test
args_hash = {}
Expand All @@ -1319,7 +1327,8 @@ def test_heat_pump_defrost_and_pan_heater

assert_equal(1, model.getCoilHeatingDXMultiSpeeds.size)
htg_coil = model.getCoilHeatingDXMultiSpeeds[0]
_check_defrost_and_pan_heater(model, htg_coil, 0.0, 0.0, backup_fuel, 0.06667, 0.0)
clg_coil = model.getCoilCoolingDXMultiSpeeds[0]
_check_defrost_and_pan_heater_and_crankcase_heater(model, htg_coil, clg_coil, 0.0, 0.0, backup_fuel, 0.06667, 0.0, 150.0, 15.0)
end

def test_mini_split_heat_pump_ductless
Expand Down Expand Up @@ -2157,23 +2166,6 @@ def test_custom_seasons
assert_includes(end_dates, end_date)
end

def test_crankcase_heater_watts
args_hash = {}
args_hash['hpxml_path'] = @tmp_hpxml_path
hpxml, hpxml_bldg = _create_hpxml('base.xml')
hpxml_bldg.cooling_systems[0].crankcase_heater_watts = 40.0
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
model, _hpxml, hpxml_bldg = _test_measure(args_hash)

# Get HPXML values
cooling_system = hpxml_bldg.cooling_systems[0]
crankcase_heater_watts = cooling_system.crankcase_heater_watts

# Check cooling coil
clg_coil = model.getCoilCoolingDXSingleSpeeds[0]
assert_in_epsilon(crankcase_heater_watts, clg_coil.crankcaseHeaterCapacity, 0.01)
end

def test_ceiling_fan
args_hash = {}
args_hash['hpxml_path'] = File.absolute_path(File.join(@sample_files_path, 'base-lighting-ceiling-fans.xml'))
Expand Down Expand Up @@ -2388,7 +2380,7 @@ def _check_onoff_thermostat_EMS(model, clg_or_htg_coil, c1_cap, c2_cap, c3_cap,
return program_values
end

def _check_defrost_and_pan_heater(model, htg_coil, supp_capacity, supp_efficiency, backup_fuel, defrost_time_fraction, defrost_power, pan_heater_watts = 150.0, num_of_ems = 1)
def _check_defrost_and_pan_heater_and_crankcase_heater(model, htg_coil, clg_coil, supp_capacity, supp_efficiency, backup_fuel, defrost_time_fraction, defrost_power, pan_heater_watts = 150.0, crankcase_heater_watts = 30.0, num_of_ems = 1)
# Check Other equipment inputs
defrost_heat_load_oe = model.getOtherEquipments.select { |oe| oe.additionalProperties.getFeatureAsString('ObjectType').to_s == Constants::ObjectTypeHPDefrostHeatLoad }
assert_equal(num_of_ems, defrost_heat_load_oe.size)
Expand All @@ -2407,13 +2399,19 @@ def _check_defrost_and_pan_heater(model, htg_coil, supp_capacity, supp_efficienc
assert_in_epsilon(htg_coil.defrostTimePeriodFraction, defrost_time_fraction, 0.01)
assert_in_delta(htg_coil.resistiveDefrostHeaterCapacity.get, defrost_power, 1.0)

# Check EMS
# Check EMS defrost/pan heater
program_values = get_ems_values(model.getEnergyManagementSystemPrograms, "#{htg_coil.name} defrost program")
assert_in_epsilon(program_values['supp_capacity'].sum, supp_capacity, 0.01)
assert_in_epsilon(program_values['supp_efficiency'].sum, supp_efficiency, 0.01)
pan_heater_act_name = program_values.keys.find { |k| k.include? 'pan_heater_energy_act' }
assert_equal(pan_heater_watts, program_values[pan_heater_act_name][0])
assert(!program_values.empty?)

# Check EMS crankcase heater
program_values = get_ems_values(model.getEnergyManagementSystemPrograms, "#{clg_coil.name} crankcase program")
crankcase_heater_act_name = program_values.keys.find { |k| k.include? 'crankcase_heater_energy_act' }
assert_equal(crankcase_heater_watts, program_values[crankcase_heater_act_name][0])
assert(!program_values.empty?)
end

def _check_ghp_standard(model, heat_pump, clg_cop, htg_cop, soil_density, soil_surface_temp_amps, phase_shift_temp_amps)
Expand Down
Loading