Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5090077
First pass on re-implementing 120V HPWHs (from #1534)
shorowit Apr 20, 2026
691ac20
Add changelog [ci skip]
shorowit Apr 20, 2026
5406d3f
Electric panel defaults.
shorowit Apr 21, 2026
ea6c9c8
Address CI errors
shorowit Apr 21, 2026
67f8ebb
Merge branch 'master' of https://github.com/NatLabRockies/OpenStudio-…
shorowit Apr 24, 2026
bd553d2
Require water heater setpoint between 105F and 160F.
shorowit Apr 24, 2026
614e88e
Merge branch 'master' of https://github.com/NatLabRockies/OpenStudio-…
shorowit Apr 24, 2026
785b97b
Connect model to mixing value inputs, add sample file. Add defaults a…
shorowit Apr 24, 2026
d100342
Remove warning (sample file uses mixing valve temp < WH setpoint temp…
shorowit Apr 24, 2026
a1ebfb3
Address CI errors
shorowit Apr 24, 2026
104dc26
Curve name can change.
shorowit Apr 24, 2026
b635ea6
Revert max setpoint temp.
shorowit Apr 27, 2026
0932903
Latest results. [skip ci]
Apr 27, 2026
240e029
Update coefficients and nominal value for COP (and capacity for share…
jmaguire1 Jun 12, 2026
569ca3a
Merge branch 'master' of https://github.com/NatLabRockies/OpenStudio-…
shorowit Jun 15, 2026
ac42ee2
Update test.
shorowit Jun 15, 2026
f15530a
oops, typo on one of the COP coefficients
jmaguire1 Jun 15, 2026
fbd8138
Merge branch 'hpwh_120v' of https://github.com/NatLabRockies/OpenStud…
jmaguire1 Jun 15, 2026
10fa662
Try removing warning exception.
shorowit Jun 15, 2026
f53ae4d
Latest results. [skip ci]
Jun 15, 2026
2590b62
Update mixing valve defaulting logic to use setpoint > 140F.
shorowit Jun 16, 2026
4c1c007
Oops, update HasMixingValve docs for all WH systems.
shorowit Jun 16, 2026
e6379de
Latest results. [skip ci]
Jun 16, 2026
150bf5f
Proposed UA value changes for 120V HPWHs based on HPWHSim information
jmaguire1 Jun 22, 2026
0325945
Merge branch 'hpwh_120v' of https://github.com/NatLabRockies/OpenStud…
jmaguire1 Jun 22, 2026
bf69034
Merge branch 'master' of https://github.com/NatLabRockies/OpenStudio-…
shorowit Jun 23, 2026
5323f22
Update test values.
shorowit Jun 23, 2026
94c0f59
Add backup capacity warnings.
shorowit Jun 23, 2026
d8c65a0
Fix incorrect units pointed out by @jmaguire1.
shorowit Jun 23, 2026
c918418
Update test.
shorowit Jun 23, 2026
a5ba1e3
Latest results. [skip ci]
Jun 23, 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
5 changes: 4 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
## OpenStudio-HPXML v1.13.0

__Features__
- **Breaking change**: Conditioned crawlspaces are no longer allowed; use unvented crawlspace instead.
- **Breaking change**: Conditioned crawlspaces are no longer allowed; use unvented crawlspace instead.
- 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`.

__Bugfixes__
- Fixes ERV supply outlet enthalpy calculation used to calculate latent effectiveness.
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>40a959a9-0600-4ced-87d0-95260a45a38b</version_id>
<version_modified>2026-06-17T16:15:54Z</version_modified>
<version_id>85b1548c-e8dd-4809-bb70-e40e7cd9ecdb</version_id>
<version_modified>2026-06-23T19:53:45Z</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>A3B194F3</checksum>
<checksum>747665BA</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>E0198FBF</checksum>
<checksum>9593B3E0</checksum>
</file>
<file>
<filename>hpxml.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>7BB98B65</checksum>
<checksum>96D4F500</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>54FA6347</checksum>
<checksum>12176FA9</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>C428F4AB</checksum>
<checksum>DDE75257</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>BE1661EA</checksum>
<checksum>49857AFE</checksum>
</file>
<file>
<filename>test_electric_panel.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>91EBCCF9</checksum>
<checksum>712AC849</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>EA35509A</checksum>
<checksum>A1843225</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>F216022C</checksum>
<checksum>8C826E16</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 @@ -3351,15 +3351,29 @@ 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
else
water_heating_system.heating_capacity = (UnitConversions.convert(0.423, '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 @@ -3380,6 +3394,25 @@ def self.apply_water_heaters(hpxml_bldg, eri_version, schedules_file)
end

end

if water_heating_system.has_mixing_valve.nil?
if not water_heating_system.mixing_valve_setpoint.nil?
water_heating_system.has_mixing_valve = true
elsif water_heating_system.temperature.to_f > 140
# Assuming 140F because most water heaters have that as the maximum setpoint, so anything above that
# would be a special case where the scalding risk goes up dramatically.
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.to_f].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 @@ -6261,22 +6294,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 # FIXME: function of uef (need to run simulations to fit)
cop = 2.71
else # 120V shared
cop = 3.44
end
return cop
end
Expand Down Expand Up @@ -6818,8 +6857,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 @@ -222,6 +222,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 @@ -8554,6 +8558,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 @@ -8563,6 +8568,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 @@ -8657,6 +8664,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 @@ -8676,6 +8684,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 @@ -8712,6 +8722,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 @@ -8721,6 +8732,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