Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ __New Features__
- Dual-fuel heat pumps with switchover temperatures > 25F are now autosized based on 25F to allow some additional heating capacity buffer.
- Improves handling of duct leakage specified using cfm25/cfm50.
- Crankcase heating energy is now disabled during unavailable periods, e.g., power outages.
- Water heater updates:
- Allows modeling 120V HPWHs (including dedicated vs shared circuits) using `WaterHeatingSystem/HPWHVoltage`.
- Allows modeling water heaters with a mixing valve using `HasMixingValve` and `MixingValveSetpoint`.
- Allows "other" for `SoilType`; adds variation to dry/wet soil conductivity and diffusivity values for unknown/other/loam soil types.
- Output updates:
- **Breaking change**: Annual peak load outputs for heating and cooling now use units of Btu/h instead of kBtu/h for consistency with other outputs.
Expand Down
24 changes: 12 additions & 12 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>hpxml_to_openstudio</name>
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
<version_id>8b05dfcf-bf6e-4880-8280-e89fa9ac1f1d</version_id>
<version_modified>2026-04-24T15:17:55Z</version_modified>
<version_id>e3879ee8-dd46-4587-bf50-45f7b0eb092f</version_id>
<version_modified>2026-04-27T16:06:36Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLToOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -367,7 +367,7 @@
<filename>defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>F91B9101</checksum>
<checksum>93CB275C</checksum>
</file>
<file>
<filename>electric_panel.rb</filename>
Expand Down Expand Up @@ -397,13 +397,13 @@
<filename>hotwater_appliances.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>5F185DC6</checksum>
<checksum>82516F0C</checksum>
</file>
<file>
<filename>hpxml.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>BF5902DA</checksum>
<checksum>3C44E95B</checksum>
</file>
<file>
<filename>hpxml_schema/HPXML.xsd</filename>
Expand All @@ -421,7 +421,7 @@
<filename>hpxml_schematron/EPvalidator.sch</filename>
<filetype>sch</filetype>
<usage_type>resource</usage_type>
<checksum>9B5934A8</checksum>
<checksum>B163569D</checksum>
</file>
<file>
<filename>hpxml_schematron/iso-schematron.xsd</filename>
Expand Down Expand Up @@ -715,7 +715,7 @@
<filename>waterheater.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>245FEA65</checksum>
<checksum>08232D17</checksum>
</file>
<file>
<filename>weather.rb</filename>
Expand Down Expand Up @@ -751,13 +751,13 @@
<filename>test_defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>191743A2</checksum>
<checksum>C8BD974C</checksum>
</file>
<file>
<filename>test_electric_panel.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>8E69A0EB</checksum>
<checksum>374CB236</checksum>
</file>
<file>
<filename>test_enclosure.rb</filename>
Expand All @@ -775,7 +775,7 @@
<filename>test_hotwater_appliance.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>6DBBC0A9</checksum>
<checksum>0A4D986F</checksum>
</file>
<file>
<filename>test_hvac.rb</filename>
Expand Down Expand Up @@ -829,7 +829,7 @@
<filename>test_validation.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>FD24774E</checksum>
<checksum>3A631903</checksum>
</file>
<file>
<filename>test_vehicle.rb</filename>
Expand All @@ -841,7 +841,7 @@
<filename>test_water_heater.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>8DBA3BD3</checksum>
<checksum>61A61CD2</checksum>
</file>
<file>
<filename>test_weather.rb</filename>
Expand Down
88 changes: 68 additions & 20 deletions HPXMLtoOpenStudio/resources/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3358,15 +3358,31 @@ def self.apply_water_heaters(hpxml_bldg, eri_version, schedules_file)
end

elsif water_heating_system.water_heater_type == HPXML::WaterHeaterTypeHeatPump

if water_heating_system.hpwh_voltage.nil?
water_heating_system.hpwh_voltage = HPXML::HPWHVoltage240
water_heating_system.hpwh_voltage_isdefaulted = true
end

water_heating_system.additional_properties.cop = get_water_heater_heat_pump_cop(water_heating_system)

if water_heating_system.heating_capacity.nil?
water_heating_system.heating_capacity = (UnitConversions.convert(0.5, 'kW', 'Btu/hr') * water_heating_system.additional_properties.cop).round
if water_heating_system.hpwh_voltage == HPXML::HPWHVoltage240
water_heating_system.heating_capacity = (UnitConversions.convert(0.5, 'kW', 'Btu/hr') * water_heating_system.additional_properties.cop).round
elsif water_heating_system.hpwh_voltage == HPXML::HPWHVoltage120Dedicated
water_heating_system.heating_capacity = (UnitConversions.convert(0.422, 'kW', 'Btu/hr') * water_heating_system.additional_properties.cop).round
else # 120V shared
water_heating_system.heating_capacity = (UnitConversions.convert(0.357, 'kW', 'Btu/hr') * water_heating_system.additional_properties.cop).round
end
water_heating_system.heating_capacity_isdefaulted = true
end

