openstudio-common-measures 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +6 -3
  4. data/Rakefile +17 -3
  5. data/lib/measures/AddDaylightSensors/LICENSE.md +27 -0
  6. data/lib/measures/AddDaylightSensors/README.md +136 -0
  7. data/lib/measures/AddDaylightSensors/README.md.erb +42 -0
  8. data/lib/measures/AddDaylightSensors/measure.rb +521 -0
  9. data/lib/measures/AddDaylightSensors/measure.xml +233 -0
  10. data/lib/measures/EnableDemandControlledVentilation/LICENSE.md +27 -0
  11. data/lib/measures/EnableDemandControlledVentilation/README.md +32 -0
  12. data/lib/measures/EnableDemandControlledVentilation/README.md.erb +42 -0
  13. data/lib/measures/EnableDemandControlledVentilation/measure.rb +154 -0
  14. data/lib/measures/EnableDemandControlledVentilation/measure.xml +99 -0
  15. data/lib/measures/EnableEconomizerControl/LICENSE.md +27 -0
  16. data/lib/measures/EnableEconomizerControl/README.md +48 -0
  17. data/lib/measures/EnableEconomizerControl/README.md.erb +42 -0
  18. data/lib/measures/EnableEconomizerControl/measure.rb +172 -0
  19. data/lib/measures/EnableEconomizerControl/measure.xml +124 -0
  20. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/LICENSE.md +27 -0
  21. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/README.md +64 -0
  22. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/README.md.erb +42 -0
  23. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.rb +422 -0
  24. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.xml +150 -0
  25. data/lib/measures/IncreaseInsulationRValueForRoofs/LICENSE.md +27 -0
  26. data/lib/measures/IncreaseInsulationRValueForRoofs/README.md +64 -0
  27. data/lib/measures/IncreaseInsulationRValueForRoofs/README.md.erb +42 -0
  28. data/lib/measures/IncreaseInsulationRValueForRoofs/measure.rb +422 -0
  29. data/lib/measures/IncreaseInsulationRValueForRoofs/measure.xml +143 -0
  30. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/LICENSE.md +27 -0
  31. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/README.md +97 -0
  32. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/README.md.erb +42 -0
  33. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.rb +450 -0
  34. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.xml +186 -0
  35. data/lib/measures/ReduceLightingLoadsByPercentage/LICENSE.md +27 -0
  36. data/lib/measures/ReduceLightingLoadsByPercentage/README.md +96 -0
  37. data/lib/measures/ReduceLightingLoadsByPercentage/README.md.erb +42 -0
  38. data/lib/measures/ReduceLightingLoadsByPercentage/measure.rb +513 -0
  39. data/lib/measures/ReduceLightingLoadsByPercentage/measure.xml +191 -0
  40. data/lib/measures/ReduceSpaceInfiltrationByPercentage/LICENSE.md +27 -0
  41. data/lib/measures/ReduceSpaceInfiltrationByPercentage/README.md +104 -0
  42. data/lib/measures/ReduceSpaceInfiltrationByPercentage/README.md.erb +42 -0
  43. data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.rb +349 -0
  44. data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.xml +181 -0
  45. data/lib/measures/ReduceVentilationByPercentage/LICENSE.md +27 -0
  46. data/lib/measures/ReduceVentilationByPercentage/README.md +40 -0
  47. data/lib/measures/ReduceVentilationByPercentage/README.md.erb +42 -0
  48. data/lib/measures/ReduceVentilationByPercentage/measure.rb +291 -0
  49. data/lib/measures/ReduceVentilationByPercentage/measure.xml +96 -0
  50. data/lib/measures/add_ems_to_control_ev_charging/{ReadMe.MD → README.md} +0 -0
  51. data/lib/measures/add_ev_load/{ReadMe.MD → README.md} +0 -0
  52. data/lib/measures/create_variable_speed_rtu/LICENSE.md +27 -0
  53. data/lib/measures/create_variable_speed_rtu/README.md +120 -0
  54. data/lib/measures/create_variable_speed_rtu/README.md.erb +42 -0
  55. data/lib/measures/create_variable_speed_rtu/measure.rb +539 -0
  56. data/lib/measures/create_variable_speed_rtu/measure.xml +207 -0
  57. data/lib/measures/generic_qaqc/measure.xml +14 -14
  58. data/lib/measures/generic_qaqc/resources/check_envelope_conductance.rb +7 -1
  59. data/lib/measures/generic_qaqc/resources/check_eui_by_end_use.rb +10 -11
  60. data/lib/measures/openstudio_results/README.md +5 -1
  61. data/lib/measures/openstudio_results/measure.rb +12 -8
  62. data/lib/measures/openstudio_results/measure.xml +56 -36
  63. data/lib/measures/openstudio_results/resources/os_lib_reporting.rb +115 -37
  64. data/lib/measures/openstudio_results/resources/os_lib_schedules.rb +27 -25
  65. data/lib/measures/set_exterior_walls_and_floors_to_adiabatic/README.md +16 -0
  66. data/lib/measures/set_exterior_walls_and_floors_to_adiabatic/measure.rb +32 -1
  67. data/lib/measures/set_exterior_walls_and_floors_to_adiabatic/measure.xml +31 -12
  68. data/lib/measures/view_data/measure.xml +8 -8
  69. data/lib/measures/view_data/resources/va3c.rb +50 -46
  70. data/lib/measures/view_model/resources/va3c.rb +50 -46
  71. data/lib/openstudio/common_measures/version.rb +1 -1
  72. data/openstudio-common-measures.gemspec +2 -2
  73. metadata +59 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7224129b55bec16c8936b284e54c7609c53e53719a4b59f7c921e11aca617ad
