diff --git a/Changelog.md b/Changelog.md
index ef5b09b3b2..326731da4a 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,6 +1,7 @@
## OpenStudio-HPXML v1.10.0
__New Features__
+- Allows requesting timeseries EnergyPlus output meters (e.g., `--hourly "MainsWater:Facility"`), similar to requesting EnergyPlus output variables.
__Bugfixes__
- Fixes zero occupants specified for one unit in a whole MF building from being treated like zero occupants for every unit.
diff --git a/ReportSimulationOutput/README.md b/ReportSimulationOutput/README.md
index 594b8dbac9..c12a865e67 100644
--- a/ReportSimulationOutput/README.md
+++ b/ReportSimulationOutput/README.md
@@ -424,6 +424,17 @@ Optionally generates timeseries EnergyPlus output variables. If multiple output
+**Generate Timeseries Output: EnergyPlus Output Meters**
+
+Optionally generates timeseries EnergyPlus output meters. If multiple output meters are desired, use a comma-separated list. Example: "Electricity:Facility, HeatingCoils:EnergyTransfer"
+
+- **Name:** ``user_output_meters``
+- **Type:** ``String``
+
+- **Required:** ``false``
+
+
+
**Annual Output File Name**
If not provided, defaults to 'results_annual.csv' (or 'results_annual.json' or 'results_annual.msgpack').
diff --git a/ReportSimulationOutput/measure.rb b/ReportSimulationOutput/measure.rb
index fcec01b1bd..ca0e0c2ae2 100644
--- a/ReportSimulationOutput/measure.rb
+++ b/ReportSimulationOutput/measure.rb
@@ -270,6 +270,11 @@ def arguments(model) # rubocop:disable Lint/UnusedMethodArgument
arg.setDescription('Optionally generates timeseries EnergyPlus output variables. If multiple output variables are desired, use a comma-separated list. Do not include key values; by default all key values will be requested. Example: "Zone People Occupant Count, Zone People Total Heating Energy"')
args << arg
+ arg = OpenStudio::Measure::OSArgument::makeStringArgument('user_output_meters', false)
+ arg.setDisplayName('Generate Timeseries Output: EnergyPlus Output Meters')
+ arg.setDescription('Optionally generates timeseries EnergyPlus output meters. If multiple output meters are desired, use a comma-separated list. Example: "Electricity:Facility, HeatingCoils:EnergyTransfer"')
+ args << arg
+
arg = OpenStudio::Measure::OSArgument::makeStringArgument('annual_output_file_name', false)
arg.setDisplayName('Annual Output File Name')
arg.setDescription("If not provided, defaults to 'results_annual.csv' (or 'results_annual.json' or 'results_annual.msgpack').")
@@ -361,7 +366,7 @@ def energyPlusOutputRequests(runner, user_arguments)
args = get_arguments(runner, arguments(model), user_arguments)
- setup_outputs(false, args[:user_output_variables])
+ setup_outputs(false, args)
args = setup_timeseries_includes(@emissions, args)
has_electricity_production = false
@@ -531,11 +536,16 @@ def energyPlusOutputRequests(runner, user_arguments)
end
end
- # Optional output variables (timeseries only)
- @output_variables_requests.each do |output_variable_name, _output_variable|
+ # Output variables (timeseries only)
+ @output_variables_requests.each do |output_variable_name|
result << OpenStudio::IdfObject.load("Output:Variable,*,#{output_variable_name},#{args[:timeseries_frequency]};").get
end
+ # Output meters (timeseries only)
+ @output_meters_requests.each do |output_meter_name|
+ result << OpenStudio::IdfObject.load("Output:Meter,#{output_meter_name},#{args[:timeseries_frequency]};").get
+ end
+
return result.uniq
end
@@ -576,7 +586,7 @@ def run(runner, user_arguments)
@hpxml_header = hpxml.header
@hpxml_bldgs = hpxml.buildings
- setup_outputs(false, args[:user_output_variables])
+ setup_outputs(false, args)
if not File.exist? File.join(output_dir, 'eplusout.msgpack')
runner.registerError('Cannot find eplusout.msgpack.')
@@ -608,7 +618,7 @@ def run(runner, user_arguments)
end
if args[:timeseries_frequency] != 'none'
- @timestamps, timestamps_dst, timestamps_utc = get_timestamps(@msgpackDataTimeseries, @hpxml_header, @hpxml_bldgs, args)
+ @timestamps, timestamps_dst, timestamps_utc = get_timestamps(@msgpackDataTimeseries, @msgpackData, @hpxml_header, @hpxml_bldgs, args)
end
# Retrieve outputs
@@ -627,15 +637,24 @@ def run(runner, user_arguments)
# TODO
#
+ # @param msgpackDataTimeseries [TODO] TODO
# @param msgpackData [TODO] TODO
# @param hpxml_header [TODO] TODO
# @param hpxml_bldgs [TODO] TODO
# @param args [Hash] Map of :argument_name => value
# @return [TODO] TODO
- def get_timestamps(msgpackData, hpxml_header, hpxml_bldgs, args)
- return if msgpackData.nil?
+ def get_timestamps(msgpackDataTimeseries, msgpackData, hpxml_header, hpxml_bldgs, args)
+ if not msgpackDataTimeseries.nil?
+ ep_timestamps = msgpackDataTimeseries['Rows'].map { |r| r.keys[0] }
+ elsif not msgpackData.nil?
+ msgpack_timeseries_name = get_msgpack_timeseries_name(args[:timeseries_frequency])
+ timeseries_data = msgpackData['MeterData'][msgpack_timeseries_name]
+ if not timeseries_data.nil?
+ ep_timestamps = timeseries_data['Rows'].map { |r| r.keys[0] }
+ end
+ end
- ep_timestamps = msgpackData['Rows'].map { |r| r.keys[0] }
+ return if ep_timestamps.nil?
if args[:add_timeseries_dst_column] || args[:use_dview_format]
dst_start_ts = Time.utc(hpxml_header.sim_calendar_year, hpxml_bldgs[0].dst_begin_month, hpxml_bldgs[0].dst_begin_day, 2)
@@ -1190,10 +1209,15 @@ def sanitize_name(name)
end
end
+ # Output Variables
@output_variables = {}
- @output_variables_requests.each do |output_variable_name, _output_variable|
+ @output_variables_requests.each do |output_variable_name|
key_values, units = get_report_variable_data_timeseries_key_values_and_units(output_variable_name)
- runner.registerWarning("Request for output variable '#{output_variable_name}' returned no key values.") if key_values.empty?
+ if key_values.empty?
+ runner.registerWarning("Request for output variable '#{output_variable_name}' returned no results.")
+ next
+ end
+
key_values.each do |key_value|
@output_variables[[output_variable_name, key_value]] = OutputVariable.new
@output_variables[[output_variable_name, key_value]].name = "#{output_variable_name}: #{key_value.split.map(&:capitalize).join(' ')}"
@@ -1202,6 +1226,21 @@ def sanitize_name(name)
end
end
+ # Output Meters
+ @output_meters = {}
+ @output_meters_requests.each do |output_meter_name|
+ units = get_report_meter_data_timeseries_units(output_meter_name, args[:timeseries_frequency])
+ if units.nil?
+ runner.registerWarning("Request for output meter '#{output_meter_name}' returned no results.")
+ next
+ end
+
+ @output_meters[output_meter_name] = OutputMeter.new
+ @output_meters[output_meter_name].name = output_meter_name
+ @output_meters[output_meter_name].timeseries_units = units
+ @output_meters[output_meter_name].timeseries_output = get_report_meter_data_timeseries([output_meter_name], 1, 0, args[:timeseries_frequency])
+ end
+
# Emissions
if not @emissions.empty?
kwh_to_mwh = UnitConversions.convert(1.0, 'kWh', 'MWh')
@@ -1806,17 +1845,24 @@ def report_timeseries_output_results(runner, outputs, timeseries_output_path, ar
output_variables_data = []
end
+ # EnergyPlus output meters
+ if not @output_meters.empty?
+ output_meters_data = @output_meters.values.map { |x| [x.name, x.timeseries_units] + x.timeseries_output }
+ else
+ output_meters_data = []
+ end
+
return if (total_energy_data.size + fuel_data.size + end_use_data.size + system_use_data.size + emissions_data.size + emission_fuel_data.size +
emission_end_use_data.size + hot_water_use_data.size + total_loads_data.size + comp_loads_data.size + unmet_hours_data.size +
- zone_temps_data.size + airflows_data.size + weather_data.size + resilience_data.size + output_variables_data.size) == 0
+ zone_temps_data.size + airflows_data.size + weather_data.size + resilience_data.size + output_variables_data.size + output_meters_data.size) == 0
fail 'Unable to obtain timestamps.' if @timestamps.empty?
if ['csv'].include? args[:output_format]
# Assemble data
data = data.zip(*timestamps2, *timestamps3, *total_energy_data, *fuel_data, *end_use_data, *system_use_data, *emissions_data,
- *emission_fuel_data, *emission_end_use_data, *hot_water_use_data, *total_loads_data, *comp_loads_data,
- *unmet_hours_data, *zone_temps_data, *airflows_data, *weather_data, *resilience_data, *output_variables_data)
+ *emission_fuel_data, *emission_end_use_data, *hot_water_use_data, *total_loads_data, *comp_loads_data, *unmet_hours_data,
+ *zone_temps_data, *airflows_data, *weather_data, *resilience_data, *output_variables_data, *output_meters_data)
# Error-check
n_elements = []
@@ -1882,7 +1928,7 @@ def report_timeseries_output_results(runner, outputs, timeseries_output_path, ar
[total_energy_data, fuel_data, end_use_data, system_use_data, emissions_data, emission_fuel_data,
emission_end_use_data, hot_water_use_data, total_loads_data, comp_loads_data, unmet_hours_data,
- zone_temps_data, airflows_data, weather_data, resilience_data, output_variables_data].each do |d|
+ zone_temps_data, airflows_data, weather_data, resilience_data, output_variables_data, output_meters_data].each do |d|
d.each do |o|
grp, name = o[0].split(':', 2)
h[grp] = {} if h[grp].nil?
@@ -2011,10 +2057,7 @@ def get_resilience_timeseries(init_time_step, batt_kwh, batt_kw, batt_soc_kwh, c
def get_report_meter_data_timeseries(meter_names, unit_conv, unit_adder, timeseries_frequency)
return [0.0] * @timestamps.size if meter_names.empty?
- msgpack_timeseries_name = { 'timestep' => 'TimeStep',
- 'hourly' => 'Hourly',
- 'daily' => 'Daily',
- 'monthly' => 'Monthly' }[timeseries_frequency]
+ msgpack_timeseries_name = get_msgpack_timeseries_name(timeseries_frequency)
timeseries_data = @msgpackData['MeterData'][msgpack_timeseries_name]
cols = timeseries_data['Cols']
rows = timeseries_data['Rows']
@@ -2107,23 +2150,55 @@ def apply_ems_shift(timeseries_frequency)
# TODO
#
- # @param var [TODO] TODO
+ # @param var_name [TODO] TODO
# @return [TODO] TODO
- def get_report_variable_data_timeseries_key_values_and_units(var)
+ def get_report_variable_data_timeseries_key_values_and_units(var_name)
keys = []
units = ''
- if not @msgpackDataTimeseries.nil?
- @msgpackDataTimeseries['Cols'].each do |col|
- next unless col['Variable'].end_with? ":#{var}"
+ return keys, units if @msgpackDataTimeseries.nil?
- keys << col['Variable'].split(':')[0..-2].join(':')
- units = col['Units']
- end
+ @msgpackDataTimeseries['Cols'].each do |col|
+ next unless col['Variable'].end_with? ":#{var_name}"
+
+ keys << col['Variable'].split(':')[0..-2].join(':')
+ units = col['Units']
end
return keys, units
end
+ # TODO
+ #
+ # @param meter_name [TODO] TODO
+ # @param timeseries_frequency [TODO] TODO
+ # @return [TODO] TODO
+ def get_report_meter_data_timeseries_units(meter_name, timeseries_frequency)
+ return if @msgpackData.nil?
+
+ msgpack_timeseries_name = get_msgpack_timeseries_name(timeseries_frequency)
+ timeseries_data = @msgpackData['MeterData'][msgpack_timeseries_name]
+ return if timeseries_data.nil?
+
+ timeseries_data['Cols'].each do |col|
+ next unless col['Variable'] == meter_name
+
+ return col['Units']
+ end
+
+ return
+ end
+
+ # TODO
+ #
+ # @param timeseries_frequency [TODO] TODO
+ # @return [TODO] TODO
+ def get_msgpack_timeseries_name(timeseries_frequency)
+ return { 'timestep' => 'TimeStep',
+ 'hourly' => 'Hourly',
+ 'daily' => 'Daily',
+ 'monthly' => 'Monthly' }[timeseries_frequency]
+ end
+
# TODO
#
# @param report_name [TODO] TODO
@@ -2425,12 +2500,20 @@ def initialize
attr_accessor()
end
+ # TODO
+ class OutputMeter < BaseOutput
+ def initialize
+ super()
+ end
+ attr_accessor()
+ end
+
# TODO
#
# @param called_from_outputs_method [TODO] TODO
- # @param user_output_variables [TODO] TODO
+ # @param args [TODO] TODO
# @return [TODO] TODO
- def setup_outputs(called_from_outputs_method, user_output_variables = nil)
+ def setup_outputs(called_from_outputs_method, args = {})
# TODO
#
# @param fuel_type [TODO] TODO
@@ -2760,13 +2843,10 @@ def get_timeseries_units_from_fuel_type(fuel_type)
end
# Output Variables
- @output_variables_requests = {}
- if not user_output_variables.nil?
- output_variables = user_output_variables.split(',').map(&:strip)
- output_variables.each do |output_variable|
- @output_variables_requests[output_variable] = OutputVariable.new
- end
- end
+ @output_variables_requests = args[:user_output_variables].to_s.split(',').map(&:strip)
+
+ # Output Meters
+ @output_meters_requests = args[:user_output_meters].to_s.split(',').map(&:strip)
end
# TODO
diff --git a/ReportSimulationOutput/measure.xml b/ReportSimulationOutput/measure.xml
index 3d43e4ede3..5ae0444c80 100644
--- a/ReportSimulationOutput/measure.xml
+++ b/ReportSimulationOutput/measure.xml
@@ -3,8 +3,8 @@
3.1
report_simulation_output
df9d170c-c21a-4130-866d-0d46b06073fd
- 5e609cb1-8e83-479b-afda-c1de10cd62ea
- 2024-11-27T02:33:41Z
+ ca741975-505b-4e74-a17b-b96671720b39
+ 2025-01-25T08:36:10Z
9BF1E6AC
ReportSimulationOutput
HPXML Simulation Output Report
@@ -712,6 +712,14 @@
false
false
+
+ user_output_meters
+ Generate Timeseries Output: EnergyPlus Output Meters
+ Optionally generates timeseries EnergyPlus output meters. If multiple output meters are desired, use a comma-separated list. Example: "Electricity:Facility, HeatingCoils:EnergyTransfer"
+ String
+ false
+ false
+
annual_output_file_name
Annual Output File Name
@@ -1912,7 +1920,7 @@
README.md
md
readme
- CDB2D617
+ E1D3B16D
README.md.erb
@@ -1929,13 +1937,13 @@
measure.rb
rb
script
- 4C7478A8
+ 2D3EC7C0
test_report_sim_output.rb
rb
test
- 8552F493
+ F630E4A7
diff --git a/ReportSimulationOutput/tests/test_report_sim_output.rb b/ReportSimulationOutput/tests/test_report_sim_output.rb
index b147a71913..151e701075 100644
--- a/ReportSimulationOutput/tests/test_report_sim_output.rb
+++ b/ReportSimulationOutput/tests/test_report_sim_output.rb
@@ -401,6 +401,11 @@ def teardown
'Surface Construction Index: Window4'
]
+ BaseHPXMLTimeseriesColsEnergyPlusOutputMeters = [
+ 'MainsWater:Facility',
+ 'HeatingCoils:EnergyTransfer'
+ ]
+
def all_base_hpxml_timeseries_cols
return (BaseHPXMLTimeseriesColsEnergy +
BaseHPXMLTimeseriesColsFuels +
@@ -1320,6 +1325,26 @@ def test_timeseries_energyplus_output_variables
assert(File.readlines(run_log).any? { |line| line.include?("Request for output variable 'Foo'") })
end
+ def test_timeseries_energyplus_output_meters
+ args_hash = { 'hpxml_path' => File.join(File.dirname(__FILE__), '../../workflow/sample_files/base.xml'),
+ 'skip_validation' => true,
+ 'add_component_loads' => true,
+ 'timeseries_frequency' => 'hourly',
+ 'user_output_meters' => 'MainsWater:Facility, Foo:Meter, HeatingCoils:EnergyTransfer' }
+ annual_csv, timeseries_csv, run_log = _test_measure(args_hash)
+ assert(File.exist?(annual_csv))
+ assert(File.exist?(timeseries_csv))
+ expected_timeseries_cols = ['Time'] + BaseHPXMLTimeseriesColsEnergyPlusOutputMeters
+ actual_timeseries_cols = File.readlines(timeseries_csv)[0].strip.split(',')
+ assert_equal(expected_timeseries_cols.sort, actual_timeseries_cols.sort)
+ timeseries_rows = CSV.read(timeseries_csv)
+ assert_equal(8760, timeseries_rows.size - 2)
+ timeseries_cols = timeseries_rows.transpose
+ assert_equal(1, _check_for_constant_timeseries_step(timeseries_cols[0]))
+ _check_for_nonzero_avg_timeseries_value(timeseries_csv, BaseHPXMLTimeseriesColsEnergyPlusOutputMeters)
+ assert(File.readlines(run_log).any? { |line| line.include?("Request for output meter 'Foo:Meter'") })
+ end
+
def test_for_unsuccessful_simulation_infinity
# Create HPXML w/ AFUE=0 to generate Infinity result
hpxml_path = File.join(File.dirname(__FILE__), '../../workflow/sample_files/base.xml')
diff --git a/docs/source/usage_instructions.rst b/docs/source/usage_instructions.rst
index 0031d80089..91dc94afb3 100644
--- a/docs/source/usage_instructions.rst
+++ b/docs/source/usage_instructions.rst
@@ -38,6 +38,7 @@ Basic Run
| ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --hourly ALL``
| ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --monthly fuels --monthly temperatures --output-format json``
| ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --monthly fuels --hourly temperatures --hourly 'Zone People Occupant Count'``
+| ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --monthly fuels --hourly temperatures --hourly 'MainsWater:Facility'``
| ``openstudio workflow/run_simulation.rb -x workflow/sample_files/base.xml --hourly ALL --output-format csv_dview``
| The last command will create a timeseries CSV output file that can be visualized by `DView `_ (available for download `here `_).
diff --git a/docs/source/workflow_outputs.rst b/docs/source/workflow_outputs.rst
index b1775f243f..936284247a 100644
--- a/docs/source/workflow_outputs.rst
+++ b/docs/source/workflow_outputs.rst
@@ -619,26 +619,26 @@ If multiple timeseries frequencies are requested (e.g., hourly and daily), the t
Depending on the outputs requested, the file may include:
- =========================== =================== ==================================================================================================================================
- Type Argument [#]_ Notes
- =========================== =================== ==================================================================================================================================
- Total Consumptions ``total`` Energy use for building total and net (i.e., subtracts any power produced by PV or generators).
- Fuel Consumptions ``fuels`` Energy use for each fuel type (in kBtu for fossil fuels and kWh for electricity).
- End Use Consumptions ``enduses`` Energy use for each end use type (in kBtu for fossil fuels and kWh for electricity).
- System Use Consumptions ``systemuses`` Energy use for each HVAC and water heating system (in kBtu).
- Emissions ``emissions`` Emissions (e.g., CO2) for each scenario defined in the HPXML file.
- Emission Fuels ``emissionfuels`` Emissions (e.g., CO2) disaggregated by fuel type for each scenario defined in the HPXML file.
- Emission End Uses ``emissionenduses`` Emissions (e.g., CO2) disaggregated by end use for each scenario defined in the HPXML file.
- Hot Water Uses ``hotwater`` Water use for each end use type (in gallons).
- Total Loads ``loads`` Heating, cooling, and hot water loads (in kBtu).
- Component Loads ``componentloads`` Heating and cooling loads (in kBtu) disaggregated by component (e.g., Walls, Windows, Infiltration, Ducts, etc.).
- Unmet Hours ``unmethours`` Heating and cooling unmet hours.
- Zone Temperatures ``temperatures`` Zone temperatures (in deg-F) for each space (e.g., conditioned space, attic, garage, basement, crawlspace, etc.) plus heating/cooling setpoints.
- Airflows ``airflows`` Airflow rates (in cfm) for infiltration, mechanical ventilation (including clothes dryer exhaust), natural ventilation, whole house fans.
- Weather ``weather`` Weather file data including outdoor temperatures, relative humidity, wind speed, and solar.
- Resilience ``resilience`` Resilience outputs (currently only average resilience hours for battery storage).
- EnergyPlus Output Variables Any user-specified EnergyPlus output variables (e.g., 'Zone People Occupant Count').
- =========================== =================== ==================================================================================================================================
+ ================================== =================== ==================================================================================================================================
+ Type Argument [#]_ Notes
+ ================================== =================== ==================================================================================================================================
+ Total Consumptions ``total`` Energy use for building total and net (i.e., subtracts any power produced by PV or generators).
+ Fuel Consumptions ``fuels`` Energy use for each fuel type (in kBtu for fossil fuels and kWh for electricity).
+ End Use Consumptions ``enduses`` Energy use for each end use type (in kBtu for fossil fuels and kWh for electricity).
+ System Use Consumptions ``systemuses`` Energy use for each HVAC and water heating system (in kBtu).
+ Emissions ``emissions`` Emissions (e.g., CO2) for each scenario defined in the HPXML file.
+ Emission Fuels ``emissionfuels`` Emissions (e.g., CO2) disaggregated by fuel type for each scenario defined in the HPXML file.
+ Emission End Uses ``emissionenduses`` Emissions (e.g., CO2) disaggregated by end use for each scenario defined in the HPXML file.
+ Hot Water Uses ``hotwater`` Water use for each end use type (in gallons).
+ Total Loads ``loads`` Heating, cooling, and hot water loads (in kBtu).
+ Component Loads ``componentloads`` Heating and cooling loads (in kBtu) disaggregated by component (e.g., Walls, Windows, Infiltration, Ducts, etc.).
+ Unmet Hours ``unmethours`` Heating and cooling unmet hours.
+ Zone Temperatures ``temperatures`` Zone temperatures (in deg-F) for each space (e.g., conditioned space, attic, garage, basement, crawlspace, etc.) plus heating/cooling setpoints.
+ Airflows ``airflows`` Airflow rates (in cfm) for infiltration, mechanical ventilation (including clothes dryer exhaust), natural ventilation, whole house fans.
+ Weather ``weather`` Weather file data including outdoor temperatures, relative humidity, wind speed, and solar.
+ Resilience ``resilience`` Resilience outputs (currently only average resilience hours for battery storage).
+ EnergyPlus Output Variables/Meters Any user-specified EnergyPlus output variables/meters (e.g., 'Zone People Occupant Count', 'MainsWater:Facility').
+ ================================== =================== ==================================================================================================================================
.. [#] This is the argument provided to ``run_simulation.rb`` as described in the :ref:`basic_run` usage instructions.
diff --git a/workflow/run_simulation.rb b/workflow/run_simulation.rb
index 83f1ce0066..9b52bbd023 100644
--- a/workflow/run_simulation.rb
+++ b/workflow/run_simulation.rb
@@ -57,6 +57,11 @@ def run_workflow(basedir, rundir, hpxml, debug, skip_validation, add_comp_loads,
'timestep' => timestep_outputs }.each do |timeseries_output_freq, timeseries_outputs|
next if (timeseries_outputs.empty? && timeseries_output_freq != 'none')
+ comma_output = timeseries_outputs.find { |o| o.include? ',' }
+ if not comma_output.nil?
+ fail "Timeseries output request '#{comma_output}' cannot include a comma."
+ end
+
if timeseries_outputs.include? 'ALL'
# Replace 'ALL' with all individual timeseries types
timeseries_outputs.delete('ALL')
@@ -85,8 +90,11 @@ def run_workflow(basedir, rundir, hpxml, debug, skip_validation, add_comp_loads,
args['include_timeseries_airflows'] = timeseries_outputs.include? 'airflows'
args['include_timeseries_weather'] = timeseries_outputs.include? 'weather'
args['include_timeseries_resilience'] = timeseries_outputs.include? 'resilience'
- user_output_variables = timeseries_outputs - $timeseries_types
- args['user_output_variables'] = user_output_variables.join(', ') unless user_output_variables.empty?
+ remaining_outputs = timeseries_outputs - $timeseries_types
+ output_variables = remaining_outputs.select { |o| !o.include?(':') }
+ output_meters = remaining_outputs.select { |o| o.include?(':') }
+ args['user_output_variables'] = output_variables.join(', ') unless output_variables.empty?
+ args['user_output_meters'] = output_meters.join(', ') unless output_meters.empty?
if n_timeseries_freqs > 1
# Need to use different timeseries filenames
args['timeseries_output_file_name'] = "results_timeseries_#{timeseries_output_freq}.#{output_format}"
@@ -125,22 +133,22 @@ def run_workflow(basedir, rundir, hpxml, debug, skip_validation, add_comp_loads,
end
options[:hourly_outputs] = []
- opts.on('--hourly NAME', 'Request hourly output category* or EnergyPlus output variable; can be called multiple times') do |t|
+ opts.on('--hourly NAME', 'Request hourly output category* or EnergyPlus output variable/meter; can be called multiple times') do |t|
options[:hourly_outputs] << t
end
options[:daily_outputs] = []
- opts.on('--daily NAME', 'Request daily output category* or EnergyPlus output variable; can be called multiple times') do |t|
+ opts.on('--daily NAME', 'Request daily output category* or EnergyPlus output variable/meter; can be called multiple times') do |t|
options[:daily_outputs] << t
end
options[:monthly_outputs] = []
- opts.on('--monthly NAME', 'Request monthly output category* or EnergyPlus output variable; can be called multiple times') do |t|
+ opts.on('--monthly NAME', 'Request monthly output category* or EnergyPlus output variable/meter; can be called multiple times') do |t|
options[:monthly_outputs] << t
end
options[:timestep_outputs] = []
- opts.on('--timestep NAME', 'Request timestep output category* or EnergyPlus output variable; can be called multiple times') do |t|
+ opts.on('--timestep NAME', 'Request timestep output category* or EnergyPlus output variable/meter; can be called multiple times') do |t|
options[:timestep_outputs] << t
end
diff --git a/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw b/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw
index da3a974b27..ef48e709fb 100644
--- a/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw
+++ b/workflow/template-build-and-run-hpxml-with-stochastic-occupancy.osw
@@ -135,7 +135,8 @@
"timeseries_timestamp_convention": "start",
"add_timeseries_dst_column": false,
"add_timeseries_utc_column": false,
- "user_output_variables": ""
+ "user_output_variables": "",
+ "user_output_meters": ""
},
"measure_dir_name": "ReportSimulationOutput"
},
diff --git a/workflow/template-run-hpxml-with-stochastic-occupancy-subset.osw b/workflow/template-run-hpxml-with-stochastic-occupancy-subset.osw
index 376cc13ca1..1552e18dc2 100644
--- a/workflow/template-run-hpxml-with-stochastic-occupancy-subset.osw
+++ b/workflow/template-run-hpxml-with-stochastic-occupancy-subset.osw
@@ -60,7 +60,8 @@
"timeseries_timestamp_convention": "start",
"add_timeseries_dst_column": false,
"add_timeseries_utc_column": false,
- "user_output_variables": ""
+ "user_output_variables": "",
+ "user_output_meters": ""
},
"measure_dir_name": "ReportSimulationOutput"
},
diff --git a/workflow/template-run-hpxml-with-stochastic-occupancy.osw b/workflow/template-run-hpxml-with-stochastic-occupancy.osw
index 737d8ab397..8a0dff1170 100644
--- a/workflow/template-run-hpxml-with-stochastic-occupancy.osw
+++ b/workflow/template-run-hpxml-with-stochastic-occupancy.osw
@@ -59,7 +59,8 @@
"timeseries_timestamp_convention": "start",
"add_timeseries_dst_column": false,
"add_timeseries_utc_column": false,
- "user_output_variables": ""
+ "user_output_variables": "",
+ "user_output_meters": ""
},
"measure_dir_name": "ReportSimulationOutput"
},
diff --git a/workflow/template-run-hpxml.osw b/workflow/template-run-hpxml.osw
index cedb0cce1a..ca67d06488 100644
--- a/workflow/template-run-hpxml.osw
+++ b/workflow/template-run-hpxml.osw
@@ -52,7 +52,8 @@
"timeseries_timestamp_convention": "start",
"add_timeseries_dst_column": false,
"add_timeseries_utc_column": false,
- "user_output_variables": ""
+ "user_output_variables": "",
+ "user_output_meters": ""
},
"measure_dir_name": "ReportSimulationOutput"
},
diff --git a/workflow/tests/test_other.rb b/workflow/tests/test_other.rb
index 59a6df1c52..1da4549bdc 100644
--- a/workflow/tests/test_other.rb
+++ b/workflow/tests/test_other.rb
@@ -135,8 +135,10 @@ def test_run_simulation_timeseries_outputs
command += ' --hourly ALL'
command += " --hourly 'Zone People Occupant Count'"
command += " --hourly 'Zone People Total Heating Energy'"
+ command += " --hourly 'MainsWater:Facility'"
end
command += " --hourly 'Foobar Variable'" # Test invalid output variable request
+ command += " --hourly 'Foobar:Meter'" # Test invalid output variable request
system(command, err: File::NULL)
# Check for output files
@@ -151,22 +153,34 @@ def test_run_simulation_timeseries_outputs
assert_equal(1, timeseries_rows[0].count { |r| r == 'Time' })
assert_equal(1, timeseries_rows[0].count { |r| r == 'Zone People Occupant Count: Conditioned Space' })
assert_equal(1, timeseries_rows[0].count { |r| r == 'Zone People Total Heating Energy: Conditioned Space' })
+ assert_equal(1, timeseries_rows[0].count { |r| r == 'MainsWater:Facility' })
else
refute(File.exist? timeseries_output_path)
end
- # Check run.log has warning about missing Foobar Variable
+ # Check run.log has warning about missing Foobar Variable & Meter
assert(File.exist? File.join(File.dirname(xml), 'run', 'run.log'))
log_lines = File.readlines(File.join(File.dirname(xml), 'run', 'run.log')).map(&:strip)
- assert(log_lines.include? "Warning: Request for output variable 'Foobar Variable' returned no key values.")
+ assert(log_lines.include? "Warning: Request for output variable 'Foobar Variable' returned no results.")
+ assert(log_lines.include? "Warning: Request for output meter 'Foobar:Meter' returned no results.")
end
end
+ def test_run_simulation_timeseries_outputs_comma
+ # Check that the simulation produces timeseries with requested outputs
+ rb_path = File.join(File.dirname(__FILE__), '..', 'run_simulation.rb')
+ xml = File.join(File.dirname(__FILE__), '..', 'sample_files', 'base.xml')
+ command = "\"#{OpenStudio.getOpenStudioCLI}\" \"#{rb_path}\" -x \"#{xml}\" --hourly 'Zone People Occupant Count,MainsWater:Facility'"
+ success = system(command, err: File::NULL)
+
+ refute(success)
+ end
+
def test_run_simulation_mixed_timeseries_frequencies
# Check that we can correctly skip the EnergyPlus simulation and reporting measures
rb_path = File.join(File.dirname(__FILE__), '..', 'run_simulation.rb')
xml = File.join(File.dirname(__FILE__), '..', 'sample_files', 'base.xml')
- command = "\"#{OpenStudio.getOpenStudioCLI}\" \"#{rb_path}\" -x \"#{xml}\" --timestep weather --hourly enduses --daily temperatures --monthly ALL --monthly 'Zone People Total Heating Energy'"
+ command = "\"#{OpenStudio.getOpenStudioCLI}\" \"#{rb_path}\" -x \"#{xml}\" --timestep weather --hourly enduses --daily temperatures --monthly ALL --monthly 'Zone People Total Heating Energy' --daily 'MainsWater:Facility'"
system(command, err: File::NULL)
# Check for output files
@@ -180,7 +194,7 @@ def test_run_simulation_mixed_timeseries_frequencies
# Check timeseries columns exist
{ 'timestep' => ['Weather:'],
'hourly' => ['End Use:'],
- 'daily' => ['Temperature:'],
+ 'daily' => ['Temperature:', 'MainsWater:Facility'],
'monthly' => ['End Use:', 'Fuel Use:', 'Zone People Total Heating Energy:'] }.each do |freq, col_names|
timeseries_rows = CSV.read(File.join(File.dirname(xml), 'run', "results_timeseries_#{freq}.csv"))
assert_equal(1, timeseries_rows[0].count { |r| r == 'Time' })