diff --git a/thermostat.py b/thermostat.py index 5466f59..21ae9d1 100644 --- a/thermostat.py +++ b/thermostat.py @@ -80,6 +80,55 @@ def status_as_text(): +'forced_mode: '+str(forced_mode)+'\n'\ +'\n'.join(['Target temperature for '+room+': '+str(rooms_settings[room][target_name])+'\n'+'Current temperature for '+room+': '+str(get_metric(rooms_settings[room]["metric"], current_time, relay_control_interval)) for room in enabled_rooms()]) +def relay_state(relay): + try: + returned_output = subprocess.check_output(["./relay.py", relay, "status"]) + return returned_output.strip().decode("utf-8") + except Exception as e: + logging.error(e) + logging.error("relay "+relay+" status: Failed to command relays board.") + sys.stdout.flush() + return "Failed" + +def set_relay(relay, state): + try: + returned_output = subprocess.check_output(["./relay.py", relay, state]) + logging.info("set relay "+relay+" to "+state+", new global status: "+returned_output.decode("utf-8").split('\n')[1]) + sys.stdout.flush() + return "OK" + except Exception as e: + logging.error(e) + logging.error("set relay "+relay+" to "+state+": Failed to command relays board.") + sys.stdout.flush() + return "KO" + +def get_metric(metric, current_time, interval): + url = "http://localhost:3000/"+metric + try: + r = requests.get(url) + data = json.loads(r.text) + timestamp = getDateTimeFromISO8601String(data['timestamp']).replace(tzinfo=timezone.utc).timestamp() + if current_time - timestamp < interval * 2: + return data['value'] + else: + logging.warning("WARNING: No recent load data available.") + except Exception as e: + logging.error(e) + sys.stdout.flush() + return None + +def get_forced_mode(cur): + cur.execute("SELECT value, timestamp FROM set_mode WHERE name='mode'") + row = cur.fetchone() + data = dict(zip(['value', 'timestamp'], row)) + timestamp = getDateTimeFromISO8601String(data['timestamp']).replace(tzinfo=timezone.utc).timestamp() + # We ignore old targets but never ignore absence modes + if data['value'] in targets and time.time() - timestamp > forced_mode_duration: + logging.debug("Ignoring old set mode.") + return None + else: + return data['value'] + ## app = Flask(__name__) @@ -147,6 +196,111 @@ first_loop = True # print(row) #sys.stdout.flush() +while True: +# if stop.is_set(): +# httpd.shutdown() +# httpd.server_close() +# dbconn.close() +# break + + if new_forced_mode is not None: + cursor.execute("INSERT OR REPLACE INTO set_mode (value) VALUES ('"+new_forced_mode+"')") + dbconn.commit() + logging.info("Switch to "+new_forced_mode) + target_name = new_forced_mode + new_forced_mode = None + # Force immediate action: + last_control_time = None + current_time = time.time() + current_date = datetime.datetime.now() + today_awake_time = current_date.replace(hour=int(awake_hour.split(':')[0]), minute=int(awake_hour.split(':')[1]), second=0, microsecond=0) + today_sleep_time = current_date.replace(hour=int(sleep_hour.split(':')[0]), minute=int(sleep_hour.split(':')[1]), second=0, microsecond=0) + forced_mode = get_forced_mode(cursor) + if forced_mode is not None and forced_mode in targets: + if target_name != forced_mode: + target_name = forced_mode + logging.info("Switch to "+forced_mode) + else: + if forced_mode == "long_absence": + if target_name != "target_frost_protection" or first_loop: + target_name = "target_frost_protection" + logging.info("Switch to "+target_name) + elif forced_mode == "short_absence" or first_loop: + if target_name != "target_sleep_temperature": + target_name = "target_sleep_temperature" + logging.info("Switch to "+target_name) + elif current_date > today_awake_time and current_date < today_sleep_time: + if target_name != "target_unconfirmed_awake_temperature" and target_name != "target_awake_temperature": + target_name = "target_unconfirmed_awake_temperature" + logging.info("Switch to unconfirmed awake mode.") + elif current_date < today_awake_time or current_date > today_sleep_time: + if target_name != "target_unconfirmed_sleep_temperature" and target_name != "target_sleep_temperature": + target_name = "target_unconfirmed_sleep_temperature" + logging.info("Switch to unconfirmed sleep mode.") + + first_loop = False + + # Load shedder + current_load = get_metric("Modane_elec_main_power", current_time, load_shedder_interval) + if current_load is None: + time.sleep(load_shedder_interval) + continue + elif max_load - current_load < load_margin: + logging.warning("Load too high: "+str(current_load)+"VA") + total_shedded = 0 + for room in shedding_order: + current_state = relay_state(rooms_settings[room]["relays"]) + if current_state != "Failed": + logging.debug("Got relay_state: '"+current_state+"'") + if current_state == "1": + result = set_relay(rooms_settings[room]["relays"], "off") + if result == "OK": + total_shedded += relays_load[rooms_settings[room]["relays"]] + if max_load - current_load - total_shedded < load_margin: + logging.info("Load should be back to normal.") + break + + # Thermostat + if last_control_time is None or current_time - last_control_time > relay_control_interval: + last_control_time = current_time + for room in rooms_settings: + if not rooms_settings[room]["enabled"]: + continue + target = rooms_settings[room][target_name] + logging.debug("Target: "+str(target)) + temperature = get_metric(rooms_settings[room]["metric"], current_time, relay_control_interval) + if temperature is None: + continue + logging.debug(room+": "+str(temperature)) + current_state = relay_state(rooms_settings[room]["relays"]) + if current_state != "Failed": + logging.debug("Got relay_state: '"+current_state+"'") + if temperature < target - hysteresis: + if current_state == "0": + logging.info(room+": Target temperature is "+str(target)) + logging.info(room+": Current temperature is "+str(temperature)) + if current_load + relays_load[rooms_settings[room]["relays"]] + load_margin > max_load: + logging.warning(room+": Load too high cannot start heaters.") + else: + logging.info(room+": Starting heaters.") + set_relay(rooms_settings[room]["relays"], "on") + sys.stdout.flush() + else: + logging.debug("Relay already on.") + + elif temperature > target + hysteresis: + if current_state == "1": + logging.info(room+": Target temperature is "+str(target)) + logging.info(room+": Current temperature is "+str(temperature)) + logging.info(room+": Stopping heaters.") + sys.stdout.flush() + set_relay(rooms_settings[room]["relays"], "off") + else: + logging.debug("Relay already off.") + time.sleep(load_shedder_interval) + +logging.info("====== Ended successfully ======") + #import subprocess #from datetime import timezone #import requests @@ -204,55 +358,7 @@ first_loop = True # -#def relay_state(relay): -# try: -# returned_output = subprocess.check_output(["./relay.py", relay, "status"]) -# return returned_output.strip().decode("utf-8") -# except Exception as e: -# logging.error(e) -# logging.error("relay "+relay+" status: Failed to command relays board.") -# sys.stdout.flush() -# return "Failed" -# -#def set_relay(relay, state): -# try: -# returned_output = subprocess.check_output(["./relay.py", relay, state]) -# logging.info("set relay "+relay+" to "+state+", new global status: "+returned_output.decode("utf-8").split('\n')[1]) -# sys.stdout.flush() -# return "OK" -# except Exception as e: -# logging.error(e) -# logging.error("set relay "+relay+" to "+state+": Failed to command relays board.") -# sys.stdout.flush() -# return "KO" -# -#def get_metric(metric, current_time, interval): -# url = "http://localhost:3000/"+metric -# try: -# r = requests.get(url) -# data = json.loads(r.text) -# timestamp = getDateTimeFromISO8601String(data['timestamp']).replace(tzinfo=timezone.utc).timestamp() -# if current_time - timestamp < interval * 2: -# return data['value'] -# else: -# logging.warning("WARNING: No recent load data available.") -# except Exception as e: -# logging.error(e) -# sys.stdout.flush() -# return None -# -#def get_forced_mode(cur): -# cur.execute("SELECT value, timestamp FROM set_mode WHERE name='mode'") -# row = cur.fetchone() -# data = dict(zip(['value', 'timestamp'], row)) -# timestamp = getDateTimeFromISO8601String(data['timestamp']).replace(tzinfo=timezone.utc).timestamp() -# # We ignore old targets but never ignore absence modes -# if data['value'] in targets and time.time() - timestamp > forced_mode_duration: -# logging.debug("Ignoring old set mode.") -# return None -# else: -# return data['value'] -# + #logging.info("====== Starting ======") #stop = Event() #last_data = {} @@ -305,108 +411,4 @@ first_loop = True #webserver_thread = WebThread() #webserver_thread.start() # -#while True: -# if stop.is_set(): -# httpd.shutdown() -# httpd.server_close() -# dbconn.close() -# break -# -# if new_forced_mode is not None: -# cursor.execute("INSERT OR REPLACE INTO set_mode (value) VALUES ('"+new_forced_mode+"')") -# dbconn.commit() -# logging.info("Switch to "+new_forced_mode) -# target_name = new_forced_mode -# new_forced_mode = None -# # Force immediate action: -# last_control_time = None -# current_time = time.time() -# current_date = datetime.datetime.now() -# today_awake_time = current_date.replace(hour=int(awake_hour.split(':')[0]), minute=int(awake_hour.split(':')[1]), second=0, microsecond=0) -# today_sleep_time = current_date.replace(hour=int(sleep_hour.split(':')[0]), minute=int(sleep_hour.split(':')[1]), second=0, microsecond=0) -# forced_mode = get_forced_mode(cursor) -# if forced_mode is not None and forced_mode in targets: -# if target_name != forced_mode: -# target_name = forced_mode -# logging.info("Switch to "+forced_mode) -# else: -# if forced_mode == "long_absence": -# if target_name != "target_frost_protection" or first_loop: -# target_name = "target_frost_protection" -# logging.info("Switch to "+target_name) -# elif forced_mode == "short_absence" or first_loop: -# if target_name != "target_sleep_temperature": -# target_name = "target_sleep_temperature" -# logging.info("Switch to "+target_name) -# elif current_date > today_awake_time and current_date < today_sleep_time: -# if target_name != "target_unconfirmed_awake_temperature" and target_name != "target_awake_temperature": -# target_name = "target_unconfirmed_awake_temperature" -# logging.info("Switch to unconfirmed awake mode.") -# elif current_date < today_awake_time or current_date > today_sleep_time: -# if target_name != "target_unconfirmed_sleep_temperature" and target_name != "target_sleep_temperature": -# target_name = "target_unconfirmed_sleep_temperature" -# logging.info("Switch to unconfirmed sleep mode.") -# -# first_loop = False -# -# # Load shedder -# current_load = get_metric("Modane_elec_main_power", current_time, load_shedder_interval) -# if current_load is None: -# time.sleep(load_shedder_interval) -# continue -# elif max_load - current_load < load_margin: -# logging.warning("Load too high: "+str(current_load)+"VA") -# total_shedded = 0 -# for room in shedding_order: -# current_state = relay_state(rooms_settings[room]["relays"]) -# if current_state != "Failed": -# logging.debug("Got relay_state: '"+current_state+"'") -# if current_state == "1": -# result = set_relay(rooms_settings[room]["relays"], "off") -# if result == "OK": -# total_shedded += relays_load[rooms_settings[room]["relays"]] -# if max_load - current_load - total_shedded < load_margin: -# logging.info("Load should be back to normal.") -# break -# -# # Thermostat -# if last_control_time is None or current_time - last_control_time > relay_control_interval: -# last_control_time = current_time -# for room in rooms_settings: -# if not rooms_settings[room]["enabled"]: -# continue -# target = rooms_settings[room][target_name] -# logging.debug("Target: "+str(target)) -# temperature = get_metric(rooms_settings[room]["metric"], current_time, relay_control_interval) -# if temperature is None: -# continue -# logging.debug(room+": "+str(temperature)) -# current_state = relay_state(rooms_settings[room]["relays"]) -# if current_state != "Failed": -# logging.debug("Got relay_state: '"+current_state+"'") -# if temperature < target - hysteresis: -# if current_state == "0": -# logging.info(room+": Target temperature is "+str(target)) -# logging.info(room+": Current temperature is "+str(temperature)) -# if current_load + relays_load[rooms_settings[room]["relays"]] + load_margin > max_load: -# logging.warning(room+": Load too high cannot start heaters.") -# else: -# logging.info(room+": Starting heaters.") -# set_relay(rooms_settings[room]["relays"], "on") -# sys.stdout.flush() -# else: -# logging.debug("Relay already on.") -# -# elif temperature > target + hysteresis: -# if current_state == "1": -# logging.info(room+": Target temperature is "+str(target)) -# logging.info(room+": Current temperature is "+str(temperature)) -# logging.info(room+": Stopping heaters.") -# sys.stdout.flush() -# set_relay(rooms_settings[room]["relays"], "off") -# else: -# logging.debug("Relay already off.") -# time.sleep(load_shedder_interval) -# -#logging.info("====== Ended successfully ======") -# +