openstudio-geb 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Rakefile +1 -1
- data/doc_templates/LICENSE.md +7 -21
- data/doc_templates/copyright_erb.txt +2 -32
- data/doc_templates/copyright_js.txt +2 -2
- data/doc_templates/copyright_ruby.txt +3 -33
- data/lib/measures/Add Output Variable/LICENSE.md +7 -21
- data/lib/measures/Add Output Variable/measure.rb +3 -33
- data/lib/measures/Add Output Variable/tests/AddOutputVariable_Test.rb +2 -32
- data/lib/measures/AddElectricVehicleChargingLoad/LICENSE.md +13 -1
- data/lib/measures/AddElectricVehicleChargingLoad/measure.rb +16 -52
- data/lib/measures/AddElectricVehicleChargingLoad/tests/add_electric_vehicle_charging_load_test.rb +2 -32
- data/lib/measures/AdjustThermostatSetpointsByDegreesForPeakHours/LICENSE.md +7 -21
- data/lib/measures/AdjustThermostatSetpointsByDegreesForPeakHours/measure.rb +14 -35
- data/lib/measures/AdjustThermostatSetpointsByDegreesForPeakHours/tests/AdjustThermostatSetpointsByDegreesForPeakHours_Test.rb +2 -32
- data/lib/measures/Enable Demand Controlled Ventilation/LICENSE.md +7 -21
- data/lib/measures/Enable Demand Controlled Ventilation/measure.rb +2 -32
- data/lib/measures/Enable Demand Controlled Ventilation/tests/EnableDemandControlledVentilation_Test.rb +2 -32
- data/lib/measures/GEB Metrics Report/LICENSE.md +13 -1
- data/lib/measures/GEB Metrics Report/measure.rb +4 -39
- data/lib/measures/GEB Metrics Report/resources/os_lib_helper_methods.rb +2 -32
- data/lib/measures/GEB Metrics Report/resources/os_lib_reporting.rb +2 -32
- data/lib/measures/GEB Metrics Report/tests/geb_metrics_report_test.rb +2 -32
- data/lib/measures/add_ceiling_fan/LICENSE.md +13 -0
- data/lib/measures/add_ceiling_fan/README.md +119 -0
- data/lib/measures/add_ceiling_fan/README.md.erb +45 -0
- data/lib/measures/add_ceiling_fan/docs/.gitkeep +0 -0
- data/lib/measures/add_ceiling_fan/measure.rb +565 -0
- data/lib/measures/add_ceiling_fan/measure.xml +226 -0
- data/lib/measures/add_ceiling_fan/tests/CZ06RV2.epw +8768 -0
- data/lib/measures/add_ceiling_fan/tests/MediumOffice-90.1-2010-ASHRAE 169-2013-5A.osm +13669 -0
- data/lib/measures/add_ceiling_fan/tests/SFD_1story_UB_UA_ASHP2_HPWH.osm +13110 -0
- data/lib/measures/add_ceiling_fan/tests/add_ceiling_fan_test.rb +82 -0
- data/lib/measures/add_chilled_water_storage_tank/LICENSE.md +13 -1
- data/lib/measures/add_chilled_water_storage_tank/measure.rb +56 -56
- data/lib/measures/add_chilled_water_storage_tank/tests/add_chilled_water_storage_tank_test.rb +4 -34
- data/lib/measures/add_electrochromic_window/LICENSE.md +13 -1
- data/lib/measures/add_electrochromic_window/measure.rb +2 -32
- data/lib/measures/add_electrochromic_window/tests/add_electrochromic_window_test.rb +2 -32
- data/lib/measures/add_exterior_blinds_and_control/LICENSE.md +13 -1
- data/lib/measures/add_exterior_blinds_and_control/measure.rb +2 -34
- data/lib/measures/add_exterior_blinds_and_control/tests/add_exterior_blinds_and_control_test.rb +2 -34
- data/lib/measures/add_heat_pump_water_heater/LICENSE.md +7 -21
- data/lib/measures/add_heat_pump_water_heater/measure.rb +2 -32
- data/lib/measures/add_heat_pump_water_heater/tests/add_hpwh_test.rb +2 -32
- data/lib/measures/add_interior_blinds_and_control/LICENSE.md +13 -1
- data/lib/measures/add_interior_blinds_and_control/measure.rb +2 -32
- data/lib/measures/add_interior_blinds_and_control/tests/add_exterior_blinds_and_control_test.rb +2 -32
- data/lib/measures/add_natural_ventilation_with_hybrid_control/LICENSE.md +13 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/README.md +133 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/README.md.erb +45 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/docs/.gitkeep +0 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/measure.rb +453 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/measure.xml +241 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/MediumOffice-90.1-2010-ASHRAE 169-2013-5A.osm +13669 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/SmallHotel-2A.osm +42899 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/USA_NY_Buffalo.Niagara.Intl.AP.725280_TMY3.epw +8768 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/USA_TX_Houston-Bush.Intercontinental.AP.722430_TMY3.epw +8768 -0
- data/lib/measures/add_natural_ventilation_with_hybrid_control/tests/add_natural_ventilation_with_hybrid_control_test.rb +95 -0
- data/lib/measures/add_rooftop_pv_simple/LICENSE.md +7 -21
- data/lib/measures/add_rooftop_pv_simple/measure.rb +2 -32
- data/lib/measures/add_rooftop_pv_simple/tests/add_rooftop_pv_test.rb +2 -32
- data/lib/measures/adjust_dhw_setpoint/LICENSE.md +13 -1
- data/lib/measures/adjust_dhw_setpoint/measure.rb +2 -32
- data/lib/measures/adjust_dhw_setpoint/tests/adjust_dhw_setpoint_test.rb +2 -32
- data/lib/measures/average_ventilation_for_peak_hours/LICENSE.md +13 -1
- data/lib/measures/average_ventilation_for_peak_hours/measure.rb +2 -32
- data/lib/measures/average_ventilation_for_peak_hours/tests/average_ventilation_for_peak_hours_test.rb +2 -32
- data/lib/measures/enable_occupancy_driven_lighting/LICENSE.md +13 -1
- data/lib/measures/enable_occupancy_driven_lighting/measure.rb +2 -32
- data/lib/measures/enable_occupancy_driven_lighting/tests/enable_occupancy_driven_lighting_test.rb +2 -32
- data/lib/measures/precooling/LICENSE.md +7 -21
- data/lib/measures/precooling/measure.rb +2 -33
- data/lib/measures/preheating/LICENSE.md +7 -21
- data/lib/measures/preheating/measure.rb +2 -33
- data/lib/measures/reduce_domestic_hot_water_use_for_peak_hours/LICENSE.md +13 -1
- data/lib/measures/reduce_domestic_hot_water_use_for_peak_hours/measure.rb +2 -32
- data/lib/measures/reduce_domestic_hot_water_use_for_peak_hours/tests/reduce_domestic_hot_water_use_for_peak_hours_test.rb +2 -32
- data/lib/measures/reduce_epd_by_percentage_for_peak_hours/LICENSE.md +13 -1
- data/lib/measures/reduce_epd_by_percentage_for_peak_hours/measure.rb +2 -32
- data/lib/measures/reduce_epd_by_percentage_for_peak_hours/tests/reduce_epd_by_percentage_for_peak_hours_copy_test.rb +2 -32
- data/lib/measures/reduce_exterior_lighting_loads/LICENSE.md +13 -0
- data/lib/measures/reduce_exterior_lighting_loads/README.md +79 -0
- data/lib/measures/reduce_exterior_lighting_loads/README.md.erb +45 -0
- data/lib/measures/reduce_exterior_lighting_loads/docs/.gitkeep +0 -0
- data/lib/measures/reduce_exterior_lighting_loads/measure.rb +385 -0
- data/lib/measures/reduce_exterior_lighting_loads/measure.xml +195 -0
- data/lib/measures/reduce_exterior_lighting_loads/tests/CZ06RV2.epw +8768 -0
- data/lib/measures/reduce_exterior_lighting_loads/tests/SFD_1story_UB_UA_ASHP2_HPWH.osm +13110 -0
- data/lib/measures/reduce_exterior_lighting_loads/tests/example_model.osm +8077 -0
- data/lib/measures/reduce_exterior_lighting_loads/tests/reduce_exterior_lighting_loads_test.rb +81 -0
- data/lib/measures/reduce_lpd_by_percentage_for_peak_hours/LICENSE.md +13 -1
- data/lib/measures/reduce_lpd_by_percentage_for_peak_hours/measure.rb +2 -32
- data/lib/measures/reduce_lpd_by_percentage_for_peak_hours/tests/reduce_lpd_by_percentage_for_peak_hours_test.rb +2 -32
- data/lib/openstudio/geb/extension.rb +2 -50
- data/lib/openstudio/geb/logging.rb +2 -32
- data/lib/openstudio/geb/run.rb +2 -32
- data/lib/openstudio/geb/utilities.rb +2 -32
- data/lib/openstudio/geb/version.rb +3 -33
- data/lib/openstudio/geb.rb +3 -51
- data/lib/openstudio-geb.rb +6 -0
- data/openstudio-geb.gemspec +2 -3
- metadata +40 -7
@@ -0,0 +1,565 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
|
3
|
+
# See also https://openstudio.net/license
|
4
|
+
# *******************************************************************************
|
5
|
+
|
6
|
+
# see the URL below for information on how to write OpenStudio measures
|
7
|
+
# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
|
8
|
+
|
9
|
+
# start the measure
|
10
|
+
class AddCeilingFan < OpenStudio::Measure::ModelMeasure
|
11
|
+
# human readable name
|
12
|
+
def name
|
13
|
+
# Measure name should be the title case of the class name.
|
14
|
+
return 'Add Ceiling Fan'
|
15
|
+
end
|
16
|
+
|
17
|
+
# human readable description
|
18
|
+
def description
|
19
|
+
return 'Install ceiling fans in buildings to increase air circulation. Ceiling fans effectively cool by introducing slow movement to induce evaporative cooling, rather than directly conditioning the air. A diversity factor is added to consider the simultaneous usage among the household.'
|
20
|
+
end
|
21
|
+
|
22
|
+
# human readable description of modeling approach
|
23
|
+
def modeler_description
|
24
|
+
return 'Ceiling fan is modeled by increasing air velocity in the People objects and adding electric equipment to consider extra fan energy use. Cooling setpoint is increased by certain degrees in the presence of ceiling fans. A schedule is also introduced to simulate ceiling fan operation. A diversity factor (different in commercial and residential buildings) is added to consider the simultaneous usage among the building/household.'
|
25
|
+
end
|
26
|
+
|
27
|
+
# define the arguments that the user will input
|
28
|
+
def arguments(model)
|
29
|
+
args = OpenStudio::Measure::OSArgumentVector.new
|
30
|
+
|
31
|
+
# building type: residential or commercial
|
32
|
+
# this determines inputs like watts per floor area and diversity factor
|
33
|
+
bldg_type = OpenStudio::Measure::OSArgument::makeChoiceArgument('bldg_type', ['residential', 'commercial'], true)
|
34
|
+
bldg_type.setDisplayName('Select building type:')
|
35
|
+
bldg_type.setDescription('Building type (residential or commercial)')
|
36
|
+
bldg_type.setDefaultValue('commercial')
|
37
|
+
args << bldg_type
|
38
|
+
|
39
|
+
# cooling setpoint increase
|
40
|
+
# default value comes from https://www.sciencedirect.com/science/article/abs/pii/S0360132321004133
|
41
|
+
cool_stp_increase_C = OpenStudio::Measure::OSArgument.makeDoubleArgument('cool_stp_increase_C', true)
|
42
|
+
cool_stp_increase_C.setDisplayName('Cooling setpoint increase - C')
|
43
|
+
cool_stp_increase_C.setDescription('Cooling setpoint increase in degree C')
|
44
|
+
cool_stp_increase_C.setDefaultValue(3)
|
45
|
+
args << cool_stp_increase_C
|
46
|
+
|
47
|
+
# ceiling fan motor type: AC or DC
|
48
|
+
motor_type = OpenStudio::Measure::OSArgument::makeChoiceArgument('motor_type', ['DC', 'AC'], true)
|
49
|
+
motor_type.setDisplayName('Select ceiling fan motor type:')
|
50
|
+
motor_type.setDescription('Ceiling fan motor type')
|
51
|
+
motor_type.setDefaultValue('DC')
|
52
|
+
args << motor_type
|
53
|
+
|
54
|
+
# ceiling fan EUI in watts per floor area (optional)
|
55
|
+
# if not user assigned, use default values in run function below, which comes from discussion with Center for Built Environment based on their research
|
56
|
+
watts_per_m2 = OpenStudio::Measure::OSArgument.makeDoubleArgument('watts_per_m2', false)
|
57
|
+
watts_per_m2.setDisplayName('Ceiling fan EUI in watts per floor area')
|
58
|
+
watts_per_m2.setDescription('Ceiling fan watts per m2')
|
59
|
+
args << watts_per_m2
|
60
|
+
|
61
|
+
# make an argument for the start date of the reduction
|
62
|
+
start_date = OpenStudio::Ruleset::OSArgument.makeStringArgument('start_date', false)
|
63
|
+
start_date.setDisplayName('First start date for the Reduction')
|
64
|
+
start_date.setDescription('In MM-DD format')
|
65
|
+
start_date.setDefaultValue('05-01')
|
66
|
+
args << start_date
|
67
|
+
|
68
|
+
# make an argument for the end date of the reduction
|
69
|
+
end_date = OpenStudio::Ruleset::OSArgument.makeStringArgument('end_date', false)
|
70
|
+
end_date.setDisplayName('First end date for the Reduction')
|
71
|
+
end_date.setDescription('In MM-DD format')
|
72
|
+
end_date.setDefaultValue('09-30')
|
73
|
+
args << end_date
|
74
|
+
|
75
|
+
# make an argument for the start time of the reduction
|
76
|
+
start_time = OpenStudio::Measure::OSArgument.makeStringArgument('start_time', false)
|
77
|
+
start_time.setDisplayName('Start Time for the Reduction')
|
78
|
+
start_time.setDescription('In HH:MM:SS format')
|
79
|
+
start_time.setDefaultValue('08:00:00')
|
80
|
+
args << start_time
|
81
|
+
|
82
|
+
# make an argument for the end time of the reduction
|
83
|
+
end_time = OpenStudio::Measure::OSArgument.makeStringArgument('end_time', false)
|
84
|
+
end_time.setDisplayName('End Time for the Reduction')
|
85
|
+
end_time.setDescription('In HH:MM:SS format')
|
86
|
+
end_time.setDefaultValue('18:00:00')
|
87
|
+
args << end_time
|
88
|
+
|
89
|
+
# diversity factor
|
90
|
+
diversity_factor = OpenStudio::Measure::OSArgument.makeDoubleArgument('diversity_factor', false)
|
91
|
+
diversity_factor.setDisplayName('Diversity factor')
|
92
|
+
diversity_factor.setDescription('Diversity factor')
|
93
|
+
args << diversity_factor
|
94
|
+
|
95
|
+
# people air velocity
|
96
|
+
people_air_velocity = OpenStudio::Measure::OSArgument.makeDoubleArgument('people_air_velocity', false)
|
97
|
+
people_air_velocity.setDisplayName('People air velocity')
|
98
|
+
people_air_velocity.setDescription('Air velocity surrounding people (m/s)')
|
99
|
+
people_air_velocity.setDefaultValue(0.8)
|
100
|
+
args << people_air_velocity
|
101
|
+
|
102
|
+
return args
|
103
|
+
end
|
104
|
+
|
105
|
+
# define what happens when the measure is run
|
106
|
+
def run(model, runner, user_arguments)
|
107
|
+
super(model, runner, user_arguments)
|
108
|
+
|
109
|
+
# use the built-in error checking
|
110
|
+
if !runner.validateUserArguments(arguments(model), user_arguments)
|
111
|
+
return false
|
112
|
+
end
|
113
|
+
|
114
|
+
# assign the user inputs to variables
|
115
|
+
bldg_type = runner.getStringArgumentValue('bldg_type', user_arguments)
|
116
|
+
motor_type = runner.getStringArgumentValue('motor_type', user_arguments)
|
117
|
+
cool_stp_increase_C = runner.getDoubleArgumentValue('cool_stp_increase_C', user_arguments)
|
118
|
+
start_date = runner.getStringArgumentValue('start_date', user_arguments)
|
119
|
+
end_date = runner.getStringArgumentValue('end_date', user_arguments)
|
120
|
+
start_time = runner.getStringArgumentValue('start_time', user_arguments)
|
121
|
+
end_time = runner.getStringArgumentValue('end_time', user_arguments)
|
122
|
+
watts_per_m2 = runner.getOptionalDoubleArgumentValue('watts_per_m2', user_arguments)
|
123
|
+
diversity_factor = runner.getOptionalDoubleArgumentValue('diversity_factor', user_arguments)
|
124
|
+
people_air_velocity = runner.getOptionalDoubleArgumentValue('people_air_velocity', user_arguments)
|
125
|
+
|
126
|
+
if bldg_type != 'commercial' && bldg_type != 'residential'
|
127
|
+
runner.registerError("Wrong building type #{bldg_type} entered. Value must be either 'commercial' or 'residential'.")
|
128
|
+
return false
|
129
|
+
end
|
130
|
+
|
131
|
+
if motor_type != 'AC' && motor_type != 'DC'
|
132
|
+
runner.registerError("Wrong ceiling fan motor type #{motor_type} entered. Value must be either 'AC' or 'DC'.")
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
|
136
|
+
# validate the cooling setpoint increase
|
137
|
+
if cool_stp_increase_C <= 0
|
138
|
+
runner.registerError('The cooling setpoint increase should be positive.')
|
139
|
+
return false
|
140
|
+
elsif cool_stp_increase_C > 10
|
141
|
+
runner.registerWarning('The cooling setpoint increase is abnormally large, normally it is between 2-5C.')
|
142
|
+
end
|
143
|
+
|
144
|
+
if watts_per_m2.empty?
|
145
|
+
# use default value based on building type and motor type
|
146
|
+
case bldg_type
|
147
|
+
when 'commercial'
|
148
|
+
if motor_type == 'DC'
|
149
|
+
watts_per_m2 = 0.16146 #0.015W/ft2
|
150
|
+
else # 'AC'
|
151
|
+
watts_per_m2 = 0.48 # 0.015W/ft2 * 3 (double/triple of DC per CBE Paul Raftery)
|
152
|
+
end
|
153
|
+
when 'residential'
|
154
|
+
if motor_type == 'DC'
|
155
|
+
watts_per_m2 = 0.35 # 0.13W/ft2 * 25% (The average power when the fan was running was between 20-25% of max)
|
156
|
+
else # 'AC'
|
157
|
+
watts_per_m2 = 1.05 # 0.13W/ft2 * 3 (double/triple of DC per CBE Paul Raftery)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
elsif watts_per_m2.to_f <= 0
|
161
|
+
runner.registerError("Invalid ceiling fan watts per m2 #{watts_per_m2} entered. Value must be >0.")
|
162
|
+
return false
|
163
|
+
else
|
164
|
+
watts_per_m2 = watts_per_m2.to_f
|
165
|
+
end
|
166
|
+
|
167
|
+
# default value references: https://electricalnotes.wordpress.com/2018/04/01/thumb-rules-14-quick-reference-demand-diversity-factor/
|
168
|
+
# https://www.electricallicenserenewal.com/Electrical-Continuing-Education-Courses/NEC-Content.php?sectionID=836.0
|
169
|
+
if diversity_factor.empty?
|
170
|
+
case bldg_type
|
171
|
+
when 'commercial'
|
172
|
+
diversity_factor = 0.9
|
173
|
+
when 'residential'
|
174
|
+
diversity_factor = 0.66
|
175
|
+
end
|
176
|
+
elsif diversity_factor.to_f <= 0 or diversity_factor.to_f > 1
|
177
|
+
runner.registerError("Invalid diversity factor #{diversity_factor} entered. Value must be >0 and <=1.")
|
178
|
+
return false
|
179
|
+
else
|
180
|
+
diversity_factor = diversity_factor.to_f
|
181
|
+
end
|
182
|
+
|
183
|
+
if people_air_velocity.empty?
|
184
|
+
people_air_velocity = 0.8 # default 0.8m/s for ceiling fan operation
|
185
|
+
elsif people_air_velocity.to_f <= 0
|
186
|
+
runner.registerError("Invalid people air velocity #{people_air_velocity} entered. Value must be positive.")
|
187
|
+
return false
|
188
|
+
elsif people_air_velocity.to_f > 2
|
189
|
+
runner.registerWarning('The people air velocity is abnormally high, normally it is between 0.2-2m/s.')
|
190
|
+
people_air_velocity = people_air_velocity.to_f
|
191
|
+
elsif people_air_velocity.to_f < 0.2
|
192
|
+
runner.registerWarning('The people air velocity is abnormally low, normally it is between 0.2-2m/s.')
|
193
|
+
people_air_velocity = people_air_velocity.to_f
|
194
|
+
else
|
195
|
+
people_air_velocity = people_air_velocity.to_f
|
196
|
+
end
|
197
|
+
|
198
|
+
# validate and assign operational period date and time
|
199
|
+
if /(\d\d):(\d\d):(\d\d)/.match(start_time)
|
200
|
+
os_start_time = OpenStudio::Time.new(start_time)
|
201
|
+
else
|
202
|
+
runner.registerError('Start time must be in HH-MM-SS format.')
|
203
|
+
return false
|
204
|
+
end
|
205
|
+
|
206
|
+
if /(\d\d):(\d\d):(\d\d)/.match(end_time)
|
207
|
+
os_end_time = OpenStudio::Time.new(end_time)
|
208
|
+
else
|
209
|
+
runner.registerError('End time must be in HH-MM-SS format.')
|
210
|
+
return false
|
211
|
+
end
|
212
|
+
|
213
|
+
if start_time.to_f > end_time.to_f
|
214
|
+
runner.registerError('The start time cannot be later than the end time.')
|
215
|
+
return false
|
216
|
+
end
|
217
|
+
|
218
|
+
start_month = nil
|
219
|
+
start_day = nil
|
220
|
+
md = /(\d\d)-(\d\d)/.match(start_date)
|
221
|
+
if md
|
222
|
+
start_month = md[1].to_i
|
223
|
+
start_day = md[2].to_i
|
224
|
+
else
|
225
|
+
runner.registerError('Start date must be in MM-DD format.')
|
226
|
+
return false
|
227
|
+
end
|
228
|
+
end_month = nil
|
229
|
+
end_day = nil
|
230
|
+
md = /(\d\d)-(\d\d)/.match(end_date)
|
231
|
+
if md
|
232
|
+
end_month = md[1].to_i
|
233
|
+
end_day = md[2].to_i
|
234
|
+
else
|
235
|
+
runner.registerError('End date must be in MM-DD format.')
|
236
|
+
return false
|
237
|
+
end
|
238
|
+
|
239
|
+
# create cooling setpoint schedule and fan operation schedule for ceiling or portable fans
|
240
|
+
def create_cooling_and_fan_schedule(model, start_date, end_date, start_time, end_time, diversity_factor)
|
241
|
+
year_start = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(1), 1, model.assumedYear)
|
242
|
+
year_end = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(12), 31, model.assumedYear)
|
243
|
+
|
244
|
+
fan_sch_week = OpenStudio::Model::ScheduleWeek.new(model)
|
245
|
+
fan_sch_week.setName("Ceiling fan operation sch week")
|
246
|
+
|
247
|
+
## Check for Schedule Type Limits and Create if Needed
|
248
|
+
if !model.getModelObjectByName('OnOff').empty?
|
249
|
+
sched_limits_onoff = model.getModelObjectByName('OnOff').get.to_ScheduleTypeLimits.get
|
250
|
+
else
|
251
|
+
sched_limits_onoff = OpenStudio::Model::ScheduleTypeLimits.new(model)
|
252
|
+
sched_limits_onoff.setName('OnOff')
|
253
|
+
sched_limits_onoff.setNumericType('Discrete')
|
254
|
+
sched_limits_onoff.setUnitType('Availability')
|
255
|
+
sched_limits_onoff.setLowerLimitValue(0.0)
|
256
|
+
sched_limits_onoff.setUpperLimitValue(1.0)
|
257
|
+
end
|
258
|
+
|
259
|
+
fan_sch_day = OpenStudio::Model::ScheduleDay.new(model)
|
260
|
+
fan_sch_day.setName("Ceiling fan operation sch default")
|
261
|
+
fan_sch_day.setScheduleTypeLimits(sched_limits_onoff)
|
262
|
+
fan_sch_day.addValue(start_time, 0)
|
263
|
+
if end_time < OpenStudio::Time.new('24:00:00')
|
264
|
+
fan_sch_day.addValue(end_time, 1.0 * diversity_factor)
|
265
|
+
fan_sch_day.addValue(OpenStudio::Time.new('24:00:00'), 0)
|
266
|
+
else
|
267
|
+
fan_sch_day.addValue(OpenStudio::Time.new('24:00:00'), 1.0 * diversity_factor)
|
268
|
+
end
|
269
|
+
fan_sch_week.setAllSchedules(fan_sch_day)
|
270
|
+
|
271
|
+
fan_off_day = OpenStudio::Model::ScheduleDay.new(model)
|
272
|
+
fan_off_day.setName("Ceiling fan off sch day")
|
273
|
+
fan_off_day.setScheduleTypeLimits(sched_limits_onoff)
|
274
|
+
fan_off_day.addValue(OpenStudio::Time.new(0,24,0,0), 0)
|
275
|
+
fan_off_week = OpenStudio::Model::ScheduleWeek.new(model)
|
276
|
+
fan_off_week.setName("Ceiling fan off sch week")
|
277
|
+
fan_off_week.setAllSchedules(fan_off_day)
|
278
|
+
|
279
|
+
fan_sch = OpenStudio::Model::ScheduleYear.new(model)
|
280
|
+
fan_sch.setName("Ceiling fan sch")
|
281
|
+
fan_sch.setScheduleTypeLimits(sched_limits_onoff)
|
282
|
+
|
283
|
+
# if ceiling fan only operates during a specific period
|
284
|
+
unless start_date == year_start
|
285
|
+
fan_sch.addScheduleWeek(start_date, fan_off_week)
|
286
|
+
end
|
287
|
+
|
288
|
+
if end_date == year_end
|
289
|
+
fan_sch.addScheduleWeek(year_end, fan_sch_week)
|
290
|
+
else
|
291
|
+
fan_sch.addScheduleWeek(end_date, fan_sch_week)
|
292
|
+
fan_sch.addScheduleWeek(year_end, fan_off_week)
|
293
|
+
end
|
294
|
+
|
295
|
+
puts "fan_sch: #{fan_sch}"
|
296
|
+
|
297
|
+
return fan_sch
|
298
|
+
end
|
299
|
+
|
300
|
+
def updateDaySchedule(sch_day, vec_time, vec_value, time_begin, time_end, add_value)
|
301
|
+
count = 0
|
302
|
+
vec_time.each_with_index do |exist_timestamp, i|
|
303
|
+
new_value = vec_value[i] + add_value
|
304
|
+
if exist_timestamp > time_begin && exist_timestamp < time_end && count == 0
|
305
|
+
sch_day.addValue(time_begin, vec_value[i])
|
306
|
+
sch_day.addValue(exist_timestamp, new_value)
|
307
|
+
count = 1
|
308
|
+
elsif exist_timestamp == time_end && count == 0
|
309
|
+
sch_day.addValue(time_begin, vec_value[i])
|
310
|
+
sch_day.addValue(exist_timestamp, new_value)
|
311
|
+
count = 2
|
312
|
+
elsif exist_timestamp == time_begin && count == 0
|
313
|
+
sch_day.addValue(exist_timestamp, vec_value[i])
|
314
|
+
count = 1
|
315
|
+
elsif exist_timestamp > time_end && count == 0
|
316
|
+
sch_day.addValue(time_begin, vec_value[i])
|
317
|
+
sch_day.addValue(time_end, new_value)
|
318
|
+
sch_day.addValue(exist_timestamp, vec_value[i])
|
319
|
+
count = 2
|
320
|
+
elsif exist_timestamp > time_begin && exist_timestamp < time_end && count==1
|
321
|
+
sch_day.addValue(exist_timestamp, new_value)
|
322
|
+
elsif exist_timestamp == time_end && count==1
|
323
|
+
sch_day.addValue(exist_timestamp, new_value)
|
324
|
+
count = 2
|
325
|
+
elsif exist_timestamp > time_end && count == 1
|
326
|
+
sch_day.addValue(time_end, new_value)
|
327
|
+
sch_day.addValue(exist_timestamp, vec_value[i])
|
328
|
+
count = 2
|
329
|
+
else
|
330
|
+
sch_day.addValue(exist_timestamp, vec_value[i])
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
return sch_day
|
335
|
+
end
|
336
|
+
|
337
|
+
# copy ScheduleRule sch_rule, copy ScheduleDay sch_day and assign to new schedule rule
|
338
|
+
def copy_sch_rule_for_period(model, sch_rule, sch_day, start_date, end_date)
|
339
|
+
new_rule = sch_rule.clone(model).to_ScheduleRule.get
|
340
|
+
new_rule.setStartDate(start_date)
|
341
|
+
new_rule.setEndDate(end_date)
|
342
|
+
|
343
|
+
new_day_sch = sch_day.clone(model)
|
344
|
+
new_day_sch.setParent(new_rule)
|
345
|
+
|
346
|
+
return new_rule
|
347
|
+
end
|
348
|
+
|
349
|
+
def create_sch_rule_from_default(model, sch_ruleset, default_sch_fule, start_date, end_date)
|
350
|
+
new_rule = OpenStudio::Model::ScheduleRule.new(sch_ruleset)
|
351
|
+
new_rule.setStartDate(start_date)
|
352
|
+
new_rule.setEndDate(end_date)
|
353
|
+
|
354
|
+
new_day_sch = default_sch_fule.clone(model)
|
355
|
+
new_day_sch.setParent(new_rule)
|
356
|
+
|
357
|
+
return new_rule
|
358
|
+
end
|
359
|
+
|
360
|
+
def checkDaysCovered(sch_rule, sch_day_covered)
|
361
|
+
if sch_rule.applySunday
|
362
|
+
sch_day_covered[0] = true
|
363
|
+
end
|
364
|
+
if sch_rule.applyMonday
|
365
|
+
sch_day_covered[1] = true
|
366
|
+
end
|
367
|
+
if sch_rule.applyTuesday
|
368
|
+
sch_day_covered[2] = true
|
369
|
+
end
|
370
|
+
if sch_rule.applyWednesday
|
371
|
+
sch_day_covered[3] = true
|
372
|
+
end
|
373
|
+
if sch_rule.applyThursday
|
374
|
+
sch_day_covered[4] = true
|
375
|
+
end
|
376
|
+
if sch_rule.applyFriday
|
377
|
+
sch_day_covered[5] = true
|
378
|
+
end
|
379
|
+
if sch_rule.applySaturday
|
380
|
+
sch_day_covered[6] = true
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def coverMissingDays(sch_rule, sch_day_covered)
|
385
|
+
if sch_day_covered[0] == false
|
386
|
+
sch_rule.setApplySunday(true)
|
387
|
+
end
|
388
|
+
if sch_day_covered[1] == false
|
389
|
+
sch_rule.setApplyMonday(true)
|
390
|
+
end
|
391
|
+
if sch_day_covered[2] == false
|
392
|
+
sch_rule.setApplyTuesday(true)
|
393
|
+
end
|
394
|
+
if sch_day_covered[3] == false
|
395
|
+
sch_rule.setApplyWednesday(true)
|
396
|
+
end
|
397
|
+
if sch_day_covered[4] == false
|
398
|
+
sch_rule.setApplyThursday(true)
|
399
|
+
end
|
400
|
+
if sch_day_covered[5] == false
|
401
|
+
sch_rule.setApplyFriday(true)
|
402
|
+
end
|
403
|
+
if sch_day_covered[6] == false
|
404
|
+
sch_rule.setApplySaturday(true)
|
405
|
+
end
|
406
|
+
|
407
|
+
end
|
408
|
+
|
409
|
+
def adjust_cool_sch(model, sch, cool_stp_inc, start_date, end_date, start_time, end_time)
|
410
|
+
new_sch = sch.get.clone(model)
|
411
|
+
new_sch = new_sch.to_Schedule.get
|
412
|
+
new_sch.setName("#{sch.get.name.to_s} increased by #{cool_stp_inc}C due to ceiling fan")
|
413
|
+
year_start = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(1), 1, model.assumedYear)
|
414
|
+
year_end = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(12), 31, model.assumedYear)
|
415
|
+
|
416
|
+
# make cooling schedule adjustments and rename. Put in check to skip and warn if schedule not ruleset
|
417
|
+
if !new_sch.to_ScheduleRuleset.empty?
|
418
|
+
schedule = new_sch.to_ScheduleRuleset.get
|
419
|
+
default_rule = schedule.defaultDaySchedule
|
420
|
+
rules = schedule.scheduleRules
|
421
|
+
days_covered = Array.new(7, false)
|
422
|
+
|
423
|
+
# TODO: when ruleset has multiple rules for each month or couple of months instead of a full year, should first see if the period overlaps with summer/winter
|
424
|
+
if rules.length > 0
|
425
|
+
rules.each do |rule|
|
426
|
+
unless start_date == year_start
|
427
|
+
unchanged_rule1 = copy_sch_rule_for_period(model, rule, rule.daySchedule, year_start, start_date)
|
428
|
+
end
|
429
|
+
|
430
|
+
unless end_date == year_end
|
431
|
+
unchanged_rule2 = copy_sch_rule_for_period(model, rule, rule.daySchedule, end_date, year_end)
|
432
|
+
end
|
433
|
+
|
434
|
+
change_rule = rule
|
435
|
+
checkDaysCovered(change_rule, days_covered)
|
436
|
+
change_rule.setStartDate(start_date)
|
437
|
+
change_rule.setEndDate(end_date)
|
438
|
+
|
439
|
+
change_day = change_rule.daySchedule
|
440
|
+
day_time_vector = change_day.times
|
441
|
+
day_value_vector = change_day.values
|
442
|
+
change_day.clearValues
|
443
|
+
|
444
|
+
change_day = updateDaySchedule(change_day, day_time_vector, day_value_vector, start_time, end_time, cool_stp_inc)
|
445
|
+
end
|
446
|
+
else
|
447
|
+
runner.registerWarning("Cooling setpoint schedule '#{sch.get.name.to_s}' is a ScheduleRuleSet, but has no ScheduleRules associated. It won't be altered by this measure.")
|
448
|
+
end
|
449
|
+
|
450
|
+
# for days not covered (not defined specifically), modify default schedule
|
451
|
+
if days_covered.include?(false)
|
452
|
+
unless start_date == year_start
|
453
|
+
unchanged_rule1 = create_sch_rule_from_default(model, schedule, default_rule, year_start, start_date)
|
454
|
+
end
|
455
|
+
|
456
|
+
unless end_date == year_end
|
457
|
+
unchanged_rule2 = create_sch_rule_from_default(model, schedule, default_rule, end_date, year_end)
|
458
|
+
end
|
459
|
+
|
460
|
+
coverMissingDays(unchanged_rule1, days_covered)
|
461
|
+
checkDaysCovered(unchanged_rule1, days_covered)
|
462
|
+
|
463
|
+
change_rule = copy_sch_rule_for_period(model, unchanged_rule1, default_rule, start_date, end_date)
|
464
|
+
|
465
|
+
change_day = change_rule.daySchedule
|
466
|
+
day_time_vector = change_day.times
|
467
|
+
day_value_vector = change_day.values
|
468
|
+
change_day.clearValues
|
469
|
+
|
470
|
+
change_day = updateDaySchedule(change_day, day_time_vector, day_value_vector, start_time, end_time, cool_stp_inc)
|
471
|
+
end
|
472
|
+
|
473
|
+
######################################################################
|
474
|
+
else
|
475
|
+
runner.registerWarning("Schedule '#{sch.get.name.to_s}' isn't a ScheduleRuleset object and won't be altered by this measure.")
|
476
|
+
new_sch.remove # remove un-used clone
|
477
|
+
end
|
478
|
+
|
479
|
+
return new_sch
|
480
|
+
end
|
481
|
+
|
482
|
+
# add model's assumed year to make sure addScheduleWeek to scheduleYear can go through
|
483
|
+
os_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_month), start_day, model.assumedYear)
|
484
|
+
os_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_month), end_day, model.assumedYear)
|
485
|
+
|
486
|
+
# generate fan on and off week schedule
|
487
|
+
ceiling_fan_sch = create_cooling_and_fan_schedule(model, os_start_date, os_end_date, os_start_time, os_end_time, diversity_factor)
|
488
|
+
|
489
|
+
# add ceiling fan electric equipment object
|
490
|
+
ceiling_fan_def = OpenStudio::Model::ElectricEquipmentDefinition.new(model)
|
491
|
+
ceiling_fan_def.setName("Ceiling fan def")
|
492
|
+
ceiling_fan_def.setWattsperSpaceFloorArea(watts_per_m2)
|
493
|
+
ceiling_fan_def.setFractionLatent(0.0)
|
494
|
+
ceiling_fan_def.setFractionRadiant(0.0)
|
495
|
+
ceiling_fan_def.setFractionLost(0.0) # all convective heat gain
|
496
|
+
|
497
|
+
# create people air velocity schedule
|
498
|
+
air_velo_sch = OpenStudio::Model::ScheduleRuleset.new(model)
|
499
|
+
air_velo_sch.setName('Air Velocity Schedule')
|
500
|
+
air_velo_sch.defaultDaySchedule.setName('Air Velocity Schedule Default')
|
501
|
+
air_velo_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), people_air_velocity)
|
502
|
+
|
503
|
+
ceiling_fan_count = 0 # init
|
504
|
+
|
505
|
+
# add ceiling fan to occupied spaces
|
506
|
+
# don't need to loop through space types as thermostat is attached to thermalZone only, which is connected to space
|
507
|
+
model.getSpaces.each do |space|
|
508
|
+
# only add to occupied zones
|
509
|
+
unless space.numberOfPeople > 0
|
510
|
+
runner.registerWarning("Skip adding ceiling fan to #{space.name.to_s} because it is not occupied.")
|
511
|
+
next
|
512
|
+
end
|
513
|
+
|
514
|
+
# only add to space => thermal zone that has cooling setpoint assigned
|
515
|
+
if space.thermalZone.empty?
|
516
|
+
runner.registerWarning("Skip adding ceiling fan to #{space.name.to_s} because it is not associated with a thermal zone.")
|
517
|
+
next
|
518
|
+
end
|
519
|
+
|
520
|
+
thermal_zone = space.thermalZone.get
|
521
|
+
if thermal_zone.thermostatSetpointDualSetpoint.empty?
|
522
|
+
runner.registerWarning("Skip adding ceiling fan to #{space.name.to_s} because it doesn't have thermostat assigned.")
|
523
|
+
next
|
524
|
+
end
|
525
|
+
|
526
|
+
thermostat = thermal_zone.thermostatSetpointDualSetpoint.get
|
527
|
+
# setup new cooling setpoint schedule
|
528
|
+
clg_set_sch = thermostat.coolingSetpointTemperatureSchedule
|
529
|
+
if clg_set_sch.empty?
|
530
|
+
runner.registerWarning("Skip adding ceiling fan to #{space.name.to_s} because its thermostat doesn't have cooling setpoint schedule assigned.")
|
531
|
+
next
|
532
|
+
end
|
533
|
+
|
534
|
+
runner.registerInfo("adjusting schedule #{clg_set_sch.get.name.to_s}")
|
535
|
+
new_sch = adjust_cool_sch(model, clg_set_sch, cool_stp_increase_C, os_start_date, os_end_date, os_start_time, os_end_time)
|
536
|
+
|
537
|
+
# hook up clone to thermostat
|
538
|
+
thermostat.setCoolingSetpointTemperatureSchedule(new_sch)
|
539
|
+
|
540
|
+
# add ceiling fan electric equipment
|
541
|
+
ceiling_fan = OpenStudio::Model::ElectricEquipment.new(ceiling_fan_def)
|
542
|
+
ceiling_fan.setName("#{space.name.to_s} ceiling fan")
|
543
|
+
ceiling_fan.setSchedule(ceiling_fan_sch)
|
544
|
+
ceiling_fan.setSpace(space)
|
545
|
+
|
546
|
+
# assign people air velocity schedule
|
547
|
+
space.people.each do |people_inst|
|
548
|
+
people_inst.setAirVelocitySchedule(air_velo_sch)
|
549
|
+
end
|
550
|
+
|
551
|
+
ceiling_fan_count += 1
|
552
|
+
end
|
553
|
+
|
554
|
+
# echo the new space's name back to the user
|
555
|
+
runner.registerInfo("#{ceiling_fan_count} ceiling fans were added to occupied conditioned spaces with #{watts_per_m2}w/m2.")
|
556
|
+
|
557
|
+
# report final condition of model
|
558
|
+
runner.registerFinalCondition("Ceiling fans were added to the building.")
|
559
|
+
|
560
|
+
return true
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
# register the measure to be used by the application
|
565
|
+
AddCeilingFan.new.registerWithApplication
|