4
- data.tar.gz: 4140c02c5f41bf2d38f972478746dd7c72ce79b6cc903b2b3d70561ce534cc6f
3
+ metadata.gz: f60fd113283f7b1c0d89fdab58e3109a910142709dd9c8aaa7ef720a54471acd
4
+ data.tar.gz: d1830f530d2c60bd5db9bf654dd290885a9c9835dabc7d2b7b23e9ce4001280b
5
5
  SHA512:
6
- metadata.gz: e02c60d2cc4625d290a2ac0d4208661138225ddca9ecd7aba333c07da3b05868d16ed32a666531dfd283563d353fa530a867787baf3acb5baab29cd9a1f3f09d
7
- data.tar.gz: 95d672a0b439bf9e0caed02b78198af51f06b6a787d2a168906b223ead4e83109cdbccf927d6baa7733487e83f4ba44db4aeec22a9ecc5a942087cd874c3a74b
6
+ metadata.gz: 653ed7725cd73ca0c7664c07257772ed882d3391acdda0a99a87712b6824ec2a40098a864a461ee53bfcded6365635db86cc5d8571e3e7ff93fdc94666e2baf9
7
+ data.tar.gz: 67791080c2703b405dfdc257728bdb88216da3ebc60ebe7b59158934ebfcd5a7002df3cc34a6bcce9231c2c8c1e1862d053a158f4dec0b34d53719ab5b114863
data/.gitignore CHANGED
@@ -12,6 +12,7 @@
12
12
  /test/
13
13
  /lib/measures/test_results
