-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
merging in backup control changes #155 #160
base: dev
Are you sure you want to change the base?
Changes from all commits
f8116a7
4175c7c
55d8cdc
f46fad7
a46360c
e6e8965
9ef52a4
e45da06
37ad463
3c37190
1fa9d4b
26716c5
c1f4522
2844fad
feb3dd3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -172,8 +172,11 @@ def __init__(self, envelope_model=None, use_ideal_capacity=None, **kwargs): | |
# Thermostat Control Parameters | ||
self.temp_setpoint = initial_setpoint | ||
self.temp_deadband = kwargs.get('Deadband Temperature (C)', 1) | ||
# Offset defines much over deadband is overshooting setpoint, (1-offset) is undershooting | ||
# Offset defaults to reflect lab results | ||
self.deadband_offset = kwargs.get("Deadband Offset (C)", 0.2) | ||
self.ext_ignore_thermostat = kwargs.get('ext_ignore_thermostat', False) | ||
self.setpoint_ramp_rate = kwargs.get('setpoint_ramp_rate') # max setpoint ramp rate, in C/min | ||
#self.setpoint_ramp_rate = kwargs.get('setpoint_ramp_rate') # max setpoint ramp rate, in C/min | ||
self.temp_indoor_prev = self.temp_setpoint | ||
self.ext_capacity = None # Option to set capacity directly, ideal capacity only | ||
self.ext_capacity_frac = 1 # Option to limit max capacity, ideal capacity only | ||
|
@@ -326,13 +329,11 @@ def update_setpoint(self): | |
# updates setpoint with ramp rate constraints | ||
# TODO: create temp_setpoint_old and update in update_results. | ||
# Could get run multiple times per time step in update_model | ||
if self.setpoint_ramp_rate is not None: | ||
delta_t = self.setpoint_ramp_rate * self.time_res.total_seconds() / 60 # in C | ||
self.temp_setpoint = min(max(t_set, self.temp_setpoint - delta_t), self.temp_setpoint + delta_t) | ||
else: | ||
self.temp_setpoint = t_set | ||
|
||
self.temp_setpoint = t_set | ||
|
||
# set envelope comfort limits | ||
# TODO: update using deadband_offset | ||
if self.envelope_model is not None: | ||
if self.is_heater: | ||
self.envelope_model.heating_setpoint = self.temp_setpoint | ||
|
@@ -346,8 +347,8 @@ def run_thermostat_control(self, setpoint=None): | |
setpoint = self.temp_setpoint | ||
|
||
# On and off limits depend on heating vs. cooling | ||
temp_turn_on = setpoint - self.hvac_mult * self.temp_deadband / 2 | ||
temp_turn_off = setpoint + self.hvac_mult * self.temp_deadband / 2 | ||
temp_turn_on = setpoint - self.hvac_mult * self.temp_deadband * (1 - self.deadband_offset) | ||
temp_turn_off = setpoint + self.hvac_mult * self.temp_deadband * (self.deadband_offset) | ||
|
||
# Determine mode | ||
if self.hvac_mult * (self.zone.temperature - temp_turn_on) < 0: | ||
|
@@ -560,8 +561,8 @@ def make_equivalent_battery_model(self): | |
# TODO: update capacitance using 1R1C model | ||
ref_temp = 10 if self.is_heater else 30 # temperature at Energy=0, in C | ||
total_capacitance = convert(self.zone.capacitance, 'kJ', 'kWh') # in kWh/K | ||
max_temp = self.temp_setpoint + self.hvac_mult * self.temp_deadband / 2 # "turn off" temperature | ||
min_temp = self.temp_setpoint - self.hvac_mult * self.temp_deadband / 2 # "turn on" temperature | ||
max_temp = self.temp_setpoint + self.hvac_mult * self.temp_deadband * (1 - self.deadband_offset) # "turn off" temperature | ||
min_temp = self.temp_setpoint - self.hvac_mult * self.temp_deadband * self.deadband_offset # "turn on" temperature | ||
return { | ||
f'{self.end_use} EBM Energy (kWh)': total_capacitance * (self.zone.temperature - ref_temp) * self.hvac_mult, | ||
f'{self.end_use} EBM Min Energy (kWh)': total_capacitance * (min_temp - ref_temp) * self.hvac_mult, | ||
|
@@ -782,8 +783,8 @@ def run_two_speed_control(self): | |
# else: | ||
# speed_idx = 0 | ||
elif self.control_type == 'Setpoint': | ||
# Setpoint-based 2-speed HVAC control: High speed uses setpoint difference of deadband / 2 (overlapping) | ||
high_mode = super().run_thermostat_control(self.temp_setpoint - self.hvac_mult * self.temp_deadband / 2) | ||
# Setpoint-based 2-speed HVAC control: High speed uses setpoint difference of deadband * deadband_offset (overlapping) | ||
high_mode = super().run_thermostat_control(self.temp_setpoint - self.hvac_mult * self.deadband_offset) | ||
if high_mode == 'On': | ||
speed = 2 | ||
elif high_mode == 'Off': | ||
|
@@ -1067,14 +1068,22 @@ def __init__(self, **kwargs): | |
|
||
super().__init__(**kwargs) | ||
|
||
# backup element parameters | ||
self.outdoor_temp_limit = kwargs.get('Supplemental Heater Cut-in Temperature (C)') # temp to shut off HP | ||
# backup element capacity and efficiency parameters | ||
self.er_capacity_rated = kwargs['Supplemental Heater Capacity (W)'] | ||
self.er_eir_rated = kwargs.get('Supplemental Heater EIR (-)', 1) | ||
self.er_capacity = 0 | ||
self.er_ext_capacity = None # Option to set ER capacity directly, ideal capacity only | ||
self.er_ext_capacity_frac = 1 # Option to limit max capacity, ideal capacity only | ||
|
||
# backup element control parameters | ||
# TODO: add options for ER temperature_offset, | ||
# min_setpoint_change_duration, hard_lockout | ||
# outdoor_temp_limit shuts off HP | ||
self.outdoor_temp_limit = kwargs.get('Supplemental Heater Cut-in Temperature (C)') | ||
self.timestep_count = 1 | ||
self.prev_setpoint = self.temp_setpoint | ||
# self.existing_stages = 0 # staged backup, number of stages on | ||
|
||
# Update minimum time for ER element | ||
er_on_time = kwargs.get(self.end_use + ' Minimum ER On Time', 0) | ||
self.min_time_in_mode['HP and ER On'] = dt.timedelta(minutes=er_on_time) | ||
|
@@ -1165,22 +1174,114 @@ def update_internal_control(self): | |
else: | ||
return 'Off' | ||
|
||
def run_er_thermostat_control(self): | ||
# run thermostat control for ER element - lower the setpoint by the deadband | ||
# TODO: add option to keep setpoint as is, e.g. when using external control | ||
er_setpoint = self.temp_setpoint - self.temp_deadband | ||
temp_indoor = self.zone.temperature | ||
def run_er_thermostat_control( | ||
self, | ||
temperature_offset = 1.6, | ||
min_setpoint_change_duration = 30, | ||
hard_lockout = 10, | ||
staged = False, | ||
max_outdoor_temp = 1.67 | ||
): | ||
# get indoor temperature | ||
temp_indoor = self.zone.temperature | ||
|
||
# if the outdoor temp is greater than input value, turn er off | ||
if self.outdoor_temp_limit is not None: | ||
if self.current_schedule['Ambient Dry Bulb (C)'] >= self.outdoor_temp_limit: | ||
self.timestep_count = 1 | ||
self.prev_setpoint = self.temp_setpoint | ||
# self.existing_stages = 0 # no staged | ||
return 'Off' | ||
else: # in case there is no outdoor temp limit provided, use a default | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want a default, it should be specified in init. I think we didn't have a |
||
if self.current_schedule['Ambient Dry Bulb (C)'] >= max_outdoor_temp: | ||
self.timestep_count = 1 | ||
self.prev_setpoint = self.temp_setpoint | ||
# self.existing_stages = 0 # no staged | ||
return 'Off' | ||
|
||
# Determine if setpoint has changed recently | ||
if min_setpoint_change_duration is not None and self.prev_setpoint is not None: | ||
min_interval = dt.timedelta(minutes=min_setpoint_change_duration) # minimum amount of time after a setpoint change that er stays off (user input) | ||
hard_lockout_interval = dt.timedelta(minutes=hard_lockout) # minimum amount of time after a setpoint change that er stays off (strictly) | ||
if hard_lockout_interval > min_interval: | ||
min_interval = hard_lockout_interval # increase the minimum interval | ||
self.warn(f"minimum setpoint change duration ({min_setpoint_change_duration} minutes) updated to comply with hard lockout interval ({hard_lockout} minutes)") | ||
if self.temp_setpoint > self.prev_setpoint: # turned up the heat | ||
if (self.timestep_count * self.time_res) > min_interval: # enough time has passed | ||
self.timestep_count = 1 # reset timestep count | ||
# control by temp_turn_on/temp_turn_off | ||
elif (self.timestep_count * self.time_res) > hard_lockout_interval: # hard lockout duration met | ||
if self.temp_indoor_prev is not None: | ||
if temp_indoor < self.temp_indoor_prev: # temp is decreasing | ||
self.timestep_count == 1 # if it turns on, will reset this | ||
# control by temp_turn_on/temp_turn_off | ||
else: | ||
# self.existing_stages = 0 # no staged | ||
self.timestep_count += 1 # continue iterating | ||
return 'Off' | ||
else: | ||
# self.existing_stages = 0 # no staged | ||
self.timestep_count += 1 # continue iterating | ||
return 'Off' | ||
else: | ||
self.timestep_count += 1 # wait longer | ||
# self.existing_stages = 0 # no staged | ||
return 'Off' | ||
elif self.temp_setpoint < self.prev_setpoint: # turned down the heat | ||
self.prev_setpoint = self.temp_setpoint | ||
self.timestep_count = 1 | ||
# self.existing_stages = 0 # no staged | ||
return 'Off' | ||
|
||
# run thermostat control for ER element - lower the setpoint by the deadband or user input | ||
# On and off limits depend on heating vs. cooling | ||
temp_turn_on = er_setpoint - self.hvac_mult * self.temp_deadband / 2 | ||
temp_turn_off = er_setpoint + self.hvac_mult * self.temp_deadband / 2 | ||
if temperature_offset is not None: | ||
er_setpoint = self.temp_setpoint | ||
temp_turn_on = er_setpoint - self.hvac_mult * temperature_offset | ||
mnblonsky marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this. Are we changing the deadband size of the ER element? |
||
temp_turn_off = er_setpoint + self.hvac_mult * (1 - self.deadband_offset) | ||
else: | ||
er_setpoint = self.temp_setpoint - self.temp_deadband | ||
temp_turn_on = er_setpoint - self.hvac_mult * self.deadband_offset | ||
temp_turn_off = er_setpoint + self.hvac_mult * (1 - self.deadband_offset) | ||
|
||
# Determine mode | ||
if self.hvac_mult * (temp_indoor - temp_turn_on) < 0: | ||
self.prev_setpoint = self.temp_setpoint | ||
self.timestep_count = 1 | ||
# if staged==True: # TODO: need to edit downstream to make use of staged backup | ||
# operating_capacity = self.staged_backup() | ||
return 'On' | ||
if self.hvac_mult * (temp_indoor - temp_turn_off) > 0: | ||
self.timestep_count = 1 | ||
self.prev_setpoint = self.temp_setpoint | ||
# self.existing_stages = 0 # no staged | ||
return 'Off' | ||
|
||
# TODO: staged backup (gradually increasing amount of capacity available) (lowest priority) | ||
# def staged_backup(self, capacity_per_stage=5): | ||
# # Returns partial capacity based on amount of stages currently on/total amount of stages | ||
# # TODO: make a time interval between adding stages (5 min default), update with ecobee/other controls: | ||
# # https://support.ecobee.com/s/articles/Threshold-settings-for-ecobee-thermostats | ||
# | ||
# # rounding to lowest integer #TODO: is the correct variable for er capacity? | ||
# number_stages = max(1, self.er_capacity_rated//capacity_per_stage) | ||
# if number_stages==1: | ||
# return self.total_capacity | ||
# else: | ||
# if self.existing_stages == number_stages: # fully on | ||
# return self.total_capacity | ||
# elif self.existing_stages > 0: #already partially on | ||
# self.existing_stages += 1 | ||
# multiplier = self.existing_stages/number_stages | ||
# if multiplier >= 1: | ||
# self.existing_stages = number_stages | ||
# return self.total_capacity | ||
# else: | ||
# return multiplier*capacity_per_stage | ||
# else: # turning on, previously off | ||
# self.existing_stages += 1 | ||
# return capacity_per_stage | ||
|
||
def update_er_capacity(self, hp_capacity): | ||
if self.use_ideal_capacity: | ||
if self.er_ext_capacity is not None: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these be options that can be changed or switched off?