class Loader(MySupport): def __init__(self, start=False, stop=False, hardstop=False, loglocation="/var/log/", log=None, localinit=False, ConfigFilePath=None): self.Start = start self.Stop = stop self.HardStop = hardstop if ConfigFilePath == None: self.ConfigFilePath = "/etc/" else: self.ConfigFilePath = ConfigFilePath self.ConfigFileName = "genloader.conf" # log errors in this module to a file if localinit == True: self.configfile = self.ConfigFileName else: self.configfile = self.ConfigFilePath + self.ConfigFileName self.ModulePath = os.path.dirname(os.path.realpath(__file__)) + "/" self.ConfPath = os.path.dirname(os.path.realpath(__file__)) + "/conf/" # log errors in this module to a file if log == None: self.log = SetupLogger("genloader", loglocation + "genloader.log") else: self.log = log self.console = SetupLogger("genloader_console", log_file="", stream=True) try: if self.Start: if not self.CheckSystem(): self.LogInfo("Error check system readiness. Exiting") sys.exit(2) self.CachedConfig = {} # check to see if genloader.conf is present, if not copy it from genmon directory if not os.path.isfile(self.configfile): self.LogInfo("Warning: unable to find config file: " + self.configfile + " Copying file to /etc/ directory.") if os.path.isfile(self.ConfPath + self.ConfigFileName): copyfile(self.ConfPath + self.ConfigFileName, self.configfile) else: self.LogInfo("Unable to find config file.") sys.exit(2) self.config = MyConfig(filename=self.configfile, section="genmon", log=self.log) if not self.GetConfig(): self.LogInfo("Error reading config file. Exiting") sys.exit(2) if not self.ValidateConfig(): self.LogInfo("Error validating config. Exiting") sys.exit(2) self.LoadOrder = self.GetLoadOrder() if self.Stop: self.StopModules() time.sleep(2) if self.Start: self.StartModules() except Exception as e1: self.LogErrorLine("Error in init: " + str(e1)) #--------------------------------------------------------------------------- def CheckSystem(self): # this function checks the system to see if the required libraries are # installed. If they are not then an attempt is made to install them. ModuleList = [ # [import name , install name] ['flask', 'flask'], ['configparser', 'configparser'], ['serial', 'pyserial'], ['crcmod', 'crcmod'], ['pyowm', 'pyowm'], ['pytz', 'pytz'] ] try: ErrorOccured = False for Module in ModuleList: if not self.LibraryIsInstalled(Module[0]): self.LogInfo("Warning: required library " + Module[1] + " not installed. Attempting to install....") if not self.InstallLibrary(Module[1]): self.LogInfo("Error: unable to install library " + Module[1]) ErrorOccured = True return not ErrorOccured except Exception as e1: self.LogInfo("Error in CheckSystem: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def LibraryIsInstalled(self, libraryname): try: import importlib my_module = importlib.import_module(libraryname) return True except Exception as e1: return False #--------------------------------------------------------------------------- def InstallLibrary(self, libraryname): try: process = Popen(['pip', 'install', libraryname], stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in InstallLibrary using pip : " + libraryname + " : " + str(_error)) rc = process.returncode return True except Exception as e1: self.LogInfo("Error installing module: " + libraryname + ": " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def ValidateConfig(self): ErrorOccured = False if not len(self.CachedConfig): self.LogInfo("Error: Empty configruation found.") return False for Module, Settiings in self.CachedConfig.items(): try: if self.CachedConfig[Module]["enable"]: if not os.path.isfile(self.ModulePath + self.CachedConfig[Module]["module"]): self.LogInfo("Enable to find file " + self.ModulePath + self.CachedConfig[Module]["module"]) ErrorOccured = True # validate config file and if it is not there then copy it. if not self.CachedConfig[Module]["conffile"] == None and len( self.CachedConfig[Module]["conffile"]): ConfFileList = self.CachedConfig[Module]["conffile"].split( ",") for ConfigFile in ConfFileList: ConfigFile = ConfigFile.strip() if not os.path.isfile(self.ConfigFilePath + ConfigFile): if os.path.isfile(self.ConfPath + ConfigFile): self.LogInfo("Copying " + ConfigFile + " to " + self.ConfigFilePath) copyfile(self.ConfPath + ConfigFile, self.ConfigFilePath + ConfigFile) else: self.LogInfo("Enable to find config file " + self.ConfPath + ConfigFile) ErrorOccured = True except Exception as e1: self.LogInfo("Error validating config for " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def AddEntry(self, section=None, module=None, conffile="", args="", priority='2'): try: if section == None or module == None: return self.config.WriteSection(section) self.config.WriteValue('module', module, section=section) self.config.WriteValue('enable', 'False', section=section) self.config.WriteValue('hardstop', 'False', section=section) self.config.WriteValue('conffile', conffile, section=section) self.config.WriteValue('args', args, section=section) self.config.WriteValue('priority', priority, section=section) except Exception as e1: self.LogInfo("Error in AddEntry: " + str(e1), LogLine=True) return #--------------------------------------------------------------------------- def UpdateIfNeeded(self): try: self.config.SetSection("gengpioin") if not self.config.HasOption('conffile'): self.config.WriteValue('conffile', "gengpioin.conf", section="gengpioin") self.LogError("Updated entry gengpioin.conf") else: defValue = self.config.ReadValue('conffile', default="") if not len(defValue): self.config.WriteValue('conffile', "gengpioin.conf", section="gengpioin") self.LogError("Updated entry gengpioin.conf") except Exception as e1: self.LogInfo("Error in UpdateIfNeeded: " + str(e1), LogLine=True) #--------------------------------------------------------------------------- def GetConfig(self): try: Sections = self.config.GetSections() ValidSections = [ 'genmon', 'genserv', 'gengpio', 'gengpioin', 'genlog', 'gensms', 'gensms_modem', 'genpushover', 'gensyslog', 'genmqtt', 'genslack', 'genexercise' ] for entry in ValidSections: if not entry in Sections: if entry == 'genslack': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genslack.py', conffile='genslack.conf') if entry == 'genexercise': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genexercise.py', conffile='genexercise.conf') else: self.LogError("Warning: Missing entry: " + entry) self.UpdateIfNeeded() Sections = self.config.GetSections() for SectionName in Sections: TempDict = {} self.config.SetSection(SectionName) if self.config.HasOption('module'): TempDict['module'] = self.config.ReadValue('module') else: TempDict['module'] = None if self.config.HasOption('enable'): TempDict['enable'] = self.config.ReadValue( 'enable', return_type=bool) else: TempDict['enable'] = False if self.config.HasOption('hardstop'): TempDict['hardstop'] = self.config.ReadValue( 'hardstop', return_type=bool) else: TempDict['hardstop'] = False if self.config.HasOption('conffile'): TempDict['conffile'] = self.config.ReadValue('conffile') else: TempDict['conffile'] = None if self.config.HasOption('args'): TempDict['args'] = self.config.ReadValue('args') else: TempDict['args'] = None if self.config.HasOption('priority'): TempDict['priority'] = self.config.ReadValue( 'priority', return_type=int, default=None) else: TempDict['priority'] = None if self.config.HasOption('postloaddelay'): TempDict['postloaddelay'] = self.config.ReadValue( 'postloaddelay', return_type=int, default=0) else: TempDict['postloaddelay'] = 0 self.CachedConfig[SectionName] = TempDict return True except Exception as e1: self.LogInfo("Error parsing config file: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def ConvertToInt(self, value, default=None): try: return int(str(value)) except: return default #--------------------------------------------------------------------------- def GetLoadOrder(self): LoadOrder = [] LoadDict = {} try: for Module, Settiings in self.CachedConfig.items(): # get the load order of all modules, even if they are disabled # since we need to stop all modules (even disabled ones) if the # conf file changed try: if self.CachedConfig[Module]["priority"] == None: LoadDict[Module] = 99 elif self.CachedConfig[Module]["priority"] >= 0: LoadDict[Module] = self.CachedConfig[Module][ "priority"] else: LoadDict[Module] = 99 except Exception as e1: self.LogInfo("Error reading load order (retrying): " + str(e1), LogLine=True) #lambda kv: (-kv[1], kv[0]) for key, value in sorted(LoadDict.items(), key=lambda kv: (-kv[1], kv[0])): #for key, value in sorted(LoadDict.iteritems(), key=lambda (k,v): (v,k)): LoadOrder.append(key) except Exception as e1: self.LogInfo("Error reading load order: " + str(e1), LogLine=True) return LoadOrder #--------------------------------------------------------------------------- def StopModules(self): self.LogConsole("Stopping....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to stop.") return False ErrorOccured = False for Module in self.LoadOrder: try: if not self.UnloadModule( self.CachedConfig[Module]["module"], HardStop=self.CachedConfig[Module]["hardstop"]): self.LogInfo("Error stopping " + Module) ErrorOccured = True except Exception as e1: self.LogInfo("Error stopping module " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def StartModules(self): self.LogConsole("Starting....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to start.") return False ErrorOccured = False for Module in reversed(self.LoadOrder): try: if self.CachedConfig[Module]["enable"]: if not self.LoadModule( self.ModulePath + self.CachedConfig[Module]["module"], args=self.CachedConfig[Module]["args"]): self.LogInfo("Error starting " + Module) ErrorOccured = True if not self.CachedConfig[Module][ "postloaddelay"] == None and self.CachedConfig[ Module]["postloaddelay"] > 0: time.sleep(self.CachedConfig[Module]["postloaddelay"]) except Exception as e1: self.LogInfo("Error starting module " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def LoadModuleAlt(self, modulename, args=None): try: self.LogConsole("Starting " + modulename) # to load as a background process we just use os.system since Popen # is problematic in doing this CommandString = sys.executable + " " + modulename if args != None and len(args): CommandString += " " + args CommandString += " &" os.system(CommandString) return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def LoadModule(self, modulename, args=None): try: self.LogConsole("Starting " + modulename) try: from subprocess import DEVNULL # py3k except ImportError: import os DEVNULL = open(os.devnull, 'wb') if not len(args): args = None if "genserv.py" in modulename: OutputStream = DEVNULL else: OutputStream = subprocess.PIPE if args == None: # close_fds=True pid = subprocess.Popen([sys.executable, modulename], stdout=OutputStream, stderr=OutputStream, stdin=OutputStream) else: pid = subprocess.Popen([sys.executable, modulename, args], stdout=OutputStream, stderr=OutputStream, stdin=OutputStream) return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def UnloadModule(self, modulename, HardStop=False): try: self.LogConsole("Stopping " + modulename) LoadInfo = [] LoadInfo.append('pkill') if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append('-u') LoadInfo.append('root') LoadInfo.append('-f') LoadInfo.append(modulename) process = Popen(LoadInfo, stdout=PIPE) output, _error = process.communicate() rc = process.returncode return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False
class MyMail(MySupport): def __init__(self, monitor=False, incoming_folder=None, processed_folder=None, incoming_callback=None, localinit=False, loglocation=ProgramDefaults.LogPath, ConfigFilePath="/etc/", log=None, start=True): self.Monitor = monitor # true if we receive IMAP email self.IncomingFolder = incoming_folder # folder to look for incoming email self.ProcessedFolder = processed_folder # folder to move mail to once processed self.IncomingCallback = incoming_callback # called back with mail subject as a parameter if ConfigFilePath == None or ConfigFilePath == "": self.ConfigFilePath = "/etc/" else: self.ConfigFilePath = ConfigFilePath self.Mailbox = 0 self.EmailSendQueue = [] # queue for email to send self.DisableEmail = False self.DisableIMAP = False self.DisableSNMP = False self.DisableSmtpAuth = False self.SSLEnabled = False self.TLSDisable = False self.UseBCC = False self.ExtendWait = 0 self.Threads = {} # Dict of mythread objects self.debug = False self.ModulePath = os.path.dirname( os.path.dirname(os.path.realpath(__file__))) # log errors in this module to a file if localinit == True: self.logfile = "mymail.log" self.configfile = "mymail.conf" else: self.logfile = os.path.join(loglocation, "mymail.log") self.configfile = os.path.join(self.ConfigFilePath, "mymail.conf") if log == None: self.log = SetupLogger("mymail", self.logfile) else: self.log = log # if mymail.conf is present attempt to copy it from the # main source directory if not os.path.isfile(self.configfile): if os.path.join(os.path.isfile(self.ModulePath, "mymail.conf")): copyfile(os.path.join(self.ModulePath, "mymail.conf"), self.configfile) else: self.LogError("Missing config file : " + self.configfile) sys.exit(1) self.config = MyConfig(filename=self.configfile, section="MyMail", log=self.log) self.GetConfig() if self.DisableEmail: self.DisableIMAP = True self.DisableSNMP = True self.Monitor = False if not len(self.SMTPServer): self.DisableSNMP = True if not len(self.IMAPServer): self.DisableIMAP = True self.Monitor = False atexit.register(self.Close) if not self.DisableEmail: if not self.DisableSMTP and self.SMTPServer != "": self.Threads["SendMailThread"] = MyThread( self.SendMailThread, Name="SendMailThread", start=start) else: self.LogError("SMTP disabled") if not self.DisableIMAP and self.Monitor and self.IMAPServer != "": # if True then we will have an IMAP monitor thread if incoming_callback and incoming_folder and processed_folder: self.Threads["EmailCommandThread"] = MyThread( self.EmailCommandThread, Name="EmailCommandThread", start=start) else: self.FatalError( "ERROR: incoming_callback, incoming_folder and processed_folder are required if receive IMAP is used" ) else: self.LogError("IMAP disabled") #---------- MyMail.TestSendSettings ---------------------------------------- @staticmethod def TestSendSettings(smtp_server=None, smtp_port=587, email_account=None, sender_account=None, sender_name=None, recipient=None, password=None, use_ssl=False, tls_disable=False, smtpauth_disable=False): if smtp_server == None or not len(smtp_server): return "Error: Invalid SMTP server" if not isinstance(smtpauth_disable, bool) and (email_account == None or not len(email_account)): return "Error: Invalid email account" if sender_account == None or not len(sender_account): sender_account = email_account if recipient == None or not len(recipient): return "Error: Invalid email recipient" if password == None or not len(password): password = "" if smtp_port == None or not isinstance(smtp_port, int): return "Error: Invalid SMTP port" if use_ssl == None or not isinstance(use_ssl, bool): return "Error: Invalid Use SSL value" if tls_disable == None or not isinstance(tls_disable, bool): return "Error: Invalid TLS Disable value" # update date dtstamp = datetime.datetime.now().strftime('%a %d-%b-%Y') # update time tmstamp = datetime.datetime.now().strftime('%I:%M:%S %p') msg = MIMEMultipart() if sender_name == None or not len(sender_name): msg['From'] = "<" + sender_account + ">" else: msg['From'] = sender_name + " <" + sender_account + ">" try: recipientList = recipient.strip().split(",") recipientList = map(lambda x: x.strip(), recipientList) recipienttemp = ">,<" recipienttemp = recipienttemp.join(recipientList) recipient = "<" + recipienttemp + ">" except Exceptin as e1: pass msg['To'] = recipient msg['Date'] = formatdate(localtime=True) msg['Subject'] = "Genmon Test Email" msgstr = '\n' + 'Test email from genmon\n' body = '\n' + 'Time: ' + tmstamp + '\n' + 'Date: ' + dtstamp + '\n' + msgstr msg.attach(MIMEText(body, 'plain', 'us-ascii')) try: if use_ssl: session = smtplib.SMTP_SSL(smtp_server, smtp_port) session.ehlo() else: session = smtplib.SMTP(smtp_server, smtp_port) if not tls_disable: session.starttls() session.ehlo # this allows support for simple TLS except Exception as e1: #self.LogErrorLine("Error Test SMTP : SSL:<" + str(use_ssl) + ">: " + str(e1)) return "Error Initializing SMTP library: " + str(e1) try: if password != "" and not smtpauth_disable: session.login(str(email_account), str(password)) if "," in recipient: multiple_recipients = recipient.split(",") session.sendmail(sender_account, multiple_recipients, msg.as_string()) else: session.sendmail(sender_account, recipient, msg.as_string()) except Exception as e1: #self.LogErrorLine("Error SMTP sendmail: " + str(e1)) session.quit() return "Error sending email: " + str(e1) session.quit() return "Success" #---------- MyMail.GetConfig ----------------------------------------------- def GetConfig(self, reload=False): try: if self.config.HasOption('disableemail'): self.DisableEmail = self.config.ReadValue('disableemail', return_type=bool) else: self.DisableEmail = False if self.config.HasOption('disablesmtp'): self.DisableSMTP = self.config.ReadValue('disablesmtp', return_type=bool) else: self.DisableSMTP = False if self.config.HasOption('smtpauth_disable'): self.DisableSmtpAuth = self.config.ReadValue( 'smtpauth_disable', return_type=bool) else: self.DisableSmtpAuth = False if self.config.HasOption('disableimap'): self.DisableIMAP = self.config.ReadValue('disableimap', return_type=bool) else: self.DisableIMAP = False if self.config.HasOption('usebcc'): self.UseBCC = self.config.ReadValue('usebcc', return_type=bool) if self.config.HasOption('extend_wait'): self.ExtendWait = self.config.ReadValue('extend_wait', return_type=int, default=0) self.debug = self.config.ReadValue('debug', return_type=bool, default=False) self.EmailPassword = self.config.ReadValue('email_pw', default="") self.EmailPassword = self.EmailPassword.strip() self.EmailAccount = self.config.ReadValue('email_account') if self.config.HasOption('sender_account'): self.SenderAccount = self.config.ReadValue('sender_account') self.SenderAccount = self.SenderAccount.strip() if not len(self.SenderAccount): self.SenderAccount = self.EmailAccount else: self.SenderAccount = self.EmailAccount self.SenderName = self.config.ReadValue('sender_name', default=None) # SMTP Recepients self.EmailRecipient = self.config.ReadValue('email_recipient') self.EmailRecipientByType = {} for type in ["outage", "error", "warn", "info"]: tempList = [] for email in self.EmailRecipient.split(','): if self.config.HasOption(email): if type in self.config.ReadValue(email).split(','): tempList.append(email) else: tempList.append(email) self.EmailRecipientByType[type] = ",".join(tempList) # SMTP Server if self.config.HasOption('smtp_server'): self.SMTPServer = self.config.ReadValue('smtp_server') self.SMTPServer = self.SMTPServer.strip() else: self.SMTPServer = "" # IMAP Server if self.config.HasOption('imap_server'): self.IMAPServer = self.config.ReadValue('imap_server') self.IMAPServer = self.IMAPServer.strip() else: self.IMAPServer = "" self.SMTPPort = self.config.ReadValue('smtp_port', return_type=int) if self.config.HasOption('ssl_enabled'): self.SSLEnabled = self.config.ReadValue('ssl_enabled', return_type=bool) self.TLSDisable = self.config.ReadValue('tls_disable', return_type=bool, default=False) except Exception as e1: self.LogErrorLine("ERROR: Unable to read config file : " + str(e1)) sys.exit(1) return True #---------- MyMail.Close --------------------------------------------------- def Close(self): try: if not self.DisableEmail: if self.SMTPServer != "" and not self.DisableSMTP: try: self.Threads["SendMailThread"].Stop() except: pass if not self.DisableEmail: if self.Monitor and self.IMAPServer != "" and not self.DisableIMAP: if self.IncomingCallback != None and self.IncomingFolder != None and self.ProcessedFolder != None: try: self.Threads["EmailCommandThread"].Stop() except: pass if self.Monitor: if self.Mailbox: try: self.Mailbox.close() self.Mailbox.logout() except: pass except Exception as e1: self.LogErrorLine("Error Closing Mail: " + str(e1)) #---------- MyMail.EmailCommandThread -------------------------------------- def EmailCommandThread(self): while True: # start email command thread try: self.Mailbox = imaplib.IMAP4_SSL(self.IMAPServer) if self.debug: self.Mailbox.Debug = 4 except Exception: self.LogError("No Internet Connection! ") if self.WaitForExit("EmailCommandThread", 120): return # exit thread continue try: if not self.DisableSmtpAuth: data = self.Mailbox.login(self.EmailAccount, self.EmailPassword) except Exception: self.LogError("LOGIN FAILED!!! ") if self.WaitForExit("EmailCommandThread", 60): return # exit thread continue while True: try: rv, data = self.Mailbox.select(self.IncomingFolder) if rv != 'OK': self.LogError("Error selecting mail folder! (select)") if self.WaitForExit("EmailCommandThread", 15): return continue rv, data = self.Mailbox.search(None, "ALL") if rv != 'OK': self.LogError("No messages found! (search)") if self.WaitForExit("EmailCommandThread", 15): return continue for num in data[0].split(): rv, data = self.Mailbox.fetch(num, '(RFC822)') if rv != 'OK': self.LogError("ERROR getting message (fetch): " + str(num)) if self.WaitForExit("EmailCommandThread", 15): return continue if sys.version_info[0] < 3: #PYTHON 2 msg = email.message_from_string(data[0][1]) else: #PYTHON 3 msg = email.message_from_bytes(data[0][1]) decode_val = email.header.decode_header( msg['Subject'])[0] if sys.version_info[0] < 3: #PYTHON 2 subject = unicode(decode_val[0]) subject = subject.decode('utf-8') else: #PYTHON 3 subject = decode_val[0] self.IncomingCallback(subject) # move the message to processed folder result = self.Mailbox.store( num, '+X-GM-LABELS', self.ProcessedFolder) #add the label self.Mailbox.store( num, '+FLAGS', '\\Deleted' ) # this is needed to remove the original label if self.WaitForExit("EmailCommandThread", 15): return except Exception as e1: self.LogErrorLine("Resetting email thread : " + str(e1)) if self.WaitForExit("EmailCommandThread", 60): # 60 sec return break if self.WaitForExit("EmailCommandThread", 15): # 15 sec return ## end of outer loop #------------ MyMail.sendEmailDirectMIME ----------------------------------- # send email, bypass queue def sendEmailDirectMIME(self, msgtype, subjectstr, msgstr, recipient=None, files=None, deletefile=False): if recipient == None: recipient = self.EmailRecipientByType[msgtype] # update date dtstamp = datetime.datetime.now().strftime('%a %d-%b-%Y') # update time tmstamp = datetime.datetime.now().strftime('%I:%M:%S %p') msg = MIMEMultipart() if self.SenderName == None or not len(self.SenderName): msg['From'] = "<" + self.SenderAccount + ">" else: msg['From'] = self.SenderName + " <" + self.SenderAccount + ">" self.LogError(msg['From']) try: recipientList = recipient.strip().split(",") recipientList = map(lambda x: x.strip(), recipientList) recipienttemp = ">,<" recipienttemp = recipienttemp.join(recipientList) recipient = "<" + recipienttemp + ">" except Exception as e1: self.LogErrorLine("Error parsing recipient format: " + str(e1)) if self.UseBCC: msg['Bcc'] = recipient else: msg['To'] = recipient msg['Date'] = formatdate(localtime=True) msg['Subject'] = subjectstr body = '\n' + 'Time: ' + tmstamp + '\n' + 'Date: ' + dtstamp + '\n' + msgstr msg.attach(MIMEText(body, 'plain', 'us-ascii')) # if the files are not found then we skip them but still send the email try: for f in files or []: with open(f, "rb") as fil: part = MIMEApplication(fil.read(), Name=basename(f)) part[ 'Content-Disposition'] = 'attachment; filename="%s"' % basename( f) msg.attach(part) if deletefile: os.remove(f) except Exception as e1: self.LogErrorLine("Error attaching file in sendEmailDirectMIME: " + str(e1)) #self.LogError("Logging in: SMTP Server <"+self.SMTPServer+">:Port <"+str(self.SMTPPort) + ">") try: if self.SSLEnabled: session = smtplib.SMTP_SSL(self.SMTPServer, self.SMTPPort) session.ehlo() else: session = smtplib.SMTP(self.SMTPServer, self.SMTPPort) if not self.TLSDisable: session.starttls() session.ehlo try: if self.debug: pass #session.set_debuglevel(1) # for some reason login fails when this enabled except Exception as e1: self.LogErrorLine("Error setting debug level: " + str(e1)) # this allows support for simple TLS except Exception as e1: self.LogErrorLine("Error SMTP Init : SSL:<" + str(self.SSLEnabled) + ">: " + str(e1)) return False try: if self.EmailPassword != "" and not self.DisableSmtpAuth: session.login(str(self.EmailAccount), str(self.EmailPassword)) if "," in recipient: multiple_recipients = recipient.split(",") session.sendmail(self.SenderAccount, multiple_recipients, msg.as_string()) else: session.sendmail(self.SenderAccount, recipient, msg.as_string()) except Exception as e1: self.LogErrorLine("Error SMTP sendmail: " + str(e1)) session.quit() return False session.quit() return True # end sendEmailDirectMIME() #------------MyMail::SendMailThread----------------------------------------- def SendMailThread(self): # once sendMail is called email messages are queued and then sent from this thread time.sleep(0.1) while True: while self.EmailSendQueue != []: MailError = False EmailItems = self.EmailSendQueue.pop() try: if not (self.sendEmailDirectMIME( EmailItems[0], EmailItems[1], EmailItems[2], EmailItems[3], EmailItems[4], EmailItems[5])): self.LogError( "Error in SendMailThread, sendEmailDirectMIME failed, retrying" ) MailError = True except Exception as e1: # put the time back at the end of the queue self.LogErrorLine( "Error in SendMailThread, retrying (2): " + str(e1)) MailError = True if MailError: self.EmailSendQueue.insert(len(self.EmailSendQueue), EmailItems) # sleep for 2 min and try again if self.WaitForExit("SendMailThread", 120 + self.ExtendWait): return if self.WaitForExit("SendMailThread", 2): return #------------MyMail::sendEmail---------------------------------------------- # msg type must be one of "outage", "error", "warn", "info" def sendEmail(self, subjectstr, msgstr, recipient=None, files=None, deletefile=False, msgtype="error"): if not self.DisableEmail: # if all email disabled, do not queue if self.SMTPServer != "" and not self.DisableSMTP: # if only sending is disabled, do not queue self.EmailSendQueue.insert(0, [ msgtype, subjectstr, msgstr, recipient, files, deletefile ])
class Loader(MySupport): def __init__(self, start = False, stop = False, hardstop = False, loglocation = ProgramDefaults.LogPath, log = None, localinit = False, ConfigFilePath = ProgramDefaults.ConfPath): self.Start = start self.Stop = stop self.HardStop = hardstop self.ConfigFilePath = ConfigFilePath self.ConfigFileName = "genloader.conf" # log errors in this module to a file if localinit == True: self.configfile = self.ConfigFileName else: self.configfile = self.ConfigFilePath + self.ConfigFileName self.ModulePath = os.path.dirname(os.path.realpath(__file__)) + "/" self.ConfPath = os.path.dirname(os.path.realpath(__file__)) + "/conf/" # log errors in this module to a file if log == None: self.log = SetupLogger("genloader", loglocation + "genloader.log") else: self.log = log self.console = SetupLogger("genloader_console", log_file = "", stream = True) try: if self.Start: if not self.CheckSystem(): self.LogInfo("Error check system readiness. Exiting") sys.exit(2) self.CachedConfig = {} if not os.path.isdir(self.ConfigFilePath): try: os.mkdir(self.ConfigFilePath) except Exception as e1: self.LogInfo("Error creating target config directory: " + str(e1), LogLine = True) # check to see if genloader.conf is present, if not copy it from genmon directory if not os.path.isfile(self.configfile): self.LogInfo("Warning: unable to find config file: " + self.configfile + " Copying file to " + self.ConfigFilePath + " directory.") if os.path.isfile(self.ConfPath + self.ConfigFileName): copyfile(self.ConfPath + self.ConfigFileName , self.configfile) else: self.LogInfo("Unable to find config file.") sys.exit(2) self.config = MyConfig(filename = self.configfile, section = "genmon", log = self.log) if not self.GetConfig(): self.LogInfo("Error reading config file. Exiting") sys.exit(2) if not self.ValidateConfig(): self.LogInfo("Error validating config. Exiting") sys.exit(2) self.LoadOrder = self.GetLoadOrder() if self.Stop: self.StopModules() time.sleep(2) if self.Start: self.StartModules() except Exception as e1: self.LogErrorLine("Error in init: " + str(e1)) #--------------------------------------------------------------------------- def CheckSystem(self): # this function checks the system to see if the required libraries are # installed. If they are not then an attempt is made to install them. ModuleList = [ # [import name , install name] ['flask','flask'], ['configparser','configparser'], ['serial','pyserial'], ['crcmod','crcmod'], ['pyowm','pyowm'], ['pytz','pytz'], ['pysnmp','pysnmp'] ] try: ErrorOccured = False for Module in ModuleList: if not self.LibraryIsInstalled(Module[0]): self.LogInfo("Warning: required library " + Module[1] + " not installed. Attempting to install....") if not self.InstallLibrary(Module[1]): self.LogInfo("Error: unable to install library " + Module[1]) ErrorOccured = True return not ErrorOccured except Exception as e1: self.LogInfo("Error in CheckSystem: " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- @staticmethod def OneTimeMaint(ConfigFilePath, log): FileList = { "feedback.json" : os.path.dirname(os.path.realpath(__file__)) + "/", "outage.txt" : os.path.dirname(os.path.realpath(__file__)) + "/", "kwlog.txt" : os.path.dirname(os.path.realpath(__file__)) + "/", "maintlog.json" : os.path.dirname(os.path.realpath(__file__)) + "/", "Feedback_dat" : os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/", "Message_dat" : os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/", 'genmon.conf' : "/etc/", 'genserv.conf': "/etc/", 'gengpio.conf' : "/etc/", 'gengpioin.conf': "/etc/", 'genlog.conf' : "/etc/", 'gensms.conf' : "/etc/", 'gensms_modem.conf': "/etc/", 'genpushover.conf': "/etc/", 'gensyslog.conf' : "/etc/", 'genmqtt.conf' : "/etc/", 'genslack.conf': "/etc/", 'genexercise.conf' : "/etc/", 'genemail2sms.conf' : "/etc/", 'genloader.conf' : "/etc/", 'mymail.conf' : "/etc/", 'mymodem.conf' : "/etc/" } try: # Check to see if we have done this already by checking files in the genmon source directory if (not os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/Message_dat") and not os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + "/maintlog.json") and not os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + "/outage.txt") and not os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + "/kwlog.txt") and not os.path.isfile("/etc/genmon.conf")): return False # validate target directory if not os.path.isdir(ConfigFilePath): try: os.mkdir(ConfigFilePath) if not os.access(ConfigFilePath + File, os.R_OK): pass except Exception as e1: log.error("Error validating target directory: " + str(e1), LogLine = True) # move files for File, Path in FileList.items(): try: SourceFile = Path + File if os.path.isfile(SourceFile): log.error("Moving " + SourceFile + " to " + ConfigFilePath ) if not MySupport.CopyFile(SourceFile, ConfigFilePath + File, move = True, log = log): log.error("Error: using alternate move method") move(SourceFile , ConfigFilePath + File) if not os.access(ConfigFilePath + File, os.R_OK): pass except Exception as e1: log.error("Error moving " + SourceFile) except Exception as e1: log.error("Error moving files: " + str(e1), LogLine = True) return True #--------------------------------------------------------------------------- def LibraryIsInstalled(self, libraryname): try: import importlib my_module = importlib.import_module(libraryname) return True except Exception as e1: return False #--------------------------------------------------------------------------- def InstallLibrary(self, libraryname): try: process = Popen(['pip','install', libraryname], stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in InstallLibrary using pip : " + libraryname + " : " + str(_error)) rc = process.returncode return True except Exception as e1: self.LogInfo("Error installing module: " + libraryname + ": "+ str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def ValidateConfig(self): ErrorOccured = False if not len(self.CachedConfig): self.LogInfo("Error: Empty configruation found.") return False for Module, Settiings in self.CachedConfig.items(): try: if self.CachedConfig[Module]["enable"]: if not os.path.isfile(self.ModulePath + self.CachedConfig[Module]["module"]): self.LogInfo("Enable to find file " + self.ModulePath + self.CachedConfig[Module]["module"]) ErrorOccured = True # validate config file and if it is not there then copy it. if not self.CachedConfig[Module]["conffile"] == None and len(self.CachedConfig[Module]["conffile"]): ConfFileList = self.CachedConfig[Module]["conffile"].split(",") for ConfigFile in ConfFileList: ConfigFile = ConfigFile.strip() if not os.path.isfile(self.ConfigFilePath + ConfigFile): if os.path.isfile(self.ConfPath + ConfigFile): self.LogInfo("Copying " + ConfigFile + " to " + self.ConfigFilePath ) copyfile(self.ConfPath + ConfigFile , self.ConfigFilePath + ConfigFile) else: self.LogInfo("Enable to find config file " + self.ConfPath + ConfigFile) ErrorOccured = True except Exception as e1: self.LogInfo("Error validating config for " + Module + " : " + str(e1), LogLine = True) return False return not ErrorOccured #--------------------------------------------------------------------------- def AddEntry(self, section = None, module = None, conffile = "", args = "", priority = '2'): try: if section == None or module == None: return self.config.WriteSection(section) self.config.WriteValue('module', module, section = section) self.config.WriteValue('enable', 'False', section = section) self.config.WriteValue('hardstop', 'False', section = section) self.config.WriteValue('conffile', conffile, section = section) self.config.WriteValue('args', args, section = section) self.config.WriteValue('priority', priority, section = section) except Exception as e1: self.LogInfo("Error in AddEntry: " + str(e1), LogLine = True) return #--------------------------------------------------------------------------- def UpdateIfNeeded(self): try: self.config.SetSection("gengpioin") if not self.config.HasOption('conffile'): self.config.WriteValue('conffile', "gengpioin.conf", section = "gengpioin") self.LogError("Updated entry gengpioin.conf") else: defValue = self.config.ReadValue('conffile', default = "") if not len(defValue): self.config.WriteValue('conffile', "gengpioin.conf", section = "gengpioin") self.LogError("Updated entry gengpioin.conf") except Exception as e1: self.LogInfo("Error in UpdateIfNeeded: " + str(e1), LogLine = True) #--------------------------------------------------------------------------- def GetConfig(self): try: Sections = self.config.GetSections() ValidSections = ['genmon', 'genserv', 'gengpio', 'gengpioin', 'genlog', 'gensms', 'gensms_modem', 'genpushover', 'gensyslog', 'genmqtt', 'genslack', 'genexercise', 'genemail2sms', 'gentankutil', 'genalexa', 'gensnmp'] for entry in ValidSections: if not entry in Sections: if entry == 'genslack': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'genslack.py', conffile = 'genslack.conf') if entry == 'genexercise': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'genexercise.py', conffile = 'genexercise.conf') if entry == 'genemail2sms': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'genemail2sms.py', conffile = 'genemail2sms.conf') if entry == 'gentankutil': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'gentankutil.py', conffile = 'gentankutil.conf') if entry == 'genalexa': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'genalexa.py', conffile = 'genalexa.conf') if entry == 'gensnmp': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section = entry, module = 'gensnmp.py', conffile = 'gensnmp.conf') else: self.LogError("Warning: Missing entry: " + entry) self.UpdateIfNeeded() Sections = self.config.GetSections() for SectionName in Sections: TempDict = {} self.config.SetSection(SectionName) if self.config.HasOption('module'): TempDict['module'] = self.config.ReadValue('module') else: TempDict['module'] = None if self.config.HasOption('enable'): TempDict['enable'] = self.config.ReadValue('enable', return_type = bool) else: TempDict['enable'] = False if self.config.HasOption('hardstop'): TempDict['hardstop'] = self.config.ReadValue('hardstop', return_type = bool) else: TempDict['hardstop'] = False if self.config.HasOption('conffile'): TempDict['conffile'] = self.config.ReadValue('conffile') else: TempDict['conffile'] = None if self.config.HasOption('args'): TempDict['args'] = self.config.ReadValue('args') else: TempDict['args'] = None if self.config.HasOption('priority'): TempDict['priority'] = self.config.ReadValue('priority', return_type = int, default = None) else: TempDict['priority'] = None if self.config.HasOption('postloaddelay'): TempDict['postloaddelay'] = self.config.ReadValue('postloaddelay', return_type = int, default = 0) else: TempDict['postloaddelay'] = 0 if self.config.HasOption('pid'): TempDict['pid'] = self.config.ReadValue('pid', return_type = int, default = 0, NoLog = True) else: TempDict['pid'] = 0 self.CachedConfig[SectionName] = TempDict return True except Exception as e1: self.LogInfo("Error parsing config file: " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def ConvertToInt(self, value, default = None): try: return int(str(value)) except: return default #--------------------------------------------------------------------------- def GetLoadOrder(self): LoadOrder = [] LoadDict = {} try: for Module, Settiings in self.CachedConfig.items(): # get the load order of all modules, even if they are disabled # since we need to stop all modules (even disabled ones) if the # conf file changed try: if self.CachedConfig[Module]["priority"] == None: LoadDict[Module] = 99 elif self.CachedConfig[Module]["priority"] >= 0: LoadDict[Module] = self.CachedConfig[Module]["priority"] else: LoadDict[Module] = 99 except Exception as e1: self.LogInfo("Error reading load order (retrying): " + str(e1), LogLine = True) #lambda kv: (-kv[1], kv[0]) for key, value in sorted(LoadDict.items(), key=lambda kv: (-kv[1], kv[0])): #for key, value in sorted(LoadDict.iteritems(), key=lambda (k,v): (v,k)): LoadOrder.append(key) except Exception as e1: self.LogInfo("Error reading load order: " + str(e1), LogLine = True) return LoadOrder #--------------------------------------------------------------------------- def StopModules(self): self.LogConsole("Stopping....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to stop.") return False ErrorOccured = False for Module in self.LoadOrder: try: if not self.UnloadModule(self.ModulePath, self.CachedConfig[Module]["module"], pid = self.CachedConfig[Module]["pid"],HardStop = self.CachedConfig[Module]["hardstop"], UsePID = True): self.LogInfo("Error stopping " + Module) ErrorOccured = True except Exception as e1: self.LogInfo("Error stopping module " + Module + " : " + str(e1), LogLine = True) return False return not ErrorOccured #--------------------------------------------------------------------------- def StartModules(self): self.LogConsole("Starting....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to start.") return False ErrorOccured = False for Module in reversed(self.LoadOrder): try: if self.CachedConfig[Module]["enable"]: if not self.LoadModule(self.ModulePath, self.CachedConfig[Module]["module"], args = self.CachedConfig[Module]["args"]): self.LogInfo("Error starting " + Module) ErrorOccured = True if not self.CachedConfig[Module]["postloaddelay"] == None and self.CachedConfig[Module]["postloaddelay"] > 0: time.sleep(self.CachedConfig[Module]["postloaddelay"]) except Exception as e1: self.LogInfo("Error starting module " + Module + " : " + str(e1), LogLine = True) return False return not ErrorOccured #--------------------------------------------------------------------------- def LoadModuleAlt(self, modulename, args = None): try: self.LogConsole("Starting " + modulename) # to load as a background process we just use os.system since Popen # is problematic in doing this CommandString = sys.executable + " " + modulename if args != None and len(args): CommandString += " " + args CommandString += " &" os.system(CommandString) return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def LoadModule(self, path, modulename, args = None): try: fullmodulename = path + modulename if args != None: self.LogConsole("Starting " + fullmodulename + " " + args) else: self.LogConsole("Starting " + fullmodulename) try: from subprocess import DEVNULL # py3k except ImportError: import os DEVNULL = open(os.devnull, 'wb') if not len(args): args = None if "genserv.py" in modulename: OutputStream = DEVNULL else: OutputStream = subprocess.PIPE executelist = [sys.executable, fullmodulename] if args != None: executelist.extend(args.split(" ")) # This will make all the programs use the same config files executelist.extend(["-c", self.ConfigFilePath]) # close_fds=True pid = subprocess.Popen(executelist, stdout=OutputStream, stderr=OutputStream, stdin=OutputStream) self.UpdatePID(modulename, pid.pid) return True except Exception as e1: self.LogInfo("Error loading module " + modulename + ": " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def UnloadModule(self, path, modulename, pid = None, HardStop = False, UsePID = False): try: LoadInfo = [] if UsePID: if pid == None or pid == "" or pid == 0: return True LoadInfo.append("kill") if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append(str(pid)) else: LoadInfo.append('pkill') if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append('-u') LoadInfo.append('root') LoadInfo.append('-f') LoadInfo.append(modulename) self.LogConsole("Stopping " + modulename) process = Popen(LoadInfo, stdout=PIPE) output, _error = process.communicate() rc = process.returncode self.UpdatePID(modulename, "") return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine = True) return False #--------------------------------------------------------------------------- def UpdatePID(self, modulename, pid = None): try: filename = os.path.splitext(modulename)[0] # remove extension self.config.SetSection(filename) self.config.WriteValue("pid", str(pid)) except Exception as e1: self.LogInfo("Error writing PID for " + modulename + " : " + str(e1))
def __init__(self, log = None, loglocation = ProgramDefaults.LogPath, host = ProgramDefaults.LocalHost, port = ProgramDefaults.ServerPort, configfilepath = ProgramDefaults.ConfPath): super(MyMQTT, self).__init__() self.LogFileName = loglocation + "genmqtt.log" if log != None: self.log = log else: # log errors in this module to a file self.log = SetupLogger("client", self.LogFileName) # cleanup # test self.console = SetupLogger("mymqtt_console", log_file = "", stream = True) self.Username = None self.Password = None self.Topic = None self.MQTTAddress = None self.MonitorAddress = host self.MQTTPort = 1883 self.Topic = "generator" self.TopicRoot = None self.BlackList = None self.UseNumeric = False self.PollTime = 2 self.FlushInterval = float('inf') # default to inifite flush interval (e.g., never) self.Debug = False try: config = MyConfig(filename = configfilepath + 'genmqtt.conf', section = 'genmqtt', log = log) self.Username = config.ReadValue('username') self.Password = config.ReadValue('password') self.MQTTAddress = config.ReadValue('mqtt_address') if self.MQTTAddress == None or not len(self.MQTTAddress): log.error("Error: invalid MQTT server address") console.error("Error: invalid MQTT server address") sys.exit(1) self.MonitorAddress = config.ReadValue('monitor_address', default = self.MonitorAddress) if self.MonitorAddress == None or not len(self.MonitorAddress): self.MonitorAddress = ProgramDefaults.LocalHost self.MQTTPort = config.ReadValue('mqtt_port', return_type = int, default = 1883) self.PollTime = config.ReadValue('poll_interval', return_type = float, default = 2.0) self.UseNumeric = config.ReadValue('numeric_json', return_type = bool, default = False) self.TopicRoot = config.ReadValue('root_topic') #http://www.steves-internet-guide.com/mosquitto-tls/ self.CertificateAuthorityPath = config.ReadValue('cert_authority_path', default = "") self.TLSVersion = config.ReadValue('tls_version', return_type = str, default = "1.0") self.CertReqs = config.ReadValue('cert_reqs', return_type = str, default = "Required") BlackList = config.ReadValue('blacklist') if BlackList != None: if len(BlackList): BList = BlackList.strip().split(",") if len(BList): self.BlackList = [] for Items in BList: self.BlackList.append(Items.strip()) self.Debug = config.ReadValue('debug', return_type = bool, default = False) if config.HasOption('flush_interval'): self.FlushInterval = config.ReadValue('flush_interval', return_type = float, default = float('inf')) if self.FlushInterval == 0: self.FlushInterval = float('inf') else: self.FlushInterval = float('inf') except Exception as e1: self.LogErrorLine("Error reading " + configfilepath + "genmqtt.conf: " + str(e1)) self.console.error("Error reading " + configfilepath + "genmqtt.conf: " + str(e1)) sys.exit(1) try: self.MQTTclient = mqtt.Client(client_id = "genmon") if self.Username != None and len(self.Username) and self.Password != None: self.MQTTclient.username_pw_set(self.Username, password=self.Password) self.MQTTclient.on_connect = self.on_connect self.MQTTclient.on_message = self.on_message if len(self.CertificateAuthorityPath): if os.path.isfile(self.CertificateAuthorityPath): cert_reqs = ssl.CERT_REQUIRED if self.CertReqs.lower() == "required": cert_reqs = ssl.CERT_REQUIRED elif self.CertReqs.lower() == "optional": cert_reqs = ssl.CERT_REQUIRED elif self.CertReqs.lower() == "none": cert_reqs = ssl.CERT_NONE else: self.LogError("Error: invalid cert required specified, defaulting to required: " + self.self.CertReq) use_tls = ssl.PROTOCOL_TLSv1 if self.TLSVersion == "1.0" or self.TLSVersion == "1": use_tls = ssl.PROTOCOL_TLSv1 elif self.TLSVersion == "1.1": use_tls = ssl.PROTOCOL_TLSv1_1 elif self.TLSVersion == "1.2": use_tls = ssl.PROTOCOL_TLSv1_2 else: self.LogError("Error: invalid TLS version specified, defaulting to 1.0: " + self.TLSVersion) self.MQTTclient.tls_set(ca_certs = self.CertificateAuthorityPath,cert_reqs = cert_reqs, tls_version = use_tls ) self.MQTTPort = 8883 # port for SSL else: self.LogError("Error: Unable to find CA cert file: " + self.CertificateAuthorityPath) self.MQTTclient.connect(self.MQTTAddress, self.MQTTPort, 60) self.Push = MyGenPush(host = self.MonitorAddress, log = self.log, callback = self.PublishCallback, polltime = self.PollTime , blacklist = self.BlackList, flush_interval = self.FlushInterval, use_numeric = self.UseNumeric, debug = self.Debug, port = port, loglocation = loglocation) atexit.register(self.Close) signal.signal(signal.SIGTERM, self.Close) signal.signal(signal.SIGINT, self.Close) self.MQTTclient.loop_start() except Exception as e1: self.LogErrorLine("Error in MyMQTT init: " + str(e1)) self.console.error("Error in MyMQTT init: " + str(e1)) sys.exit(1)
class Monitor(MySupport): def __init__(self, ConfigFilePath = ProgramDefaults.ConfPath): super(Monitor, self).__init__() self.ProgramName = "Generator Monitor" self.Version = "Unknown" self.log = None self.IsStopping = False self.ProgramComplete = False if ConfigFilePath == None or ConfigFilePath == "": self.ConfigFilePath = ProgramDefaults.ConfPath else: self.ConfigFilePath = ConfigFilePath self.ConnectionList = [] # list of incoming connections for heartbeat # defautl values self.SiteName = "Home" self.ServerSocket = None self.ServerSocketPort = ProgramDefaults.ServerPort # server socket for nagios heartbeat and command/status self.IncomingEmailFolder = "Generator" self.ProcessedEmailFolder = "Generator/Processed" self.FeedbackLogFile = self.ConfigFilePath + "feedback.json" self.LogLocation = ProgramDefaults.LogPath self.LastLogFileSize = 0 self.NumberOfLogSizeErrors = 0 # set defaults for optional parameters self.NewInstall = False # True if newly installed or newly upgraded version self.FeedbackEnabled = False # True if sending autoated feedback on missing information self.FeedbackMessages = {} self.OneTimeMessages = {} self.MailInit = False # set to true once mail is init self.CommunicationsActive = False # Flag to let the heartbeat thread know we are communicating self.Controller = None self.ControllerSelected = None self.bDisablePlatformStats = False self.ReadOnlyEmailCommands = False self.SlowCPUOptimization = False # weather parameters self.WeatherAPIKey = None self.WeatherLocation = None self.UseMetric = False self.WeatherMinimum = True self.DisableWeather = False self.MyWeather = None # Time Sync Related Data self.bSyncTime = False # Sync gen to system time self.bSyncDST = False # sync time at DST change self.bDST = False # Daylight Savings Time active if True # simulation self.Simulation = False self.SimulationFile = None self.console = SetupLogger("genmon_console", log_file = "", stream = True) if os.geteuid() != 0: self.LogConsole("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'.") sys.exit(1) if not os.path.isfile(self.ConfigFilePath + 'genmon.conf'): self.LogConsole("Missing config file : " + self.ConfigFilePath + 'genmon.conf') sys.exit(1) if not os.path.isfile(self.ConfigFilePath + 'mymail.conf'): self.LogConsole("Missing config file : " + self.ConfigFilePath + 'mymail.conf') sys.exit(1) self.config = MyConfig(filename = self.ConfigFilePath + 'genmon.conf', section = "GenMon", log = self.console) # read config file if not self.GetConfig(): self.LogConsole("Failure in Monitor GetConfig") sys.exit(1) # log errors in this module to a file self.log = SetupLogger("genmon", self.LogLocation + "genmon.log") self.config.log = self.log if self.IsLoaded(): self.LogConsole("ERROR: genmon.py is already loaded.") self.LogError("ERROR: genmon.py is already loaded.") sys.exit(1) if self.NewInstall: self.LogError("New version detected: Old = %s, New = %s" % (self.Version, GENMON_VERSION)) self.Version = GENMON_VERSION self.ProgramStartTime = datetime.datetime.now() # used for com metrics self.LastSofwareUpdateCheck = datetime.datetime.now() atexit.register(self.Close) signal.signal(signal.SIGTERM, self.Close) signal.signal(signal.SIGINT, self.Close) # start thread to accept incoming sockets for nagios heartbeat and command / status clients self.Threads["InterfaceServerThread"] = MyThread(self.InterfaceServerThread, Name = "InterfaceServerThread") # init mail, start processing incoming email self.mail = MyMail(monitor=True, incoming_folder = self.IncomingEmailFolder, processed_folder =self.ProcessedEmailFolder,incoming_callback = self.ProcessCommand, loglocation = self.LogLocation, ConfigFilePath = ConfigFilePath) self.Threads = self.MergeDicts(self.Threads, self.mail.Threads) self.MailInit = True self.FeedbackPipe = MyPipe("Feedback", self.FeedbackReceiver, log = self.log, ConfigFilePath = self.ConfigFilePath) self.Threads = self.MergeDicts(self.Threads, self.FeedbackPipe.Threads) self.MessagePipe = MyPipe("Message", self.MessageReceiver, log = self.log, nullpipe = self.mail.DisableSNMP, ConfigFilePath = self.ConfigFilePath) self.Threads = self.MergeDicts(self.Threads, self.MessagePipe.Threads) try: #Starting device connection if self.Simulation: self.LogError("Simulation Running") if not self.ControllerSelected == None and len(self.ControllerSelected): self.LogError("Selected Controller: " + str(self.ControllerSelected)) else: self.ControllerSelected = "generac_evo_nexus" if self.ControllerSelected.lower() == "h_100" : self.Controller = HPanel(self.log, newinstall = self.NewInstall, simulation = self.Simulation, simulationfile = self.SimulationFile, message = self.MessagePipe, feedback = self.FeedbackPipe, config = self.config) else: self.Controller = Evolution(self.log, self.NewInstall, simulation = self.Simulation, simulationfile = self.SimulationFile, message = self.MessagePipe, feedback = self.FeedbackPipe, config = self.config) self.Threads = self.MergeDicts(self.Threads, self.Controller.Threads) except Exception as e1: self.LogErrorLine("Error opening controller device: " + str(e1)) sys.exit(1) self.StartThreads() self.ProcessFeedbackInfo() # send mail to tell we are starting self.MessagePipe.SendMessage("Generator Monitor Starting at " + self.SiteName, "Generator Monitor Starting at " + self.SiteName , msgtype = "info") self.LogError("GenMon Loaded for site: " + self.SiteName) # ------------------------ Monitor::StartThreads---------------------------- def StartThreads(self, reload = False): try: # start thread to accept incoming sockets for nagios heartbeat self.Threads["ComWatchDog"] = MyThread(self.ComWatchDog, Name = "ComWatchDog") if self.bSyncDST or self.bSyncTime: # Sync time thread self.Threads["TimeSyncThread"] = MyThread(self.TimeSyncThread, Name = "TimeSyncThread") if not self.DisableWeather and not self.WeatherAPIKey == None and len(self.WeatherAPIKey) and not self.WeatherLocation == None and len(self.WeatherLocation): Unit = 'metric' if self.UseMetric else 'imperial' self.MyWeather = MyWeather(self.WeatherAPIKey, location = self.WeatherLocation, unit = Unit, log = self.log) self.Threads = self.MergeDicts(self.Threads, self.MyWeather.Threads) except Exception as e1: self.LogErrorLine("Error in StartThreads: " + str(e1)) # -------------------- Monitor::GetConfig----------------------------------- def GetConfig(self): try: if self.config.HasOption('sitename'): self.SiteName = self.config.ReadValue('sitename') if self.config.HasOption('incoming_mail_folder'): self.IncomingEmailFolder = self.config.ReadValue('incoming_mail_folder') # imap folder for incoming mail if self.config.HasOption('processed_mail_folder'): self.ProcessedEmailFolder = self.config.ReadValue('processed_mail_folder') # imap folder for processed mail # server_port, must match value in myclient.py and check_monitor_system.py and any calling client apps if self.config.HasOption('server_port'): self.ServerSocketPort = self.config.ReadValue('server_port', return_type = int) self.LogLocation = self.config.ReadValue('loglocation', default = ProgramDefaults.LogPath) if self.config.HasOption('syncdst'): self.bSyncDST = self.config.ReadValue('syncdst', return_type = bool) if self.config.HasOption('synctime'): self.bSyncTime = self.config.ReadValue('synctime', return_type = bool) if self.config.HasOption('disableplatformstats'): self.bDisablePlatformStats = self.config.ReadValue('disableplatformstats', return_type = bool) if self.config.HasOption('simulation'): self.Simulation = self.config.ReadValue('simulation', return_type = bool) if self.config.HasOption('simulationfile'): self.SimulationFile = self.config.ReadValue('simulationfile') if self.config.HasOption('controllertype'): self.ControllerSelected = self.config.ReadValue('controllertype') if self.config.HasOption('disableweather'): self.DisableWeather = self.config.ReadValue('disableweather', return_type = bool) else: self.DisableWeather = False if self.config.HasOption('weatherkey'): self.WeatherAPIKey = self.config.ReadValue('weatherkey') if self.config.HasOption('weatherlocation'): self.WeatherLocation = self.config.ReadValue('weatherlocation') if self.config.HasOption('metricweather'): self.UseMetric = self.config.ReadValue('metricweather', return_type = bool) if self.config.HasOption('minimumweatherinfo'): self.WeatherMinimum = self.config.ReadValue('minimumweatherinfo', return_type = bool) if self.config.HasOption('readonlyemailcommands'): self.ReadOnlyEmailCommands = self.config.ReadValue('readonlyemailcommands', return_type = bool) if self.config.HasOption('optimizeforslowercpu'): self.SlowCPUOptimization = self.config.ReadValue('optimizeforslowercpu', return_type = bool) if self.config.HasOption('version'): self.Version = self.config.ReadValue('version') if not self.Version == GENMON_VERSION: self.config.WriteValue('version', GENMON_VERSION) self.NewInstall = True else: self.config.WriteValue('version', GENMON_VERSION) self.NewInstall = True self.Version = GENMON_VERSION if self.config.HasOption("autofeedback"): self.FeedbackEnabled = self.config.ReadValue('autofeedback', return_type = bool) else: self.config.WriteValue('autofeedback', "False") self.FeedbackEnabled = False # Load saved feedback log if log is present if os.path.isfile(self.FeedbackLogFile): try: with open(self.FeedbackLogFile) as infile: self.FeedbackMessages = json.load(infile) except Exception as e1: os.remove(self.FeedbackLogFile) self.UpdateCheck = self.config.ReadValue('update_check', return_type = bool, default = True) self.UserURL = self.config.ReadValue('user_url', default = "").strip() except Exception as e1: self.Console("Missing config file or config file entries (genmon): " + str(e1)) return False return True #--------------------------------------------------------------------------- def ProcessFeedbackInfo(self): try: if self.FeedbackEnabled: for Key, Entry in self.FeedbackMessages.items(): self.MessagePipe.SendMessage("Generator Monitor Submission", Entry , recipient = self.MaintainerAddress, files = self.GetLogFileNames(), msgtype = "error") # delete unsent Messages if os.path.isfile(self.FeedbackLogFile): os.remove(self.FeedbackLogFile) except Exception as e1: self.LogErrorLine("Error in ProcessFeedbackInfo: " + str(e1)) #--------------------------------------------------------------------------- def FeedbackReceiver(self, Message): try: FeedbackDict = {} FeedbackDict = json.loads(Message) self.SendFeedbackInfo(FeedbackDict["Reason"], Always = FeedbackDict["Always"], Message = FeedbackDict["Message"], FullLogs = FeedbackDict["FullLogs"], NoCheck = FeedbackDict["NoCheck"]) except Exception as e1: self.LogErrorLine("Error in FeedbackReceiver: " + str(e1)) self.LogError("Size : " + str(len(Message))) self.LogError("Message : " + str(Message)) #--------------------------------------------------------------------------- def MessageReceiver(self, Message): try: MessageDict = {} MessageDict = json.loads(Message) if MessageDict["onlyonce"]: Subject = self.OneTimeMessages.get(MessageDict["subjectstr"], None) if Subject == None: self.OneTimeMessages[MessageDict["subjectstr"]] = MessageDict["msgstr"] else: return self.mail.sendEmail(MessageDict["subjectstr"], MessageDict["msgstr"], recipient = MessageDict["recipient"], files = MessageDict["files"], deletefile= MessageDict["deletefile"], msgtype= MessageDict["msgtype"]) except Exception as e1: self.LogErrorLine("Error in MessageReceiver: " + str(e1)) #--------------------------------------------------------------------------- def SendFeedbackInfo(self, Reason, Always = False, Message = None, FullLogs = True, NoCheck = False): try: if self.NewInstall or Always: CheckedSent = self.FeedbackMessages.get(Reason, "") if not CheckedSent == "" and not NoCheck: return if not NoCheck: self.LogError(Reason + " : " + Message) msgbody = "Reason = " + Reason + "\n" if Message != None: msgbody += "Message : " + Message + "\n" msgbody += self.printToString(self.ProcessDispatch(self.GetStartInfo(NoTile = True),"")) if not self.bDisablePlatformStats: msgbody += self.printToString(self.ProcessDispatch({"Platform Stats" : self.GetPlatformStats()},"")) msgbody += self.Controller.DisplayRegisters(AllRegs = FullLogs) msgbody += "\n" + self.GetSupportData() + "\n" if self.FeedbackEnabled: self.MessagePipe.SendMessage("Generator Monitor Submission", msgbody , recipient = self.MaintainerAddress, files = self.GetLogFileNames(), msgtype = "error") self.FeedbackMessages[Reason] = msgbody # if feedback not enabled, save the log to file if not self.FeedbackEnabled: with open(self.FeedbackLogFile, 'w') as outfile: json.dump(self.FeedbackMessages, outfile, sort_keys = True, indent = 4, ensure_ascii = False) except Exception as e1: self.LogErrorLine("Error in SendFeedbackInfo: " + str(e1)) #---------- Monitor::EmailSendIsEnabled------------------------------------- def EmailSendIsEnabled(self): EmailThread = self.Threads.get("SendMailThread",None) if EmailThread == None: return False return True #---------- Monitor::GetSupportData----------------------------------------- def GetSupportData(self): SupportData = collections.OrderedDict() SupportData["Program Run Time"] = self.GetProgramRunTime() SupportData["Monitor Health"] = self.GetSystemHealth() SupportData["StartInfo"] = self.GetStartInfo(NoTile = True) if not self.bDisablePlatformStats: SupportData["PlatformStats"] = self.GetPlatformStats() SupportData["Data"] = self.Controller.DisplayRegisters(AllRegs = True, DictOut = True) # Raw Modbus data SupportData["Registers"] = self.Controller.Registers SupportData["Strings"] = self.Controller.Strings SupportData["FileData"] = self.Controller.FileData return json.dumps(SupportData, sort_keys=False) #---------- Monitor::GetLogFileNames---------------------------------------- def GetLogFileNames(self): try: LogList = [] FilesToSend = ["genmon.log", "genserv.log", "mymail.log", "myserial.log", "mymodbus.log", "gengpio.log", "gengpioin.log", "gensms.log", "gensms_modem.log", "genmqtt.log", "genpushover.log", "gensyslog.log", "genloader.log", "myserialtcp.log", "genlog.log", "genslack.log", "genexercise.log","genemail2sms.log", "gentankutil.log", "genalexa.log", "gensnmp.log"] for File in FilesToSend: LogFile = self.LogLocation + File if os.path.isfile(LogFile): LogList.append(LogFile) return LogList except Exception as e1: return None #---------- Monitor::SendSupportInfo---------------------------------------- def SendSupportInfo(self, SendLogs = True): try: if not self.EmailSendIsEnabled(): self.LogError("Error in SendSupportInfo: send email is not enabled") return "Send Email is not enabled." msgbody = "" msgbody += self.printToString(self.ProcessDispatch(self.GetStartInfo(NoTile = True),"")) if not self.bDisablePlatformStats: msgbody += self.printToString(self.ProcessDispatch({"Platform Stats" : self.GetPlatformStats()},"")) msgbody += self.Controller.DisplayRegisters(AllRegs = True) msgbody += "\n" + self.GetSupportData() + "\n" msgtitle = "Generator Monitor Log File Submission" if SendLogs == True: LogList = self.GetLogFileNames() else: msgtitle = "Generator Monitor Register Submission" LogList = None self.MessagePipe.SendMessage(msgtitle, msgbody , recipient = self.MaintainerAddress, files = LogList, msgtype = "error") return "Log files submitted" except Exception as e1: self.LogErrorLine("Error in SendSupportInfo: " + str(e1)) #---------- process command from email and socket -------------------------- def ProcessCommand(self, command, fromsocket = False): LocalError = False command = command.decode('utf-8') msgsubject = "Generator Command Response at " + self.SiteName if not fromsocket: msgbody = "\n" else: msgbody = "" if(len(command)) == 0: msgsubject = "Error in Generator Command (Lenght is zero)" msgbody += "Invalid GENERATOR command: zero length command." LocalError = True if not LocalError: if(not command.lower().startswith( 'generator:' )): msgsubject = "Error in Generator Command (command prefix)" msgbody += "Invalid GENERATOR command: all commands must be prefixed by \"generator: \"" LocalError = True if LocalError: if not fromsocket: self.MessagePipe.SendMessage(msgsubject, msgbody, msgtype = "error") return "" # ignored by email module else: msgbody += "EndOfMessage" return msgbody if command.lower().startswith('generator:'): command = command[len('generator:'):] CommandDict = { "registers" : [self.Controller.DisplayRegisters,(False,), False], # display registers "allregs" : [self.Controller.DisplayRegisters, (True,), False], # display registers "logs" : [self.Controller.DisplayLogs, (True, False), False], "status" : [self.Controller.DisplayStatus, (), False], # display decoded generator info "maint" : [self.Controller.DisplayMaintenance, (), False], "monitor" : [self.DisplayMonitor, (), False], "outage" : [self.Controller.DisplayOutage, (), False], "settime" : [self.StartTimeThread, (), False], # set time and date "setexercise" : [self.Controller.SetGeneratorExerciseTime, (command.lower(),), False], "setquiet" : [self.Controller.SetGeneratorQuietMode, ( command.lower(),), False], "setremote" : [self.Controller.SetGeneratorRemoteCommand, (command.lower(),), False], "testcommand" : [self.Controller.TestCommand, (command.lower(),), False], "network_status": [self.InternetConnected, (), False], "help" : [self.DisplayHelp, (), False], # display help screen ## These commands are used by the web / socket interface only "power_log_json" : [self.Controller.GetPowerHistory, (command.lower(),), True], "power_log_clear" : [self.Controller.ClearPowerLog, (), True], "start_info_json" : [self.GetStartInfo, (), True], "registers_json" : [self.Controller.DisplayRegisters, (False, True), True], # display registers "allregs_json" : [self.Controller.DisplayRegisters, (True, True), True], # display registers "logs_json" : [self.Controller.DisplayLogs, (True, True), True], "status_json" : [self.Controller.DisplayStatus, (True,), True], "status_num_json" : [self.Controller.DisplayStatus, (True,True), True], "maint_json" : [self.Controller.DisplayMaintenance, (True,), True], "maint_num_json" : [self.Controller.DisplayMaintenance, (True,True), True], "monitor_json" : [self.DisplayMonitor, (True,), True], "monitor_num_json" : [self.DisplayMonitor, (True,True), True], "weather_json" : [self.DisplayWeather, (True,), True], "outage_json" : [self.Controller.DisplayOutage, (True,), True], "outage_num_json" : [self.Controller.DisplayOutage, (True,True), True], "gui_status_json" : [self.GetStatusForGUI, (), True], "get_maint_log_json": [self.Controller.GetMaintLog, (), True], "add_maint_log" : [self.Controller.AddEntryToMaintLog, (command,), True], # Do not do command.lower() since this input is JSON "clear_maint_log" : [self.Controller.ClearMaintLog, (), True], "getsitename" : [self.GetSiteName, (), True], "getbase" : [self.Controller.GetBaseStatus, (), True], # (UI changes color based on exercise, running , ready status) "gethealth" : [self.GetSystemHealth, (), True], "getregvalue" : [self.Controller.GetRegValue, (command.lower(),), True], # only used for debug purposes, read a cached register value "readregvalue" : [self.Controller.ReadRegValue, (command.lower(),), True], # only used for debug purposes, Read Register Non Cached "getdebug" : [self.GetDeadThreadName, (), True], # only used for debug purposes. If a thread crashes it tells you the thread name "sendregisters" : [self.SendSupportInfo, (False,), True], "sendlogfiles" : [self.SendSupportInfo, (True,), True], "set_tank_data" : [self.Controller.SetExternalTankData, (command,), True] } CommandList = command.split(' ') ValidCommand = False try: for item in CommandList: if not len(item): continue item = item.strip() LookUp = item if "=" in item: BaseCmd = item.split('=') LookUp = BaseCmd[0] # check if we disallow write commands via email if self.ReadOnlyEmailCommands and not fromsocket and LookUp in ["settime", "setexercise", "setquiet", "setremote"]: continue ExecList = CommandDict.get(LookUp.lower(),None) if ExecList == None: continue if ExecList[0] == None: continue if not fromsocket and ExecList[2]: continue # Execut Command ReturnMessage = ExecList[0](*ExecList[1]) ValidCommand = True if LookUp.lower().endswith("_json") and not isinstance(ReturnMessage, str): msgbody += json.dumps(ReturnMessage, sort_keys=False) else: msgbody += ReturnMessage if not fromsocket: msgbody += "\n" except Exception as e1: self.LogErrorLine("Error Processing Commands: " + command + ": "+ str(e1)) if not ValidCommand: msgbody += "No valid command recognized." if not fromsocket: self.MessagePipe.SendMessage(msgsubject, msgbody, msgtype = "warn") return "" # ignored by email module else: msgbody += "EndOfMessage" return msgbody #------------ Monitor::DisplayHelp ----------------------------------------- def DisplayHelp(self): outstring = "" outstring += "Help:\n" outstring += self.printToString("\nCommands:") outstring += self.printToString(" status - display engine and line information") outstring += self.printToString(" maint - display maintenance and service information") outstring += self.printToString(" outage - display current and last outage (since program launched)") outstring += self.printToString(" info, also shows utility min and max values") outstring += self.printToString(" monitor - display communication statistics and monitor health") outstring += self.printToString(" logs - display all alarm, on/off, and maintenance logs") outstring += self.printToString(" registers - display contents of registers being monitored") outstring += self.printToString(" settime - set generator time to system time") outstring += self.printToString(" setexercise - set the exercise time of the generator. ") outstring += self.printToString(" i.e. setexercise=Monday,13:30,Weekly") outstring += self.printToString(" if Enhanced Exercise Frequency is supported by your generator:") outstring += self.printToString(" i.e. setexercise=Monday,13:30,BiWeekly") outstring += self.printToString(" i.e. setexercise=15,13:30,Monthly") outstring += self.printToString(" setquiet - enable or disable exercise quiet mode, ") outstring += self.printToString(" i.e. setquiet=on or setquiet=off") outstring += self.printToString(" setremote - issue remote command. format is setremote=command, ") outstring += self.printToString(" where command is start, stop, starttransfer,") outstring += self.printToString(" startexercise. i.e. setremote=start") outstring += self.printToString(" help - Display help on commands") outstring += self.printToString("\n") outstring += self.printToString("To clear the Alarm/Warning message, press OFF on the control panel keypad") outstring += self.printToString("followed by the ENTER key. To access Dealer Menu on the Evolution") outstring += self.printToString("controller, from the top menu selection (SYSTEM, DATE/TIME,BATTERY, SUB-MENUS)") outstring += self.printToString("enter UP UP ESC DOWN UP ESC UP, then go to the dealer menu and press enter.") outstring += self.printToString("For liquid cooled models a level 2 dealer code can be entered, ESC UP UP DOWN") outstring += self.printToString("DOWN ESC ESC, then navigate to the dealer menu and press enter.") outstring += self.printToString("Passcode for Nexus controller is ESC, UP, UP ESC, DOWN, UP, ESC, UP, UP, ENTER.") outstring += self.printToString("\n") return outstring #------------ Monitor::GetProgramRunTime ----------------------------------- def GetProgramRunTime(self): try: ProgramRunTime = datetime.datetime.now() - self.ProgramStartTime outstr = str(ProgramRunTime).split(".")[0] # remove microseconds from string return self.ProgramName + " running for " + outstr + "." except Exception as e1: self.LogErrorLine("Error in GetProgramRunTime:" + str(e1)) return "Unknown" #------------ Monitor::GetWeatherData -------------------------------------- def GetWeatherData(self, ForUI = False): if self.MyWeather == None: return None ReturnData = self.MyWeather.GetWeather(minimum = self.WeatherMinimum, ForUI = ForUI) if not len(ReturnData): return None return ReturnData #------------ Monitor::GetUserDefinedData ---------------------------------- # this assumes one json object, the file can be formatted (i.e. on multiple # lines) or can be on a single line def GetUserDefinedData(self): try: FileName = os.path.dirname(os.path.realpath(__file__)) + "/userdefined.json" if not os.path.isfile(FileName): return None if os.path.getsize(FileName) == 0: return None with open(FileName) as f: data = json.load(f,object_pairs_hook=collections.OrderedDict) return data except Exception as e1: self.LogErrorLine("Error in GetUserDefinedData: " + str(e1)) return None #------------ Monitor::DisplayWeather -------------------------------------- def DisplayWeather(self, DictOut = False): WeatherData = collections.OrderedDict() try: ReturnData = self.GetWeatherData() if not ReturnData == None and len(ReturnData): WeatherData["Weather"] = ReturnData if not DictOut: return self.printToString(self.ProcessDispatch(WeatherData,"")) except Exception as e1: self.LogErrorLine("Error in DisplayWeather: " + str(e1)) return WeatherData #------------ Monitor::DisplayMonitor -------------------------------------- def DisplayMonitor(self, DictOut = False, JSONNum = False): try: Monitor = collections.OrderedDict() MonitorData = [] Monitor["Monitor"] = MonitorData GenMonStats = [] SerialStats = [] MonitorData.append({"Generator Monitor Stats" : GenMonStats}) MonitorData.append({"Communication Stats" : self.Controller.GetCommStatus()}) GenMonStats.append({"Monitor Health" : self.GetSystemHealth()}) GenMonStats.append({"Controller" : self.Controller.GetController(Actual = False)}) GenMonStats.append({"Run time" : self.GetProgramRunTime()}) if self.Controller.PowerMeterIsSupported(): GenMonStats.append({"Power log file size" : self.Controller.GetPowerLogFileDetails()}) GenMonStats.append({"Generator Monitor Version" : GENMON_VERSION}) if not self.bDisablePlatformStats: PlatformStats = self.GetPlatformStats() if not PlatformStats == None: MonitorData.append({"Platform Stats" : PlatformStats}) WeatherData = self.GetWeatherData() if not WeatherData == None and len(WeatherData): MonitorData.append({"Weather" : WeatherData}) UserData = self.GetUserDefinedData() if not UserData == None and len(UserData): MonitorData.append({"External Data" : UserData}) if not DictOut: return self.printToString(self.ProcessDispatch(Monitor,"")) except Exception as e1: self.LogErrorLine("Error in DisplayMonitor: " + str(e1)) return Monitor #------------ Monitor::GetStartInfo----------------------------------------- def GetStartInfo(self, NoTile = False): StartInfo = collections.OrderedDict() StartInfo["version"] = GENMON_VERSION StartInfo["sitename"] = self.SiteName ControllerStartInfo = self.Controller.GetStartInfo(NoTile = NoTile) StartInfo = self.MergeDicts(StartInfo, ControllerStartInfo) return StartInfo #------------ Monitor::GetStatusForGUI ------------------------------------- def GetStatusForGUI(self): Status = {} Status["SystemHealth"] = self.GetSystemHealth() Status["UnsentFeedback"] = str(os.path.isfile(self.FeedbackLogFile)) if not self.bDisablePlatformStats: PlatformStats = self.GetPlatformStats(usemetric = True) if not PlatformStats == None and len(PlatformStats): Status["PlatformStats"] = PlatformStats WeatherData = self.GetWeatherData(ForUI = True) if not WeatherData == None and len(WeatherData): Status["Weather"] = WeatherData # Monitor Time Status["MonitorTime"] = datetime.datetime.now().strftime("%m/%d/%Y %H:%M") # Engine run hours Status["RunHours"] = self.Controller.GetRunHours() ReturnDict = self.MergeDicts(Status, self.Controller.GetStatusForGUI()) return ReturnDict #-------------Monitor::GetSystemHealth-------------------------------------- # returns the health of the monitor program def GetSystemHealth(self): outstr = "" if not self.Controller.InitComplete: outstr += "System Initializing. " if not self.AreThreadsAlive(): outstr += " Threads are dead. " if not self.CommunicationsActive: outstr += " Not receiving data. " if not self.LogFileIsOK(): outstr += " Log file is reporting errors." if len(outstr) == 0: outstr = "OK" return outstr #---------- Monitor::StartTimeThread--------------------------------------- def StartTimeThread(self): # This is done is a separate thread as not to block any return email processing # since we attempt to sync with generator time MyThread(self.Controller.SetGeneratorTimeDate, Name = "SetTimeThread") return "Time Set: Command Sent\n" #---------- Monitor::TimeSyncThread---------------------------------------- def TimeSyncThread(self): self.bDST = self.is_dst() # set initial DST state time.sleep(0.25) while True: if self.WaitForExit("TimeSyncThread", 1): # ten min return if self.Controller.InitComplete: break # if we are not always syncing, then set the time once if not self.bSyncTime: self.StartTimeThread() while True: if self.bSyncDST: if self.bDST != self.is_dst(): # has DST changed? self.bDST = self.is_dst() # update Flag # time changed so some comm stats may be off self.Controller.ResetCommStats() # set new time self.StartTimeThread() # start settime thread self.MessagePipe.SendMessage("Generator Time Update at " + self.SiteName, "Time updated due to daylight savings time change", msgtype = "info") if self.bSyncTime: # update gen time self.StartTimeThread() if self.WaitForExit("TimeSyncThread", 60 * 60): # 1 hour return #---------- Monitor::is_dst------------------------------------------------ def is_dst(self): #Determine whether or not Daylight Savings Time (DST) is currently in effect t = time.localtime() isdst = t.tm_isdst return (isdst != 0) #---------- Monitor::ComWatchDog------------------------------------------- #---------- monitors receive data status to make sure we are still communicating def ComWatchDog(self): self.CommunicationsActive = False time.sleep(0.25) NoticeSent = False LastActiveTime = datetime.datetime.now() while True: if self.WaitForExit("ComWatchDog", 1): return if self.Controller.InitComplete: break if self.Controller.ModBus.UseTCP: WatchDogPollTime = 8 else: WatchDogPollTime = 2 while True: try: # check for software update self.CheckSoftwareUpdate() self.CommunicationsActive = self.Controller.ComminicationsIsActive() if self.CommunicationsActive: LastActiveTime = datetime.datetime.now() if NoticeSent: NoticeSent = False self.MessagePipe.SendMessage("Generator Monitor Communication Restored at " + self.SiteName, "Generator Monitor communications with the controller has been restored at " + self.SiteName , msgtype = "info") else: if self.GetDeltaTimeMinutes(datetime.datetime.now() - LastActiveTime) > 1 : if not NoticeSent: NoticeSent = True self.MessagePipe.SendMessage("Generator Monitor Communication WARNING at " + self.SiteName, "Generator Monitor is not communicating with the controller at " + self.SiteName , msgtype = "error") except Exception as e1: self.LogErrorLine("Error in ComWatchDog: " + str(e1)) if self.WaitForExit("ComWatchDog", WatchDogPollTime): return #---------- Monitor::CheckSoftwareUpdate------------------------------------ def CheckSoftwareUpdate(self): if not self.UpdateCheck: return try: if self.GetDeltaTimeMinutes(datetime.datetime.now() - self.LastSofwareUpdateCheck) > 1440 : # check every day self.LastSofwareUpdateCheck = datetime.datetime.now() # Do the check try: import urllib2 url = "https://raw.githubusercontent.com/jgyates/genmon/master/genmon.py" data = urllib2.urlopen(url).read(20000) # read only 2000 chars data = data.split("\n") # then split it into lines for line in data: if 'GENMON_VERSION = "V' in line: import re quoted = re.compile('"([^"]*)"') for value in quoted.findall(line): if value != GENMON_VERSION: # Update Available title = self.ProgramName + " Software Update " + value + " is available for site " + self.SiteName msgbody = "\nA software update is available for the " + self.ProgramName + ". The new version (" + value + ") can be updated on the About page of the web interface. You can disable this email from being sent on the Settings page." if len(self.UserURL): msgbody += "\n\nWeb Interface URL: " + self.UserURL msgbody += "\n\nChange Log: https://raw.githubusercontent.com/jgyates/genmon/master/changelog.md" self.MessagePipe.SendMessage(title , msgbody, msgtype = "info", onlyonce = True) except Exception as e1: self.LogErrorLine("Error checking for software update: " + str(e1)) except Exception as e1: self.LogErrorLine("Error in CheckSoftwareUpdate: " + str(e1)) #---------- Monitor::LogFileIsOK-------------------------------------------- def LogFileIsOK(self): try: if not self.Controller.InitComplete: return True LogFile = self.LogLocation + "genmon.log" LogFileSize = os.path.getsize(LogFile) if LogFileSize <= self.LastLogFileSize: # log is unchanged or has rotated self.LastLogFileSize = LogFileSize self.NumberOfLogSizeErrors = 0 return True LogFileSizeDiff = LogFileSize - self.LastLogFileSize self.LastLogFileSize = LogFileSize if LogFileSizeDiff >= 100: self.NumberOfLogSizeErrors += 1 if self.NumberOfLogSizeErrors > 3: return False else: self.NumberOfLogSizeErrors = 0 return True except Exception as e1: self.LogErrorLine("Error in LogFileIsOK: " + str(e1)) return True #---------- Monitor::SocketWorkThread------------------------------------- # This thread spawns for each connection established by a client # in InterfaceServerThread def SocketWorkThread(self, conn): try: statusstr = "" if self.Controller == None: outstr = "WARNING: System Initializing" conn.sendall(outstr.encode()) else: if self.Controller.SystemInAlarm(): statusstr += "CRITICAL: System in alarm! " HealthStr = self.GetSystemHealth() if HealthStr != "OK": statusstr += "WARNING: " + HealthStr if statusstr == "": statusstr = "OK " outstr = statusstr + ": "+ self.Controller.GetOneLineStatus() conn.sendall(outstr.encode()) while True: try: data = conn.recv(1024) if len(data): if self.Controller == None: outstr = "Retry, System Initializing" else: outstr = self.ProcessCommand(data, True) conn.sendall(outstr.encode("utf-8")) else: # socket closed remotely break except socket.timeout: if self.IsStopping: break continue except socket.error as msg: try: self.ConnectionList.remove(conn) conn.close() except: pass break except socket.error as msg: self.LogError("Error in SocketWorkThread: " + str(msg)) pass try: self.ConnectionList.remove(conn) conn.close() except: pass # end SocketWorkThread #---------- interface for heartbeat server thread ------------------------- def InterfaceServerThread(self): #create an INET, STREAMing socket self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # set some socket options so we can resuse the port self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.ServerSocket.settimeout(.5) #bind the socket to a host, and a port self.ServerSocket.bind(('', self.ServerSocketPort)) #become a server socket self.ServerSocket.listen(5) #wait to accept a connection - blocking call while True: try: conn, addr = self.ServerSocket.accept() #self.LogError('Connected with ' + addr[0] + ':' + str(addr[1])) conn.settimeout(0.5) self.ConnectionList.append(conn) SocketThread = threading.Thread(target=self.SocketWorkThread, args = (conn,), name = "SocketWorkThread") SocketThread.daemon = True SocketThread.start() # start server thread except socket.timeout: if self.IsStopping: break continue except Exception as e1: if self.IsStopping: break self.LogErrorLine("Exception in InterfaceServerThread" + str(e1)) if self.WaitForExit("InterfaceServerThread", 0.5 ): break continue if self.ServerSocket != None: if len(self.ConnectionList): try: self.ServerSocket.shutdown(socket.SHUT_RDWR) except: pass self.ServerSocket.close() self.ServerSocket = None # #---------------------Monitor::Close---------------------------------------- def Close(self): # we dont really care about the errors that may be generated on shutdown try: self.IsStopping = True try: if self.MyWeather != None: self.MyWeather.Close() except: pass try: if self.bSyncDST or self.bSyncTime: self.KillThread("TimeSyncThread") except: pass try: self.KillThread("ComWatchDog") except: pass try: if not self.Controller == None: self.Controller.Close() except: pass # try: self.mail.Close() except: pass try: for item in self.ConnectionList: try: item.close() except: continue self.ConnectionList.remove(item) except: pass try: if(self.ServerSocket != None): self.ServerSocket.shutdown(socket.SHUT_RDWR) self.ServerSocket.close() self.KillThread("InterfaceServerThread") except: pass try: self.FeedbackPipe.Close() except: pass try: self.MessagePipe.Close() except: pass # Tell any remaining threads to stop for name, object in self.Threads.items(): try: if self.Threads[name].IsAlive(): self.Threads[name].Stop() except Exception as e1: self.LogErrorLine("Error killing thread in Monitor Close: " + name + ":" + str(e1)) except Exception as e1: self.LogErrorLine("Error Closing Monitor: " + str(e1)) with self.CriticalLock: self.LogError("Generator Monitor Shutdown") try: self.ProgramComplete = True sys.exit(0) except: pass
class Loader(MySupport): def __init__(self, start=False, stop=False, hardstop=False, loglocation=ProgramDefaults.LogPath, log=None, localinit=False, ConfigFilePath=ProgramDefaults.ConfPath): self.Start = start self.Stop = stop self.HardStop = hardstop self.PipChecked = False self.NewInstall = False self.Upgrade = False self.version = None self.ConfigFilePath = ConfigFilePath self.ConfigFileName = "genloader.conf" # log errors in this module to a file if localinit == True: self.configfile = self.ConfigFileName else: self.configfile = os.path.join(self.ConfigFilePath, self.ConfigFileName) self.ModulePath = os.path.dirname(os.path.realpath(__file__)) self.ConfPath = os.path.join( os.path.dirname(os.path.realpath(__file__)), "conf") # log errors in this module to a file if log == None: self.log = SetupLogger("genloader", os.path.join(loglocation, "genloader.log")) else: self.log = log self.console = SetupLogger("genloader_console", log_file="", stream=True) try: if self.Start: if not self.CheckSystem(): self.LogInfo("Error check system readiness. Exiting") sys.exit(2) self.CachedConfig = {} if not os.path.isdir(self.ConfigFilePath): try: os.mkdir(self.ConfigFilePath) except Exception as e1: self.LogInfo("Error creating target config directory: " + str(e1), LogLine=True) # check to see if genloader.conf is present, if not copy it from genmon directory if not os.path.isfile(self.configfile): self.LogInfo("Warning: unable to find config file: " + self.configfile + " Copying file to " + self.ConfigFilePath + " directory.") if not self.CopyConfFile(): sys.exit(2) self.config = MyConfig(filename=self.configfile, section="genmon", log=self.log) if not self.GetConfig(): self.CopyConfFile() self.LogInfo("Error validating config. Retrying..") self.config = MyConfig(filename=self.configfile, section="genmon", log=self.log) if not self.GetConfig(): self.LogInfo( "Error reading config file, 2nd attempt (1), Exiting") sys.exit(2) if not self.ValidateConfig(): self.CopyConfFile() self.LogInfo("Error validating config. Retrying..") self.config = MyConfig(filename=self.configfile, section="genmon", log=self.log) if not self.GetConfig(): self.LogInfo( "Error reading config file, 2nd attempt (2), Exiting") sys.exit(2) if not self.ValidateConfig(): self.LogInfo("Error validating config file, Exiting") sys.exit(2) self.LoadOrder = self.GetLoadOrder() if self.Stop: self.StopModules() time.sleep(2) if self.Start: self.StartModules() except Exception as e1: self.LogErrorLine("Error in init: " + str(e1)) #--------------------------------------------------------------------------- def CopyConfFile(self): if os.path.isfile(os.path.join(self.ConfPath, self.ConfigFileName)): copyfile(os.path.join(self.ConfPath, self.ConfigFileName), self.configfile) return True else: self.LogInfo("Unable to find config file.") return False sys.exit(2) #--------------------------------------------------------------------------- def CheckSystem(self): # this function checks the system to see if the required libraries are # installed. If they are not then an attempt is made to install them. ModuleList = [ # [import name , install name, required version] ['flask', 'flask', None], # Web server # we will not use the check for configparser as this look like it is in backports on 2.7 # and our myconfig modules uses the default so this generates an error that is not warranted #['configparser','configparser',None], # reading config files ['serial', 'pyserial', None], # Serial ['crcmod', 'crcmod', None], # Modbus CRC ['pyowm', 'pyowm', '2.10.0'], # Open Weather API ['pytz', 'pytz', None], # Time zone support ['pysnmp', 'pysnmp', None], # SNMP ['ldap3', 'ldap3', None], # LDAP ['smbus', 'smbus', None], # SMBus reading of temp sensors ['pyotp', 'pyotp', '2.3.0'], # 2FA support ['psutil', 'psutil', None], # process utilities ['chump', 'chump', None], # for genpushover ['twilio', 'twilio', None], # for gensms ['paho.mqtt.client', 'paho-mqtt', None], # for genmqtt ['OpenSSL', 'pyopenssl', None], # SSL ['spidev', 'spidev', None] # spidev ] try: ErrorOccured = False for Module in ModuleList: if not self.LibraryIsInstalled(Module[0]): self.LogInfo("Warning: required library " + Module[1] + " not installed. Attempting to install....") if not self.InstallLibrary(Module[1], version=Module[2]): self.LogInfo("Error: unable to install library " + Module[1]) ErrorOccured = True if Module[0] == "ldap3": # This will correct and issue with the ldap3 modbule not being recogonized in LibrayIsInstalled self.InstallLibrary("pyasn1", update=True) return not ErrorOccured except Exception as e1: self.LogInfo("Error in CheckSystem: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def CheckBaseSoftware(self): try: if self.PipChecked: return True if sys.version_info[0] < 3: pipProgram = "pip2" else: pipProgram = "pip3" install_list = [pipProgram, '-V'] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in CheckBaseSoftware : " + str(_error)) rc = process.returncode self.PipChecked = True return True except Exception as e1: self.LogInfo("Error in CheckBaseSoftware: " + str(e1), LogLine=True) self.InstallBaseSoftware() return False #--------------------------------------------------------------------------- def InstallBaseSoftware(self): try: if sys.version_info[0] < 3: pipProgram = "python-pip" else: pipProgram = "python3-pip" self.LogInfo("Installing " + pipProgram) install_list = ["sudo", "apt-get", "-yqq", "update"] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in InstallBaseSoftware : " + str(_error)) rc = process.returncode install_list = ["sudo", "apt-get", "-yqq", "install", pipProgram] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() return True except Exception as e1: self.LogInfo("Error in InstallBaseSoftware: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- @staticmethod def OneTimeMaint(ConfigFilePath, log): FileList = { "feedback.json": os.path.dirname(os.path.realpath(__file__)) + "/", "outage.txt": os.path.dirname(os.path.realpath(__file__)) + "/", "kwlog.txt": os.path.dirname(os.path.realpath(__file__)) + "/", "maintlog.json": os.path.dirname(os.path.realpath(__file__)) + "/", "Feedback_dat": os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/", "Message_dat": os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/", 'genmon.conf': "/etc/", 'genserv.conf': "/etc/", 'gengpio.conf': "/etc/", 'gengpioin.conf': "/etc/", 'genlog.conf': "/etc/", 'gensms.conf': "/etc/", 'gensms_modem.conf': "/etc/", 'genpushover.conf': "/etc/", 'gensyslog.conf': "/etc/", 'genmqtt.conf': "/etc/", 'genslack.conf': "/etc/", 'genexercise.conf': "/etc/", 'genemail2sms.conf': "/etc/", 'genloader.conf': "/etc/", 'mymail.conf': "/etc/", 'mymodem.conf': "/etc/" } try: # Check to see if we have done this already by checking files in the genmon source directory if (not os.path.isfile( os.path.dirname(os.path.realpath(__file__)) + "/genmonlib/Message_dat") and not os.path.isfile( os.path.dirname(os.path.realpath(__file__)) + "/maintlog.json") and not os.path.isfile( os.path.dirname(os.path.realpath(__file__)) + "/outage.txt") and not os.path.isfile( os.path.dirname(os.path.realpath(__file__)) + "/kwlog.txt") and not os.path.isfile("/etc/genmon.conf")): return False # validate target directory if not os.path.isdir(ConfigFilePath): try: os.mkdir(ConfigFilePath) if not os.access(ConfigFilePath + File, os.R_OK): pass except Exception as e1: log.error("Error validating target directory: " + str(e1), LogLine=True) # move files for File, Path in FileList.items(): try: SourceFile = Path + File if os.path.isfile(SourceFile): log.error("Moving " + SourceFile + " to " + ConfigFilePath) if not MySupport.CopyFile(SourceFile, ConfigFilePath + File, move=True, log=log): log.error("Error: using alternate move method") move(SourceFile, ConfigFilePath + File) if not os.access(ConfigFilePath + File, os.R_OK): pass except Exception as e1: log.error("Error moving " + SourceFile) except Exception as e1: log.error("Error moving files: " + str(e1), LogLine=True) return True #--------------------------------------------------------------------------- def FixPyOWMMaintIssues(self): try: # check version of pyowm import pyowm if sys.version_info[0] < 3: required_version = "2.9.0" else: required_version = "2.10.0" if not self.LibraryIsInstalled("pyowm"): self.LogError( "Error in FixPyOWMMaintIssues: pyowm not installed") return False installed_version = self.GetLibararyVersion("pyowm") if installed_version == None: self.LogError( "Error in FixPyOWMMaintIssues: pyowm version not found") return None if self.VersionTuple(installed_version) <= self.VersionTuple( required_version): return True self.LogInfo( "Found wrong version of pyowm, uninstalling and installing the correct version." ) self.InstallLibrary("pyowm", uninstall=True) self.InstallLibrary("pyowm", version=required_version) return True except Exception as e1: self.LogErrorLine("Error in FixPyOWMMaintIssues: " + str(e1)) return False #--------------------------------------------------------------------------- def GetLibararyVersion(self, libraryname, importonly=False): try: try: import importlib my_module = importlib.import_module(libraryname) return my_module.__version__ except: if importonly: return None # if we get here then the libarary does not support a __version__ attribute # lets use pip to get the version try: if sys.version_info[0] < 3: pipProgram = "pip2" else: pipProgram = "pip3" # This will check if pip is installed if "linux" in sys.platform: self.CheckBaseSoftware() install_list = [pipProgram, 'freeze', libraryname] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo( "Error in GetLibararyVersion using pip : " + libraryname + ": " + str(_error)) rc = process.returncode # process output of pip freeze lines = output.splitlines() for line in lines: line = line.decode("utf-8") line = line.strip() if line.startswith(libraryname): items = line.split('==') if len(items) <= 2: return items[1] return None except Exception as e1: self.LogInfo("Error getting version of module: " + libraryname + ": " + str(e1), LogLine=True) return None except Exception as e1: self.LogErrorLine("Error in GetLibararyVersion: " + str(e1)) return None #--------------------------------------------------------------------------- def LibraryIsInstalled(self, libraryname): try: import importlib my_module = importlib.import_module(libraryname) return True except Exception as e1: return False #--------------------------------------------------------------------------- def InstallLibrary(self, libraryname, update=False, version=None, uninstall=False): try: if sys.version_info[0] < 3: pipProgram = "pip2" else: pipProgram = "pip3" if version != None and uninstall == False: libraryname = libraryname + "==" + version # This will check if pip is installed if "linux" in sys.platform: self.CheckBaseSoftware() if update: install_list = [pipProgram, 'install', libraryname, '-U'] elif uninstall: install_list = [pipProgram, 'uninstall', '-y', libraryname] else: install_list = [pipProgram, 'install', libraryname] process = Popen(install_list, stdout=PIPE, stderr=PIPE) output, _error = process.communicate() if _error: self.LogInfo("Error in InstallLibrary using pip : " + libraryname + " : UnInstall: " + str(uninstall) + ": " + str(_error)) rc = process.returncode return True except Exception as e1: self.LogInfo("Error installing module: " + libraryname + " : UnInstall: " + str(uninstall) + ": " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def ValidateConfig(self): ErrorOccured = False if not len(self.CachedConfig): self.LogInfo("Error: Empty configruation found.") return False for Module, Settiings in self.CachedConfig.items(): try: if self.CachedConfig[Module]["enable"]: if not os.path.isfile( os.path.join(self.ModulePath, self.CachedConfig[Module]["module"])): self.LogInfo( "Enable to find file " + os.path.join(self.ModulePath, self.CachedConfig[Module]["module"])) ErrorOccured = True # validate config file and if it is not there then copy it. if not self.CachedConfig[Module]["conffile"] == None and len( self.CachedConfig[Module]["conffile"]): ConfFileList = self.CachedConfig[Module]["conffile"].split( ",") for ConfigFile in ConfFileList: ConfigFile = ConfigFile.strip() if not os.path.isfile( os.path.join(self.ConfigFilePath, ConfigFile)): if os.path.isfile( os.path.join(self.ConfPath, ConfigFile)): self.LogInfo("Copying " + ConfigFile + " to " + self.ConfigFilePath) copyfile( os.path.join(self.ConfPath, ConfigFile), os.path.join(self.ConfigFilePath, ConfigFile)) else: self.LogInfo( "Enable to find config file " + os.path.join(self.ConfPath, ConfigFile)) ErrorOccured = True except Exception as e1: self.LogInfo("Error validating config for " + Module + " : " + str(e1), LogLine=True) return False try: if not self.CachedConfig["genmon"]["enable"]: self.LogError( "Warning: Genmon is not enabled, assume corrupt file.") ErrorOccured = True if not self.CachedConfig["genserv"]["enable"]: self.LogError("Warning: Genserv is not enabled") except Exception as e1: self.LogErrorLine( "Error in ValidateConfig, possible corrupt file. " + str(e1)) ErrorOccured = True return not ErrorOccured #--------------------------------------------------------------------------- def AddEntry(self, section=None, module=None, conffile="", args="", priority='2'): try: if section == None or module == None: return self.config.WriteSection(section) self.config.WriteValue('module', module, section=section) self.config.WriteValue('enable', 'False', section=section) self.config.WriteValue('hardstop', 'False', section=section) self.config.WriteValue('conffile', conffile, section=section) self.config.WriteValue('args', args, section=section) self.config.WriteValue('priority', priority, section=section) except Exception as e1: self.LogInfo("Error in AddEntry: " + str(e1), LogLine=True) return #--------------------------------------------------------------------------- def UpdateIfNeeded(self): try: self.config.SetSection("gengpioin") if not self.config.HasOption('conffile'): self.config.WriteValue('conffile', "gengpioin.conf", section="gengpioin") self.LogError("Updated entry gengpioin.conf") else: defValue = self.config.ReadValue('conffile', default="") if not len(defValue): self.config.WriteValue('conffile', "gengpioin.conf", section="gengpioin") self.LogError("Updated entry gengpioin.conf") self.config.SetSection("gengpio") if not self.config.HasOption('conffile'): self.config.WriteValue('conffile', "gengpio.conf", section="gengpio") self.LogError("Updated entry gengpio.conf") else: defValue = self.config.ReadValue('conffile', default="") if not len(defValue): self.config.WriteValue('conffile', "gengpio.conf", section="gengpio") self.LogError("Updated entry gengpio.conf") # check version info self.config.SetSection("genloader") self.version = self.config.ReadValue("version", default="0.0.0") if self.version == "0.0.0" or not len(self.version): self.version = "0.0.0" self.NewInstall = True if self.VersionTuple(self.version) < self.VersionTuple( ProgramDefaults.GENMON_VERSION): self.Upgrade = True if self.NewInstall or self.Upgrade: self.config.WriteValue("version", ProgramDefaults.GENMON_VERSION, section='genloader') if self.NewInstall: self.LogInfo("Running one time maintenance check") self.FixPyOWMMaintIssues() # TODO other version checks can be added here self.version = ProgramDefaults.GENMON_VERSION except Exception as e1: self.LogInfo("Error in UpdateIfNeeded: " + str(e1), LogLine=True) #--------------------------------------------------------------------------- def GetConfig(self): try: Sections = self.config.GetSections() ValidSections = [ 'genmon', 'genserv', 'gengpio', 'gengpioin', 'genlog', 'gensms', 'gensms_modem', 'genpushover', 'gensyslog', 'genmqtt', 'genslack', 'genexercise', 'genemail2sms', 'gentankutil', 'gentankdiy', 'genalexa', 'gensnmp', 'gentemp', 'gengpioledblink', 'gencthat', 'genloader' ] for entry in ValidSections: if not entry in Sections: if entry == 'genmon' or entry == 'genserv': self.LogError("Warning: Missing entry: " + entry + " , file corruption. ") return False if entry == 'genslack': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genslack.py', conffile='genslack.conf') if entry == 'genexercise': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genexercise.py', conffile='genexercise.conf') if entry == 'genemail2sms': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genemail2sms.py', conffile='genemail2sms.conf') if entry == 'gentankutil': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gentankutil.py', conffile='gentankutil.conf') if entry == 'genalexa': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='genalexa.py', conffile='genalexa.conf') if entry == 'gensnmp': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gensnmp.py', conffile='gensnmp.conf') if entry == 'gentemp': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gentemp.py', conffile='gentemp.conf') if entry == 'gentankdiy': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gentankdiy.py', conffile='gentankdiy.conf') if entry == 'gengpioledblink': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gengpioledblink.py', conffile='gengpioledblink.conf') if entry == 'gencthat': self.LogError("Warning: Missing entry: " + entry + " , adding entry") self.AddEntry(section=entry, module='gencthat.py', conffile='gencthat.conf') if entry == 'genloader': self.LogError("Adding entry: " + entry) self.config.WriteSection(entry) else: self.LogError("Warning: Missing entry: " + entry) self.UpdateIfNeeded() Sections = self.config.GetSections() for SectionName in Sections: if SectionName == 'genloader': continue TempDict = {} self.config.SetSection(SectionName) if self.config.HasOption('module'): TempDict['module'] = self.config.ReadValue('module') else: self.LogError( "Error in GetConfig: expcting module in section " + str(SectionName)) TempDict['module'] = None if self.config.HasOption('enable'): TempDict['enable'] = self.config.ReadValue( 'enable', return_type=bool) else: self.LogError( "Error in GetConfig: expcting enable in section " + str(SectionName)) TempDict['enable'] = False if self.config.HasOption('hardstop'): TempDict['hardstop'] = self.config.ReadValue( 'hardstop', return_type=bool) else: self.LogError( "Error in GetConfig: expcting hardstop in section " + str(SectionName)) TempDict['hardstop'] = False if self.config.HasOption('conffile'): TempDict['conffile'] = self.config.ReadValue('conffile') else: self.LogError( "Error in GetConfig: expcting confile in section " + str(SectionName)) TempDict['conffile'] = None if self.config.HasOption('args'): TempDict['args'] = self.config.ReadValue('args') else: self.LogError( "Error in GetConfig: expcting args in section " + str(SectionName)) TempDict['args'] = None if self.config.HasOption('priority'): TempDict['priority'] = self.config.ReadValue( 'priority', return_type=int, default=None) else: self.LogError( "Error in GetConfig: expcting priority in section " + str(SectionName)) TempDict['priority'] = None if self.config.HasOption('postloaddelay'): TempDict['postloaddelay'] = self.config.ReadValue( 'postloaddelay', return_type=int, default=0) else: TempDict['postloaddelay'] = 0 if self.config.HasOption('pid'): TempDict['pid'] = self.config.ReadValue('pid', return_type=int, default=0, NoLog=True) else: TempDict['pid'] = 0 self.CachedConfig[SectionName] = TempDict return True except Exception as e1: self.LogInfo("Error parsing config file: " + str(e1), LogLine=True) return False return True #--------------------------------------------------------------------------- def ConvertToInt(self, value, default=None): try: return int(str(value)) except: return default #--------------------------------------------------------------------------- def GetLoadOrder(self): LoadOrder = [] LoadDict = {} try: for Module, Settiings in self.CachedConfig.items(): # get the load order of all modules, even if they are disabled # since we need to stop all modules (even disabled ones) if the # conf file changed try: if self.CachedConfig[Module]["priority"] == None: LoadDict[Module] = 99 elif self.CachedConfig[Module]["priority"] >= 0: LoadDict[Module] = self.CachedConfig[Module][ "priority"] else: LoadDict[Module] = 99 except Exception as e1: self.LogInfo("Error reading load order (retrying): " + str(e1), LogLine=True) #lambda kv: (-kv[1], kv[0]) for key, value in sorted(LoadDict.items(), key=lambda kv: (-kv[1], kv[0])): #for key, value in sorted(LoadDict.iteritems(), key=lambda (k,v): (v,k)): LoadOrder.append(key) except Exception as e1: self.LogInfo("Error reading load order: " + str(e1), LogLine=True) return LoadOrder #--------------------------------------------------------------------------- def StopModules(self): self.LogConsole("Stopping....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to stop.") return False ErrorOccured = False for Module in self.LoadOrder: try: if not self.UnloadModule( self.ModulePath, self.CachedConfig[Module]["module"], pid=self.CachedConfig[Module]["pid"], HardStop=self.CachedConfig[Module]["hardstop"], UsePID=True): self.LogInfo("Error stopping " + Module) ErrorOccured = True except Exception as e1: self.LogInfo("Error stopping module " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def StartModules(self): self.LogConsole("Starting....") if not len(self.LoadOrder): self.LogInfo("Error, nothing to start.") return False ErrorOccured = False for Module in reversed(self.LoadOrder): try: if self.CachedConfig[Module]["enable"]: if not multi_instance: # check that module is not loaded already, if it is then force it (hard) to unload attempts = 0 while True: if MySupport.IsRunning( prog_name=self.CachedConfig[Module] ["module"], log=self.log, multi_instance=multi_instance): # if loaded then kill it if attempts >= 4: # kill it if not self.UnloadModule( self.ModulePath, self.CachedConfig[Module] ["module"], pid=None, HardStop=True, UsePID=False): self.LogInfo("Error killing " + self.CachedConfig[Module] ["module"]) else: attempts += 1 time.sleep(1) else: break if not self.LoadModule( self.ModulePath, self.CachedConfig[Module]["module"], args=self.CachedConfig[Module]["args"]): self.LogInfo("Error starting " + Module) ErrorOccured = True if not self.CachedConfig[Module][ "postloaddelay"] == None and self.CachedConfig[ Module]["postloaddelay"] > 0: time.sleep(self.CachedConfig[Module]["postloaddelay"]) except Exception as e1: self.LogInfo("Error starting module " + Module + " : " + str(e1), LogLine=True) return False return not ErrorOccured #--------------------------------------------------------------------------- def LoadModuleAlt(self, modulename, args=None): try: self.LogConsole("Starting " + modulename) # to load as a background process we just use os.system since Popen # is problematic in doing this CommandString = sys.executable + " " + modulename if args != None and len(args): CommandString += " " + args CommandString += " &" os.system(CommandString) return True except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def LoadModule(self, path, modulename, args=None): try: try: import os fullmodulename = os.path.join(path, modulename) except Exception as e1: fullmodulename = path + "/" + modulename if args != None: self.LogConsole("Starting " + fullmodulename + " " + args) else: self.LogConsole("Starting " + fullmodulename) try: from subprocess import DEVNULL # py3k except ImportError: import os DEVNULL = open(os.devnull, 'wb') if not len(args): args = None if "genserv.py" in modulename: OutputStream = DEVNULL else: OutputStream = subprocess.PIPE executelist = [sys.executable, fullmodulename] if args != None: executelist.extend(args.split(" ")) # This will make all the programs use the same config files executelist.extend(["-c", self.ConfigFilePath]) # close_fds=True pid = subprocess.Popen(executelist, stdout=OutputStream, stderr=OutputStream, stdin=OutputStream) return self.UpdatePID(modulename, pid.pid) except Exception as e1: self.LogInfo("Error loading module " + path + ": " + modulename + ": " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def UnloadModule(self, path, modulename, pid=None, HardStop=False, UsePID=False): try: LoadInfo = [] if UsePID: if pid == None or pid == "" or pid == 0: return True LoadInfo.append("kill") if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append(str(pid)) else: LoadInfo.append('pkill') if HardStop or self.HardStop: LoadInfo.append('-9') LoadInfo.append('-u') LoadInfo.append('root') LoadInfo.append('-f') LoadInfo.append(modulename) self.LogConsole("Stopping " + modulename) process = Popen(LoadInfo, stdout=PIPE) output, _error = process.communicate() rc = process.returncode return self.UpdatePID(modulename, "") except Exception as e1: self.LogInfo("Error loading module: " + str(e1), LogLine=True) return False #--------------------------------------------------------------------------- def UpdatePID(self, modulename, pid=None): try: filename = os.path.splitext(modulename)[0] # remove extension if not self.config.SetSection(filename): self.LogError("Error settting section name in UpdatePID: " + str(filename)) return False self.config.WriteValue("pid", str(pid)) return True except Exception as e1: self.LogInfo("Error writing PID for " + modulename + " : " + str(e1)) return False return True