diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index 9789069849..00973803a7 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - b47e5bc1-d61f-47e6-a666-87ee96ba5a14 - 2025-01-14T17:40:54Z + b037a307-ab67-4971-9b3b-116d5b731eb6 + 2025-01-14T20:21:44Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -363,7 +363,7 @@ hpxml.rb rb resource - E35D68B4 + 9CB0E51C hpxml_schema/HPXML.xsd @@ -645,7 +645,7 @@ vehicle.rb rb resource - A355B556 + E49A8C65 version.rb @@ -771,7 +771,7 @@ test_validation.rb rb test - 0062A418 + 5DB1F814 test_vehicle.rb diff --git a/HPXMLtoOpenStudio/resources/hpxml.rb b/HPXMLtoOpenStudio/resources/hpxml.rb index 3ffd081e49..8f62c40311 100644 --- a/HPXMLtoOpenStudio/resources/hpxml.rb +++ b/HPXMLtoOpenStudio/resources/hpxml.rb @@ -9411,13 +9411,12 @@ def to_doc(building) sys_id = XMLHelper.add_element(vehicle, 'SystemIdentifier') XMLHelper.add_attribute(sys_id, 'id', @id) vehicle_type_element = XMLHelper.add_element(vehicle, 'VehicleType') + vehicle_type = XMLHelper.add_element(vehicle_type_element, @vehicle_type) case @vehicle_type when HPXML::VehicleTypeBEV - electric_vehicle = XMLHelper.add_element(vehicle_type_element, @vehicle_type) - battery = XMLHelper.add_element(electric_vehicle, 'Battery') - # Battery + battery = XMLHelper.add_element(vehicle_type, 'Battery') XMLHelper.add_element(battery, 'BatteryType', @battery_type, :string, @battery_type_isdefaulted) unless @battery_type.nil? if not @nominal_capacity_kwh.nil? nominal_capacity = XMLHelper.add_element(battery, 'NominalCapacity') @@ -9443,16 +9442,16 @@ def to_doc(building) XMLHelper.add_extension(battery, 'LifetimeModel', @lifetime_model, :string, @lifetime_model_isdefaulted) unless @lifetime_model.nil? # Battery-Electric Vehicle - fraction_charged_location = XMLHelper.add_element(electric_vehicle, 'FractionChargedLocation') unless @fraction_charged_home.nil? + fraction_charged_location = XMLHelper.add_element(vehicle_type, 'FractionChargedLocation') unless @fraction_charged_home.nil? XMLHelper.add_element(fraction_charged_location, 'Location', HPXML::ElectricVehicleChargingLocation, :string) unless @fraction_charged_home.nil? XMLHelper.add_element(fraction_charged_location, 'Percentage', @fraction_charged_home, :float, @fraction_charged_home_isdefaulted) unless @fraction_charged_home.nil? if not @ev_charger_idref.nil? - charger = XMLHelper.add_element(electric_vehicle, 'ConnectedCharger') + charger = XMLHelper.add_element(vehicle_type, 'ConnectedCharger') XMLHelper.add_attribute(charger, 'idref', @ev_charger_idref) end - XMLHelper.add_extension(electric_vehicle, 'WeekdayScheduleFractions', @ev_charging_weekday_fractions, :string, @ev_charging_weekday_fractions_isdefaulted) unless @ev_charging_weekday_fractions.nil? - XMLHelper.add_extension(electric_vehicle, 'WeekendScheduleFractions', @ev_charging_weekend_fractions, :string, @ev_charging_weekend_fractions_isdefaulted) unless @ev_charging_weekend_fractions.nil? - XMLHelper.add_extension(electric_vehicle, 'MonthlyScheduleMultipliers', @ev_charging_monthly_multipliers, :string, @ev_charging_monthly_multipliers_isdefaulted) unless @ev_charging_monthly_multipliers.nil? + XMLHelper.add_extension(vehicle_type, 'WeekdayScheduleFractions', @ev_charging_weekday_fractions, :string, @ev_charging_weekday_fractions_isdefaulted) unless @ev_charging_weekday_fractions.nil? + XMLHelper.add_extension(vehicle_type, 'WeekendScheduleFractions', @ev_charging_weekend_fractions, :string, @ev_charging_weekend_fractions_isdefaulted) unless @ev_charging_weekend_fractions.nil? + XMLHelper.add_extension(vehicle_type, 'MonthlyScheduleMultipliers', @ev_charging_monthly_multipliers, :string, @ev_charging_monthly_multipliers_isdefaulted) unless @ev_charging_monthly_multipliers.nil? end # Vehicle @@ -9476,19 +9475,21 @@ def from_doc(vehicle) @fuel_economy = XMLHelper.get_value(vehicle, 'FuelEconomyCombined/Value', :float) @fuel_economy_units = XMLHelper.get_value(vehicle, 'FuelEconomyCombined/Units', :string) @vehicle_type = XMLHelper.get_child_name(vehicle, 'VehicleType') - battery_prefix = "VehicleType/#{@vehicle_type}/Battery" - @battery_type = XMLHelper.get_value(vehicle, "#{battery_prefix}/BatteryType", :string) - @nominal_capacity_kwh = XMLHelper.get_value(vehicle, "#{battery_prefix}/NominalCapacity[Units='#{UnitsKwh}']/Value", :float) - @nominal_capacity_ah = XMLHelper.get_value(vehicle, "#{battery_prefix}/NominalCapacity[Units='#{UnitsAh}']/Value", :float) - @usable_capacity_kwh = XMLHelper.get_value(vehicle, "#{battery_prefix}/UsableCapacity[Units='#{UnitsKwh}']/Value", :float) - @usable_capacity_ah = XMLHelper.get_value(vehicle, "#{battery_prefix}/UsableCapacity[Units='#{UnitsAh}']/Value", :float) - @nominal_voltage = XMLHelper.get_value(vehicle, "#{battery_prefix}/NominalVoltage", :float) - @fraction_charged_home = XMLHelper.get_value(vehicle, "VehicleType/#{@vehicle_type}/FractionChargedLocation/Percentage", :float) - @ev_charger_idref = HPXML::get_idref(XMLHelper.get_element(vehicle, "VehicleType/#{@vehicle_type}/ConnectedCharger")) - @lifetime_model = XMLHelper.get_value(vehicle, "#{battery_prefix}/extension/LifetimeModel", :string) - @ev_charging_weekday_fractions = XMLHelper.get_value(vehicle, "VehicleType/#{@vehicle_type}/extension/WeekdayScheduleFractions", :string) - @ev_charging_weekend_fractions = XMLHelper.get_value(vehicle, "VehicleType/#{@vehicle_type}/extension/WeekendScheduleFractions", :string) - @ev_charging_monthly_multipliers = XMLHelper.get_value(vehicle, "VehicleType/#{@vehicle_type}/extension/MonthlyScheduleMultipliers", :string) + if @vehicle_type == HPXML::VehicleTypeBEV + battery_prefix = "VehicleType/#{@vehicle_type}/Battery" + @battery_type = XMLHelper.get_value(vehicle, "#{battery_prefix}/BatteryType", :string) + @nominal_capacity_kwh = XMLHelper.get_value(vehicle, "#{battery_prefix}/NominalCapacity[Units='#{UnitsKwh}']/Value", :float) + @nominal_capacity_ah = XMLHelper.get_value(vehicle, "#{battery_prefix}/NominalCapacity[Units='#{UnitsAh}']/Value", :float) + @usable_capacity_kwh = XMLHelper.get_value(vehicle, "#{battery_prefix}/UsableCapacity[Units='#{UnitsKwh}']/Value", :float) + @usable_capacity_ah = XMLHelper.get_value(vehicle, "#{battery_prefix}/UsableCapacity[Units='#{UnitsAh}']/Value", :float) + @nominal_voltage = XMLHelper.get_value(vehicle, "#{battery_prefix}/NominalVoltage", :float) + @fraction_charged_home = XMLHelper.get_value(vehicle, "VehicleType/#{@vehicle_type}/FractionChargedLocation/Percentage", :float) + @ev_charger_idref = HPXML::get_idref(XMLHelper.get_element(vehicle, "VehicleType/#{@vehicle_type}/ConnectedCharger")) + @lifetime_model = XMLHelper.get_value(vehicle, "#{battery_prefix}/extension/LifetimeModel", :string) + @ev_charging_weekday_fractions = XMLHelper.get_value(vehicle, "VehicleType/#{@vehicle_type}/extension/WeekdayScheduleFractions", :string) + @ev_charging_weekend_fractions = XMLHelper.get_value(vehicle, "VehicleType/#{@vehicle_type}/extension/WeekendScheduleFractions", :string) + @ev_charging_monthly_multipliers = XMLHelper.get_value(vehicle, "VehicleType/#{@vehicle_type}/extension/MonthlyScheduleMultipliers", :string) + end end # Returns the EV charger for the vehicle. diff --git a/HPXMLtoOpenStudio/resources/vehicle.rb b/HPXMLtoOpenStudio/resources/vehicle.rb index 037b2762c0..37e05a9f1f 100644 --- a/HPXMLtoOpenStudio/resources/vehicle.rb +++ b/HPXMLtoOpenStudio/resources/vehicle.rb @@ -13,8 +13,10 @@ class Vehicle # @return [nil] def self.apply(runner, model, spaces, hpxml_bldg, schedules_file) hpxml_bldg.vehicles.each do |vehicle| - next unless vehicle.vehicle_type == HPXML::VehicleTypeBEV - + if vehicle.vehicle_type != HPXML::VehicleTypeBEV + runner.registerWarning("Unexpected vehicle type '#{vehicle.vehicle_type}'. Detailed vehicle charging will not be modeled.") + next + end apply_electric_vehicle(runner, model, spaces, hpxml_bldg, vehicle, schedules_file) end end @@ -70,7 +72,7 @@ def self.get_ev_charging_schedules(runner, model, vehicle, schedules_file) def self.apply_electric_vehicle(runner, model, spaces, hpxml_bldg, vehicle, schedules_file) model.getElectricEquipments.sort.each do |ee| if ee.endUseSubcategory.start_with? Constants::ObjectTypeMiscElectricVehicleCharging - runner.registerWarning('Electric vehicle was specified as a plug load and as a battery, vehicle charging will be modeled as a plug load.') + runner.registerWarning('Electric vehicle charging was specified as both a PlugLoad and a Vehicle, the latter will be ignored.') return end end @@ -78,19 +80,13 @@ def self.apply_electric_vehicle(runner, model, spaces, hpxml_bldg, vehicle, sche # Assign charging and vehicle space ev_charger = vehicle.ev_charger if ev_charger.nil? - runner.registerWarning('Electric vehicle specified with no charger provided; battery will not be modeled.') + runner.registerWarning('Electric vehicle specified with no charger provided; detailed EV charging will not be modeled.') return end vehicle.location = ev_charger.location - # Get schedules to calculate effective discharge power - charging_schedule, discharging_schedule = get_ev_charging_schedules(runner, model, vehicle, schedules_file) - if charging_schedule.nil? && discharging_schedule.nil? - runner.registerWarning('Electric vehicle battery specified with no charging/discharging schedule provided; battery will not be modeled.') - return - end - # Calculate annual driving hours + charging_schedule, discharging_schedule = get_ev_charging_schedules(runner, model, vehicle, schedules_file) if discharging_schedule.to_ScheduleFile.is_initialized dis_sch = discharging_schedule.to_ScheduleFile.get col = dis_sch.columnNumber - 1 diff --git a/HPXMLtoOpenStudio/tests/test_validation.rb b/HPXMLtoOpenStudio/tests/test_validation.rb index 30564087d1..9f4b11f1e9 100644 --- a/HPXMLtoOpenStudio/tests/test_validation.rb +++ b/HPXMLtoOpenStudio/tests/test_validation.rb @@ -1851,7 +1851,8 @@ def test_ruby_warning_messages 'schedule-file-max-power-ratio-with-single-speed-system' => ['Maximum power ratio schedule is only supported for variable speed systems.'], 'schedule-file-max-power-ratio-with-two-speed-system' => ['Maximum power ratio schedule is only supported for variable speed systems.'], 'schedule-file-max-power-ratio-with-separate-backup-system' => ['Maximum power ratio schedule is only supported for integrated backup system. Schedule is ignored for heating.'], - 'ev-charging-methods' => ['Electric vehicle was specified as a plug load and as a battery, vehicle charging will be modeled as a plug load.'] } + 'ev-charging-methods' => ['Electric vehicle charging was specified as both a PlugLoad and a Vehicle, the latter will be ignored.'], + 'vehicle-phev' => ["Unexpected vehicle type 'PlugInHybridElectricVehicle'. Detailed vehicle charging will not be modeled."] } all_expected_warnings.each_with_index do |(warning_case, expected_warnings), i| puts "[#{i + 1}/#{all_expected_warnings.size}] Testing #{warning_case}..." @@ -2005,6 +2006,9 @@ def test_ruby_warning_messages hpxml_bldg.header.schedules_filepaths << File.join(File.dirname(__FILE__), '../resources/schedule_files/hvac-variable-system-maximum-power-ratios-varied.csv') when 'ev-charging-methods' hpxml, hpxml_bldg = _create_hpxml('base-battery-ev-plug-load-ev.xml') + when 'vehicle-phev' + hpxml, hpxml_bldg = _create_hpxml('base-battery-ev-scheduled.xml') + hpxml_bldg.vehicles[0].vehicle_type = 'PlugInHybridElectricVehicle' else fail "Unhandled case: #{warning_case}." end diff --git a/docs/source/workflow_inputs.rst b/docs/source/workflow_inputs.rst index dbc8c7855b..26cc55689c 100644 --- a/docs/source/workflow_inputs.rst +++ b/docs/source/workflow_inputs.rst @@ -4640,7 +4640,7 @@ If not entered, the simulation will not include batteries. HPXML Vehicles ************** -A vehicle can can be entered in ``/HPXML/Building/BuildingDetails/Systems/Vehicles/Vehicle``. Currently only a battery electric vehicle can be modeled with ``/Vehicle/VehicleType/BatteryElectricVehicle``. +A vehicle can be entered in ``/HPXML/Building/BuildingDetails/Systems/Vehicles/Vehicle``. Currently only a battery electric vehicle can be modeled with ``/Vehicle/VehicleType/BatteryElectricVehicle``. This provides detailed modeling of electric vehicles (batteries and charging/discharging) as an alternative to the simple EV charging in :ref:`plug_loads`. If not entered, the simulation will not include a detailed electric vehicle model. diff --git a/workflow/tests/util.rb b/workflow/tests/util.rb index b479ef5c01..3e69e4f237 100644 --- a/workflow/tests/util.rb +++ b/workflow/tests/util.rb @@ -240,11 +240,8 @@ def _verify_outputs(rundir, hpxml_path, results, hpxml, unit_multiplier) if !hpxml_bldg.vehicles.empty? && !hpxml_bldg.vehicles[0].ev_charger_idref.nil? && !hpxml_bldg.vehicles[0].ev_charging_weekday_fractions.nil? next if message.include? 'Electric vehicle hours per week inputted (14.0) do not match the hours per week calculated from the discharging schedule (8.9). The inputted hours per week value will be ignored.' end - if !hpxml_bldg.vehicles.empty? && hpxml_bldg.vehicles[0].ev_charger_idref.nil? - next if message.include? 'Electric vehicle specified with no charger provided; battery will not be modeled.' - end if !hpxml_bldg.vehicles.empty? && !hpxml_bldg.plug_loads.select { |p| p.plug_load_type == HPXML::PlugLoadTypeElectricVehicleCharging }.empty? - next if message.include? 'Electric vehicle was specified as a plug load and as a battery, vehicle charging will be modeled as a plug load.' + next if message.include? 'Electric vehicle charging was specified as both a PlugLoad and a Vehicle, the latter will be ignored.' end if hpxml_path.include? 'base-location-capetown-zaf.xml' next if message.include? 'OS Message: Minutes field (60) on line 9 of EPW file'