def hvac_control(cfg, advise_cfg, tstats, client, thermal_model, zone, building, now, debug=False, simulate=False): """ :param cfg: :param advise_cfg: :param tstats: :param client: :param thermal_model: :param zone: :param now: datetime object in UTC which tells the control what now is. :param debug: wether to actuate the tstat. :param simulate: boolean whether to run the control as a simulation or to actually actuate. :return: boolean, dict. Success Boolean indicates whether writing action has succeeded. Dictionary {cooling_setpoint: float, heating_setpoint: float, override: bool, mode: int} and None if success boolean is flase. """ try: zone_temperatures = { dict_zone: dict_tstat.temperature for dict_zone, dict_tstat in tstats.items() } tstat = tstats[zone] tstat_temperature = zone_temperatures[ zone] # to make sure we get all temperatures at the same time # get datamanagers dataManager = DataManager(cfg, advise_cfg, client, zone, now=now) thermal_data_manager = ThermalDataManager(cfg, client) safety_constraints = dataManager.safety_constraints() prices = dataManager.prices() building_setpoints = dataManager.building_setpoints() if simulate or not advise_cfg["Advise"]["Occupancy_Sensors"]: occ_predictions = dataManager.preprocess_occ_cfg() else: occ_predictions = dataManager.preprocess_occ_mdal() if not simulate: # TODO FIX THE UPDATE STEP. PUT THIS OUTSIDE OF HVAC CONTROL. # NOTE: call update before setWeatherPredictions and set_temperatures thermal_model.update(zone_temperatures, interval=cfg["Interval_Length"]) # need to set weather predictions for every loop and set current zone temperatures. thermal_model.set_temperatures(zone_temperatures) # ===== Future and past outside temperature combine ===== # Get correct weather predictions. # we might have that the given now is before the actual current time # hence need to get historic data and combine with weather predictions. # finding out where the historic/future intervals start and end. utc_now = utils.get_utc_now() # If simulation window is partially in the past and in the future if utils.in_between_datetime( utc_now, now, now + datetime.timedelta( hours=advise_cfg["Advise"]["MPCPredictiveHorizon"])): historic_start = now historic_end = utc_now future_start = utc_now future_end = now + datetime.timedelta( hours=advise_cfg["Advise"]["MPCPredictiveHorizon"]) # If simulation window is fully in the future elif now >= utc_now: historic_start = None historic_end = None future_start = now future_end = now + datetime.timedelta( hours=advise_cfg["Advise"]["MPCPredictiveHorizon"]) # If simulation window is fully in the past else: historic_start = now historic_end = now + datetime.timedelta( hours=advise_cfg["Advise"]["MPCPredictiveHorizon"]) future_start = None future_end = None # Populating the outside_temperatures dictionary for MPC use. Ouput is in cfg timezone. outside_temperatures = {} if future_start is not None: # TODO implement end for weather_fetch future_weather = dataManager.weather_fetch(start=future_start) outside_temperatures = future_weather # Combining historic data with outside_temperatures correctly if exists. if historic_start is not None: historic_weather = thermal_data_manager._get_outside_data( historic_start, historic_end, inclusive=True) historic_weather = thermal_data_manager._preprocess_outside_data( historic_weather.values()) # Down sample the historic weather to hourly entries, and take the mean for each hour. historic_weather = historic_weather.groupby( [pd.Grouper(freq="1H")])["t_out"].mean() # Convert historic_weather to cfg timezone. historic_weather.index = historic_weather.index.tz_convert( tz=cfg["Pytz_Timezone"]) # Popluate the outside_temperature array. If we have the simulation time in the past and future then # we will take a weighted averege of the historic and future temperatures in the hour in which # historic_end and future_start happen. for row in historic_weather.iteritems(): row_time, t_out = row[0], row[1] # taking a weighted average of the past and future outside temperature since for now # we only have one outside temperature per hour. if row_time.hour in outside_temperatures and \ row_time.hour == historic_end.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"])).hour: future_t_out = outside_temperatures[row_time.hour] # Checking if start and end are in the same hour, because then we have to weigh the temperature by # less. if historic_end.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"])).hour ==\ historic_start.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"])).hour: historic_weight = (historic_end - historic_start).seconds // 60 else: historic_weight = historic_end.astimezone( tz=pytz.timezone(cfg["Pytz_Timezone"])).minute if future_start.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"])).hour ==\ future_end.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"])).hour: future_weight = (future_end - future_start).seconds // 60 else: # the remainder of the hour. future_weight = 60 - future_start.astimezone( tz=pytz.timezone(cfg["Pytz_Timezone"])).minute # Normalize total_weight = future_weight + historic_weight future_weight /= float(total_weight) historic_weight /= float(total_weight) outside_temperatures[row_time.hour] = future_weight * future_t_out + \ historic_weight * float(t_out) else: outside_temperatures[row_time.hour] = float(t_out) # setting outside temperature data for the thermal model. thermal_model.set_outside_temperature(outside_temperatures) # ===== END: Future and past outside temperature combine ===== if (cfg["Pricing"]["DR"] and utils.in_between( now.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"])).time(), utils.get_time_datetime(cfg["Pricing"]["DR_Start"]), utils.get_time_datetime(cfg["Pricing"]["DR_Finish"]))): # \ # or now.weekday() == 4: # TODO REMOVE ALWAYS HAVING DR ON FRIDAY WHEN DR SUBSCRIBE IS IMPLEMENTED DR = True else: DR = False adv_start = time.time() adv = Advise( [ zone ], # array because we might use more than one zone. Multiclass approach. now.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"])), occ_predictions, [tstat_temperature], thermal_model, prices, advise_cfg["Advise"]["General_Lambda"], advise_cfg["Advise"]["DR_Lambda"], DR, cfg["Interval_Length"], advise_cfg["Advise"]["MPCPredictiveHorizon"], advise_cfg["Advise"]["Heating_Consumption"], advise_cfg["Advise"]["Cooling_Consumption"], advise_cfg["Advise"]["Ventilation_Consumption"], advise_cfg["Advise"]["Thermal_Precision"], advise_cfg["Advise"]["Occupancy_Obs_Len_Addition"], building_setpoints, advise_cfg["Advise"]["Occupancy_Sensors"] if not simulate else False, # TODO Only using config file occupancy for now. safety_constraints) action = adv.advise() adv_end = time.time() except Exception: print("ERROR: For zone %s." % zone) print(traceback.format_exc()) # TODO Find a better way for exceptions return False # action "0" is Do Nothing, action "1" is Heating, action "2" is Cooling if action == "0": heating_setpoint = tstat_temperature - advise_cfg["Advise"][ "Minimum_Comfortband_Height"] / 2. cooling_setpoint = tstat_temperature + advise_cfg["Advise"][ "Minimum_Comfortband_Height"] / 2. if heating_setpoint < safety_constraints[0][0]: heating_setpoint = safety_constraints[0][0] if (cooling_setpoint - heating_setpoint ) < advise_cfg["Advise"]["Minimum_Comfortband_Height"]: cooling_setpoint = min( safety_constraints[0][1], heating_setpoint + advise_cfg["Advise"]["Minimum_Comfortband_Height"]) elif cooling_setpoint > safety_constraints[0][1]: cooling_setpoint = safety_constraints[0][1] if (cooling_setpoint - heating_setpoint ) < advise_cfg["Advise"]["Minimum_Comfortband_Height"]: heating_setpoint = max( safety_constraints[0][0], cooling_setpoint - advise_cfg["Advise"]["Minimum_Comfortband_Height"]) # round to integers since the thermostats round internally. heating_setpoint = math.floor(heating_setpoint) cooling_setpoint = math.ceil(cooling_setpoint) p = { "override": True, "heating_setpoint": heating_setpoint, "cooling_setpoint": cooling_setpoint, "mode": 3 } print "Doing nothing" # TODO Rethink how we set setpoints for heating and cooling and for DR events. # heating elif action == "1": heating_setpoint = tstat_temperature + 2 * advise_cfg["Advise"][ "Hysterisis"] cooling_setpoint = heating_setpoint + advise_cfg["Advise"][ "Minimum_Comfortband_Height"] if cooling_setpoint > safety_constraints[0][1]: cooling_setpoint = safety_constraints[0][1] # making sure we are in the comfortband if (cooling_setpoint - heating_setpoint ) < advise_cfg["Advise"]["Minimum_Comfortband_Height"]: heating_setpoint = max( safety_constraints[0][0], cooling_setpoint - advise_cfg["Advise"]["Minimum_Comfortband_Height"]) # round to integers since the thermostats round internally. heating_setpoint = math.ceil(heating_setpoint) cooling_setpoint = math.ceil(cooling_setpoint) p = { "override": True, "heating_setpoint": heating_setpoint, "cooling_setpoint": cooling_setpoint, "mode": 3 } print "Heating" # cooling elif action == "2": cooling_setpoint = tstat_temperature - 2 * advise_cfg["Advise"][ "Hysterisis"] heating_setpoint = cooling_setpoint - advise_cfg["Advise"][ "Minimum_Comfortband_Height"] if heating_setpoint < safety_constraints[0][0]: heating_setpoint = safety_constraints[0][0] # making sure we are in the comfortband if (cooling_setpoint - heating_setpoint ) < advise_cfg["Advise"]["Minimum_Comfortband_Height"]: cooling_setpoint = min( safety_constraints[0][1], heating_setpoint + advise_cfg["Advise"]["Minimum_Comfortband_Height"]) # round to integers since the thermostats round internally. heating_setpoint = math.floor(heating_setpoint) cooling_setpoint = math.floor(cooling_setpoint) p = { "override": True, "heating_setpoint": heating_setpoint, "cooling_setpoint": cooling_setpoint, "mode": 3 } print "Cooling" else: print "Problem with action." return False, None print("Zone: " + zone + ", action: " + str(p)) # Plot the MPC graph. if advise_cfg["Advise"]["Print_Graph"]: adv.g_plot(zone) # Log the information related to the current MPC Debugger.debug_print(now, building, zone, adv, safety_constraints, prices, building_setpoints, adv_end - adv_start, file=True) # try to commit the changes to the thermostat, if it doesnt work 10 times in a row ignore and try again later for i in range(advise_cfg["Advise"]["Thermostat_Write_Tries"]): try: if not debug and not simulate: tstat.write(p) # Setting last action in the thermal model after we have succeeded in writing to the tstat. thermal_model.set_last_action(int(action)) break except: if i == advise_cfg["Advise"]["Thermostat_Write_Tries"] - 1: e = sys.exc_info()[0] print e return False, None continue return True, p
};""" % cfg["Building"] import pickle with open("../Thermal Data/ciee_thermal_data_demo", "r") as f: thermal_data = pickle.load(f) dm = DataManager(cfg, advise_cfg, c, ZONE) tstat_query_data = hc.do_query(q)['Rows'] tstats = {tstat["?zone"]: Thermostat(c, tstat["?uri"]) for tstat in tstat_query_data} # TODO INTERVAL SHOULD NOT BE IN config_file.yml, THERE SHOULD BE A DIFFERENT INTERVAL FOR EACH ZONE from ThermalModel import * thermal_model = MPCThermalModel(thermal_data, interval_length=cfg["Interval_Length"]) thermal_model.setZoneTemperaturesAndFit( {dict_zone: dict_tstat.temperature for dict_zone, dict_tstat in tstats.items()}, dt=cfg["Interval_Length"]) thermal_model.setWeahterPredictions(dm.weather_fetch()) adv = Advise(["HVAC_Zone_Centralzone"], datetime.datetime.utcnow().replace(tzinfo=pytz.utc).astimezone( tz=pytz.timezone("America/Los_Angeles")), dm.preprocess_occ(), [80], # [{dict_zone: dict_tstat.temperature for dict_zone, dict_tstat in tstats.items()}[ZONE]], thermal_model, dm.prices(), 0.995, 0.995, False, 15, 2, True, 0.075, 1.25, 0.01, 400., 4, dm.building_setpoints(), advise_cfg["Advise"]["Occupancy_Sensors"], dm.safety_constraints()) print adv.advise()
def hvac_control(cfg, advise_cfg, tstats, client, thermal_model, zone): """ :param cfg: :param advise_cfg: :param tstats: :param client: :param thermal_model: :param zone: :return: boolean, dict. Success Boolean indicates whether writing action has succeeded. Dictionary {cooling_setpoint: float, heating_setpoint: float, override: bool, mode: int} and None if success boolean is flase. """ # now in UTC time. now = pytz.timezone("UTC").localize(datetime.datetime.utcnow()) try: zone_temperatures = { dict_zone: dict_tstat.temperature for dict_zone, dict_tstat in tstats.items() } tstat = tstats[zone] tstat_temperature = zone_temperatures[ zone] # to make sure we get all temperatures at the same time dataManager = DataManager(cfg, advise_cfg, client, zone, now=now) safety_constraints = dataManager.safety_constraints() # need to set weather predictions for every loop and set current zone temperatures and fit the model given the new data (if possible). # NOTE: call setZoneTemperaturesAndFit before setWeahterPredictions # TODO Double Check if update to new thermal model was correct thermal_model.set_temperatures_and_fit( zone_temperatures, interval=cfg["Interval_Length"], now=now.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"]))) # TODO Look at the weather_fetch function and make sure correct locks are implemented and we are getting the right data. weather = dataManager.weather_fetch() thermal_model.set_weather_predictions(weather) if (cfg["Pricing"]["DR"] and in_between( now.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"])).time(), getDatetime(cfg["Pricing"]["DR_Start"]), getDatetime(cfg["Pricing"]["DR_Finish"]))): #\ #or now.weekday() == 4: # TODO REMOVE ALLWAYS HAVING DR ON FRIDAY WHEN DR SUBSCRIBE IS IMPLEMENTED DR = True else: DR = False adv = Advise( [ zone ], # array because we might use more than one zone. Multiclass approach. now.astimezone(tz=pytz.timezone(cfg["Pytz_Timezone"])), dataManager.preprocess_occ(), [tstat_temperature], thermal_model, dataManager.prices(), advise_cfg["Advise"]["General_Lambda"], advise_cfg["Advise"]["DR_Lambda"], DR, cfg["Interval_Length"], advise_cfg["Advise"]["MPCPredictiveHorizon"], advise_cfg["Advise"]["Print_Graph"], advise_cfg["Advise"]["Heating_Consumption"], advise_cfg["Advise"]["Cooling_Consumption"], advise_cfg["Advise"]["Ventilation_Consumption"], advise_cfg["Advise"]["Thermal_Precision"], advise_cfg["Advise"]["Occupancy_Obs_Len_Addition"], dataManager.building_setpoints(), advise_cfg["Advise"]["Occupancy_Sensors"], safety_constraints) action = adv.advise() except Exception: print(traceback.format_exc()) # TODO Find a better way for exceptions return False # action "0" is Do Nothing, action "1" is Heating, action "2" is Cooling if action == "0": heating_setpoint = tstat_temperature - advise_cfg["Advise"][ "Minimum_Comfortband_Height"] / 2. cooling_setpoint = tstat_temperature + advise_cfg["Advise"][ "Minimum_Comfortband_Height"] / 2. if heating_setpoint < safety_constraints[0][0]: heating_setpoint = safety_constraints[0][0] if (cooling_setpoint - heating_setpoint ) < advise_cfg["Advise"]["Minimum_Comfortband_Height"]: cooling_setpoint = min( safety_constraints[0][1], heating_setpoint + advise_cfg["Advise"]["Minimum_Comfortband_Height"]) elif cooling_setpoint > safety_constraints[0][1]: cooling_setpoint = safety_constraints[0][1] if (cooling_setpoint - heating_setpoint ) < advise_cfg["Advise"]["Minimum_Comfortband_Height"]: heating_setpoint = max( safety_constraints[0][0], cooling_setpoint - advise_cfg["Advise"]["Minimum_Comfortband_Height"]) # round to integers since the thermostats round internally. heating_setpoint = math.floor(heating_setpoint) cooling_setpoint = math.ceil(cooling_setpoint) p = { "override": True, "heating_setpoint": heating_setpoint, "cooling_setpoint": cooling_setpoint, "mode": 3 } print "Doing nothing" # TODO Rethink how we set setpoints for heating and cooling and for DR events. # heating elif action == "1": heating_setpoint = tstat_temperature + 2 * advise_cfg["Advise"][ "Hysterisis"] cooling_setpoint = heating_setpoint + advise_cfg["Advise"][ "Minimum_Comfortband_Height"] if cooling_setpoint > safety_constraints[0][1]: cooling_setpoint = safety_constraints[0][1] # making sure we are in the comfortband if (cooling_setpoint - heating_setpoint ) < advise_cfg["Advise"]["Minimum_Comfortband_Height"]: heating_setpoint = max( safety_constraints[0][0], cooling_setpoint - advise_cfg["Advise"]["Minimum_Comfortband_Height"]) # round to integers since the thermostats round internally. heating_setpoint = math.ceil(heating_setpoint) cooling_setpoint = math.ceil(cooling_setpoint) p = { "override": True, "heating_setpoint": heating_setpoint, "cooling_setpoint": cooling_setpoint, "mode": 3 } print "Heating" # cooling elif action == "2": cooling_setpoint = tstat_temperature - 2 * advise_cfg["Advise"][ "Hysterisis"] heating_setpoint = cooling_setpoint - advise_cfg["Advise"][ "Minimum_Comfortband_Height"] if heating_setpoint < safety_constraints[0][0]: heating_setpoint = safety_constraints[0][0] # making sure we are in the comfortband if (cooling_setpoint - heating_setpoint ) < advise_cfg["Advise"]["Minimum_Comfortband_Height"]: cooling_setpoint = min( safety_constraints[0][1], heating_setpoint + advise_cfg["Advise"]["Minimum_Comfortband_Height"]) # round to integers since the thermostats round internally. heating_setpoint = math.floor(heating_setpoint) cooling_setpoint = math.floor(cooling_setpoint) p = { "override": True, "heating_setpoint": heating_setpoint, "cooling_setpoint": cooling_setpoint, "mode": 3 } print "Cooling" else: print "Problem with action." return False, None print("Zone: " + zone + ", action: " + str(p)) # try to commit the changes to the thermostat, if it doesnt work 10 times in a row ignore and try again later for i in range(advise_cfg["Advise"]["Thermostat_Write_Tries"]): try: # TODO Uncomment tstat.write(p) thermal_model.set_last_action( action ) # TODO Document that this needs to be set after we are sure that writing has succeeded. break except: if i == advise_cfg["Advise"]["Thermostat_Write_Tries"] - 1: e = sys.exc_info()[0] print e return False, None continue return True, p
zone_thermal_models = { thermal_zone: MPCThermalModel(zone, zone_data[zone_data['dt'] == 5], interval_length=cfg["Interval_Length"], thermal_precision=0.05) for thermal_zone, zone_data in all_data.items() } print("Trained Thermal Model") # -------------------------------------- advise_cfg = utils.get_zone_config(building, zone) dataManager = DataManager(cfg, advise_cfg, client, zone, now=now) safety_constraints = dataManager.safety_constraints() prices = dataManager.prices() building_setpoints = dataManager.building_setpoints() temperature = 65 DR = False # set outside temperatures and zone temperatures for each zone. for thermal_zone, zone_thermal_model in zone_thermal_models.items(): zone_thermal_model.set_weather_predictions([70] * 24) zone_thermal_model.set_temperatures_and_fit({ temp_thermal_zone: 70 for temp_thermal_zone in zone_thermal_models.keys() }) adv = Advise( [ zone