if water_heating_system.backup_heating_capacity.nil?
water_heating_system.backup_heating_capacity = UnitConversions.convert(4.5, 'kW', 'Btu/hr').round
if water_heating_system.hpwh_voltage == HPXML::HPWHVoltage240
water_heating_system.backup_heating_capacity = UnitConversions.convert(4.5, 'kW', 'Btu/hr').round
else
water_heating_system.backup_heating_capacity = 0.0 # No backup elements
end
water_heating_system.backup_heating_capacity_isdefaulted = true
end

Expand All @@ -3387,6 +3403,22 @@ def self.apply_water_heaters(hpxml_bldg, eri_version, schedules_file)
end

end

if water_heating_system.has_mixing_valve.nil?
if water_heating_system.water_heater_type == HPXML::WaterHeaterTypeHeatPump && water_heating_system.hpwh_voltage != HPXML::HPWHVoltage240
# 120V HPWH
water_heating_system.has_mixing_valve = true
else
water_heating_system.has_mixing_valve = false
end
water_heating_system.has_mixing_valve_isdefaulted = true
end

if water_heating_system.has_mixing_valve && water_heating_system.mixing_valve_setpoint.nil?
water_heating_system.mixing_valve_setpoint = [125.0, water_heating_system.temperature].min
water_heating_system.mixing_valve_setpoint_isdefaulted = true
end

next unless water_heating_system.location.nil?

iecc_zone = hpxml_bldg.climate_and_risk_zones.climate_zone_ieccs.empty? ? nil : hpxml_bldg.climate_and_risk_zones.climate_zone_ieccs[0].zone
Expand Down Expand Up @@ -6120,6 +6152,7 @@ def self.get_water_heater_location(hpxml_bldg, iecc_zone = nil)
# @param eri_version [String] Version of the ANSI/RESNET/ICC 301 Standard to use for equations/assumptions
# @return [Double] Water heater setpoint temperature (F)
def self.get_water_heater_temperature(eri_version)
# FIXME: Should we default to a different value for 120V HPWHs?
if Constants::ERIVersions.index(eri_version) >= Constants::ERIVersions.index('2014A')
# 2014 w/ Addendum A or newer
return 125.0
Expand Down Expand Up @@ -6268,22 +6301,28 @@ def self.get_water_heater_tank_volume(fuel, is_hpwh, nbeds, nbaths, n_occ)
# @param water_heating_system [HPXML::WaterHeatingSystem] The HPXML water heating system of interest
# @return [Double] COP of the heat pump (W/W)
def self.get_water_heater_heat_pump_cop(water_heating_system)
# Based on simulations of the UEF test procedure at varying COPs
if not water_heating_system.energy_factor.nil?
uef = (0.60522 + water_heating_system.energy_factor) / 1.2101
cop = 1.174536058 * uef
elsif not water_heating_system.uniform_energy_factor.nil?
uef = water_heating_system.uniform_energy_factor
case water_heating_system.usage_bin
when HPXML::WaterHeaterUsageBinVerySmall
fail 'It is unlikely that a heat pump water heater falls into the very small bin of the First Hour Rating (FHR) test. Double check input.'
when HPXML::WaterHeaterUsageBinLow
cop = 1.0005 * uef - 0.0789
when HPXML::WaterHeaterUsageBinMedium
cop = 1.0909 * uef - 0.0868
when HPXML::WaterHeaterUsageBinHigh
cop = 1.1022 * uef - 0.0877
end
if water_heating_system.hpwh_voltage == HPXML::HPWHVoltage240
# Based on simulations of the UEF test procedure at varying COPs
if not water_heating_system.energy_factor.nil?
uef = (0.60522 + water_heating_system.energy_factor) / 1.2101
cop = 1.174536058 * uef
elsif not water_heating_system.uniform_energy_factor.nil?
uef = water_heating_system.uniform_energy_factor
case water_heating_system.usage_bin
when HPXML::WaterHeaterUsageBinVerySmall
fail 'It is unlikely that a heat pump water heater falls into the very small bin of the First Hour Rating (FHR) test. Double check input.'
when HPXML::WaterHeaterUsageBinLow
cop = 1.0005 * uef - 0.0789
when HPXML::WaterHeaterUsageBinMedium
cop = 1.0909 * uef - 0.0868
when HPXML::WaterHeaterUsageBinHigh
cop = 1.1022 * uef - 0.0877
end
end
elsif water_heating_system.hpwh_voltage == HPXML::HPWHVoltage120Dedicated
cop = 3.6
else # 120V shared
cop = 4.2
Comment on lines +6322 to +6325

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Need to replace with equations based on UEF. @jmaguire1

