port, loglocation = MySupport.GetGenmonInitInfo(ConfigFilePath, log=console) log = SetupLogger("client", loglocation + "gengpio.log") except Exception as e1: console.error("Error : " + str(e1)) sys.exit(1) try: # Set the signal handler signal.signal(signal.SIGINT, signal_handler) MyClientInterface = ClientInterface(host=address, port=port, log=log) #setup GPIO using Board numbering GPIO.setmode(GPIO.BOARD) console.info(GPIO.RPI_INFO) GPIO.setwarnings(False) # These are the GPIP pins numbers on the Raspberry PI GPIO header # https://www.element14.com/community/servlet/JiveServlet/previewBody/73950-102-10-339300/pi3_gpio.png STATUS_READY = 16 # READY GPIO 23 (pin 16) STATUS_ALARM = 18 # ALARM GPIO 24 (pin 18) STATUS_SERVICE = 22 # SERVICE DUE GPIO 25 (pin 22) STATUS_RUNNING = 26 # RUNNING GPIO 7 (pin 26) STATUS_EXERCISING = 24 # EXERCISING GPIO 8 (pin 24) STATUS_OFF = 21 # OFF GPIO 9 (pin 21) # Set additional GPIO based on these error codes ER_GENMON = 3 # Genmon is reporting errors due to modbus or internal problems GPIO 2(pin3)
class ClientInterface(MyCommon): def __init__(self, host=ProgramDefaults.LocalHost, port=ProgramDefaults.ServerPort, log=None, loglocation=ProgramDefaults.LogPath): super(ClientInterface, self).__init__() if log != None: self.log = log else: # log errors in this module to a file self.log = SetupLogger("client", os.path.join(loglocation, "myclient.log")) self.console = SetupLogger("client_console", log_file="", stream=True) self.AccessLock = threading.RLock() self.EndOfMessage = "EndOfMessage" self.rxdatasize = 2000 self.host = host self.port = port self.max_reties = 10 self.Connect() #---------- ClientInterface::Connect -------------------------------------- def Connect(self): retries = 0 while True: try: #create an INET, STREAMing socket self.Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #now connect to the server on our port self.Socket.connect((self.host, self.port)) sRetData, data = self.Receive( noeom=True) # Get initial status before commands are sent self.console.info(data) return except Exception as e1: retries += 1 if retries >= self.max_reties: self.LogErrorLine("Error: Connect : " + str(e1)) self.console.error("Genmon not loaded.") sys.exit(1) else: time.sleep(1) continue #---------- ClientInterface::SendCommand ---------------------------------- def SendCommand(self, cmd): try: self.Socket.sendall(cmd.encode("utf-8")) except Exception as e1: self.LogErrorLine("Error: TX: " + str(e1)) self.Close() self.Connect() #---------- ClientInterface::Receive -------------------------------------- def Receive(self, noeom=False): with self.AccessLock: RetStatus = True try: bytedata = self.Socket.recv(self.rxdatasize) data = bytedata.decode("utf-8") if len(data): if not self.CheckForStarupMessage(data) or not noeom: while not self.EndOfMessage in data: morebytes = self.Socket.recv(self.rxdatasize) more = morebytes.decode("utf-8") if len(more): if self.CheckForStarupMessage(more): data = "" RetStatus = False break data += more if data.endswith(self.EndOfMessage): data = data[:-len(self.EndOfMessage)] RetStatus = True else: self.Connect() return False, data except Exception as e1: self.LogErrorLine("Error: RX:" + str(e1)) self.Close() self.Connect() RetStatus = False data = "Retry" return RetStatus, data #---------- ClientInterface::CheckForStarupMessage ------------------------ def CheckForStarupMessage(self, data): # check for initial status response from monitor if data.startswith("OK") or data.startswith( "CRITICAL:") or data.startswith("WARNING:"): return True else: return False #---------- ClientInterface::Close ---------------------------------------- def Close(self): self.Socket.close() #---------- ClientInterface::ProcessMonitorCommand ------------------------ def ProcessMonitorCommand(self, cmd): data = "" try: with self.AccessLock: RetStatus = False while RetStatus == False: self.SendCommand(cmd) RetStatus, data = self.Receive() except Exception as e1: self.LogErrorLine("Error in ProcessMonitorCommand:" + str(e1)) return data
class GenNotify(MyCommon): def __init__(self, host="127.0.0.1", port=9082, log=None, onready=None, onexercise=None, onrun=None, onrunmanual=None, onalarm=None, onservice=None, onoff=None, onmanual=None, onutilitychange=None): super(GenNotify, self).__init__() self.AccessLock = threading.Lock() self.Threads = {} self.LastEvent = None self.LastOutageStatus = None self.Events = {} # Dict for handling events if log != None: self.log = log else: # log errors in this module to a file self.log = SetupLogger("client", "/var/log/myclient.log") self.console = SetupLogger("notify_console", log_file="", stream=True) try: # init event callbacks if onready != None: self.Events["READY"] = onready if onexercise != None: self.Events["EXERCISING"] = onexercise if onrun != None: self.Events["RUNNING"] = onrun if onrunmanual != None: self.Events["RUNNING-MANUAL"] = onrunmanual if onalarm != None: self.Events["ALARM"] = onalarm if onservice != None: self.Events["SERVICEDUE"] = onservice if onoff != None: self.Events["OFF"] = onoff if onmanual != None: self.Events["MANUAL"] = onmanual if onutilitychange != None: self.Events["OUTAGE"] = onutilitychange startcount = 0 while startcount <= 10: try: self.Generator = ClientInterface(host=host, log=log) break except Exception as e1: startcount += 1 if startcount >= 10: self.console.info("genmon not loaded.") sys.exit(1) time.sleep(1) continue # start thread to accept incoming sockets for nagios heartbeat self.Threads["PollingThread"] = MyThread(self.MainPollingThread, Name="PollingThread") except Exception as e1: self.LogErrorLine("Error in mynotify init: " + str(e1)) # ---------- GenNotify::MainPollingThread----------------------------------- def MainPollingThread(self): while True: try: data = self.SendCommand("generator: getbase") outagedata = self.SendCommand("generator: outage_json") try: OutageDict = collections.OrderedDict() OutageDict = json.loads(outagedata) OutageState = True if OutageDict["Outage"][ "System In Outage"].lower() == "yes" else False except Exception as e1: # The system does no support outage tracking (i.e. H-100) #self.LogErrorLine("Unable to get outage state: " + str(e1)) OutageState = None if OutageState != None: self.ProcessOutageState(OutageState) if self.LastEvent == data: time.sleep(3) continue if self.LastEvent != None: self.console.info("Last : <" + self.LastEvent + ">, New : <" + data + ">") self.CallEventHandler(False) # end last event self.LastEvent = data self.CallEventHandler(True) # begin new event time.sleep(3) except Exception as e1: self.LogErrorLine("Error in mynotify:MainPollingThread: " + str(e1)) time.sleep(3) #---------- GenNotify::CallEventHandler ----------------------------------- def CallEventHandler(self, Status): try: if self.LastEvent == None: return EventCallback = self.Events.get(self.LastEvent, None) # Event has ended if EventCallback != None: if callable(EventCallback): EventCallback(Status) else: self.LogError("Invalid Callback in CallEventHandler : " + str(EventCallback)) else: self.LogError("Invalid Callback in CallEventHandler : None") except Exception as e1: self.LogErrorLine("Error in CallEventHandler: " + str(e1)) #---------- GenNotify::ProcessOutageState --------------------------------- def ProcessOutageState(self, outagestate): try: if self.LastOutageStatus == outagestate: return self.LastOutageStatus = outagestate EventCallback = self.Events.get("OUTAGE", None) if EventCallback != None: if callable(EventCallback): EventCallback(self.LastOutageStatus) else: self.LogError("Invalid Callback in ProcessOutageState : " + str(EventCallback)) else: self.LogError("Invalid Callback in ProcessOutageState : None") except Exception as e1: self.LogErrorLine("Error in ProcessOutageState: " + str(e1)) #---------- GenNotify::SendCommand ---------------------------------------- def SendCommand(self, Command): if len(Command) == 0: return "Invalid Command" try: with self.AccessLock: data = self.Generator.ProcessMonitorCommand(Command) except Exception as e1: self.LogErrorLine("Error calling ProcessMonitorCommand: " + str(Command)) data = "" return data #---------- GenNotify::Close ---------------------------------------------- def Close(self): self.Generator.Close() return False
class MyGenPush(MySupport): #------------ MyGenPush::init----------------------------------------------- def __init__(self, host=ProgramDefaults.LocalHost, port=ProgramDefaults.ServerPort, log = None, callback = None, polltime = None, blacklist = None, flush_interval = float('inf'), use_numeric = False, debug = False, loglocation = ProgramDefaults.LogPath): super(MyGenPush, self).__init__() self.Callback = callback self.UseNumeric = use_numeric self.Debug = debug if polltime == None: self.PollTime = 3 else: self.PollTime = float(polltime) if log != None: self.log = log else: # log errors in this module to a file self.log = SetupLogger("client", loglocation + "mygenpush.log") self.console = SetupLogger("mygenpush_console", log_file = "", stream = True) self.AccessLock = threading.Lock() self.BlackList = blacklist self.LastValues = {} self.FlushInterval = flush_interval self.LastChange = {} try: startcount = 0 while startcount <= 10: try: self.Generator = ClientInterface(host = host, port = port, log = log) break except Exception as e1: startcount += 1 if startcount >= 10: self.console.info("genmon not loaded.") self.LogError("Unable to connect to genmon.") sys.exit(1) time.sleep(1) continue # start thread to accept incoming sockets for nagios heartbeat self.Threads["PollingThread"] = MyThread(self.MainPollingThread, Name = "PollingThread") except Exception as e1: self.LogErrorLine("Error in mygenpush init: " + str(e1)) #---------- MyGenPush::SendCommand ---------------------------------------- def SendCommand(self, Command): if len(Command) == 0: return "Invalid Command" try: with self.AccessLock: data = self.Generator.ProcessMonitorCommand(Command) except Exception as e1: self.LogErrorLine("Error calling ProcessMonitorCommand: " + str(Command)) data = "" return data # ---------- MyGenPush::MainPollingThread----------------------------------- def MainPollingThread(self): while True: try: if not self.UseNumeric: statusdata = self.SendCommand("generator: status_json") else: statusdata = self.SendCommand("generator: status_num_json") outagedata = self.SendCommand("generator: outage_json") monitordata = self.SendCommand("generator: monitor_json") maintdata = self.SendCommand("generator: maint_json") try: GenmonDict = {} TempDict = {} TempDict = json.loads(statusdata) GenmonDict["Status"] = TempDict["Status"] TempDict = json.loads(maintdata) GenmonDict["Maintenance"] = TempDict["Maintenance"] TempDict = json.loads(outagedata) GenmonDict["Outage"] = TempDict["Outage"] TempDict = json.loads(monitordata) GenmonDict["Monitor"] = TempDict["Monitor"] self.CheckDictForChanges(GenmonDict, "generator") except Exception as e1: self.LogErrorLine("Unable to get status: " + str(e1)) if self.WaitForExit("PollingThread", float(self.PollTime)): return except Exception as e1: self.LogErrorLine("Error in mynotify:MainPollingThread: " + str(e1)) if self.WaitForExit("PollingThread", float(self.PollTime)): return #------------ MySupport::CheckDictForChanges ------------------------------- # This function is recursive, it will turn a nested dict into a flat dict keys # that have a directory structure with corrposonding values and determine if # anyting changed. If it has then call our callback function def CheckDictForChanges(self, node, PathPrefix): CurrentPath = PathPrefix if not isinstance(PathPrefix, str): return "" if isinstance(node, dict): for key, item in node.items(): if isinstance(item, dict): CurrentPath = PathPrefix + "/" + str(key) self.CheckDictForChanges(item, CurrentPath) elif isinstance(item, list): CurrentPath = PathPrefix + "/" + str(key) for listitem in item: if isinstance(listitem, dict): self.CheckDictForChanges(listitem, CurrentPath) elif isinstance(listitem, str) or isinstance(listitem, unicode): CurrentPath = PathPrefix + "/" + str(key) #todo list support pass else: self.LogError("Invalid type in CheckDictForChanges: %s %s (2)" % (key, str(type(listitem)))) else: CurrentPath = PathPrefix + "/" + str(key) self.CheckForChanges(CurrentPath, item) else: self.LogError("Invalid type in CheckDictForChanges %s " % str(type(node))) # ---------- MyGenPush::CheckForChanges------------------------------------- def CheckForChanges(self, Path, Value): try: if self.BlackList != None: for BlackItem in self.BlackList: if BlackItem.lower() in Path.lower(): return LastValue = self.LastValues.get(str(Path), None) LastChange = self.LastChange.get(str(Path), 0) if LastValue == None or LastValue != Value or (time.time() - LastChange) > self.FlushInterval: self.LastValues[str(Path)] = Value self.LastChange[str(Path)] = time.time() if self.Callback != None: self.Callback(str(Path), Value) except Exception as e1: self.LogErrorLine("Error in mygenpush:CheckForChanges: " + str(e1)) # ---------- MyGenPush::Close----------------------------------------------- def Close(self): self.KillThread("PollingThread") self.Generator.Close()
#------------------- Command-line interface for gengpioin ---------------------- if __name__ == '__main__': # usage program.py [server_address] address = '127.0.0.1' if len(sys.argv) < 2 else sys.argv[1] try: console = SetupLogger("gengpioin_console", log_file="", stream=True) log = SetupLogger("client", "/var/log/gengpioin.log") # Set the signal handler signal.signal(signal.SIGINT, signal_handler) MyClientInterface = ClientInterface(host=address, log=log) #setup GPIO using Board numbering GPIO.setmode(GPIO.BOARD) console.info(GPIO.RPI_INFO) GPIO.setwarnings(True) GPIO.setup( INPUT_STOP, GPIO.IN, pull_up_down=GPIO.PUD_UP) # input, set internal pull up resistor# GPIO.add_event_detect(INPUT_STOP, GPIO.FALLING) # detect falling edge GPIO.add_event_callback(INPUT_STOP, callback=StopCallBack) #, bouncetime=1000) GPIO.setup( INPUT_START, GPIO.IN, pull_up_down=GPIO.PUD_UP) # input, set internal pull up resistor# GPIO.add_event_detect(INPUT_START, GPIO.FALLING) # detect falling edge GPIO.add_event_callback(INPUT_START,
class MyMQTT(MyCommon): #------------ MyMQTT::init-------------------------------------------------- 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) #------------ MyMQTT::PublishCallback--------------------------------------- # Callback to publish data via MQTT def PublishCallback(self, name, value): try: if self.TopicRoot != None and len(self.TopicRoot): FullPath = self.TopicRoot + "/" + str(name) else: FullPath = str(name) if self.Debug: self.console.info("Publish: " + FullPath + ": " + str(value) + ": " + str(type(value))) self.MQTTclient.publish(FullPath, value) except Exception as e1: self.LogErrorLine("Error in MyMQTT:PublishCallback: " + str(e1)) #------------ MyMQTT::on_connect-------------------------------------------- # The callback for when the client receives a CONNACK response from the server. def on_connect(self, client, userdata, flags, rc): if rc != 0: self.LogError("Error connecting to MQTT server: return code: " + str(rc)) self.console.info("Connected with result code "+str(rc)) # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. self.MQTTclient.subscribe(self.Topic + "/#") #------------ MyMQTT::on_message-------------------------------------------- # The callback for when a PUBLISH message is received from the server. def on_message(self, client, userdata, msg): if self.Debug: self.console.info("Confirmed: " + msg.topic + ": " + str(msg.payload)) # ---------- MyMQTT::Close-------------------------------------------------- def Close(self): self.Push.Close()
class GenTankData(MySupport): #------------ GenTankData::init--------------------------------------------- def __init__(self, log=None, loglocation=ProgramDefaults.LogPath, ConfigFilePath=MyCommon.DefaultConfPath, host=ProgramDefaults.LocalHost, port=ProgramDefaults.ServerPort): super(GenTankData, self).__init__() self.LogFileName = loglocation + "gentankutil.log" self.AccessLock = threading.Lock() # log errors in this module to a file self.log = SetupLogger("gentankutil", self.LogFileName) self.console = SetupLogger("gentankutil_console", log_file="", stream=True) self.MonitorAddress = host self.PollTime = 2 self.TankID = "" self.debug = False configfile = ConfigFilePath + 'gentankutil.conf' try: if not os.path.isfile(configfile): self.LogConsole("Missing config file : " + configfile) self.LogError("Missing config file : " + configfile) sys.exit(1) self.config = MyConfig(filename=configfile, section='gentankutil', log=self.log) self.PollTime = self.config.ReadValue('poll_frequency', return_type=float, default=60) self.debug = self.config.ReadValue('debug', return_type=bool, default=False) self.username = self.config.ReadValue('username', default="") self.password = self.config.ReadValue('password', default="") self.tank_name = self.config.ReadValue('tank_name', default="") if self.MonitorAddress == None or not len(self.MonitorAddress): self.MonitorAddress = ProgramDefaults.LocalHost except Exception as e1: self.LogErrorLine("Error reading " + configfile + ": " + str(e1)) self.LogConsole("Error reading " + configfile + ": " + str(e1)) sys.exit(1) if self.username == "" or self.username == None or self.password == "" or self.password == None: self.LogError("Invalid user name or password, exiting") sys.exit(1) try: try: startcount = 0 while startcount <= 10: try: self.Generator = ClientInterface( host=self.MonitorAddress, port=port, log=self.log) break except Exception as e1: startcount += 1 if startcount >= 10: self.console.info("genmon not loaded.") self.LogError("Unable to connect to genmon.") sys.exit(1) time.sleep(1) continue except Exception as e1: self.LogErrorLine("Error in GenTankData init: " + str(e1)) #if not self.CheckGeneratorRequirement(): # self.LogError("Requirements not met. Exiting.") # sys.exit(1) self.tank = tankutility(self.username, self.password, self.log, debug=self.debug) # start thread monitor time for exercise self.Threads["TankCheckThread"] = MyThread(self.TankCheckThread, Name="TankCheckThread", start=False) self.Threads["TankCheckThread"].Start() atexit.register(self.Close) signal.signal(signal.SIGTERM, self.Close) signal.signal(signal.SIGINT, self.Close) except Exception as e1: self.LogErrorLine("Error in GenTankData init: " + str(e1)) self.console.error("Error in GenTankData init: " + str(e1)) sys.exit(1) #---------- GenTankData::SendCommand -------------------------------------- def SendCommand(self, Command): if len(Command) == 0: return "Invalid Command" try: with self.AccessLock: data = self.Generator.ProcessMonitorCommand(Command) except Exception as e1: self.LogErrorLine("Error calling ProcessMonitorCommand: " + str(Command)) data = "" return data #---------- GenTankData::CheckGeneratorRequirement ------------------------ def CheckGeneratorRequirement(self): try: data = self.SendCommand("generator: start_info_json") StartInfo = {} StartInfo = json.loads(data) if not "evolution" in StartInfo["Controller"].lower( ) and not "nexus" in StartInfo["Controller"].lower(): self.LogError( "Error: Only Evolution or Nexus controllers are supported for this feature: " + StartInfo["Controller"]) return False return True except Exception as e1: self.LogErrorLine("Error in CheckGeneratorRequirement: " + str(e1)) return False # ---------- GenTankData::Login--------------------------------------------- def Login(self, force=False): if force: self.TankID = "" if len(self.TankID): # already logged in return True if not self.tank.Login(): return False self.TankID = self.tank.GetIDFromName(self.tank_name) if not len(self.TankID): return False return True # ---------- GenTankData::TankCheckThread----------------------------------- def TankCheckThread(self): time.sleep(1) LastLoginTime = datetime.datetime.now() while True: try: NUMBER_OF_SECONDS = 60 * 60 * 12 # 12 hours if ((datetime.datetime.now() - LastLoginTime).total_seconds() > NUMBER_OF_SECONDS) or not len(self.TankID): self.LogDebug("Login ") if not self.Login(force=True): self.LogError( "Error logging in in TankCheckThread, retrying") dataforgenmon = {} tankdata = self.tank.GetData(self.TankID) if tankdata != None: dataforgenmon["Tank Name"] = tankdata["name"] dataforgenmon["Capacity"] = self.tank.GetCapacity() dataforgenmon["Percentage"] = self.tank.GetPercentage() retVal = self.SendCommand("generator: set_tank_data=" + json.dumps(dataforgenmon)) self.LogDebug(retVal) if self.WaitForExit("TankCheckThread", float(self.PollTime * 60)): return except Exception as e1: self.LogErrorLine("Error in TankCheckThread: " + str(e1)) if self.WaitForExit("TankCheckThread", float(self.PollTime * 60)): return # ----------GenTankData::Close---------------------------------------------- def Close(self): self.KillThread("TankCheckThread") self.Generator.Close()
class GenTankData(MySupport): # The device is the ADS1115 I2C ADC # reference python http://www.smartypies.com/projects/ads1115-with-raspberrypi-and-python/ads1115runner/ RESET_ADDRESS = 0b0000000 RESET_COMMAND = 0b00000110 POINTER_CONVERSION = 0x0 POINTER_CONFIGURATION = 0x1 POINTER_LOW_THRESHOLD = 0x2 POINTER_HIGH_THRESHOLD = 0x3 #------------ GenTankData::init--------------------------------------------- def __init__(self, log=None, loglocation=ProgramDefaults.LogPath, ConfigFilePath=MyCommon.DefaultConfPath, host=ProgramDefaults.LocalHost, port=ProgramDefaults.ServerPort): super(GenTankData, self).__init__() self.LogFileName = os.path.join(loglocation, "gentankdiy.log") self.AccessLock = threading.Lock() # log errors in this module to a file self.log = SetupLogger("gentankdiy", self.LogFileName) self.console = SetupLogger("gentankdiy_console", log_file="", stream=True) self.MonitorAddress = host self.PollTime = 2 self.debug = False configfile = os.path.join(ConfigFilePath, 'gentankdiy.conf') try: if not os.path.isfile(configfile): self.LogConsole("Missing config file : " + configfile) self.LogError("Missing config file : " + configfile) sys.exit(1) self.config = MyConfig(filename=configfile, section='gentankdiy', log=self.log) self.PollTime = self.config.ReadValue('poll_frequency', return_type=float, default=60) self.debug = self.config.ReadValue('debug', return_type=bool, default=False) self.i2c_address = self.config.ReadValue('i2c_address', return_type=int, default=72) self.mv_per_step = self.config.ReadValue('mv_per_step', return_type=int, default=125) self.Multiplier = self.config.ReadValue( 'volts_to_percent_multiplier', return_type=float, default=20.0) # I2C channel 1 is connected to the GPIO pins self.i2c_channel = self.config.ReadValue('i2c_channel', return_type=int, default=1) if self.MonitorAddress == None or not len(self.MonitorAddress): self.MonitorAddress = ProgramDefaults.LocalHost except Exception as e1: self.LogErrorLine("Error reading " + configfile + ": " + str(e1)) self.LogConsole("Error reading " + configfile + ": " + str(e1)) sys.exit(1) try: try: startcount = 0 while startcount <= 10: try: self.Generator = ClientInterface( host=self.MonitorAddress, port=port, log=self.log) break except Exception as e1: startcount += 1 if startcount >= 10: self.console.info("genmon not loaded.") self.LogError("Unable to connect to genmon.") sys.exit(1) time.sleep(1) continue except Exception as e1: self.LogErrorLine("Error in GenTankData init: " + str(e1)) # start thread monitor time for exercise self.Threads["TankCheckThread"] = MyThread(self.TankCheckThread, Name="TankCheckThread", start=False) if not self.InitADC(): self.LogError("InitADC failed, exiting") sys.exit(1) self.Threads["TankCheckThread"].Start() atexit.register(self.Close) signal.signal(signal.SIGTERM, self.Close) signal.signal(signal.SIGINT, self.Close) except Exception as e1: self.LogErrorLine("Error in GenTankData init: " + str(e1)) self.console.error("Error in GenTankData init: " + str(e1)) sys.exit(1) #---------- GenTankData::SendCommand -------------------------------------- def SendCommand(self, Command): if len(Command) == 0: return "Invalid Command" try: with self.AccessLock: data = self.Generator.ProcessMonitorCommand(Command) except Exception as e1: self.LogErrorLine("Error calling ProcessMonitorCommand: " + str(Command)) data = "" return data # ---------- GenTankData::InitADC------------------------------------------- def InitADC(self): try: # I2C channel 1 is connected to the GPIO pins self.I2Cbus = smbus.SMBus(self.i2c_channel) # Reset ADC self.I2Cbus.write_byte(self.RESET_ADDRESS, self.RESET_COMMAND) # set config register and start conversion # ANC1 and GND, 4.096v, 128s/s # Customized - Port A0 and 4.096 V input # 0b11000011; # bit 15-8 = 0xC3 # bit 15 flag bit for single shot # Bits 14-12 input selection: # 100 ANC0; 101 ANC1; 110 ANC2; 111 ANC3 # Bits 11-9 Amp gain. Default to 010 here 001 P19 # Bit 8 Operational mode of the ADS1115. # 0 : Continuous conversion mode # 1 : Power-down single-shot mode (default) CONFIG_VALUE_1 = 0xC3 # bits 7-0 0b10000101 = 0x85 # Bits 7-5 data rate default to 100 for 128SPS # Bits 4-0 comparator functions see spec sheet. CONFIG_VALUE_2 = 0x85 self.I2Cbus.write_i2c_block_data(self.i2c_address, self.POINTER_CONFIGURATION, [CONFIG_VALUE_1, CONFIG_VALUE_2]) self.LogDebug("I2C Init complete: success") except Exception as e1: self.LogErrorLine("Error calling InitADC: " + str(e1)) return False return True # ---------- GenTankData::GetGaugeData-------------------------------------- def GetGaugeData(self): try: val = self.I2Cbus.read_i2c_block_data(self.i2c_address, self.POINTER_CONVERSION, 2) self.LogDebug(str(val)) # convert display results reading = val[0] << 8 | val[1] if (reading < 0): reading = 0 #reading = self.I2Cbus.read_word_data(self.i2c_address, self.i2c_channel) volts = round( float(reading * (float(self.mv_per_step) / 1000000.0)), 2) gauge_data = float(self.Multiplier) * volts self.LogDebug("Reading Gauge Data: %4.2f%%" % gauge_data) return gauge_data except Exception as e1: self.LogErrorLine("Error calling GetGaugeData: " + str(e1)) return 0.0 # ---------- GenTankData::TankCheckThread----------------------------------- def TankCheckThread(self): time.sleep(1) while True: try: dataforgenmon = {} tankdata = self.GetGaugeData() if tankdata != None: dataforgenmon["Tank Name"] = "External Tank" dataforgenmon["Capacity"] = 0 dataforgenmon["Percentage"] = self.GetGaugeData() retVal = self.SendCommand("generator: set_tank_data=" + json.dumps(dataforgenmon)) self.LogDebug(retVal) if self.WaitForExit("TankCheckThread", float(self.PollTime * 60)): return except Exception as e1: self.LogErrorLine("Error in TankCheckThread: " + str(e1)) if self.WaitForExit("TankCheckThread", float(self.PollTime * 60)): return # ----------GenTankData::Close---------------------------------------------- def Close(self): self.KillThread("TankCheckThread") self.Generator.Close()
class GenExercise(MySupport): #------------ GenExercise::init--------------------------------------------- def __init__(self, log = None, loglocation = ProgramDefaults.LogPath, ConfigFilePath = MyCommon.DefaultConfPath, host = ProgramDefaults.LocalHost, port = ProgramDefaults.ServerPort): super(GenExercise, self).__init__() self.LogFileName = loglocation + "genexercise.log" self.AccessLock = threading.Lock() # log errors in this module to a file self.log = SetupLogger("genexercise", self.LogFileName) self.console = SetupLogger("genexercise_console", log_file = "", stream = True) self.MonitorAddress = host self.PollTime = 2 self.ExerciseActive = False self.Debug = False try: self.config = MyConfig(filename = ConfigFilePath + 'genexercise.conf', section = 'genexercise', log = self.log) self.ExerciseType = self.config.ReadValue('exercise_type', default = "Normal") self.ExerciseHour = self.config.ReadValue('exercise_hour', return_type = int, default = 12) self.ExerciseMinute = self.config.ReadValue('exercise_minute', return_type = int, default = 0) self.ExerciseDayOfMonth = self.config.ReadValue('exercise_day_of_month', return_type = int, default = 1) self.ExerciseDayOfWeek = self.config.ReadValue('exercise_day_of_week', default = "Monday") self.ExerciseDuration = self.config.ReadValue('exercise_duration', return_type = float, default = 12) self.ExerciseWarmup = self.config.ReadValue('exercise_warmup', return_type = float, default = 0) self.ExerciseFrequency = self.config.ReadValue('exercise_frequency', default = "Monthly") self.MonitorAddress = self.config.ReadValue('monitor_address', default = ProgramDefaults.LocalHost) self.LastExerciseTime = self.config.ReadValue('last_exercise', default = None) self.UseGeneratorTime = self.config.ReadValue('use_gen_time', return_type = bool, default = False) self.Debug = self.config.ReadValue('debug', return_type = bool, default = False) # Validate settings if not self.ExerciseType.lower() in ["normal","quiet", "transfer"]: self.ExerciseType = "normal" if self.ExerciseHour > 23 or self.ExerciseHour < 0: self.ExerciseHour = 12 if self.ExerciseMinute > 59 or self.ExerciseMinute < 0: self.ExerciseMinute = 0 if not self.ExerciseDayOfWeek.lower() in ["monday", "tuesday", "wednesday","thursday","friday","saturday","sunday"]: self.ExerciseDayOfWeek = "Monday" if self.ExerciseDayOfMonth > 28 or self.ExerciseDayOfMonth < 1: self.ExerciseDayOfMonth = 1 if self.ExerciseDuration > 60: self.ExerciseDuration = 60 if self.ExerciseDuration < 5: self.ExerciseDuration = 5 if self.ExerciseWarmup > 30: self.ExerciseWarmup = 30 if self.ExerciseWarmup < 0: self.ExerciseWarmup = 0 if not self.ExerciseFrequency.lower() in ["weekly","biweekly", "monthly"]: self.ExerciseFrequency = "Monthly" if self.MonitorAddress == None or not len(self.MonitorAddress): self.MonitorAddress = ProgramDefaults.LocalHost except Exception as e1: self.LogErrorLine("Error reading " + ConfigFilePath + "genexercise.conf: " + str(e1)) self.console.error("Error reading " + ConfigFilePath + "genexercise.conf: " + str(e1)) sys.exit(1) try: try: startcount = 0 while startcount <= 10: try: self.Generator = ClientInterface(host = self.MonitorAddress, port = port, log = self.log) break except Exception as e1: startcount += 1 if startcount >= 10: self.console.info("genmon not loaded.") self.LogError("Unable to connect to genmon.") sys.exit(1) time.sleep(1) continue except Exception as e1: self.LogErrorLine("Error in genexercise init: " + str(e1)) if not self.CheckGeneratorRequirement(): self.LogError("Requirements not met. Exiting.") sys.exit(1) # start thread monitor time for exercise self.Threads["ExerciseThread"] = MyThread(self.ExerciseThread, Name = "ExerciseThread", start = False) self.Threads["ExerciseThread"].Start() try: if self.ExerciseFrequency.lower() == "monthly": DayStr = "Day " + str(self.ExerciseDayOfMonth) else: DayStr = str(self.ExerciseDayOfWeek) self.LogError("Execise: " + self.ExerciseType + ", " + self.ExerciseFrequency + " at " + str(self.ExerciseHour) + ":" + str(self.ExerciseMinute) + " on " + DayStr + " for " + str(self.ExerciseDuration) + " min. Warmup: " + str(self.ExerciseWarmup)) self.DebugOutput("Debug Enabled") except Exception as e1: self.LogErrorLine(str(e1)) atexit.register(self.Close) signal.signal(signal.SIGTERM, self.Close) signal.signal(signal.SIGINT, self.Close) except Exception as e1: self.LogErrorLine("Error in GenExercise init: " + str(e1)) self.console.error("Error in GenExercise init: " + str(e1)) sys.exit(1) #---------- GenExercise::SendCommand -------------------------------------- def SendCommand(self, Command): if len(Command) == 0: return "Invalid Command" try: with self.AccessLock: data = self.Generator.ProcessMonitorCommand(Command) except Exception as e1: self.LogErrorLine("Error calling ProcessMonitorCommand: " + str(Command)) data = "" return data #---------- GenExercise::CheckGeneratorRequirement ------------------------ def CheckGeneratorRequirement(self): try: data = self.SendCommand("generator: start_info_json") StartInfo = {} StartInfo = json.loads(data) if not "evolution" in StartInfo["Controller"].lower() and not "nexus" in StartInfo["Controller"].lower(): self.LogError("Error: Only Evolution or Nexus controllers are supported for this feature: " + StartInfo["Controller"]) return False return True except Exception as e1: self.LogErrorLine("Error in CheckGeneratorRequirement: " + str(e1)) return False # ---------- GenExercise::PostWarmup---------------------------------------- def PostWarmup(self): # check to see if the generator is running status = self.SendCommand("generator: getbase") if not status.lower() in ["running", "exercising"]: self.LogError("WARNING: generator not running post warmup. Transfer switch not activated.") self.SendCommand("generator: setremote=stop") return self.SendCommand("generator: setremote=starttransfer") self.DebugOutput("Starting transfer exercise cycle (post warmup).") # set timer to stop self.StopTimer = threading.Timer(float(self.ExerciseDuration * 60.0), self.StopExercise) self.StopTimer.start() # ---------- GenExercise::ReadyToExercise----------------------------------- def ReadyToExercise(self): status = self.SendCommand("generator: getbase") if not status.lower() in ["ready"]: self.LogError("Generator not in Ready state, exercise cycle not started") return False return True # ---------- GenExercise::StartExercise------------------------------------- def StartExercise(self): if self.ExerciseActive: # already active return # Start generator if self.ExerciseType.lower() == "normal" and self.ReadyToExercise(): self.SendCommand("generator: setremote=start") self.DebugOutput("Starting normal exercise cycle.") self.StopTimer = threading.Timer(float(self.ExerciseDuration * 60.0), self.StopExercise) self.StopTimer.start() elif self.ExerciseType.lower() == "quiet" and self.ReadyToExercise(): self.SendCommand("generator: setremote=startexercise") self.DebugOutput("Starting quiet exercise cycle.") self.StopTimer = threading.Timer(float(self.ExerciseDuration * 60.0), self.StopExercise) self.StopTimer.start() elif self.ExerciseType.lower() == "transfer" and self.ReadyToExercise(): if self.ExerciseWarmup == 0: self.SendCommand("generator: setremote=starttransfer") self.DebugOutput("Starting transfer exercise cycle.") self.StopTimer = threading.Timer(float(self.ExerciseDuration * 60.0), self.StopExercise) self.StopTimer.start() else: self.SendCommand("generator: setremote=start") self.DebugOutput("Starting warmup for transfer exercise cycle.") # start timer for post warmup transition to starttransfer command self.WarmupTimer = threading.Timer(float(self.ExerciseWarmup * 60.0), self.PostWarmup) self.WarmupTimer.start() else: self.LogError("Invalid mode in StartExercise: " + str(self.ExerciseType)) return self.WriteLastExerciseTime() self.ExerciseActive = True # ---------- GenExercise::StopExercise-------------------------------------- def StopExercise(self): if self.ExerciseActive: self.SendCommand("generator: setremote=stop") self.DebugOutput("Stopping exercise cycle.") self.ExerciseActive = False else: self.DebugOutput("Calling Stop Exercise (not needed)") # ---------- GenExercise::DebugOutput----------------------------- def DebugOutput(self, Message): if self.Debug: self.LogError(Message) # ---------- GenExercise::WriteLastExerciseTime----------------------------- def WriteLastExerciseTime(self): try: NowString = datetime.datetime.now().strftime("%A %B %d, %Y %H:%M:%S") if self.ExerciseFrequency.lower() == "biweekly": self.config.WriteValue("last_exercise", NowString) self.config.LastExerciseTime = NowString self.DebugOutput("Last Exercise Cycle: " + NowString) except Exception as e1: self.LogErrorLine("Error in WriteLastExerciseTime: " + str(e1)) # ---------- GenExercise::TimeForExercise----------------------------------- def TimeForExercise(self): try: if self.UseGeneratorTime: TimeNow = self.GetGeneratorTime() else: TimeNow = datetime.datetime.now() if TimeNow.hour != self.ExerciseHour or TimeNow.minute != self.ExerciseMinute: return False weekDays = ("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday") WeekDayString = weekDays[TimeNow.weekday()] if not self.ExerciseFrequency.lower() in ["weekly", "biweekly", "monthly"]: self.LogError("Invalid Exercise Frequency in TimeForExercise: " + str(self.ExerciseFrequency)) return False if self.ExerciseFrequency.lower() == "weekly" and self.ExerciseDayOfWeek.lower() == WeekDayString.lower(): return True elif self.ExerciseFrequency.lower() == "biweekly" and self.ExerciseDayOfWeek.lower() == WeekDayString.lower(): if self.LastExerciseTime == None: return True LastExerciseTime = datetime.datetime.strptime(self.LastExerciseTime, "%A %B %d, %Y %H:%M:%S") if (TimeNow - LastExerciseTime).days >= 14: return True return False elif self.ExerciseFrequency.lower() == "monthly" and TimeNow.day == self.ExerciseDayOfMonth: return True else: return False except Exception as e1: self.LogErrorLine("Error in TimeForExercise: " + str(e1)) return False # ---------- GenExercise::GetGeneratorTime---------------------------------- def GetGeneratorTime(self): try: GenTimeStr = "" data = self.SendCommand("generator: status_json") Status = {} Status = json.loads(data) TimeDict = self.FindDictValueInListByKey("Time", Status["Status"]) if TimeDict != None: TimeDictStr = self.FindDictValueInListByKey("Generator Time", TimeDict) if TimeDictStr != None or not len(TimeDictStr): GenTimeStr = TimeDictStr # Format is "Wednesday March 6, 2019 13:10" or " "Friday May 3, 2019 11:11" GenTime = datetime.datetime.strptime(GenTimeStr, "%A %B %d, %Y %H:%M") else: self.LogError("Error getting generator time! Genmon may be starting up.") GenTime = datetime.datetime.now() else: self.LogError("Error getting generator time (2)!") GenTime = datetime.datetime.now() return GenTime except Exception as e1: self.LogErrorLine("Error in GetGeneratorTime: " + str(e1) + ": " + GenTimeStr) return datetime.datetime.now() # ---------- GenExercise::ExerciseThread------------------------------------ def ExerciseThread(self): time.sleep(1) while True: try: if not self.ExerciseActive: if self.TimeForExercise(): self.StartExercise() if self.WaitForExit("ExerciseThread", float(self.PollTime)): return except Exception as e1: self.LogErrorLine("Error in ExerciseThread: " + str(e1)) if self.WaitForExit("ExerciseThread", float(self.PollTime)): return # ----------GenExercise::Close---------------------------------------------- def Close(self): self.KillThread("ExerciseThread") if self.ExerciseActive: try: self.WarmupTimer.cancel() except: pass try: self.StopTimer.cancel() except: pass self.StopExercise() self.Generator.Close()
# Set up our singleton for polling the sockets for data ready p = poller(log = log) # Set up our singleton listener for UPnP broadcasts u = upnp_broadcast_responder(log = log, debug = Debug) u.init_socket() # Add the UPnP broadcast listener to the poller so we can respond # when a broadcast is received. p.add(u) switch = fauxmo(FauxmoName, u, p, None, FauxmoPort, action_handler = FauxmoAction, log = log, debug = Debug) if Debug: log.info("Entering main loop") while True: try: # Allow time for a ctrl-c to stop the process p.poll(100) time.sleep(0.1) except Exception as e: log.error("Exception occured in main loop: " + str(e)) time.sleep(60) # break except Exception as e1: log.error("Error : " + str(e1)) console.error("Error: " + str(e1))