def update_ignores_2sit(self): """ Update open window variables according to weather :return: nothing """ self.var.situation = weather.weather_for_woeid( self.setup.yahoo_location, self.setup.owm_location, self.setup.owm_api_key) if None not in self.var.situation.viewvalues(): temp = int(self.var.situation["current_temp"]) if temp is not None: # modify OWW # interval for oww 5min to 360min tmr = weather.interval_scale(temp, (-35.0, 35.0), (0, 10), (5, 360), True) self.setup.intervals["oww"] = [ tmr * 60, (3 * tmr) * 60, (3 * tmr) * 60 ] logmsg.update("OWW interval updated to " + str(self.setup.intervals["oww"])) # and now modify valve ignore time # interval for valve ignore 20min to 120min tmr = weather.interval_scale(temp, (0.0, 35.0), (1.7, 3.0), (20, 120), False) self.eq3.ignore_time = self.setup.window_ignore_time + tmr bridge.put("ign_op", self.eq3.ignore_time) logmsg.update( "Valve ignore interval updated to " + str(self.eq3.ignore_time), 'D')
def open(self): if self.is_init(): raise NameError("Wrong init!") if os.path.exists(self.file): try: os.rename( self.file, self.place + self.dev_name + "_" + time.strftime("%Y%m%d-%H%M%S", time.localtime()) + ".csv") except Exception: pass try: self.handle = open(self.file, "a") except IOError: this_path = os.path.dirname(self.file) if not os.path.exists(this_path): try: os.makedirs(this_path) except Exception: logmsg.update("Can't create directory for CSV files (" + str(this_path) + ")") raise except Exception: logmsg.update("Can't open CSV file: " + str(self.file)) raise else: self.state = True
def process_windows(self, ow): """ Process dictionary and set ventilation status, then update owl web file :param ow: dictionary, open windows :return: nothing """ # if count of open windows >= number of windows, that mean ventilation if len(ow) >= self.setup.ventilate_num: self.var.ventilating = True else: self.var.ventilating = False # write second web file secweb.write("owl", ow) # write bridge value bridge.put("owl", ow) # if open window warning is on and open window(s) count < number of open windows, that mean ventilation # send warnings for these windows if not self.setup.no_oww and len(ow) < self.setup.ventilate_num: for k, v in ow.iteritems(): if self._is_win_open_too_long(k): logmsg.update("Warning condition for window " + str(k) + " met") self.send_warning("window", k, "")
def control(self): self.check_var() open_windows = self.get_open_windows() # process open windows self.process_windows(open_windows) # and now showtime # heat: 0 = disable, 1 = heat per, 2 = total, 3 = svpnmw # heat, grt, heat_valve = self.heat_or_not_room() heat, grt, heat_valve = self.heat_or_not() # increment number of readings with heat on if bool(heat): self.var.heat_readings += 1 if bool(heat) != self.var.heating: mode = bridge.try_read("mode").upper() == "AUTO" logmsg.debug("mode=", mode, ", heat=", heat, ", bool(heat)=", bool(heat)) if heat > 0: logmsg.update(self.get_heat_string(heat, heat_valve, grt), 'I') else: logmsg.update("heating stopped.", 'I') if mode: self._do_heat(bool(heat))
def check_day_table(): global table if len(table.day) > 1: for i in range(len(table.day) - 1): if time.strptime(table.day[i + 1][0], "%H:%M") <= time.strptime(table.day[i][0], "%H:%M"): logmsg.update("Day mode table is wrong! Using default table!", 'E') table.day = [["00:00", "23:59", 40, 185, "total", 120, 1]]
def save_location(location): global sw try: f = open(sw.location["loc"], "w") except IOError: logmsg.update("IOError during opening location file for writing!", 'E') f.write(str({"location": str(location)})) f.close()
def save_location(location): global sw try: f = open(sw.location["loc"], "w") except IOError: logmsg.update("IOError during opening location file for writing!", 'E') f.write(str({"location":str(location)})) f.close()
def temp_mode(): global table c_temp = int(table.sit["current_temp"]) for k in table.temp: kv = table.temp[k] if kv[1] > c_temp >= kv[0]: table.act_mode_idx = k logmsg.update("Switching temp mode to " + str(kv[0]) + " ~ " + str(kv[1]), 'I') return kv
def _mute(self, key): """ Mute warning for windows key :param key: string :return: nothing """ if key in self.eq3.windows: self.eq3.windows[key][0] = datetime.datetime.now() self.eq3.windows[key][1] = True logmsg.update("OWW for key " + str(key) + " is muted for " + str(self.setup.intervals["oww"][2]) + " seconds.")
def time_mode(): global table # day = [0-from_str, 1-to_str, 2-total or per, 3-mode ("total"/"per"), 4-check interval, 5-valves] md = is_time() # logmsg.update("Actual index=" + str(table.act_mode_idx), 'D') if md != -1: if md != table.act_mode_idx: kv = table.day[md] table.act_mode_idx = md logmsg.update("Switching day mode to " + str(md) + " = " + str(kv), 'I') return kv
def _mute(self, key): """ Mute warning for windows key :param key: string :return: nothing """ if key in self.eq3.windows: self.eq3.windows[key][0] = datetime.datetime.now() self.eq3.windows[key][1] = True logmsg.update("OWW for key " + str(key) + " is muted for " + str( self.setup.intervals["oww"][2]) + " seconds.")
def check_day_table(): """ :return: """ global table if len(table.day) > 1: for i in range(len(table.day) - 1): if time.strptime(table.day[i + 1][0], "%H:%M") <= time.strptime( table.day[i][0], "%H:%M"): logmsg.update("Day mode table is wrong! Using default table!", 'E') table.day = [["00:00", "23:59", 35, 185, "total", 120, 2]]
def _do_autoupdate(self): """ Perform autoupdate :return: nothing """ if autoupdate.do(self.setup.version): logmsg.update("thermeq3 updated.", 'I') temp_key = self.eq3.maxid["sn"] body = """<h1>Device upgrade information.</h1> <p>Hello, I'm your thermostat and I have a information for you.<br/> Please take a note, that I found new version of me and I'll be upgraded in few seconds.</br> Resistance is futile :).<br/>""" self.send_warning("upgrade", temp_key, body) self.queue_msg('R')
def send_email(eq3_setup, message): """ sends email :param eq3_setup: thermeq3.setup :param message: :return: boolean, true if success """ try: server = smtplib.SMTP(eq3_setup.mail_server, eq3_setup.mail_port) except Exception, error: logmsg.update("Error connecting to mail server " + str(eq3_setup.mail_server) + ":" + str(eq3_setup.mail_port) + ". Error code: " + str(error)) logmsg.update("Traceback: " + str(traceback.format_exc()))
def update_ignores_2sit(self): self.var.situation = weather.weather_for_woeid(self.setup.location) temp = int(self.var.situation["current_temp"]) # modify OWW tmr = weather.interval_scale(temp, (-35.0, 35.0), (0, 10), (10, 360), True) self.setup.intervals["oww"] = [tmr * 60, (3 * tmr) * 60, (3 * tmr) * 60] logmsg.update("OWW interval updated to " + str(self.setup.intervals["oww"])) # and now modify valve ignore time tmr = weather.interval_scale(temp, (0.0, 35.0), (1.7, 3.0), (15, 120), False) self.var.ignore_time = self.setup.window_ignore_time + tmr bridge.put("ign_op", self.var.ignore_time) logmsg.update("Valve ignore interval updated to " + str(self.var.ignore_time), 'D')
def send_email(eq3_setup, message): """ sends email :param eq3_setup: thermeq3.setup :param message: :return: boolean, true if success """ try: server = smtplib.SMTP(eq3_setup.mail_server, eq3_setup.mail_port) except Exception, error: logmsg.update( "Error connecting to mail server " + str(eq3_setup.mail_server) + ":" + str( eq3_setup.mail_port) + ". Error code: " + str(error)) logmsg.update("Traceback: " + str(traceback.format_exc()))
def weather_for_woeid(woeid, owm_id, owm_api_key): """ Returns weather from yahoo weather from given WOEID :param owm_id: :param owm_api_key: :param woeid: integer, yahoo weather ID :return: dictionary, {"current_temp": temp, "city": city, "humidity": humidity} """ city = "Error city" temp = None humidity = None if woeid is None or owm_id is None: logmsg.update("Wrong WOEID! Please set WOEID in config.py.", 'E') elif owm_api_key is None: logmsg.update("OWM API key not set!", 'E') else: # please change u='c' to u='f' for fahrenheit below base_url = "https://query.yahooapis.com/v1/public/yql?" yql_query = "select * from weather.forecast where woeid=" + str( woeid) + " and u='c'" yql_url = base_url + urllib.urlencode({'q': yql_query }) + "&format=json" try: result = urllib2.urlopen(yql_url).read() data = json.loads(result) except Exception, error: logmsg.update("Yahoo communication error: " + str(error), 'E') logmsg.update("Traceback: " + str(traceback.format_exc()), 'E') else:
def write(cw, message): """ Save message to file which is in secondary web directory :param message: message or string to write to file :param cw: string, selector what to save """ global sw if cw in sw.location: fn = sw.location[str(cw)] try: swf = open(fn, "w") except IOError as e: logmsg.update("Error opening file " + fn + "! I/O error({0}): {1}".format(e.errno, e.strerror)) except EnvironmentError: logmsg.update("Environment error while opening " + fn + "!", 'E') else: try: swf.write(str(message)) except EnvironmentError: logmsg.update("Environment error while writing " + fn + "!", 'E') swf.close() else: logmsg.update("Wrong target [" + str(cw) + "] for saving file!", 'E')
def weather_for_woeid(woeid, owm_id, owm_api_key): """ Returns weather from yahoo weather from given WOEID :param owm_id: :param owm_api_key: :param woeid: integer, yahoo weather ID :return: dictionary, {"current_temp": temp, "city": city, "humidity": humidity} """ city = "Error city" temp = None humidity = None if woeid is None or owm_id is None: logmsg.update("Wrong WOEID! Please set WOEID in config.py.", 'E') elif owm_api_key is None: logmsg.update("OWM API key not set!", 'E') else: # please change u='c' to u='f' for fahrenheit below base_url = "https://query.yahooapis.com/v1/public/yql?" yql_query = "select * from weather.forecast where woeid=" + str(woeid) + " and u='c'" yql_url = base_url + urllib.urlencode({'q': yql_query}) + "&format=json" try: result = urllib2.urlopen(yql_url).read() data = json.loads(result) except Exception, error: logmsg.update("Yahoo communication error: " + str(error), 'E') logmsg.update("Traceback: " + str(traceback.format_exc()), 'E') else:
def check_var(self): """ Check if variables are set correctly, reports any problem :return: nothing """ if self.setup.valve_num > len(self.eq3.valves): logmsg.update("You have only " + str(len(self.eq3.valves)) + " valves, but you want to " + str(self.setup.valve_num) + " of them be checked to turn on heating!") self.setup.valve_num = len(self.eq3.valves) if self.setup.valve_switch > 90: logmsg.update("Valve switch position over 90%!") self.setup.valve_switch = 90 if self.setup.svpnmw > 100: logmsg.update( "Single valve switch position over 100%! Please check svpnmw variable!" ) # uncomment below if you want auto correct value # self.setup.svpnmw = 100 if self.setup.valve_switch > self.setup.svpnmw: logmsg.update("svpnmw (" + str(self.setup.svpnmw) + "%) is less or equal to valve switch setup (" + str(self.setup.valve_switch) + "%)!") self.setup.svpnmw = self.setup.valve_switch
def time_mode(): """ :return: """ global table # day = [0-from_str, 1-to_str, 2-total or per, 3-mode ("total"/"per"), 4-check interval, 5-valves] md = is_time() # logmsg.update("Actual index=" + str(table.act_mode_idx), 'D') if md != -1: if md != table.act_mode_idx: kv = table.day[md] table.act_mode_idx = md logmsg.update("Switching day mode to " + str(md) + " = " + str(kv), 'I') return kv
def _literal_process(self): for k, v in bridge.cw.iteritems(): if v[2]: tmp = bridge.get(k) codeword = v[0] setvalue = v[1] try: lit_result = literal_eval(tmp) except Exception: logmsg.update("Bridge error codeword [" + str(codeword) + "] value [" + str(setvalue) + "]", 'D') else: if codeword == bridge.rcw("ht"): self.var.ht = lit_result elif codeword == bridge.rcw("ign"): self.eq3.ignored_valves = lit_result
def get_ip(self): """ Get IP and set variables :return: nothing """ tmp = public_ip.get() if tmp == 0xFF: logmsg.update("Error getting IP address from hostname, please check resolv.conf or hosts or both!", 'E') else: self.setup.myip = tmp if public_ip.is_private(tmp): logstr = "Local" else: logstr = "Public" logmsg.update(logstr + " IP address: " + self.setup.myip)
def temp_mode(): """ :return: """ global table if None not in table.sit.viewvalues(): c_temp = int(table.sit["current_temp"]) for k in range(0, len(table.temp)): kv = table.temp[k] if kv[1] > c_temp >= kv[0]: table.act_mode_idx = k logmsg.update("Switching temp mode to " + str(kv[0]) + " ~ " + str(kv[1]), 'I') return kv else: logmsg.update("Weather situation error", 'E')
def adjust_ignored_valves(self, ow): """ Adjust ignored valves, if window associated with ignored valve was opened too long, extend valve ignore time :param ow: dictionary, open windows :return: nothing """ ow_now = self.get_open_windows() if not cmp(ow_now, ow) == 0: adj_list = set(ow) ^ set(ow_now) for a in adj_list: room = self.eq3.devices[a][3] for k, v in self.eq3.devices.iteritems(): # this is heating thermostat and is in room where we want ignore all heating thermostats if v[0] == 1 and v[3] == room and k in self.eq3.ignored_valves: tmp_time = self.eq3.ignored_valves[k] self.eq3.ignored_valves.update({k: tmp_time + (1 * self.eq3.ignore_time) * 60}) logmsg.update("Valve: " + str(k) + " adjusted.")
def write(self, cw, message): """ saves txt to file which is in secondary web directory :param message: message/string to write to file :param cw: string, selector what to save """ if cw in self.location: fn = self.location[str(cw)] try: swf = open(fn, "w") except Exception: logmsg.update("Error writing to file " + fn + "!", 'E') else: swf.write(str(message)) swf.close() else: logmsg.update("Wrong target [" + str(cw) + "] for saving file!", 'E')
def get_ip(self): """ Get IP and set variables :return: nothing """ tmp = public_ip.get() if tmp == 0xFF: logmsg.update( "Error getting IP address from hostname, please check resolv.conf or hosts or both!", 'E') else: self.setup.my_ip = tmp if public_ip.is_private(tmp): log_str = "Local" else: log_str = "Public" logmsg.update(log_str + " IP address: " + self.setup.my_ip)
def temp_mode(): """ :return: """ global table if None not in table.sit.viewvalues(): c_temp = int(table.sit["current_temp"]) for k in range(0, len(table.temp)): kv = table.temp[k] if kv[1] > c_temp >= kv[0]: table.act_mode_idx = k logmsg.update( "Switching temp mode to " + str(kv[0]) + " ~ " + str(kv[1]), 'I') return kv else: logmsg.update("Weather situation error", 'E')
def close(self): try: self.handle.flush() except: pass try: os.fsync(self.handle.fileno()) except: pass try: self.handle.close() except Exception: logmsg.update("Can't close CSV file!") else: self.state = False
def import_(config_str): """ Import config_str into bridgeclient.json dictionary :param config_str: string/json :return: boolean """ global bridgeclient result = False try: tmp = json.loads(config_str) except ValueError: logmsg.update("Error during importing JSON.", 'E') else: result = True bridgeclient.json.update(tmp) return result
def save(bridgefile): global bridgeclient, cw try: f = open(bridgefile, "w") except Exception: logmsg.update("Error writing to bridgefile!", 'E') else: for k, v in cw.iteritems(): try: tmp = bridgeclient.get(v[0]) except Exception: tmp = "" if tmp == "None" or tmp is None: tmp = str(v[1]) f.write(v[0] + "=" + str(tmp) + "\r\n") f.close() return True
def send_email(m_id, message): """ sends email :param m_id: dictionary :param message: :return: """ m_server = m_id["sr"] m_port = m_id["p"] m_fromaddr = m_id["f"] m_frompwd = m_id["pw"] m_toaddr = m_id["t"] try: server = smtplib.SMTP(m_server, m_port) except Exception, error: logmsg.update( "Error connecting to mail server " + str(m_server) + ":" + str(m_port) + ". Error code: " + str(error)) logmsg.update("Traceback: " + str(traceback.format_exc()))
def _process_command(self): """ Process command from cmd bridge :return: True if quit is received """ cmd = bridge.get_cmd() if cmd == "quit": return True elif cmd[0:6] == "adjust": key = cmd[7:] if key in self.eq3.ignored_valves: del self.eq3.ignored_valves[key] elif cmd == "log_debug": logmsg.level('D') elif cmd == "log_info": logmsg.level('I') elif cmd[0:4] == "mute": self._mute(cmd[5:]) elif cmd == "rebridge": br_data = bridge.load(self.setup.bridge_file) self._literal_process(br_data) self.update_all() elif cmd == "led": if self.var.heating: self.queue_msg("H") else: self.queue_msg("S") elif cmd == "upgrade": self._do_autoupdate() # add dummy room, valve, window, for testing, format: add:o = add open window, add:c = add closed window # add:r = remove elif cmd[0:4] == "dmy:": if cmd[4:5] == 'o': dummy.add_dummy(True) elif cmd[4:5] == 'c': dummy.add_dummy(False) elif cmd[4:5] == 'r': dummy.remove_dummy() elif cmd[0:7] == "del_dev:": tmp = str(cmd[8:]) if self.eq3.delete(tmp): logmsg.update("Device " + tmp + " deleted successfully.", 'I') else: logmsg.update("Can't delete device " + tmp, 'I')
def send_error_log(eq3_setup, stderr_log): """ Send error log if any :param eq3_setup: thermeq3.setup :param stderr_log: string :return: -1 if no error log """ if not (not os.path.isfile(stderr_log) or not (os.path.getsize(stderr_log) > 0)): subject = eq3_setup.device_name + " log email (thermeq3 device)" body = ("<h1>%(a0)s status email.</h1>\n" "<p>Hello, I'm your thermostat.<br/>" " I found this error log. It's attached.<br/>") % {"a0": str(eq3_setup.device_name)} msg = compose(eq3_setup, subject, body) msg.attach(attach_file(stderr_log)) return send_email(eq3_setup, msg.as_string()) else: logmsg.update("Logfile: " + stderr_log) logmsg.update("Zero sized stderr log file, nothing'll be send") return False
def adjust_ignored_valves(self, ow): """ Adjust ignored valves, if window associated with ignored valve was opened too long, extend valve ignore time :param ow: dictionary, open windows :return: nothing """ ow_now = self.get_open_windows() if not cmp(ow_now, ow) == 0: adj_list = set(ow) ^ set(ow_now) for a in adj_list: room = self.eq3.devices[a][3] for k, v in self.eq3.devices.iteritems(): # this is heating thermostat and is in room where we want ignore all heating thermostats if v[0] == 1 and v[ 3] == room and k in self.eq3.ignored_valves: tmp_time = self.eq3.ignored_valves[k] self.eq3.ignored_valves.update( {k: tmp_time + (1 * self.eq3.ignore_time) * 60}) logmsg.update("Valve: " + str(k) + " adjusted.")
def send_error_log(m_id, stderr_log, devname): """ Send error log if any :param m_id: list :param stderr_log: string :param devname: string :return: -1 if no error log """ if os.path.isfile(stderr_log) and os.path.getsize(stderr_log) > 0: body = ("<h1>%(a0)s status email.</h1>\n" "<p>Hello, I'm your thermostat and I sending you this email with error logfile as attachment.<br/>") \ % {"a0": str(devname)} m_id.update({"s": m_id["d"] + " log email (thermeq3 device)"}) msg = compose(m_id, body) msg.attach(attach_file(stderr_log)) return send_email(m_id, msg.as_string()) else: logmsg.update("Zero sized stderr log file, nothing'll be send") return False
def silence(self, key, is_win): # # eq3.windows = key: OW_time(thisnow), is muted by user(False), warning/error count(0), multiplicand # # is there key in dict? dt = datetime.datetime.now() if key not in self.eq3.windows: # there no key, so its new warning logmsg.update("No key " + str(key) + " in windows. Key added.") if is_win: self.eq3.windows.update( {key: [self.eq3.devices[key][5], False, 0, 1]}) else: self.eq3.windows.update({key: [dt, False, 0, 1]}) return 2 else: # yes, there it is, so check if we are silent, if so exit, otherwise reset mute # threshold, send every X, muted for X # "oww": [10*60, 30*60, 45*60] # threshold, muted for X, time.time() # "wrn": [60*60, 60*60, tm] if self.eq3.windows[key][1]: # yes, we must be silent if is_win: tmp = self.eq3.windows[key][0] + datetime.timedelta( seconds=self.setup.intervals["oww"][2]) else: tmp = self.eq3.windows[key][0] + datetime.timedelta( seconds=self.setup.intervals["wrn"][1]) if tmp < dt: return 1 else: # silence is over self.eq3.windows[key][1] = False # increment warning counter for this key self.eq3.windows[key][2] += 1 if self.eq3.windows[key][2] > self.setup.abnormalCount: logmsg.update("Abnormal #warnings for device [" + str(key) + "], name [" + str(self.eq3.devices[key][2]) + "]") self.eq3.windows[key][2] = 0 return 0
def weather_for_woeid(woeid): """ Returns weather from yahoo weather from given WOEID :param woeid: integer, yahoo weather ID :return: dictonary, {"current_temp": temp, "city": city, "humidity": humidity} """ # please change u='c' to u='f' for farenheit below baseurl = "https://query.yahooapis.com/v1/public/yql?" yql_query = "select * from weather.forecast where woeid=" + str(woeid) + " and u='c'" yql_url = baseurl + urllib.urlencode({'q': yql_query}) + "&format=json" try: result = urllib2.urlopen(yql_url).read() data = json.loads(result) except Exception, error: logmsg.update("Yahoo communication error: " + str(error), 'E') logmsg.update("Traceback: " + str(traceback.format_exc()), 'E') city = "Error city" temp = 0 humidity = 50
def export_csv(self, cmd="init"): csv_file = self.setup.csv_log if cmd == "init": if os.path.exists(csv_file): os.rename(csv_file, self.setup.place + self.setup.devname + "_" + time.strftime("%Y%m%d-%H%M%S", time.localtime()) + ".csv") try: self.var.csv = open(csv_file, "a") except Exception: logmsg.update("Can't open CSV file: " + str(csv_file)) raise else: # headers here self.var.csv.write(self.eq3.headers()) self.var.csv.write("\r\n") elif cmd == "close": try: self.var.csv.close() except Exception: logmsg.update("Can't close CSV file!")
def _do_thermeq(self): """ Do what thermeq must do :return: nothing """ # save open window dictionary for adjustment ow = self.get_open_windows() eq3_result, eq3_error = self.eq3.read_data(False) if eq3_result: # set values self.get_control_values() # log messages logmsg.update(self._status_msg() + " Checking #" + str(self.setup.intervals["max"][0]) + " sec", 'I') logmsg.update(self.eq3.plain(), 'I') # do control self.control() # update JSONs self.write_strings() # set status self._set_status() # do logging self.do_device_logging() # do some valve_ignored adjustment self.adjust_ignored_valves(ow) # save bridge self.set_bridge_control_values() bridge.save(self.setup.bridge_file) else: self.var.heating = None if any("response" in e for e in eq3_error): self.queue_msg('M') else: self.queue_msg('E') # flush error to log logmsg.update("".join(str(e) + " " for e in eq3_error), 'E')
def process_msg(self): """ Process message queue :return: nothing """ cw_msg = bridge.rcw("msg") while len(self.var.msgQ) > 0: logmsg.update("Message queue=" + str(self.var.msgQ), 'D') while not str(bridge.get(cw_msg)) == "": time.sleep(self.setup.timeout) tosend = self.var.msgQ.pop() logmsg.update("Sending message [" + str(tosend) + "]", 'D') if tosend == "E": self.var.error = True self.var.err2Clear = True elif tosend == "C": self.var.err2Clear = False self.var.err2LastStatus = True logmsg.update("Clearing error LED", 'D') elif tosend == "R": bridge.save(self.setup.bridgefile) if self.setup.target == "yun": bridge.put(cw_msg, tosend) elif self.setup.target == "rpi": if tosend == "H": # action.do(True) pass elif tosend == "S": # action.do(False) pass
def send_error_log(eq3_setup, stderr_log): """ Send error log if any :param eq3_setup: thermeq3.setup :param stderr_log: string :return: -1 if no error log """ if not (not os.path.isfile(stderr_log) or not (os.path.getsize(stderr_log) > 0)): subject = eq3_setup.device_name + " log email (thermeq3 device)" body = ("<h1>%(a0)s status email.</h1>\n" "<p>Hello, I'm your thermostat.<br/>" " I found this error log. It's attached.<br/>") % { "a0": str(eq3_setup.device_name) } msg = compose(eq3_setup, subject, body) msg.attach(attach_file(stderr_log)) return send_email(eq3_setup, msg.as_string()) else: logmsg.update("Logfile: " + stderr_log) logmsg.update("Zero sized stderr log file, nothing'll be send") return False
def update_ignores_2sit(self): """ Update open window variables according to weather :return: nothing """ self.var.situation = weather.weather_for_woeid(self.setup.yahoo_location, self.setup.owm_location, self.setup.owm_api_key) if None not in self.var.situation.viewvalues(): temp = int(self.var.situation["current_temp"]) if temp is not None: # modify OWW # interval for oww 5min to 360min tmr = weather.interval_scale(temp, (-35.0, 35.0), (0, 10), (5, 360), True) self.setup.intervals["oww"] = [tmr * 60, (3 * tmr) * 60, (3 * tmr) * 60] logmsg.update("OWW interval updated to " + str(self.setup.intervals["oww"])) # and now modify valve ignore time # interval for valve ignore 20min to 120min tmr = weather.interval_scale(temp, (0.0, 35.0), (1.7, 3.0), (20, 120), False) self.eq3.ignore_time = self.setup.window_ignore_time + tmr bridge.put("ign_op", self.eq3.ignore_time) logmsg.update("Valve ignore interval updated to " + str(self.eq3.ignore_time), 'D')
def silence(self, key, is_win): # # d_w = key: OW_time(thisnow), isMuted(False), warning/error count(0) # # is there key in dict? dt = datetime.datetime.now() if key not in self.eq3.windows: # there no key, so its new warning logmsg.update("No key " + str(key) + " in windows. Key added.") if is_win: self.eq3.windows.update({key: [self.eq3.devices[key][5], False, 0]}) else: self.eq3.windows.update({key: [dt, False, 0]}) return 2 else: # yes, there it is, so check if we are silent, if so exit, otherwise reset mute # threshold, send every X, muted for X # "oww": [10*60, 30*60, 45*60] # threshold, muted for X, time.time() # "wrn": [60*60, 60*60, tm] if self.eq3.windows[key][1]: # yes, we must be silent if is_win: tmp = self.eq3.windows[key][0] + datetime.timedelta(seconds=self.setup.intervals["oww"][2]) else: tmp = self.eq3.windows[key][0] + datetime.timedelta(seconds=self.setup.intervals["wrn"][1]) if tmp < dt: return 1 else: # silence is over self.eq3.windows[key][1] = False # increment warning counter for this key self.eq3.windows[key][2] += 1 if self.eq3.windows[key][2] > self.setup.abnormalCount: logmsg.update( "Abnormal #warnings for device [" + str(key) + "], name [" + str(self.eq3.devices[key][2]) + "]") self.eq3.windows[key][2] = 0 return 0
def load(bridgefile): global bridgeclient, cw # prepare dictionary lcw = {} for k in cw.iteritems(): # key : [default, literal] lcw.update({k[1][0]: [k[1][1], k[1][2]]}) if os.path.exists(bridgefile): with open(bridgefile, "r") as f: for line in f: t = (line.rstrip("\r\n")).split('=') localcw = t[0] setvalue = t[1] if localcw in lcw: process_bridge_cw(localcw, lcw[localcw][0], setvalue) f.close() logmsg.update("Bridge file loaded.", 'D') # >>>>>>> updateAllTimes() else: for k, v in lcw.iteritems(): process_bridge_cw(k, v[0], v[0]) logmsg.update("Error loading bridge file, using defaults!", 'E')
def check_var(self): """ Check if variables are set correctly, reports any problem :return: nothing """ if self.setup.valve_num > len(self.eq3.valves): logmsg.update("You have only " + str(len(self.eq3.valves)) + " valves, but you want to " + str(self.setup.valve_num) + " of them be checked to turn on heating!") self.setup.valve_num = len(self.eq3.valves) if self.setup.valve_switch > 90: logmsg.update("Valve switch position over 90%!") self.setup.valve_switch = 90 if self.setup.svpnmw > 100: logmsg.update("Single valve switch position over 100%! Please check svpnmw variable!") # uncomment below if you want auto correct value # self.setup.svpnmw = 100 if self.setup.valve_switch > self.setup.svpnmw: logmsg.update("svpnmw (" + str(self.setup.svpnmw) + "%) is less or equal to valve switch setup (" + str(self.setup.valve_switch) + "%)!") self.setup.svpnmw = self.setup.valve_switch
def _literal_process(self, bridge_data, obj_prefix="self."): """ Process bridge string, update variables as typed in bridge.cw dictionary :param bridge_data: dictionary :param obj_prefix: string :return: nothing """ try: for k, v in bridge_data.iteritems(): if k in bridge.pcw: pcw_v = bridge.pcw[k] if pcw_v[1]: d = pcw_v[2].split(".") default = pcw_v[0] cwt = type(default) obj = obj_prefix + d[0] name = d[1] if cwt is str: value = str(v) elif cwt is int: value = int(v) elif cwt is bool: value = bool(v) elif cwt is float: value = float(v) elif cwt is dict: value = ast.literal_eval(v) else: value = v try: obj_obj = eval(obj) except SyntaxError: logmsg.update( "Error evaluating object: " + str(obj), 'E') else: try: setattr(obj_obj, name, value) # logmsg.debug("object:", obj, ", name:", name, " to:", value) except NameError: logmsg.update( "Error processing object: " + str(obj) + ", with name: " + str(name), 'E') except AttributeError: logmsg.update( 'Bridge file has attribute error. Possibly file missing.')
def load(bridge_file): """ Load data from bridge_file and return dictionary or None :param bridge_file: string :return: dictionary """ data = {} if os.path.exists(bridge_file): with open(bridge_file, "r") as f: try: data = json.load(f) except ValueError: logmsg.update("Bridge value error during loading bridge!", 'E') finally: f.close() logmsg.update("Bridge file loaded.", 'D') else: logmsg.update("Error loading bridge file, file not exist!", 'E') # load empty dict, not None # data = None data = {} return data
def save(bridge_file): """ Save bridge to bridge_file, if success return True, else False :param bridge_file: string :return: boolean """ global bridge_client try: tmp = bridge_client.getall() except ValueError: logmsg.update("Error reading bridge!", 'E') else: try: f = open(bridge_file, "w") except IOError: logmsg.update("Error writing to bridge file!", 'E') else: f.write(json.dumps(tmp, sort_keys=True)) f.close() logmsg.update("Bridge file (" + str(bridge_file) + ") saved.", 'D') return True return False
def do(version): """ Perform update :param version: string :return: boolean, True if something updated """ home_dir = "/root/thermeq3" chk, filename = check_update(version) result = False if chk == 2: # unzip files with zipfile.ZipFile(home_dir + "-install/" + filename, "r") as z: try: z.extractall(home_dir + "/") except Exception: logmsg.update("Error during archive extraction", 'E') else: logmsg.update("Archive successfully extracted.", 'I') result = True elif chk == 1: logmsg.update("Update is not necessary.") return result
def process_msg(self): """ Process message queue :return: nothing """ while len(self.var.msgQ) > 0: logmsg.update("Message queue: " + str(self.var.msgQ), 'D') wait_cycle = False if support.is_yun(): # this is waiting routine to ensure that msg queue is processed by 32u4 part tmp_br = bridge.get("msg") if not support.is_empty(tmp_br): time.sleep(self.setup.timeout) wait_cycle = True if not wait_cycle: to_send = self.var.msgQ.pop() logmsg.update("Sending message [" + str(to_send) + "]", 'D') # if we are going to reset, save bridge file if to_send == "R": bridge.save(self.setup.bridge_file) if support.is_yun(): # signal to 32u4 that we have something to process bridge.put("msg", to_send) elif support.is_rpi(): if to_send == "H": # TBI RPI # uncomment line below for RPi # action.do(True) pass elif to_send == "S": # TBI RPI # uncomment line below for RPi # action.do(False) pass elif support.is_win(): # insert windows code here # print "nt" logmsg.update("Processing message on Windows: " + str(to_send))
def _do_thermeq(self): """ Do what thermeq must do :return: nothing """ # save open window dictionary for adjustment ow = self.get_open_windows() eq3_result, eq3_error = self.eq3.read_data(False) if eq3_result: # set values self.get_control_values() # log messages logmsg.update( self._status_msg() + " Checking #" + str(self.setup.intervals["max"][0]) + " sec", 'I') logmsg.update(self.eq3.plain(), 'I') # do control self.control() # update JSONs self.write_strings() # set status self._set_status() # do logging self.do_device_logging() # do some valve_ignored adjustment self.adjust_ignored_valves(ow) # save bridge self.set_bridge_control_values() bridge.save(self.setup.bridge_file) else: self.var.heating = None if any("response" in e for e in eq3_error): self.queue_msg('M') else: self.queue_msg('E') # flush error to log logmsg.update("".join(str(e) + " " for e in eq3_error), 'E')
tmp_dir = os.path.dirname(put_file) if not os.path.exists(tmp_dir): os.mkdir(tmp_dir) try: f = file(put_file, "wb") except Exception, e: err_str = "Problem during saving new version. File: " + put_file + ". Error: " + str( e) + " Traceback: " + str(traceback.format_exc()) else: f.write(response) f.close() err_str = "" request.close() finally: if not err_str == "": logmsg.update(err_str) return False return True def check_update(version): github = "https://github.com/autopower/thermeq3/raw/master/install/current/" home_dir = "/root/thermeq3" err_str = "Unable to get latest version info - " try: request = urllib2.urlopen(github + "autoupdate.data") response = request.read().rstrip("\r\n") except urllib2.HTTPError, e: err_str += "HTTPError = " + str(e.reason) except urllib2.URLError, e:
city = data["query"]["results"]["channel"]["location"][ "city"] temp = int(data["query"]["results"]["channel"]["item"] ["condition"]["temp"]) humidity = int(data["query"]["results"]["channel"] ["atmosphere"]["humidity"]) except Exception: pass else: # and check if yahoo is correct url = "http://api.openweathermap.org/data/2.5/weather?id=" + str(owm_id) + "&appid=" + \ owm_api_key + "&units=metric" try: result = json.load(urllib2.urlopen(url)) except Exception, error: logmsg.update("OWM communication error: " + str(error), 'E') logmsg.update( "Traceback: " + str(traceback.format_exc()), 'E') else: if "main" in result: owm_temp = round(result["main"]["temp"]) yho_temp = round(temp) if abs(yho_temp - owm_temp) > 1.5: logmsg.update( "Difference between Yahoo and OWM temperatures. Yahoo=" + str(yho_temp) + " OWM=" + str(owm_temp), 'I') # end check else: logmsg.update("Error during parsing result.", 'D') logmsg.update(
def send_warning(self, selector, dev_key, body_txt): dt_now = datetime.datetime.now() try: device = self.eq3.devices[dev_key] except KeyError: device = [ 1, "KeyError", "Key Error", 99, 0, datetime.datetime(2016, 01, 01, 12, 00, 00), 18, 56, 7 ] device_name = device[2] room_name = "Error room" logmsg.update("Key error: " + str(dev_key), 'E') else: device_name = device[2] room = device[3] room_name = self.eq3.rooms[str(room)] sil = self.silence(dev_key, selector == "window") if not sil == 1: mute_str = "http://" + self.setup.my_ip + ":" + str(self.setup.ext_port) + "/data/put/command/mute:" + \ str(dev_key) msg = None if selector == "window": oww = int( (dt_now - self.eq3.windows[dev_key][0]).total_seconds()) owd = dt_now - self.eq3.devices[dev_key][5] if sil == 0 and oww < self.setup.intervals["oww"][1]: return msg = mailer.new_compose(selector, room_name[0], device_name, room_name[0], str(owd).split('.')[0], (self.setup.intervals["oww"][0] * self.eq3.windows[dev_key][3]) / 60, mute_str, self.setup.intervals["oww"][2] / 60) else: if sil == 0 and not self.isrt("wrn"): return if selector == "battery" or selector == "error": msg = mailer.new_compose( selector, device_name, device_name, room_name[0], mute_str, self.setup.intervals["wrn"][1] / 60) elif selector == "openmax" or selector == "upgrade": msg = mailer.new_compose(selector, "", body_txt) if mailer.send_email(self.setup, msg.as_string()) and selector == "window": # original code # self.eq3.windows[dev_key][0] = dt_now # dev code # extend warning period by incrementing multiplicand * oww time self.eq3.windows[dev_key][3] += 1 self.eq3.windows[dev_key][0] = dt_now + datetime.timedelta( seconds=(self.setup.intervals["oww"][0] * self.eq3.windows[dev_key][3])) logmsg.update( "Multiplicand for " + str(dev_key) + " updated to " + str(self.eq3.windows[dev_key][3]), 'D') else: logmsg.update("Warning for device " + str(dev_key) + " is muted!")
""" try: server = smtplib.SMTP(eq3_setup.mail_server, eq3_setup.mail_port) except Exception, error: logmsg.update("Error connecting to mail server " + str(eq3_setup.mail_server) + ":" + str(eq3_setup.mail_port) + ". Error code: " + str(error)) logmsg.update("Traceback: " + str(traceback.format_exc())) else: try: server.ehlo() if server.has_extn('STARTTLS'): server.starttls() server.ehlo() server.login(eq3_setup.from_addr, eq3_setup.from_pwd) tmp = str_to_list(eq3_setup.to_addr) server.sendmail(eq3_setup.from_addr, str_to_list(eq3_setup.to_addr), message) except smtplib.SMTPAuthenticationError: logmsg.update("Authentication error during sending email.") except smtplib.SMTPRecipientsRefused: logmsg.update("Recipient refused.") except Exception, error: logmsg.update("Error during sending email. Error code: " + str(error)) else: server.quit() logmsg.update("Mail was sent.") return True return False