14
14
  /lib/measures/*/tests/output
15
+ /lib/measures/staged
15
16
 
16
17
  .rubocop*s3*
17
18
 
@@ -1,7 +1,12 @@
1
1
  # OpenStudio Common Measures Gem
2
2
 
3
+ ## Version 0.3.0
4
+
5
+ * Support for OpenStudio 3.1 (upgrade to extension gem 0.3.1)
6
+
3
7
  ## Version 0.2.1
4
- * Removes the following from lib/measures:
8
+
9
+ * Removes the following from lib/measures and moves them to the OpenStudio-calibration-gem:
5
10
  * AddDaylightSensors
6
11
  * EnableDemandControlledVentilation
7
12
  * EnableEconomizerControl
@@ -42,8 +47,6 @@
42
47
  * Upgrade Bundler to 2.1.0
43
48
  * Upgrade openstudio-extension to 0.2.3
44
49
 
45
-
46
-
47
50
  ## Version 0.2.0
48
51
 
49
52
  * Support for OpenStudio 3.0
data/Rakefile CHANGED
@@ -31,9 +31,6 @@ require 'rspec/core/rake_task'
31
31
 
32
32
  RSpec::Core::RakeTask.new(:spec)
33
33
 
34
- require 'rubocop/rake_task'
35
- RuboCop::RakeTask.new
36
-
37
34
  # Load in the rake tasks from the base extension gem
38
35
  require 'openstudio/extension/rake_task'
39
36
  require 'openstudio/common_measures'
@@ -44,3 +41,20 @@ require 'openstudio_measure_tester/rake_task'
44
41
  OpenStudioMeasureTester::RakeTask.new
45
42
 
46
43
  task default: :spec
44
+
45
+ desc 'Delete measure test output'
46
+ task :delete_measure_test_outputs do
47
+ require 'fileutils'
48
+
49
+ puts 'Deleting tests/output directory from measures.'
50
+
51
+ # get measures in repo
52
+ measures = Dir.glob('**/**/**/measure.rb')
53
+
54
+ #create unique list of parent directories for measures.
55
+ measures.each do |i|
56
+ FileUtils.rm_rf(i.gsub("measure.rb","tests/output"))
57
+ end
58
+ puts "Deleted test outputs"
59
+
60
+ end
@@ -0,0 +1,27 @@
1
+ OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted
4
+ provided that the following conditions are met:
5
+
6
+ (1) Redistributions of source code must retain the above copyright notice, this list of conditions
7
+ and the following disclaimer.
8
+
9
+ (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions
10
+ and the following disclaimer in the documentation and/or other materials provided with the distribution.
11
+
12
+ (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse
13
+ or promote products derived from this software without specific prior written permission from the
14
+ respective party.
15
+
16
+ (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other
17
+ derivative works may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar
18
+ designation without specific prior written permission from Alliance for Sustainable Energy, LLC.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
21
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES GOVERNMENT,
23
+ OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
25
+ OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,136 @@
1
+
2
+
3
+ ###### (Automatically generated documentation)
4
+
5
+ # Add Daylight Sensor at the Center of Spaces with a Specified Space Type Assigned
6
+
7
+ ## Description
8
+ This measure will add daylighting controls to spaces that that have space types assigned with names containing the string in the argument. You can also add a cost per space for sensors added to the model.
9
+
10
+ ## Modeler Description
11
+ Make an array of the spaces that meet the criteria. Locate the sensor x and y values by averaging the min and max X and Y values from floor surfaces in the space. If a space already has a daylighting control, do not add a new one and leave the original in place. Warn the user if the space isn't assigned to a thermal zone, or if the space doesn't have any translucent surfaces. Note that the cost is added to the space not the sensor. If the sensor is removed at a later date, the cost will remain.
12
+
13
+ ## Measure Type
14
+ ModelMeasure
15
+
16
+ ## Taxonomy
17
+
18
+
19
+ ## Arguments
20
+
21
+
22
+ ### Add Daylight Sensors to Spaces of This Space Type
23
+
24
+ **Name:** space_type,
25
+ **Type:** Choice,
26
+ **Units:** ,
27
+ **Required:** true,
28
+ **Model Dependent:** false
29
+
30
+ ### Daylighting Setpoint
31
+
32
+ **Name:** setpoint,
33
+ **Type:** Double,
34
+ **Units:** fc,
35
+ **Required:** true,
36
+ **Model Dependent:** false
37
+
38
+ ### Daylighting Control Type
39
+
40
+ **Name:** control_type,
41
+ **Type:** Choice,
42
+ **Units:** ,
43
+ **Required:** true,
44
+ **Model Dependent:** false
45
+
46
+ ### Daylighting Minimum Input Power Fraction
47
+ min = 0 max = 0.6
48
+ **Name:** min_power_fraction,
49
+ **Type:** Double,
50
+ **Units:** ,
51
+ **Required:** true,
52
+ **Model Dependent:** false
53
+
54
+ ### Daylighting Minimum Light Output Fraction
55
+ min = 0 max = 0.6
56
+ **Name:** min_light_fraction,
57
+ **Type:** Double,
58
+ **Units:** ,
59
+ **Required:** true,
60
+ **Model Dependent:** false
61
+
62
+ ### Fraction of zone controlled by daylight sensors
63
+
64
+ **Name:** fraction_zone_controlled,
65
+ **Type:** Double,
66
+ **Units:** ,
67
+ **Required:** true,
68
+ **Model Dependent:** false
69
+
70
+ ### Sensor Height
71
+
72
+ **Name:** height,
73
+ **Type:** Double,
74
+ **Units:** inches,
75
+ **Required:** true,
76
+ **Model Dependent:** false
77
+
78
+ ### Material and Installation Costs per Space for Daylight Sensor
79
+
80
+ **Name:** material_cost,
81
+ **Type:** Double,
82
+ **Units:** $,
83
+ **Required:** true,
84
+ **Model Dependent:** false
85
+
86
+ ### Demolition Costs per Space for Daylight Sensor
87
+
88
+ **Name:** demolition_cost,
89
+ **Type:** Double,
90
+ **Units:** $,
91
+ **Required:** true,
92
+ **Model Dependent:** false
93
+
94
+ ### Years Until Costs Start
95
+
96
+ **Name:** years_until_costs_start,
97
+ **Type:** Integer,
98
+ **Units:** whole years,
99
+ **Required:** true,
100
+ **Model Dependent:** false
101
+
102
+ ### Demolition Costs Occur During Initial Construction
103
+
104
+ **Name:** demo_cost_initial_const,
105
+ **Type:** Boolean,
106
+ **Units:** ,
107
+ **Required:** true,
108
+ **Model Dependent:** false
109
+
110
+ ### Expected Life
111
+
112
+ **Name:** expected_life,
113
+ **Type:** Integer,
114
+ **Units:** whole years,
115
+ **Required:** true,
116
+ **Model Dependent:** false
117
+
118
+ ### O & M Costs per Space for Daylight Sensor
119
+
120
+ **Name:** om_cost,
121
+ **Type:** Double,
122
+ **Units:** $,
123
+ **Required:** true,
124
+ **Model Dependent:** false
125
+
126
+ ### O & M Frequency
127
+
128
+ **Name:** om_frequency,
129
+ **Type:** Integer,
130
+ **Units:** whole years,
131
+ **Required:** true,
132
+ **Model Dependent:** false
133
+
134
+
135
+
136
+
@@ -0,0 +1,42 @@
1
+ <%#= README.md.erb is used to auto-generate README.md. %>
2
+ <%#= To manually maintain README.md throw away README.md.erb and manually edit README.md %>
3
+ ###### (Automatically generated documentation)
4
+
5
+ # <%= name %>
6
+
7
+ ## Description
8
+ <%= description %>
9
+
10
+ ## Modeler Description
11
+ <%= modelerDescription %>
12
+
13
+ ## Measure Type
14
+ <%= measureType %>
15
+
16
+ ## Taxonomy
17
+ <%= taxonomy %>
18
+
19
+ ## Arguments
20
+
21
+ <% arguments.each do |argument| %>
22
+ ### <%= argument[:display_name] %>
23
+ <%= argument[:description] %>
24
+ **Name:** <%= argument[:name] %>,
25
+ **Type:** <%= argument[:type] %>,
26
+ **Units:** <%= argument[:units] %>,
27
+ **Required:** <%= argument[:required] %>,
28
+ **Model Dependent:** <%= argument[:model_dependent] %>
29
+ <% end %>
30
+
31
+ <% if arguments.size == 0 %>
32
+ <%= "This measure does not have any user arguments" %>
33
+ <% end %>
34
+
35
+ <% if outputs.size > 0 %>
36
+ ## Outputs
37
+ <% output_names = [] %>
38
+ <% outputs.each do |output| %>
39
+ <% output_names << output[:display_name] %>
40
+ <% end %>
41
+ <%= output_names.join(", ") %>
42
+ <% end %>
@@ -0,0 +1,521 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ # start the measure
37
+ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
38
+ # define the name that a user will see
39
+ def name
40
+ return 'Add Daylight Sensor at the Center of Spaces with a Specified Space Type Assigned'
41
+ end
42
+
43
+ # human readable description
44
+ def description
45
+ return 'This measure will add daylighting controls to spaces that that have space types assigned with names containing the string in the argument. You can also add a cost per space for sensors added to the model.'
46
+ end
47
+
48
+ # human readable description of modeling approach
49
+ def modeler_description
50
+ return "Make an array of the spaces that meet the criteria. Locate the sensor x and y values by averaging the min and max X and Y values from floor surfaces in the space. If a space already has a daylighting control, do not add a new one and leave the original in place. Warn the user if the space isn't assigned to a thermal zone, or if the space doesn't have any translucent surfaces. Note that the cost is added to the space not the sensor. If the sensor is removed at a later date, the cost will remain."
51
+ end
52
+
53
+ # define the arguments that the user will input
54
+ def arguments(model)
55
+ args = OpenStudio::Measure::OSArgumentVector.new
56
+
57
+ # make a choice argument for model objects
58
+ space_type_handles = OpenStudio::StringVector.new
59
+ space_type_display_names = OpenStudio::StringVector.new
60
+
61
+ # putting model object and names into hash
62
+ space_type_args = model.getSpaceTypes
63
+ space_type_args_hash = {}
64
+ space_type_args.each do |space_type_arg|
65
+ space_type_args_hash[space_type_arg.name.to_s] = space_type_arg
66
+ end
67
+
68
+ # looping through sorted hash of model objects
69
+ space_type_args_hash.sort.map do |key, value|
70
+ # only include if space type is used in the model
71
+ if !value.spaces.empty?
72
+ space_type_handles << value.handle.to_s
73
+ space_type_display_names << key
74
+ end
75
+ end
76
+
77
+ # make a choice argument for space type
78
+ space_type = OpenStudio::Measure::OSArgument.makeChoiceArgument('space_type', space_type_handles, space_type_display_names, true)
79
+ space_type.setDisplayName('Add Daylight Sensors to Spaces of This Space Type')
80
+ args << space_type
81
+
82
+ # make an argument for setpoint
83
+ setpoint = OpenStudio::Measure::OSArgument.makeDoubleArgument('setpoint', true)
84
+ setpoint.setDisplayName('Daylighting Setpoint')
85
+ setpoint.setUnits('fc')
86
+ setpoint.setDefaultValue(45.0)
87
+ args << setpoint
88
+
89
+ # make an argument for control_type
90
+ chs = OpenStudio::StringVector.new
91
+ chs << 'None'
92
+ chs << 'Continuous'
93
+ chs << 'Stepped'
94
+ chs << 'Continuous/Off'
95
+ control_type = OpenStudio::Measure::OSArgument.makeChoiceArgument('control_type', chs)
96
+ control_type.setDisplayName('Daylighting Control Type')
97
+ control_type.setDefaultValue('Continuous/Off')
98
+ args << control_type
99
+
100
+ # make an argument for min_power_fraction
101
+ min_power_fraction = OpenStudio::Measure::OSArgument.makeDoubleArgument('min_power_fraction', true)
102
+ min_power_fraction.setDisplayName('Daylighting Minimum Input Power Fraction')
103
+ min_power_fraction.setDescription('min = 0 max = 0.6')
104
+ min_power_fraction.setDefaultValue(0.3)
105
+ args << min_power_fraction
106
+
107
+ # make an argument for min_light_fraction
108
+ min_light_fraction = OpenStudio::Measure::OSArgument.makeDoubleArgument('min_light_fraction', true)
109
+ min_light_fraction.setDisplayName('Daylighting Minimum Light Output Fraction')
110
+ min_light_fraction.setDescription('min = 0 max = 0.6')
111
+ min_light_fraction.setDefaultValue(0.2)
112
+ args << min_light_fraction
113
+
114
+ # make an argument for fraction_zone_controlled
115
+ fraction_zone_controlled = OpenStudio::Measure::OSArgument.makeDoubleArgument('fraction_zone_controlled', true)
116
+ fraction_zone_controlled.setDisplayName('Fraction of zone controlled by daylight sensors')
117
+ fraction_zone_controlled.setDefaultValue(1.0)
118
+ args << fraction_zone_controlled
119
+
120
+ # make an argument for height
121
+ height = OpenStudio::Measure::OSArgument.makeDoubleArgument('height', true)
122
+ height.setDisplayName('Sensor Height')
123
+ height.setUnits('inches')
124
+ height.setDefaultValue(30.0)
125
+ args << height
126
+
127
+ # make an argument for material and installation cost
128
+ material_cost = OpenStudio::Measure::OSArgument.makeDoubleArgument('material_cost', true)
129
+ material_cost.setDisplayName('Material and Installation Costs per Space for Daylight Sensor')
130
+ material_cost.setUnits('$')
131
+ material_cost.setDefaultValue(0.0)
132
+ args << material_cost
133
+
134
+ # make an argument for demolition cost
135
+ demolition_cost = OpenStudio::Measure::OSArgument.makeDoubleArgument('demolition_cost', true)
136
+ demolition_cost.setDisplayName('Demolition Costs per Space for Daylight Sensor')
137
+ demolition_cost.setUnits('$')
138
+ demolition_cost.setDefaultValue(0.0)
139
+ args << demolition_cost
140
+
141
+ # make an argument for duration in years until costs start
142
+ years_until_costs_start = OpenStudio::Measure::OSArgument.makeIntegerArgument('years_until_costs_start', true)
143
+ years_until_costs_start.setDisplayName('Years Until Costs Start')
144
+ years_until_costs_start.setUnits('whole years')
145
+ years_until_costs_start.setDefaultValue(0)
146
+ args << years_until_costs_start
147
+
148
+ # make an argument to determine if demolition costs should be included in initial construction
149
+ demo_cost_initial_const = OpenStudio::Measure::OSArgument.makeBoolArgument('demo_cost_initial_const', true)
150
+ demo_cost_initial_const.setDisplayName('Demolition Costs Occur During Initial Construction')
151
+ demo_cost_initial_const.setDefaultValue(false)
152
+ args << demo_cost_initial_const
153
+
154
+ # make an argument for expected life
155
+ expected_life = OpenStudio::Measure::OSArgument.makeIntegerArgument('expected_life', true)
156
+ expected_life.setDisplayName('Expected Life')
157
+ expected_life.setUnits('whole years')
158
+ expected_life.setDefaultValue(20)
159
+ args << expected_life
160
+
161
+ # make an argument for o&m cost
162
+ om_cost = OpenStudio::Measure::OSArgument.makeDoubleArgument('om_cost', true)
163
+ om_cost.setDisplayName('O & M Costs per Space for Daylight Sensor')
164
+ om_cost.setUnits('$')
165
+ om_cost.setDefaultValue(0.0)
166
+ args << om_cost
167
+
168
+ # make an argument for o&m frequency
169
+ om_frequency = OpenStudio::Measure::OSArgument.makeIntegerArgument('om_frequency', true)
170
+ om_frequency.setDisplayName('O & M Frequency')
171
+ om_frequency.setUnits('whole years')
172
+ om_frequency.setDefaultValue(1)
173
+ args << om_frequency
174
+
175
+ return args
176
+ end
177
+
178
+ # define what happens when the measure is run
179
+ def run(model, runner, user_arguments)
180
+ super(model, runner, user_arguments)
181
+
182
+ # use the built-in error checking
183
+ if !runner.validateUserArguments(arguments(model), user_arguments)
184
+ return false
185
+ end
186
+
187
+ # assign the user inputs to variables
188
+ space_type = runner.getOptionalWorkspaceObjectChoiceValue('space_type', user_arguments, model)
189
+ setpoint = runner.getDoubleArgumentValue('setpoint', user_arguments)
190
+ control_type = runner.getStringArgumentValue('control_type', user_arguments)
191
+ min_power_fraction = runner.getDoubleArgumentValue('min_power_fraction', user_arguments)
192
+ min_light_fraction = runner.getDoubleArgumentValue('min_light_fraction', user_arguments)
193
+ fraction_zone_controlled = runner.getDoubleArgumentValue('fraction_zone_controlled', user_arguments)
194
+ height = runner.getDoubleArgumentValue('height', user_arguments)
195
+ material_cost = runner.getDoubleArgumentValue('material_cost', user_arguments)
196
+ demolition_cost = runner.getDoubleArgumentValue('demolition_cost', user_arguments)
197
+ years_until_costs_start = runner.getIntegerArgumentValue('years_until_costs_start', user_arguments)
198
+ demo_cost_initial_const = runner.getBoolArgumentValue('demo_cost_initial_const', user_arguments)
199
+ expected_life = runner.getIntegerArgumentValue('expected_life', user_arguments)
200
+ om_cost = runner.getDoubleArgumentValue('om_cost', user_arguments)
201
+ om_frequency = runner.getIntegerArgumentValue('om_frequency', user_arguments)
202
+
203
+ # check the space_type for reasonableness
204
+ if space_type.empty?
205
+ handle = runner.getStringArgumentValue('space_type', user_arguments)
206
+ if handle.empty?
207
+ runner.registerError('No SpaceType was chosen.')
208
+ else
209
+ runner.registerError("The selected space type with handle '#{handle}' was not found in the model. It may have been removed by another measure.")
210
+ end
211
+ return false
212
+ else
213
+ if !space_type.get.to_SpaceType.empty?
214
+ space_type = space_type.get.to_SpaceType.get
215
+ else
216
+ runner.registerError('Script Error - argument not showing up as space type.')
217
+ return false
218
+ end
219
+ end
220
+
221
+ # check the setpoint for reasonableness
222
+ if (setpoint < 0) || (setpoint > 9999) # dfg need input on good value
223
+ runner.registerError("A setpoint of #{setpoint} foot-candles is outside the measure limit.")
224
+ return false
225
+ elsif setpoint > 999
226
+ runner.registerWarning("A setpoint of #{setpoint} foot-candles is abnormally high.") # dfg need input on good value
227
+ end
228
+
229
+ # check the min_power_fraction for reasonableness
230
+ if (min_power_fraction < 0.0) || (min_power_fraction > 0.6)
231
+ runner.registerError("The requested minimum input power fraction of #{min_power_fraction} for continuous dimming control is outside the acceptable range of 0 to 0.6.")
232
+ return false
233
+ end
234
+
235
+ # check the min_light_fraction for reasonableness
236
+ if (min_light_fraction < 0.0) || (min_light_fraction > 0.6)
237
+ runner.registerError("The requested minimum light output fraction of #{min_light_fraction} for continuous dimming control is outside the acceptable range of 0 to 0.6.")
238
+ return false
239
+ end
240
+
241
+ # check the height for reasonableness
242
+ if (height < -360) || (height > 360) # neg ok because space origin may not be floor
243
+ runner.registerError("A setpoint of #{height} inches is outside the measure limit.")
244
+ return false
245
+ elsif height > 72
246
+ runner.registerWarning("A setpoint of #{height} inches is abnormally high.")
247
+ elseif height < 0
248
+ runner.registerWarning('Typically the sensor height should be a positive number, however if your space origin is above the floor then a negative sensor height may be approriate.')
249
+ end
250
+
251
+ # set flags to use later
252
+ costs_requested = false
253
+ warning_cost_assign_to_space = false
254
+
255
+ # check costs for reasonableness
256
+ if material_cost.abs + demolition_cost.abs + om_cost.abs == 0
257
+ runner.registerInfo('No costs were requested for Daylight Sensors.')
258
+ else
259
+ costs_requested = true
260
+ end
261
+
262
+ # check lifecycle arguments for reasonableness
263
+ if (years_until_costs_start < 0) && (years_until_costs_start > expected_life)
264
+ runner.registerError('Years until costs start should be a non-negative integer less than Expected Life.')
265
+ end
266
+ if (expected_life < 1) && (expected_life > 100)
267
+ runner.registerError('Choose an integer greater than 0 and less than or equal to 100 for Expected Life.')
268
+ end
269
+ if om_frequency < 1
270
+ runner.registerError('Choose an integer greater than 0 for O & M Frequency.')
271
+ end
272
+
273
+ # short def to make numbers pretty (converts 4125001.25641 to 4,125,001.26 or 4,125,001). The definition be called through this measure
274
+ def neat_numbers(number, roundto = 2) # round to 0 or 2)
275
+ if roundto == 2
276
+ number = format '%.2f', number
277
+ else
278
+ number = number.round
279
+ end
280
+ # regex to add commas
281
+ number.to_s.reverse.gsub(/([0-9]{3}(?=([0-9])))/, '\\1,').reverse
282
+ end
283
+
284
+ # helper that loops through lifecycle costs getting total costs under "Construction" or "Salvage" category and add to counter if occurs during year 0
285
+ def get_total_costs_for_objects(objects)
286
+ counter = 0
287
+ objects.each do |object|
288
+ object_LCCs = object.lifeCycleCosts
289
+ object_LCCs.each do |object_LCC|
290
+ if (object_LCC.category == 'Construction') || (object_LCC.category == 'Salvage')
291
+ if object_LCC.yearsFromStart == 0
292
+ counter += object_LCC.totalCost
293
+ end
294
+ end
295
+ end
296
+ end
297
+ return counter
298
+ end
299
+
300
+ # unit conversion from IP units to SI units
301
+ setpoint_si = OpenStudio.convert(setpoint, 'fc', 'lux').get
302
+ height_si = OpenStudio.convert(height, 'in', 'm').get
303
+
304
+ # variable to tally the area to which the overall measure is applied
305
+ area = 0
306
+ # variables to aggregate the number of sensors installed and the area affected
307
+ sensor_count = 0
308
+ sensor_area = 0
309
+ spaces_using_space_type = space_type.spaces
310
+ # array with subset of spaces
311
+ spaces_using_space_type_in_zones_without_sensors = []
312
+ affected_zones = []
313
+ affected_zone_names = []
314
+ # hash to hold sensor objects
315
+ new_sensor_objects = {}
316
+
317
+ # reporting initial condition of model
318
+ starting_spaces = model.getSpaces
319
+ runner.registerInitialCondition("#{spaces_using_space_type.size} spaces are assigned to space type '#{space_type.name}'.")
320
+
321
+ # get starting costs for spaces
322
+ yr0_capital_totalCosts = -1 * get_total_costs_for_objects(spaces_using_space_type)
323
+
324
+ # test that there is no sensor already in the space, and that zone object doesn't already have sensors assigned.
325
+ spaces_using_space_type.each do |space_using_space_type|
326
+ if space_using_space_type.daylightingControls.empty?
327
+ space_zone = space_using_space_type.thermalZone
328
+ if !space_zone.empty?
329
+ space_zone = space_zone.get
330
+ if space_zone.primaryDaylightingControl.empty? && space_zone.secondaryDaylightingControl.empty?
331
+ spaces_using_space_type_in_zones_without_sensors << space_using_space_type
332
+ elsif
333
+ runner.registerWarning("Thermal zone '#{space_zone.name}' which includes space '#{space_using_space_type.name}' already had a daylighting sensor. No sensor was added to space '#{space_using_space_type.name}'.")
334
+ end
335
+ else
336
+ runner.registerWarning("Space '#{space_using_space_type.name}' is not associated with a thermal zone. It won't be part of the EnergyPlus simulation.")
337
+ end
338
+ else
339
+ runner.registerWarning("Space '#{space_using_space_type.name}' already has a daylighting sensor. No sensor was added.")
340
+ end
341
+ end
342
+
343
+ # loop through all spaces,
344
+ # and add a daylighting sensor with dimming to each
345
+ space_count = 0
346
+ spaces_using_space_type_in_zones_without_sensors.each do |space|
347
+ space_count += 1
348
+ area += space.floorArea
349
+
350
+ # eliminate spaces that don't have exterior natural lighting
351
+ has_ext_nat_light = false
352
+ space.surfaces.each do |surface|
353
+ next if surface.outsideBoundaryCondition != 'Outdoors'
354
+ surface.subSurfaces.each do |sub_surface|
355
+ next if sub_surface.subSurfaceType == 'Door'
356
+ next if sub_surface.subSurfaceType == 'OverheadDoor'
357
+ has_ext_nat_light = true
358
+ end
359
+ end
360
+ if has_ext_nat_light == false
361
+ runner.registerWarning("Space '#{space.name}' has no exterior natural lighting. No sensor will be added.")
362
+ next
363
+ end
364
+
365
+ # find floors
366
+ floors = []
367
+ space.surfaces.each do |surface|
368
+ next if surface.surfaceType != 'Floor'
369
+ floors << surface
370
+ end
371
+
372
+ # this method only works for flat (non-inclined) floors
373
+ boundingBox = OpenStudio::BoundingBox.new
374
+ floors.each do |floor|
375
+ boundingBox.addPoints(floor.vertices)
376
+ end
377
+ xmin = boundingBox.minX.get
378
+ ymin = boundingBox.minY.get
379
+ zmin = boundingBox.minZ.get
380
+ xmax = boundingBox.maxX.get
381
+ ymax = boundingBox.maxY.get
382
+
383
+ # create a new sensor and put at the center of the space
384
+ sensor = OpenStudio::Model::DaylightingControl.new(model)
385
+ sensor.setName("#{space.name} daylighting control")
386
+ x_pos = (xmin + xmax) / 2
387
+ y_pos = (ymin + ymax) / 2
388
+ z_pos = zmin + height_si # put it 1 meter above the floor
389
+ new_point = OpenStudio::Point3d.new(x_pos, y_pos, z_pos)
390
+ sensor.setPosition(new_point)
391
+ sensor.setIlluminanceSetpoint(setpoint_si)
392
+ sensor.setLightingControlType(control_type)
393
+ sensor.setMinimumInputPowerFractionforContinuousDimmingControl(min_power_fraction)
394
+ sensor.setMinimumLightOutputFractionforContinuousDimmingControl(min_light_fraction)
395
+ sensor.setSpace(space)
396
+ puts sensor
397
+
398
+ # add lifeCycleCost objects if there is a non-zero value in one of the cost arguments
399
+ if costs_requested == true
400
+
401
+ starting_lcc_counter = space.lifeCycleCosts.size
402
+
403
+ # adding new cost items
404
+ lcc_mat = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Mat - #{sensor.name}", space, material_cost, 'CostPerEach', 'Construction', expected_life, years_until_costs_start)
405
+ if demo_cost_initial_const
406
+ lcc_demo = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Demo - #{sensor.name}", space, demolition_cost, 'CostPerEach', 'Salvage', expected_life, years_until_costs_start)
407
+ else
408
+ lcc_demo = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Demo - #{sensor.name}", space, demolition_cost, 'CostPerEach', 'Salvage', expected_life, years_until_costs_start + expected_life)
409
+ end
410
+ lcc_om = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_OM - #{sensor.name}", space, om_cost, 'CostPerEach', 'Maintenance', om_frequency, 0)
411
+
412
+ if space.lifeCycleCosts.size - starting_lcc_counter == 3
413
+ if !warning_cost_assign_to_space
414
+ runner.registerInfo('Cost for daylight sensors was added to spaces. The cost will remain in the model unless the space is removed. Removing only the sensor will not remove the cost.')
415
+ warning_cost_assign_to_space = true
416
+ end
417
+ else
418
+ runner.registerWarning("The measure did not function as expected. #{space.lifeCycleCosts.size - starting_lcc_counter} LifeCycleCost objects were made, 3 were expected.")
419
+ end
420
+
421
+ end
422
+
423
+ # push unique zones to array for use later in measure
424
+ temp_zone = space.thermalZone.get
425
+ if affected_zone_names.include?(temp_zone.name.to_s) == false
426
+ affected_zones << temp_zone
427
+ affected_zone_names << temp_zone.name.to_s
428
+ end
429
+
430
+ # push sensor object into hash with space name
431
+ new_sensor_objects[space.name.to_s] = sensor
432
+
433
+ # add floor area to the daylighting area tally
434
+ sensor_area += space.floorArea
435
+
436
+ # add to sensor count for reporting
437
+ sensor_count += 1
438
+ end
439
+
440
+ if (sensor_count == 0) && (costs_requested == false)
441
+ runner.registerAsNotApplicable("No spaces that currently don't have sensor required a new sensor, and no lifecycle costs objects were requested.")
442
+ return true
443
+ end
444
+
445
+ # loop through thermal Zones for spaces with daylighting controls added
446
+ affected_zones.each do |zone|
447
+ zone_spaces = zone.spaces
448
+ zone_spaces_with_new_sensors = []
449
+ zone_spaces.each do |zone_space|
450
+ if !zone_space.daylightingControls.empty? && (zone_space.spaceType.get == space_type)
451
+ zone_spaces_with_new_sensors << zone_space
452
+ end
453
+ end
454
+
455
+ if !zone_spaces_with_new_sensors.empty?
456
+ # need to identify the two largest spaces
457
+ primary_area = 0
458
+ secondary_area = 0
459
+ primary_space = nil
460
+ secondary_space = nil
461
+ three_or_more_sensors = false
462
+
463
+ # dfg temp - need to add another if statement so only get spaces with sensors
464
+ zone_spaces_with_new_sensors.each do |zone_space|
465
+ zone_space_area = zone_space.floorArea
466
+ if zone_space_area > primary_area
467
+ primary_area = zone_space_area
468
+ primary_space = zone_space
469
+ elsif zone_space_area > secondary_area
470
+ secondary_area = zone_space_area
471
+ secondary_space = zone_space
472
+ else
473
+ # setup flag to warn user that more than 2 sensors can't be added to a space
474
+ three_or_more_sensors = true
475
+ end
476
+ end
477
+
478
+ if primary_space
479
+ # setup primary sensor
480
+ sensor_primary = new_sensor_objects[primary_space.name.to_s]
481
+ zone.setPrimaryDaylightingControl(sensor_primary)
482
+ zone.setFractionofZoneControlledbyPrimaryDaylightingControl(fraction_zone_controlled * primary_area / (primary_area + secondary_area))
483
+ end
484
+
485
+ if secondary_space
486
+ # setup secondary sensor
487
+ sensor_secondary = new_sensor_objects[secondary_space.name.to_s]
488
+ zone.setSecondaryDaylightingControl(sensor_secondary)
489
+ zone.setFractionofZoneControlledbySecondaryDaylightingControl(fraction_zone_controlled * secondary_area / (primary_area + secondary_area))
490
+ end
491
+
492
+ # warn that additional sensors were not used
493
+ if three_or_more_sensors == true
494
+ runner.registerWarning("Thermal zone '#{zone.name}' had more than two spaces with sensors. Only two sensors were associated with the thermal zone.")
495
+ end
496
+
497
+ end
498
+ end
499
+
500
+ # setup OpenStudio units that we will need
501
+ unit_area_ip = OpenStudio.createUnit('ft^2').get
502
+ unit_area_si = OpenStudio.createUnit('m^2').get
503
+
504
+ # define starting units
505
+ area_si = OpenStudio::Quantity.new(sensor_area, unit_area_si)
506
+
507
+ # unit conversion from IP units to SI units
508
+ area_ip = OpenStudio.convert(area_si, unit_area_ip).get
509
+
510
+ # get final costs for spaces
511
+ yr0_capital_totalCosts = get_total_costs_for_objects(spaces_using_space_type)
512
+
513
+ # reporting final condition of model
514
+ runner.registerFinalCondition("Added daylighting controls to #{sensor_count} spaces, covering #{area_ip}. Initial year costs associated with the daylighting controls is $#{neat_numbers(yr0_capital_totalCosts, 0)}.")
515
+
516
+ return true
517
+ end
518
+ end
519
+
520
+ # this allows the measure to be used by the application
521
+ AddDaylightSensors.new.registerWithApplication