end
return cop
end
Expand Down Expand Up @@ -6826,8 +6865,17 @@ def self.get_branch_circuit_voltage_default_values(branch_circuit)
end
elsif component.is_a?(HPXML::PVSystem)
voltages << HPXML::ElectricPanelVoltage240
elsif component.is_a?(HPXML::WaterHeatingSystem) ||
component.is_a?(HPXML::ClothesDryer) ||
elsif component.is_a?(HPXML::WaterHeatingSystem)
if component.fuel_type == HPXML::FuelTypeElectricity
if component.hpwh_voltage.nil? # Not HPWH
voltages << HPXML::ElectricPanelVoltage240
elsif component.hpwh_voltage == HPXML::HPWHVoltage240
voltages << HPXML::ElectricPanelVoltage240
else # 120V HPWH
voltages << HPXML::ElectricPanelVoltage120
end
end
elsif component.is_a?(HPXML::ClothesDryer) ||
component.is_a?(HPXML::CookingRange)
if component.fuel_type == HPXML::FuelTypeElectricity
voltages << HPXML::ElectricPanelVoltage240
Expand Down
14 changes: 12 additions & 2 deletions HPXMLtoOpenStudio/resources/hotwater_appliances.rb
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,16 @@ def self.apply(runner, model, weather, spaces, hpxml_bldg, hpxml_header, schedul
hpxml_bldg.water_heating_systems.each do |water_heating_system|
non_solar_fraction = 1.0 - Waterheater.get_water_heater_solar_fraction(water_heating_system, hpxml_bldg)

hw_temp_schedule = nil
if water_heating_system.has_mixing_valve
hw_temp_schedule = Model.add_schedule_constant(
model,
name: 'hot water temperature schedule',
value: UnitConversions.convert(water_heating_system.mixing_valve_setpoint, 'F', 'C'),
limits: EPlus::ScheduleTypeLimitsTemperature
)
end

gpd_frac = water_heating_system.fraction_dhw_load_served # Fixtures fraction
if gpd_frac > 0

Expand Down Expand Up @@ -489,7 +499,7 @@ def self.apply(runner, model, weather, spaces, hpxml_bldg, hpxml_header, schedul
peak_flow_rate: unit_multiplier * cw_peak_flow * gpd_frac * non_solar_fraction,
flow_rate_schedule: water_cw_schedule,
water_use_connections: water_use_connections[water_heating_system.id],
target_temperature_schedule: nil
target_temperature_schedule: hw_temp_schedule
)
cw_wue.additionalProperties.setFeature('HPXML_ID', water_heating_system.id) # Used by reporting measure
end
Expand Down Expand Up @@ -526,7 +536,7 @@ def self.apply(runner, model, weather, spaces, hpxml_bldg, hpxml_header, schedul
peak_flow_rate: unit_multiplier * dw_peak_flow * gpd_frac * non_solar_fraction,
flow_rate_schedule: water_dw_schedule,
water_use_connections: water_use_connections[water_heating_system.id],
target_temperature_schedule: nil
target_temperature_schedule: hw_temp_schedule
)
dw_wue.additionalProperties.setFeature('HPXML_ID', water_heating_system.id) # Used by reporting measure
end
Expand Down
13 changes: 13 additions & 0 deletions HPXMLtoOpenStudio/resources/hpxml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ class HPXML < Object
HeatPumpSizingACCA = 'ACCA'
HeatPumpSizingHERS = 'HERS'
HeatPumpSizingMaxLoad = 'MaxLoad'
HPWHVoltage240 = '240V'
HPWHVoltage120 = '120V'
HPWHVoltage120Dedicated = '120V dedicated circuit'
HPWHVoltage120Shared = '120V shared circuit'
HVACCompressorTypeSingleStage = 'single stage'
HVACCompressorTypeTwoStage = 'two stage'
HVACCompressorTypeVariableSpeed = 'variable speed'
Expand Down Expand Up @@ -8595,6 +8599,7 @@ class WaterHeatingSystem < BaseElement
:energy_factor, # [Double] EnergyFactor (frac)
:uniform_energy_factor, # [Double] UniformEnergyFactor (frac)
:hpwh_operating_mode, # [String] HPWHOperatingMode (HPXML::WaterHeaterHPWHOperatingModeXXX)
:hpwh_voltage, # [String] HPWHVoltage (HPXML::HPWHVoltageXXX)
:hpwh_ducting_supply, # [String] HPWHDucting/SupplyAirSource (HPXML::LocationXXX)
:hpwh_ducting_exhaust, # [String] HPWHDucting/ExhaustAirTermination (HPXML::LocationXXX)
:first_hour_rating, # [Double] FirstHourRating (gal/hr)
Expand All @@ -8604,6 +8609,8 @@ class WaterHeatingSystem < BaseElement
:standby_loss_units, # [String] StandbyLoss/Units (HPXML::UnitsXXX)
:standby_loss_value, # [Double] StandbyLoss/Value
:temperature, # [Double] HotWaterTemperature (F)
:has_mixing_valve, # [Boolean] HasMixingValve
:mixing_valve_setpoint, # [Double] MixingValveSetpoint (F)
:uses_desuperheater, # [Boolean] UsesDesuperheater
:related_hvac_idref, # [String] RelatedHVACSystem/@idref
:tank_model_type, # [String] extension/TankModelType (HPXML::WaterHeaterTankModelTypeXXX)
Expand Down Expand Up @@ -8698,6 +8705,7 @@ def to_doc(building)
XMLHelper.add_element(water_heating_system, 'EnergyFactor', @energy_factor, :float, @energy_factor_isdefaulted) unless @energy_factor.nil?
XMLHelper.add_element(water_heating_system, 'UniformEnergyFactor', @uniform_energy_factor, :float) unless @uniform_energy_factor.nil?
XMLHelper.add_element(water_heating_system, 'HPWHOperatingMode', @hpwh_operating_mode, :string, @hpwh_operating_mode_isdefaulted) unless @hpwh_operating_mode.nil?
XMLHelper.add_element(water_heating_system, 'HPWHVoltage', @hpwh_voltage, :string, @hpwh_voltage_isdefaulted) unless @hpwh_voltage.nil?
if (not @hpwh_ducting_exhaust.nil?) || (not @hpwh_ducting_supply.nil?)
hpwh_ducting = XMLHelper.add_element(water_heating_system, 'HPWHDucting')
XMLHelper.add_element(hpwh_ducting, 'SupplyAirSource', @hpwh_ducting_supply, :string, @hpwh_ducting_supply_isdefaulted) unless @hpwh_ducting_supply.nil?
Expand All @@ -8717,6 +8725,8 @@ def to_doc(building)
XMLHelper.add_element(standby_loss, 'Value', @standby_loss_value, :float, @standby_loss_value_isdefaulted)
end
XMLHelper.add_element(water_heating_system, 'HotWaterTemperature', @temperature, :float, @temperature_isdefaulted) unless @temperature.nil?
XMLHelper.add_element(water_heating_system, 'HasMixingValve', @has_mixing_valve, :boolean, @has_mixing_valve_isdefaulted) unless @has_mixing_valve.nil?
XMLHelper.add_element(water_heating_system, 'MixingValveSetpoint', @mixing_valve_setpoint, :float, @mixing_valve_setpoint_isdefaulted) unless @mixing_valve_setpoint.nil?
XMLHelper.add_element(water_heating_system, 'UsesDesuperheater', @uses_desuperheater, :boolean) unless @uses_desuperheater.nil?
if not @related_hvac_idref.nil?
related_hvac_idref_el = XMLHelper.add_element(water_heating_system, 'RelatedHVACSystem')
Expand Down Expand Up @@ -8753,6 +8763,7 @@ def from_doc(water_heating_system)
@energy_factor = XMLHelper.get_value(water_heating_system, 'EnergyFactor', :float)
@uniform_energy_factor = XMLHelper.get_value(water_heating_system, 'UniformEnergyFactor', :float)
@hpwh_operating_mode = XMLHelper.get_value(water_heating_system, 'HPWHOperatingMode', :string)
@hpwh_voltage = XMLHelper.get_value(water_heating_system, 'HPWHVoltage', :string)
@hpwh_ducting_supply = XMLHelper.get_value(water_heating_system, 'HPWHDucting/SupplyAirSource', :string)
@hpwh_ducting_exhaust = XMLHelper.get_value(water_heating_system, 'HPWHDucting/ExhaustAirTermination', :string)
@first_hour_rating = XMLHelper.get_value(water_heating_system, 'FirstHourRating', :float)
Expand All @@ -8762,6 +8773,8 @@ def from_doc(water_heating_system)
@standby_loss_units = XMLHelper.get_value(water_heating_system, 'StandbyLoss/Units', :string)
@standby_loss_value = XMLHelper.get_value(water_heating_system, 'StandbyLoss/Value', :float)
@temperature = XMLHelper.get_value(water_heating_system, 'HotWaterTemperature', :float)
@has_mixing_valve = XMLHelper.get_value(water_heating_system, 'HasMixingValve', :boolean)
@mixing_valve_setpoint = XMLHelper.get_value(water_heating_system, 'MixingValveSetpoint', :float)
@uses_desuperheater = XMLHelper.get_value(water_heating_system, 'UsesDesuperheater', :boolean)
@related_hvac_idref = HPXML::get_idref(XMLHelper.get_element(water_heating_system, 'RelatedHVACSystem'))
@tank_model_type = XMLHelper.get_value(water_heating_system, 'extension/TankModelType', :string)
Expand Down
Loading