def main(argv): """Main entry point for starting the agent client""" global pidfile configfile = None logfile = None i = 1 setInfo() while i < len(argv): if argv[i] in ["-c", "-C", "--config-file"]: configfile = argv[i + 1] i += 1 elif argv[i] in ["-l", "-L", "--log-file"]: logfile = argv[i + 1] i += 1 elif argv[i] in ["-h", "-H", "--help"]: displayHelp() elif argv[i] in ["-d", "--debug"]: setDebug() elif argv[i] in ["-P", "--pidfile"]: pidfile = argv[i + 1] i += 1 i += 1 if configfile == None: configfile = '/etc/myDevices/Network.ini' writePidToFile(pidfile) logToFile(logfile) config = Config(configfile) HOST = config.get('CONFIG', 'ServerAddress', 'mqtt.mydevices.com') PORT = config.getInt('CONFIG', 'ServerPort', 8883) CayenneApiHost = config.get('CONFIG', 'CayenneApi', 'https://api.mydevices.com') global client client = CloudServerClient(HOST, PORT, CayenneApiHost) client.Start()
def RetrieveUpdate(self): try: info('Checking update version') debug(UPDATE_URL + ' ' + UPDATE_CFG) retValue = self.DownloadFile(UPDATE_URL, UPDATE_CFG) if retValue is False: error('failed to download update file') return retValue updateConfig = Config(UPDATE_CFG) try: self.newVersion = updateConfig.get('UPDATES', 'Version') self.downloadUrl = updateConfig.get('UPDATES', 'Url') except: error('Updater missing: update version or Url') info('Updater retrieve update success') return True except: error('Updater retrieve update failure') return False
def main(argv): global pidfile configfile = None scriptfile = None logfile = None isDebug = False i = 1 setInfo() while i < len(argv): if argv[i] in ["-c", "-C", "--config-file"]: configfile = argv[i + 1] i += 1 elif argv[i] in ["-l", "-L", "--log-file"]: logfile = argv[i + 1] i += 1 elif argv[i] in ["-h", "-H", "--help"]: displayHelp() elif argv[i] in ["-d", "--debug"]: setDebug() elif argv[i] in ["-P", "--pidfile"]: pidfile = argv[i + 1] i += 1 i += 1 if configfile == None: configfile = '/etc/myDevices/Network.ini' writePidToFile(pidfile) logToFile(logfile) # SET HOST AND PORT config = Config(configfile) HOST = config.get('CONFIG', 'ServerAddress', 'cloud.mydevices.com') PORT = config.getInt('CONFIG', 'ServerPort', 8181) CayenneApiHost = config.get('CONFIG', 'CayenneApi', 'https://api.mydevices.com') # CREATE SOCKET global client client = CloudServerClient(HOST, PORT, CayenneApiHost)
def getMessageBody(self, inviteCode): body = {'id': inviteCode} hardware = Hardware() if hardware.Serial and hardware.isRaspberryPi(): body['type'] = 'rpi' body['hardware_id'] = hardware.Serial else: hardware_id = hardware.getMac() if hardware_id: body['type'] = 'mac' body['hardware_id'] = hardware_id try: system_data = [] cayennemqtt.DataChannel.add(system_data, cayennemqtt.SYS_HARDWARE_MAKE, value=hardware.getManufacturer(), type='string', unit='utf8') cayennemqtt.DataChannel.add(system_data, cayennemqtt.SYS_HARDWARE_MODEL, value=hardware.getModel(), type='string', unit='utf8') config = Config(APP_SETTINGS) cayennemqtt.DataChannel.add(system_data, cayennemqtt.AGENT_VERSION, value=config.get( 'Agent', 'Version', __version__)) system_info = SystemInfo() capacity_data = system_info.getMemoryInfo((cayennemqtt.CAPACITY, )) capacity_data += system_info.getDiskInfo((cayennemqtt.CAPACITY, )) for item in capacity_data: system_data.append(item) body['properties'] = {} body['properties']['pinmap'] = NativeGPIO().MAPPING if system_data: body['properties']['sysinfo'] = system_data except: exception('Error getting system info') return json.dumps(body)
def RetrieveUpdate(self): try: info('Checking update version') debug('Retrieve update config: {} {}'.format( UPDATE_URL, UPDATE_CFG)) retValue = self.DownloadFile(UPDATE_URL, UPDATE_CFG) if retValue is False: error('Failed to download update file') return retValue updateConfig = Config(UPDATE_CFG) try: self.newVersion = updateConfig.get('UPDATES', 'Version') except: error('Updater missing version') # try: # self.downloadUrl = updateConfig.get('UPDATES','Url') # except: # error('Updater missing url') info('Updater retrieve update success') return True except: error('Updater retrieve update failure') return False
class CloudServerClient: def __init__(self, host, port, cayenneApiHost): self.HOST = host self.PORT = port self.CayenneApiHost = cayenneApiHost self.onMessageReceived = None self.onMessageSent = None self.initialized = False self.machineName = gethostname() self.config = Config(APP_SETTINGS) inviteCode = self.config.get('Agent', 'InviteCode', fallback=None) if not inviteCode: error('No invite code found in {}'.format(APP_SETTINGS)) print( 'Please input an invite code. This can be retrieved from the Cayenne dashboard by adding a new Raspberry Pi device.\n' 'The invite code will be part of the script name shown there: rpi_[invitecode].sh.' ) inviteCode = input('Invite code: ') if inviteCode: self.config.set('Agent', 'InviteCode', inviteCode) else: print('No invite code set, exiting.') quit() self.installDate = None try: self.installDate = self.config.get('Agent', 'InstallDate', fallback=None) except: pass if not self.installDate: self.installDate = int(time()) self.config.set('Agent', 'InstallDate', self.installDate) self.networkConfig = Config(NETWORK_SETTINGS) #self.defaultRDServer = self.networkConfig.get('CONFIG','RemoteDesktopServerAddress') self.schedulerEngine = SchedulerEngine(self, 'client_scheduler') self.Initialize() self.CheckSubscription() self.FirstRun() self.updater = Updater(self.config, self.OnUpdateConfig) self.updater.start() self.initialized = True def __del__(self): self.Destroy() def OnUpdateConfig(self): pass # info('Requesting PT_AGENT_CONFIGURATION ') # data = {} # data['MachineName'] = self.MachineId # data['Timestamp'] = int(time()) # data['Platform'] = 1 # raspberrypi platform id is 1 # data['PacketType'] = PacketTypes.PT_AGENT_CONFIGURATION.value # self.EnqueuePacket(data) def Initialize(self): #debug('CloudServerClient init') try: self.mutex = RLock() self.readQueue = Queue() self.writeQueue = Queue() self.pingRate = 10 self.pingTimeout = 35 self.waitPing = 0 self.lastPing = time() - self.pingRate - 1 self.PublicIP = myDevices.ipgetter.myip() self.hardware = Hardware() self.oSInfo = OSInfo() self.downloadSpeed = DownloadSpeed(self.config) self.MachineId = None self.connected = False self.exiting = False self.Start self.count = 10000 self.buff = bytearray(self.count) #start thread only after init of other fields self.sensorsClient = sensors.SensorsClient() self.sensorsClient.SetDataChanged(self.OnDataChanged, self.BuildPT_SYSTEM_INFO) self.processManager = services.ProcessManager() self.serviceManager = services.ServiceManager() self.wifiManager = WifiManager.WifiManager() self.writerThread = WriterThread('writer', self) self.writerThread.start() self.readerThread = ReaderThread('reader', self) self.readerThread.start() self.processorThread = ProcessorThread('processor', self) self.processorThread.start() #TimerThread(self.RequestSchedules, 600, 10) TimerThread(self.CheckConnectionAndPing, self.pingRate) self.sentHistoryData = {} self.historySendFails = 0 self.historyThread = Thread(target=self.SendHistoryData) self.historyThread.setDaemon(True) self.historyThread.start() except Exception as e: exception('Initialize error: ' + str(e)) def Destroy(self): info('Shutting down client') self.exiting = True self.sensorsClient.StopMonitoring() if hasattr(self, 'schedulerEngine'): self.schedulerEngine.stop() if hasattr(self, 'updater'): self.updater.stop() if hasattr(self, 'writerThread'): self.writerThread.stop() if hasattr(self, 'readerThread'): self.readerThread.stop() if hasattr(self, 'processorThread'): self.processorThread.stop() ThreadPool.Shutdown() self.Stop() info('Client shut down') # def Test(self): # message = {} # message['PacketType'] = PacketTypes.PT_DEVICE_COMMAND.value # message['Type'] = '' # message['Service'] = 'config' # message['Id']=1021 # parameters = {} # parameters['id'] = 16 # parameters['arguments'] = 'Asia/Tokyo' # message['Parameters'] = parameters # self.ExecuteMessage(message) # message = {} # message['PacketType'] = PacketTypes.PT_DEVICE_COMMAND.value # message['Type'] = '' # message['Service'] = 'config' # message['Id']=1021 # parameters = {} # parameters['id'] = 15 # parameters['arguments'] = '' # message['Parameters'] = parameters # self.ExecuteMessage(message) # message = {} # message['PacketType'] = PacketTypes.PT_DEVICE_COMMAND.value # message['Type'] = '' # message['Service'] = 'config' # message['Id']=1021 # parameters = {} # parameters['id'] = 0 # parameters['arguments'] = 'test' # message['Parameters'] = parameters # self.ExecuteMessage(message) def FirstRun(self): # self.BuildPT_LOCATION() self.BuildPT_SYSTEM_INFO() # data = {} # data['MachineName'] = self.MachineId # data['Timestamp'] = int(time()) # data['PacketType'] = PacketTypes.PT_UTILIZATION.value # self.processManager.RefreshProcessManager() # data['VisibleMemory'] = 1000000 # data['AvailableMemory'] = 100000 # data['AverageProcessorUsage'] = 20 # data['PeakProcessorUsage'] = 98 # data['AverageMemoryUsage'] = 30 # data['PeakMemoryUsage'] = 99 # data['PercentProcessorTime'] = 80 # self.EnqueuePacket(data) # data['PacketType'] = PacketTypes.PT_PROCESS_LIST.value # self.EnqueuePacket(data) # data['PacketType'] = PacketTypes.PT_DRIVE_LIST.value # self.EnqueuePacket(data) # data['PacketType'] = PacketTypes.PT_PRINTER_INFO.value # self.EnqueuePacket(data) self.RequestSchedules() # self.BuildPT_LOCATION() self.OnUpdateConfig() def BuildPT_LOCATION(self): data = {} data['position'] = {} data['position']['latitude'] = '30.022112' data['position']['longitude'] = '45.022112' data['position']['accuracy'] = '20' data['position']['method'] = 'Windows location provider' data['provider'] = 'other' data['time'] = int(time()) data['PacketType'] = PacketTypes.PT_LOCATION.value data['MachineName'] = self.MachineId self.EnqueuePacket(data) def BuildPT_UTILIZATION(self): #debug('BuildPT_UTILIZATION') data = {} data['MachineName'] = self.MachineId data['Timestamp'] = int(time()) data['PacketType'] = PacketTypes.PT_UTILIZATION.value self.processManager.RefreshProcessManager() data['VisibleMemory'] = self.processManager.VisibleMemory data['AvailableMemory'] = self.processManager.AvailableMemory data[ 'AverageProcessorUsage'] = self.processManager.AverageProcessorUsage data['PeakProcessorUsage'] = self.processManager.PeakProcessorUsage data['AverageMemoryUsage'] = self.processManager.AverageMemoryUsage data['PeakMemoryUsage'] = self.processManager.AverageMemoryUsage data['PercentProcessorTime'] = self.processManager.PercentProcessorTime self.EnqueuePacket(data) def OnDataChanged(self, raspberryValue): data = {} data['MachineName'] = self.MachineId data['PacketType'] = PacketTypes.PT_DATA_CHANGED.value data['Timestamp'] = int(time()) data['RaspberryInfo'] = raspberryValue self.EnqueuePacket(data) del data del raspberryValue def BuildPT_SYSTEM_INFO(self): try: data = {} data['MachineName'] = self.MachineId data['PacketType'] = PacketTypes.PT_SYSTEM_INFO.value data['Timestamp'] = int(time()) data['IpAddress'] = self.PublicIP data['GatewayMACAddress'] = self.hardware.getMac() raspberryValue = {} raspberryValue['NetworkSpeed'] = str( self.downloadSpeed.getDownloadSpeed()) raspberryValue['AntiVirus'] = 'None' raspberryValue['Firewall'] = 'iptables' raspberryValue['FirewallEnabled'] = 'true' raspberryValue['ComputerMake'] = self.hardware.getManufacturer() raspberryValue['ComputerModel'] = self.hardware.getModel() raspberryValue['OsName'] = self.oSInfo.ID raspberryValue['OsBuild'] = self.oSInfo.ID_LIKE if hasattr( self.oSInfo, 'ID_LIKE') else self.oSInfo.ID raspberryValue['OsArchitecture'] = self.hardware.Revision raspberryValue['OsVersion'] = self.oSInfo.VERSION_ID raspberryValue['ComputerName'] = self.machineName raspberryValue['AgentVersion'] = self.config.get( 'Agent', 'Version', fallback='1.0.1.0') raspberryValue['InstallDate'] = self.installDate raspberryValue['GatewayMACAddress'] = self.hardware.getMac() with self.sensorsClient.sensorMutex: raspberryValue[ 'SystemInfo'] = self.sensorsClient.currentSystemInfo raspberryValue[ 'SensorsInfo'] = self.sensorsClient.currentSensorsInfo raspberryValue['BusInfo'] = self.sensorsClient.currentBusInfo raspberryValue['OsSettings'] = RaspiConfig.getConfig() raspberryValue['NetworkId'] = WifiManager.Network.GetNetworkId() raspberryValue['WifiStatus'] = self.wifiManager.GetStatus() try: history = History() history.SaveAverages(raspberryValue) except: exception('History error') data['RaspberryInfo'] = raspberryValue self.EnqueuePacket(data) logJson('PT_SYSTEM_INFO: ' + dumps(data), 'PT_SYSTEM_INFO') del raspberryValue del data data = None except Exception as e: exception('ThreadSystemInfo unexpected error: ' + str(e)) Debug() def BuildPT_STARTUP_APPLICATIONS(self): ThreadPool.Submit(self.ThreadServiceManager) def ThreadServiceManager(self): self.serviceManager.Run() sleep(GENERAL_SLEEP_THREAD) data = {} data['MachineName'] = self.MachineId data['PacketType'] = PacketTypes.PT_STARTUP_APPLICATIONS.value data['ProcessList'] = self.serviceManager.GetServiceList() self.EnqueuePacket(data) def BuildPT_PROCESS_LIST(self): ThreadPool.Submit(self.ThreadProcessManager) def ThreadProcessManager(self): self.processManager.Run() sleep(GENERAL_SLEEP_THREAD) data = {} data['MachineName'] = self.MachineId data['PacketType'] = PacketTypes.PT_PROCESS_LIST.value data['ProcessList'] = self.processManager.GetProcessList() self.EnqueuePacket(data) def ProcessPT_KILL_PROCESS(self, message): #debug('ProcessPT_KILL_PROCESS') pid = message['Pid'] retVal = self.processManager.KillProcess(int(pid)) data = {} data['MachineName'] = self.MachineId data['PacketType'] = PacketTypes.PT_AGENT_MESSAGE.value data['Type'] = 'Info' if retVal: data['Message'] = 'Process Killed!' else: data['Message'] = 'Process not Killed!' self.EnqueuePacket(data) def CheckSubscription(self): inviteCode = self.config.get('Agent', 'InviteCode') cayenneApiClient = CayenneApiClient(self.CayenneApiHost) authId = cayenneApiClient.loginDevice(inviteCode) if authId == None: error( 'Registration failed for invite code {}, closing the process'. format(inviteCode)) Daemon.Exit() else: info('Registration succeeded for invite code {}, auth id = {}'. format(inviteCode, authId)) self.config.set('Agent', 'Initialized', 'true') self.MachineId = authId @property def Start(self): #debug('Start') if self.connected: ret = False error('Start already connected') else: info('Connecting to: {}:{}'.format(self.HOST, self.PORT)) count = 0 with self.mutex: count += 1 while self.connected == False and count < 30: try: self.sock = None self.wrappedSocket = None ##debug('Start wrap_socket') self.sock = socket(AF_INET, SOCK_STREAM) #self.wrappedSocket = wrap_socket(self.sock, ca_certs="/etc/myDevices/ca.crt", cert_reqs=CERT_REQUIRED) self.wrappedSocket = wrap_socket(self.sock) self.wrappedSocket.connect((self.HOST, self.PORT)) info('myDevices cloud connected') self.connected = True except socket_error as serr: Daemon.OnFailure('cloud', serr.errno) error('Start failed: ' + str(self.HOST) + ':' + str(self.PORT) + ' Error:' + str(serr)) self.connected = False sleep(30 - count) return self.connected def Stop(self): #debug('Stop started') Daemon.Reset('cloud') ret = True if self.connected == False: ret = False error('Stop not connected') else: with self.mutex: try: self.wrappedSocket.shutdown(SHUT_RDWR) self.wrappedSocket.close() info('myDevices cloud disconnected') except socket_error as serr: debug(str(serr)) error('myDevices cloud disconnected error:' + str(serr)) ret = False self.connected = False #debug('Stop finished') return ret def Restart(self): if not self.exiting: debug('Restarting cycle...') sleep(1) self.Stop() self.Start def SendMessage(self, message): logJson(message, 'SendMessage') ret = True if self.connected == False: error('SendMessage fail') ret = False else: try: data = bytes(message, 'UTF-8') max_size = 16383 if len(data) > max_size: start = 0 current = max_size end = len(data) self.wrappedSocket.send(data[start:current]) while current < end: start = current current = start + max_size if start + max_size < end else end self.wrappedSocket.send(data[start:current]) else: self.wrappedSocket.send(data) if self.onMessageSent: self.onMessageSent(message) message = None except socket_error as serr: error('SendMessage:' + str(serr)) ret = False Daemon.OnFailure('cloud', serr.errno) sleep(1) except IOError as ioerr: debug('IOError: ' + str(ioerr)) self.Restart() #Daemon.OnFailure('cloud', ioerr.errno) except socket_error as serr: Daemon.OnFailure('cloud', serr.errno) except: exception('SendMessage error') return ret def CheckJson(self, message): try: test = loads(message) except ValueError: return False return True def ReadMessage(self): ret = True if self.connected == False: ret = False else: try: self.count = 4096 timeout_in_seconds = 10 ready = select([self.wrappedSocket], [], [], timeout_in_seconds) if ready[0]: message = self.wrappedSocket.recv(self.count).decode() buffering = len(message) == 4096 while buffering and message: if self.CheckJson(message): buffering = False else: more = self.wrappedSocket.recv(self.count).decode() if not more: buffering = False else: message += more try: if message: messageObject = loads(message) del message self.readQueue.put(messageObject) else: error('ReadMessage received empty message string') except: exception('ReadMessage error: ' + str(message)) return False Daemon.Reset('cloud') except IOError as ioerr: debug('IOError: ' + str(ioerr)) self.Restart() #Daemon.OnFailure('cloud', ioerr.errno) except socket_error as serr: Daemon.OnFailure('cloud', serr.errno) except: exception('ReadMessage error') ret = False sleep(1) Daemon.OnFailure('cloud') return ret def RunAction(self, action): #execute action in machine debug('RunAction') if 'MachineName' in action and self.MachineId != action['MachineName']: debug('Scheduler action is not assigned for this machine: ' + str(action)) return self.ExecuteMessage(action) def SendNotification(self, notify, subject, body): info('SendNotification: ' + str(notify) + ' ' + str(subject) + ' ' + str(body)) try: data = {} data['PacketType'] = PacketTypes.PT_NOTIFICATION.value data['MachineName'] = self.MachineId data['Subject'] = subject data['Body'] = body data['Notify'] = notify self.EnqueuePacket(data) except: debug('') return False return True def ProcessMessage(self): try: messageObject = self.readQueue.get(False) if not messageObject: return False except Empty: return False with self.mutex: retVal = self.CheckPT_ACK(messageObject) if retVal: return self.ExecuteMessage(messageObject) def CheckPT_ACK(self, messageObject): try: packetType = int(messageObject['PacketType']) if packetType == PacketTypes.PT_ACK.value: self.lastPing = time() return True except: debug('') error('CheckPT_ACK failure: ' + str(messageObject)) return False def ExecuteMessage(self, messageObject): if not messageObject: return info("ExecuteMessage: " + str(messageObject['PacketType'])) packetType = int(messageObject['PacketType']) if packetType == PacketTypes.PT_UTILIZATION.value: self.BuildPT_UTILIZATION() info(PacketTypes.PT_UTILIZATION) return if packetType == PacketTypes.PT_SYSTEM_INFO.value: self.BuildPT_SYSTEM_INFO() info(PacketTypes.PT_SYSTEM_INFO) return if packetType == PacketTypes.PT_UNINSTALL_AGENT.value: command = "sudo /etc/myDevices/uninstall/uninstall.sh" services.ServiceManager.ExecuteCommand(command) return if packetType == PacketTypes.PT_STARTUP_APPLICATIONS.value: self.BuildPT_STARTUP_APPLICATIONS() info(PacketTypes.PT_STARTUP_APPLICATIONS) return if packetType == PacketTypes.PT_PROCESS_LIST.value: self.BuildPT_PROCESS_LIST() info(PacketTypes.PT_PROCESS_LIST) return if packetType == PacketTypes.PT_KILL_PROCESS.value: self.ProcessPT_KILL_PROCESS(messageObject) info(PacketTypes.PT_KILL_PROCESS) return if packetType == PacketTypes.PT_INITIALIZED.value: #self.libMYOPX.SetSubscription(messageObject) info(PacketTypes.PT_INITIALIZED) return if packetType == PacketTypes.PT_PRODUCT_INFO.value: self.config.set('Subscription', 'ProductCode', messageObject['ProductCode']) info(PacketTypes.PT_PRODUCT_INFO) return if packetType == PacketTypes.PT_START_RDS_LOCAL_INIT.value: error('PT_START_RDS_LOCAL_INIT not implemented') info(PacketTypes.PT_START_RDS_LOCAL_INIT) return if packetType == PacketTypes.PT_RESTART_COMPUTER.value: info(PacketTypes.PT_RESTART_COMPUTER) data = {} data['PacketType'] = PacketTypes.PT_AGENT_MESSAGE.value data['MachineName'] = self.MachineId data['Message'] = 'Computer Restarted!' self.EnqueuePacket(data) command = "sudo shutdown -r now" services.ServiceManager.ExecuteCommand(command) return if packetType == PacketTypes.PT_SHUTDOWN_COMPUTER.value: info(PacketTypes.PT_SHUTDOWN_COMPUTER) data = {} data['PacketType'] = PacketTypes.PT_AGENT_MESSAGE.value data['MachineName'] = self.MachineId data['Message'] = 'Computer Powered Off!' self.EnqueuePacket(data) command = "sudo shutdown -h now" services.ServiceManager.ExecuteCommand(command) return if packetType == PacketTypes.PT_SUPPORTED_SENSORS.value: self.sensorsClient.SupportedSensorsUpdate(messageObject) info(PacketTypes.PT_SUPPORTED_SENSORS) return if packetType == PacketTypes.PT_MACHINE_SENSORS.value: self.sensorsClient.OnDbSensors(messageObject) info(PacketTypes.PT_MACHINE_SENSORS) return if packetType == PacketTypes.PT_AGENT_CONFIGURATION.value: info('PT_AGENT_CONFIGURATION: ' + str(messageObject.Data)) self.config.setCloudConfig(messageObject.Data) return if packetType == PacketTypes.PT_ADD_SENSOR.value: try: info(PacketTypes.PT_ADD_SENSOR) parameters = None deviceName = None deviceClass = None description = None #for backward compatibility check the DisplayName and overwrite it over the other variables displayName = None if 'DisplayName' in messageObject: displayName = messageObject['DisplayName'] if 'Parameters' in messageObject: parameters = messageObject['Parameters'] if 'DeviceName' in messageObject: deviceName = messageObject['DeviceName'] else: deviceName = displayName if 'Description' in messageObject: description = messageObject['Description'] else: description = deviceName if 'Class' in messageObject: deviceClass = messageObject['Class'] retValue = True retValue = self.sensorsClient.AddSensor( deviceName, description, deviceClass, parameters) except Exception as ex: exception("PT_ADD_SENSOR Unexpected error" + str(ex)) retValue = False data = {} if 'Id' in messageObject: data['Id'] = messageObject['Id'] #0 - None, 1 - Pending, 2-Success, 3 - Not responding, 4 - Failure if retValue: data['State'] = 2 else: data['State'] = 4 data['PacketType'] = PacketTypes.PT_UPDATE_SENSOR.value data['MachineName'] = self.MachineId self.EnqueuePacket(data) return if packetType == PacketTypes.PT_REMOVE_SENSOR.value: try: info(PacketTypes.PT_REMOVE_SENSOR) retValue = False if 'Name' in messageObject: Name = messageObject['Name'] retValue = self.sensorsClient.RemoveSensor(Name) data = {} data['Name'] = Name data['PacketType'] = PacketTypes.PT_REMOVE_SENSOR.value data['MachineName'] = self.MachineId data['Response'] = retValue self.EnqueuePacket(data) except Exception as ex: exception("PT_REMOVE_SENSOR Unexpected error" + str(ex)) retValue = False return if packetType == PacketTypes.PT_DEVICE_COMMAND.value: info(PacketTypes.PT_DEVICE_COMMAND) self.ProcessDeviceCommand(messageObject) return if packetType == PacketTypes.PT_ADD_SCHEDULE.value: info(PacketTypes.PT_ADD_SCHEDULE.value) retVal = self.schedulerEngine.AddScheduledItem(messageObject, True) if 'Update' in messageObject: messageObject['Update'] = messageObject['Update'] messageObject['PacketType'] = PacketTypes.PT_ADD_SCHEDULE.value messageObject['MachineName'] = self.MachineId messageObject['Status'] = str(retVal) self.EnqueuePacket(messageObject) return if packetType == PacketTypes.PT_REMOVE_SCHEDULE.value: info(PacketTypes.PT_REMOVE_SCHEDULE) retVal = self.schedulerEngine.RemoveScheduledItem(messageObject) messageObject['PacketType'] = PacketTypes.PT_REMOVE_SCHEDULE.value messageObject['MachineName'] = self.MachineId messageObject['Status'] = str(retVal) self.EnqueuePacket(messageObject) return if packetType == PacketTypes.PT_GET_SCHEDULES.value: info(PacketTypes.PT_GET_SCHEDULES) schedulesJson = self.schedulerEngine.GetSchedules() data['Schedules'] = schedulesJson data['PacketType'] = PacketTypes.PT_GET_SCHEDULES.value data['MachineName'] = self.MachineId self.EnqueuePacket(data) return if packetType == PacketTypes.PT_UPDATE_SCHEDULES.value: info(PacketTypes.PT_UPDATE_SCHEDULES) retVal = self.schedulerEngine.UpdateSchedules(messageObject) return if packetType == PacketTypes.PT_HISTORY_DATA_RESPONSE.value: info(PacketTypes.PT_HISTORY_DATA_RESPONSE) try: id = messageObject['Id'] history = History() if messageObject['Status']: history.Sent(True, self.sentHistoryData[id]['HistoryData']) self.historySendFails = 0 else: history.Sent(False, self.sentHistoryData[id]['HistoryData']) self.historySendFails += 1 del self.sentHistoryData[id] except: exception('Processing history response packet failed') return info("Skipping not required packet: " + str(packetType)) def ProcessDeviceCommand(self, messageObject): # t1 = Thread(target=self.ThreadDeviceCommand) # t1.start() # def ThreadDeviceCommand(self): commandType = messageObject['Type'] commandService = messageObject['Service'] parameters = messageObject['Parameters'] info('PT_DEVICE_COMMAND: ' + dumps(messageObject)) debug('ProcessDeviceCommand: ' + commandType + ' ' + commandService + ' ' + str(parameters)) id = messageObject['Id'] sensorId = None if 'SensorId' in messageObject: sensorId = messageObject['SensorId'] data = {} retValue = '' if commandService == 'wifi': if commandType == 'status': retValue = self.wifiManager.GetStatus() if commandType == 'scan': retValue = self.wifiManager.GetWirelessNetworks() if commandType == 'setup': try: ssid = parameters["ssid"] password = parameters["password"] interface = parameters["interface"] retValue = self.wifiManager.Setup(ssid, password, interface) except: retValue = False if commandService == 'services': serviceName = parameters['ServiceName'] if commandType == 'status': retValue = self.serviceManager.Status(serviceName) if commandType == 'start': retValue = self.serviceManager.Start(serviceName) if commandType == 'stop': retValue = self.serviceManager.Stop(serviceName) if commandService == 'sensor': debug('SENSOR_COMMAND processing: ' + str(parameters)) method = None channel = None value = None driverClass = None sensorType = None sensorName = None if 'SensorName' in parameters: sensorName = parameters["SensorName"] if 'DriverClass' in parameters: driverClass = parameters["DriverClass"] if commandType == 'enable': sensor = None enable = None if 'Sensor' in parameters: sensor = parameters["Sensor"] if 'Enable' in parameters: enable = parameters["Enable"] retValue = self.sensorsClient.EnableSensor(sensor, enable) else: if commandType == 'edit': description = sensorName device = None if "Description" in parameters: description = parameters["Description"] if "Args" in parameters: args = parameters["Args"] retValue = self.sensorsClient.EditSensor( sensorName, description, driverClass, args) else: if 'Channel' in parameters: channel = parameters["Channel"] if 'Method' in parameters: method = parameters["Method"] if 'Value' in parameters: value = parameters["Value"] if 'SensorType' in parameters: sensorType = parameters["SensorType"] #(self, commandType, sensorName, sensorType, driverClass, method, channel, value): retValue = self.sensorsClient.SensorCommand( commandType, sensorName, sensorType, driverClass, method, channel, value) if commandService == 'gpio': method = parameters["Method"] channel = parameters["Channel"] value = parameters["Value"] debug('ProcessDeviceCommand: ' + commandService + ' ' + method + ' ' + str(channel) + ' ' + str(value)) retValue = str( self.sensorsClient.GpioCommand(commandType, method, channel, value)) debug('ProcessDeviceCommand gpio returned value: ' + retValue) if commandService == 'config': try: config_id = parameters["id"] arguments = parameters["arguments"] (retValue, output) = RaspiConfig.Config(config_id, arguments) data["Output"] = output retValue = str(retValue) except: exception("Exception on config") data['Response'] = retValue data['Id'] = id data['PacketType'] = PacketTypes.PT_DEVICE_COMMAND_RESPONSE.value data['MachineName'] = self.MachineId info('PT_DEVICE_COMMAND_RESPONSE: ' + dumps(data)) if sensorId: data['SensorId'] = sensorId self.EnqueuePacket(data) #if commandService == 'processes': #Kill command is handled with PT_KILL_PROCESS def EnqueuePacket(self, message): message['PacketTime'] = GetTime() #datetime.now().strftime("%Y-%m-%dT%H:%M:%S%z") json_data = dumps(message) + '\n' message = None #debug(json_data) self.writeQueue.put(json_data) def DequeuePacket(self): packet = None try: packet = self.writeQueue.get() except Empty: packet = None return packet def CheckConnectionAndPing(self): ticksStart = time() with self.mutex: try: if (ticksStart - self.lastPing > self.pingTimeout): #debug('CheckConnectionAndPing EXPIRED - trying to reconnect') self.Stop() self.Start self.lastPing = time() - self.pingRate - 1 warn( 'Restarting cloud connection -> CheckConnectionAndPing EXPIRED: ' + str(self.lastPing)) if (ticksStart - self.waitPing >= self.pingRate): #debug("CheckConnectionAndPing sending ACK packet") self.SendAckPacket() except: debug('') error('CheckConnectionAndPing error') def SendAckPacket(self): data = {} debug('Last ping: ' + str(self.lastPing) + ' Wait ping: ' + str(self.waitPing)) data['MachineName'] = self.MachineId data['IPAddress'] = self.PublicIP data['PacketType'] = PacketTypes.PT_ACK.value self.EnqueuePacket(data) self.waitPing = time() def RequestSchedules(self): data = {} data['MachineName'] = self.MachineId data['Stored'] = "dynamodb" data['PacketType'] = PacketTypes.PT_REQUEST_SCHEDULES.value self.EnqueuePacket(data) def SendHistoryData(self): try: info('SendHistoryData start') history = History() history.Reset() while True: try: #If there is no acknowledgment after a minute we assume failure sendFailed = [ key for key, item in self.sentHistoryData.items() if (item['Timestamp'] + 60) < time() ] info('SendHistoryData previously SendFailed items: ' + str(sendFailed)) for id in sendFailed: self.historySendFails += len(sendFailed) history.Sent(False, self.sentHistoryData[id]['HistoryData']) del self.sentHistoryData[id] historyData = history.GetHistoricalData() if historyData: data = {} info('SendHistoryData historyData: ' + str(historyData)) data['MachineName'] = self.MachineId data['Timestamp'] = int(time()) data['PacketType'] = PacketTypes.PT_HISTORY_DATA.value id = sha256( dumps(historyData).encode('utf8')).hexdigest() data['Id'] = id data['HistoryData'] = historyData info('Sending history data, id = {}'.format(id)) debug('SendHistoryData historyData: ' + str(data)) self.EnqueuePacket(data) #this will keep acumulating self.sentHistoryData[id] = data except Exception as ex: exception('SendHistoryData error' + str(ex)) delay = 60 if self.historySendFails > 2: delay = 120 if self.historySendFails > 4: #Wait an hour if we keep getting send failures. delay = 3600 self.historySendFails = 0 sleep(delay) except Exception as ex: exception('SendHistoryData general exception: ' + str(ex))
class CloudServerClient: """Class to connect to the server and send and receive data""" def __init__(self, host, port, cayenneApiHost): """Initialize the client configuration""" self.HOST = host self.PORT = port self.CayenneApiHost = cayenneApiHost self.config = Config(APP_SETTINGS) self.networkConfig = Config(NETWORK_SETTINGS) self.username = self.config.get('Agent', 'Username', None) self.password = self.config.get('Agent', 'Password', None) self.clientId = self.config.get('Agent', 'ClientID', None) self.location = self.config.get('Agent', 'Location', "house0@room0@") self.mqtt_dis_prefix = self.config.get('Agent', 'MQTT_DIS_PREFIX', "homeassistant") self.connected = False self.exiting = Event() self.sensorsClient = None def __del__(self): """Delete the client""" self.Destroy() def Start(self): """Connect to server and start background threads""" try: self.installDate = None try: self.installDate = self.config.get('Agent', 'InstallDate', fallback=None) except: pass if not self.installDate: self.installDate = int(time()) self.config.set('Agent', 'InstallDate', self.installDate) if not self.username and not self.password and not self.clientId: self.CheckSubscription() if not self.Connect(): error('Error starting agent') return self.schedulerEngine = SchedulerEngine(self, 'client_scheduler') self.readQueue = Queue() self.writeQueue = Queue() self.hardware = Hardware() self.oSInfo = OSInfo() self.count = 10000 self.buff = bytearray(self.count) self.writerThread = WriterThread('writer', self) self.writerThread.start() self.processorThread = ProcessorThread('processor', self) self.processorThread.start() self.systemInfo = [] TimerThread(self.SendSystemInfo, 300) self.sensorsClient = sensors.SensorsClient(self) self.sensorsClient.SetDataChanged(self.OnDataChanged) # TimerThread(self.SendSystemState, 30, 5) self.updater = Updater(self.config) self.updater.start() events = self.schedulerEngine.get_scheduled_events() self.EnqueuePacket(events, cayennemqtt.JOBS_TOPIC) # self.sentHistoryData = {} # self.historySendFails = 0 # self.historyThread = Thread(target=self.SendHistoryData) # self.historyThread.setDaemon(True) # self.historyThread.start() except Exception as e: exception('Initialize error: ' + str(e)) def Destroy(self): """Destroy client and stop client threads""" info('Shutting down client') self.exiting.set() if hasattr(self, 'sensorsClient'): self.sensorsClient.StopMonitoring() if hasattr(self, 'schedulerEngine'): self.schedulerEngine.stop() if hasattr(self, 'updater'): self.updater.stop() if hasattr(self, 'writerThread'): self.writerThread.stop() if hasattr(self, 'processorThread'): self.processorThread.stop() ThreadPool.Shutdown() self.Disconnect() info('Client shut down') def OnDataChanged(self, data): """Enqueue a packet containing changed system data to send to the server""" try: if len(data) > 15: items = [ { item['channel']: item['value'] } for item in data if not item['channel'].startswith(cayennemqtt.SYS_GPIO) ] info('Send changed data: {} + {}'.format( items, cayennemqtt.SYS_GPIO)) else: info('Send changed data: {}'.format([{ item['channel']: item['value'] } for item in data])) # items = {} # gpio_items = {} # for item in data: # if not item['channel'].startswith(cayennemqtt.SYS_GPIO): # items[item['channel']] = item['value'] # else: # channel = item['channel'].replace(cayennemqtt.SYS_GPIO + ':', '').split(';') # if not channel[0] in gpio_items: # gpio_items[channel[0]] = str(item['value']) # else: # gpio_items[channel[0]] += ',' + str(item['value']) # info('Send changed data: {}, {}: {}'.format(items, cayennemqtt.SYS_GPIO, gpio_items)) except: info('Send changed data') pass self.EnqueuePacket(data) def SendSystemInfo(self): """Enqueue a packet containing system info to send to the server""" try: currentSystemInfo = [] cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.SYS_OS_NAME, value=self.oSInfo.ID) cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.SYS_OS_VERSION, value=self.oSInfo.VERSION_ID) cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.AGENT_VERSION, value=self.config.get( 'Agent', 'Version', __version__)) cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.SYS_POWER_RESET, value=0) cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.SYS_POWER_HALT, value=0) config = SystemConfig.getConfig() if config: channel_map = { 'I2C': cayennemqtt.SYS_I2C, 'SPI': cayennemqtt.SYS_SPI, 'Serial': cayennemqtt.SYS_UART, 'OneWire': cayennemqtt.SYS_ONEWIRE, 'DeviceTree': cayennemqtt.SYS_DEVICETREE } for key, channel in channel_map.items(): try: cayennemqtt.DataChannel.add(currentSystemInfo, channel, value=config[key]) except: pass if currentSystemInfo != self.systemInfo: data = currentSystemInfo if self.systemInfo: data = [x for x in data if x not in self.systemInfo] if data: self.systemInfo = currentSystemInfo info('Send system info: {}'.format([{ item['channel']: item['value'] } for item in data])) self.EnqueuePacket(data) except Exception: exception('SendSystemInfo unexpected error') def CheckSubscription(self): """Check that an invite code is valid""" inviteCode = self.config.get('Agent', 'InviteCode', fallback=None) if not inviteCode: error('No invite code found in {}'.format(self.config.path)) print( 'Please input an invite code. This can be retrieved from the Cayenne dashboard by adding a new Raspberry Pi device.\n' 'The invite code will be part of the script name shown there: rpi_[invitecode].sh.' ) inviteCode = input('Invite code: ') if inviteCode: self.config.set('Agent', 'InviteCode', inviteCode) else: print('No invite code set, exiting.') raise SystemExit inviteCode = self.config.get('Agent', 'InviteCode') cayenneApiClient = CayenneApiClient(self.CayenneApiHost) credentials = cayenneApiClient.loginDevice(inviteCode) if credentials == None: error( 'Registration failed for invite code {}, closing the process'. format(inviteCode)) raise SystemExit else: info('Registration succeeded for invite code {}, credentials = {}'. format(inviteCode, credentials)) self.config.set('Agent', 'Initialized', 'true') try: self.username = credentials['mqtt']['username'] self.password = credentials['mqtt']['password'] self.clientId = credentials['mqtt']['clientId'] self.config.set('Agent', 'Username', self.username) self.config.set('Agent', 'Password', self.password) self.config.set('Agent', 'ClientID', self.clientId) except: exception('Invalid credentials, closing the process') raise SystemExit def Connect(self): """Connect to the server""" self.connected = False count = 0 while self.connected == False and count < 30 and not self.exiting.is_set( ): try: self.mqttClient = cayennemqtt.CayenneMQTTClient() self.mqttClient.on_message = self.OnMessage self.mqttClient.begin(self.username, self.password, self.clientId, self.HOST, self.PORT) self.mqttClient.loop_start() self.connected = True except OSError as oserror: Daemon.OnFailure('cloud', oserror.errno) error('Connect failed: ' + str(self.HOST) + ':' + str(self.PORT) + ' Error:' + str(oserror)) if self.exiting.wait(30): # If we are exiting return immediately return self.connected count += 1 return self.connected def Disconnect(self): """Disconnect from the server""" Daemon.Reset('cloud') try: if hasattr(self, 'mqttClient'): self.mqttClient.loop_stop() info('myDevices cloud disconnected') except: exception('Error stopping client') def Restart(self): """Restart the server connection""" if not self.exiting.is_set(): debug('Restarting cycle...') sleep(1) self.Disconnect() self.Connect() def CheckJson(self, message): """Check if a JSON message is valid""" try: test = loads(message) except ValueError: return False return True def OnMessage(self, message): """Add message from the server to the queue""" info('OnMessage: {}'.format(message)) self.readQueue.put(message) def RunAction(self, action): """Run a specified action""" debug('RunAction: {}'.format(action)) result = True command = action.copy() self.mqttClient.transform_command(command) result = self.ExecuteMessage(command) return result def ProcessMessage(self): """Process a message from the server""" try: messageObject = self.readQueue.get(False) if not messageObject: return False except Empty: return False self.ExecuteMessage(messageObject) def ExecuteMessage(self, message): """Execute an action described in a message object Returns: True if action was executed, False otherwise.""" result = False if not message: return result channel = message['channel'] info('ExecuteMessage: {}'.format(message)) if channel in (cayennemqtt.SYS_POWER_RESET, cayennemqtt.SYS_POWER_HALT): result = self.ProcessPowerCommand(message) elif channel.startswith(cayennemqtt.DEV_SENSOR): result = self.ProcessSensorCommand(message) elif channel.startswith(cayennemqtt.SYS_GPIO): result = self.ProcessGpioCommand(message) elif channel == cayennemqtt.AGENT_DEVICES: result = self.ProcessDeviceCommand(message) elif channel in (cayennemqtt.SYS_I2C, cayennemqtt.SYS_SPI, cayennemqtt.SYS_UART, cayennemqtt.SYS_ONEWIRE): result = self.ProcessConfigCommand(message) elif channel == cayennemqtt.AGENT_MANAGE: result = self.ProcessAgentCommand(message) elif channel == cayennemqtt.AGENT_SCHEDULER: result = self.ProcessSchedulerCommand(message) else: info('Unknown message') return result def ProcessPowerCommand(self, message): """Process command to reboot/shutdown the system Returns: True if command was processed, False otherwise.""" error_message = None try: self.EnqueueCommandResponse(message, error_message) commands = { cayennemqtt.SYS_POWER_RESET: 'sudo shutdown -r now', cayennemqtt.SYS_POWER_HALT: 'sudo shutdown -h now' } if int(message['payload']) == 1: debug('Processing power command') data = [] cayennemqtt.DataChannel.add(data, message['channel'], value=1) self.EnqueuePacket(data) self.writeQueue.join() info('Calling execute: {}'.format( commands[message['channel']])) output, result = executeCommand(commands[message['channel']]) debug('ProcessPowerCommand: {}, result: {}, output: {}'.format( message, result, output)) if result != 0: error_message = 'Error executing shutdown command' except Exception as ex: error_message = '{}: {}'.format(type(ex).__name__, ex) if error_message: error(error_message) data = [] cayennemqtt.DataChannel.add(data, message['channel'], value=0) self.EnqueuePacket(data) raise ExecuteMessageError(error_message) return error_message == None def ProcessAgentCommand(self, message): """Process command to manage the agent state Returns: True if command was processed, False otherwise.""" error = None try: if message['suffix'] == 'uninstall': output, result = executeCommand( 'sudo -n /etc/myDevices/uninstall/uninstall.sh', disablePipe=True) debug('ProcessAgentCommand: {}, result: {}, output: {}'.format( message, result, output)) if result != 0: error = 'Error uninstalling agent' # elif message['suffix'] == 'config': # for key, value in message['payload'].items(): # if value is None: # info('Remove config item: {}'.format(key)) # self.config.remove('Agent', key) # else: # info('Set config item: {} {}'.format(key, value)) # self.config.set('Agent', key, value) else: error = 'Unknown agent command: {}'.format(message['suffix']) except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) if error: raise ExecuteMessageError(error) return error == None def ProcessConfigCommand(self, message): """Process system configuration command Returns: True if command was processed, False otherwise.""" error = None try: value = 1 - int( message['payload'] ) #Invert the value since the config script uses 0 for enable and 1 for disable command_id = { cayennemqtt.SYS_I2C: 11, cayennemqtt.SYS_SPI: 12, cayennemqtt.SYS_UART: 13, cayennemqtt.SYS_ONEWIRE: 19 } result, output = SystemConfig.ExecuteConfigCommand( command_id[message['channel']], value) debug('ProcessConfigCommand: {}, result: {}, output: {}'.format( message, result, output)) if result != 0: error = 'Error executing config command' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def ProcessGpioCommand(self, message): """Process GPIO command Returns: True if command was processed, False otherwise.""" error = None try: channel = int(message['channel'].replace( cayennemqtt.SYS_GPIO + ':', '')) result = self.sensorsClient.GpioCommand( message.get('suffix', 'value'), channel, message['payload']) debug('ProcessGpioCommand result: {}'.format(result)) if result == 'failure': error = 'GPIO command failed' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def ProcessSensorCommand(self, message): """Process sensor command Returns: True if command was processed, False otherwise.""" error = None try: sensor_info = message['channel'].replace( cayennemqtt.DEV_SENSOR + ':', '').split(':') sensor = sensor_info[0] channel = None if len(sensor_info) > 1: channel = sensor_info[1] result = self.sensorsClient.SensorCommand( message.get('suffix', 'value'), sensor, channel, message['payload']) debug('ProcessSensorCommand result: {}'.format(result)) if result is False: error = 'Sensor command failed' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def ProcessDeviceCommand(self, message): """Process a device command to add/edit/remove a sensor Returns: True if command was processed, False otherwise.""" error = None try: payload = message['payload'] info('ProcessDeviceCommand payload: {}'.format(payload)) if message['suffix'] == 'add': result = self.sensorsClient.AddSensor(payload['sensorId'], payload['description'], payload['class'], payload['args']) elif message['suffix'] == 'edit': result = self.sensorsClient.EditSensor(payload['sensorId'], payload['description'], payload['class'], payload['args']) elif message['suffix'] == 'delete': result = self.sensorsClient.RemoveSensor(payload['sensorId']) else: error = 'Unknown device command: {}'.format(message['suffix']) debug('ProcessDeviceCommand result: {}'.format(result)) if result is False: error = 'Device command failed' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def ProcessSchedulerCommand(self, message): """Process command to add/edit/remove a scheduled action Returns: True if command was processed, False otherwise.""" error = None try: if message['suffix'] == 'add': result = self.schedulerEngine.add_scheduled_event( message['payload'], True) elif message['suffix'] == 'edit': result = self.schedulerEngine.update_scheduled_event( message['payload']) elif message['suffix'] == 'delete': result = self.schedulerEngine.remove_scheduled_event( message['payload']) elif message['suffix'] == 'get': events = self.schedulerEngine.get_scheduled_events() self.EnqueuePacket(events, cayennemqtt.JOBS_TOPIC) else: error = 'Unknown schedule command: {}'.format( message['suffix']) debug('ProcessSchedulerCommand result: {}'.format(result)) if result is False: error = 'Schedule command failed' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def EnqueueCommandResponse(self, message, error): """Send response after processing a command message""" if not 'cmdId' in message: # If there is no command id we assume this is a scheduled command and don't send a response message. return debug('EnqueueCommandResponse error: {}'.format(error)) if error: response = 'error,{}={}'.format(message['cmdId'], error) else: response = 'ok,{}'.format(message['cmdId']) info(response) self.EnqueuePacket(response, cayennemqtt.COMMAND_RESPONSE_TOPIC) def EnqueuePacket(self, message, topic=cayennemqtt.DATA_TOPIC): """Enqueue a message packet to send to the server""" packet = (topic, message) self.writeQueue.put(packet) def DequeuePacket(self): """Dequeue a message packet to send to the server""" packet = (None, None) try: packet = self.writeQueue.get(False) except Empty: pass return packet
class SensorsClient(): """Class for interfacing with sensors and actuators""" def __init__(self, client): """Initialize the bus and sensor info and start monitoring sensor states""" self.cloudClient = client self.sensorMutex = RLock() self.realTimeMutex = RLock() self.exiting = Event() self.onDataChanged = None self.systemData = [] self.currentSystemState = [] self.currentRealTimeData = {} self.queuedRealTimeData = {} self.disabledSensors = {} self.disabledSensorTable = "disabled_sensors" checkAllBus() self.gpio = GPIO() # self.downloadSpeed = DownloadSpeed(Config(APP_SETTINGS)) # self.downloadSpeed.getDownloadSpeed() manager.addDeviceInstance("GPIO", "GPIO", "GPIO", self.gpio, [], "system") manager.loadJsonDevices("rest") if not DYNAMIC_DEVICES: warn("loadJsonDevices is None") for sensor in sensors.values(): # info('--------{} {} {}'.format(sensor['name'], sensor['description'], sensor['device'])) self.AddSensor(sensor['name'], sensor['description'], sensor['device'], sensor['args']) # # info(DYNAMIC_DEVICES) self.config = Config(APP_SETTINGS) self.clientId = self.config.get('Agent', 'ClientID', None) self.mqtt_dis_prefix = self.config.get('Agent', 'MQTT_DIS_PREFIX', "homeassistant") self.serial = self.cloudClient.hardware.Serial for name, device in DYNAMIC_DEVICES.items(): for type in device['type']: if type in ['DAC', 'ADC']: continue topic, message = self.AddMQTTSensorDevice(name, type, device) if self.cloudClient: info("{} {}".format(topic, message)) self.cloudClient.EnqueuePacket(message, topic) # info(mqttsensor) results = DbManager.Select(self.disabledSensorTable) if results: for row in results: self.disabledSensors[row[0]] = 1 self.realTimeMonitorRunning = False self.pluginManager = PluginManager(self.OnPluginChange) self.pluginManager.load_plugins() self.InitCallbacks() self.StartMonitoring() def AddMQTTSensorDevice(self, name, type, device): sensor_topic = "sensor/{}/{}/config".format(self.serial, name) unit = { "Temperature": "℃", "Humidity": "%", "Pressure": "pa", "Distance": "mm", "MQSensor": "ppm", "Luminosity": "lu", "CO2Sensor": "ppm" } icon = { "Temperature": "mdi:coolant-temperature", "Humidity": "mdi:water", "Pressure": "mdi:file-powerpoint-box", "Distance": "mdi:ruler", "MQSensor": "mdi:gas-cylinder", "Luminosity": "mdi:white-balance-sunny", "CO2Sensor": "mdi:periodic-table-co2" } sensor_config = \ { "device_class": "temperature", "name": "{}{}".format(self.cloudClient.location,name) , "state_topic": "{}/sensor/{}/dev:{}/state".format(self.mqtt_dis_prefix, self.serial, name), "unit_of_measurement": unit[type], "icon": icon[type], "value_template": "{{ value_json.value}}" } return sensor_topic, sensor_config def SetDataChanged(self, onDataChanged=None): """Set callback to call when data has changed Args: onDataChanged: Function to call when sensor data changes """ self.onDataChanged = onDataChanged def QueueRealTimeData(self, name, data): """Add real-time data to queue to be sent on thread Args: name: The name to use for the data data: The data to send """ with self.realTimeMutex: if name not in self.currentRealTimeData: self.currentRealTimeData[name] = data else: self.queuedRealTimeData[name] = data def OnSensorChange(self, device, value): """Callback that is called when digital sensor data has changed Args: device: The device that has changed data value: The new data value """ debug('OnSensorChange: {}, {}'.format(device, value)) with self.realTimeMutex: data = { 'name': device['description'], 'value': value, 'type': 'digital_sensor', 'unit': 'd' } if 'args' in device: data['args'] = device['args'] self.QueueRealTimeData(device['name'], data) def OnPluginChange(self, data): """Callback that is called when digital sensor data has changed Args: data: The new data value """ debug('OnPluginChange: {}'.format(data)) self.QueueRealTimeData(data['id'], data) with self.realTimeMutex: if not self.realTimeMonitorRunning: ThreadPool.Submit(self.RealTimeMonitor) def OnGpioStateChange(self, channel, value): """Send updated pin state when it has changed Args: channel: The pin number value: The new value for the pin """ debug('OnGpioStateChange: channel {}, value {}'.format(channel, value)) data = [] cayennemqtt.DataChannel.add_unique(data, cayennemqtt.SYS_GPIO, channel, cayennemqtt.VALUE, value) if not self.realTimeMonitorRunning: self.onDataChanged(data) else: self.QueueRealTimeData(data[0]['channel'], data[0]) def InitCallbacks(self): """Set callback function for any digital devices that support them""" devices = manager.getDeviceList() for device in devices: sensor = instance.deviceInstance(device['name']) if 'DigitalSensor' in device['type'] and hasattr( sensor, 'setCallback'): debug('Set callback for {}'.format(sensor)) sensor.setCallback(self.OnSensorChange, device) if not self.realTimeMonitorRunning: ThreadPool.Submit(self.RealTimeMonitor) def RemoveCallbacks(self): """Remove callback function for all digital devices""" devices = manager.getDeviceList() for device in devices: sensor = instance.deviceInstance(device['name']) if 'DigitalSensor' in device['type'] and hasattr( sensor, 'removeCallback'): sensor.removeCallback() def StartMonitoring(self): """Start thread monitoring sensor data""" # pass ThreadPool.Submit(self.Monitor) def StopMonitoring(self): """Stop thread monitoring sensor data""" self.RemoveCallbacks() self.exiting.set() def Monitor(self): """Monitor bus/sensor states and system info and report changed data via callbacks""" debug('Monitoring sensors and os resources started') sendAllDataCount = 0 nextTime = datetime.now() while not self.exiting.is_set(): try: difference = nextTime - datetime.now() delay = min(REFRESH_FREQUENCY, difference.total_seconds()) delay = max(0, delay) if not self.exiting.wait(delay): nextTime = datetime.now() + timedelta( seconds=REFRESH_FREQUENCY) self.currentSystemState = [] self.MonitorSystemInformation() self.MonitorSensors() self.MonitorPlugins() self.MonitorBus() if self.currentSystemState != self.systemData: data = self.currentSystemState if self.systemData and not sendAllDataCount == 0: data = [ x for x in self.currentSystemState if x not in self.systemData ] if self.onDataChanged and data: self.onDataChanged(data) sendAllDataCount += 1 if sendAllDataCount >= 4: sendAllDataCount = 0 self.systemData = self.currentSystemState except: exception('Monitoring sensors and os resources failed') debug('Monitoring sensors and os resources finished') def RealTimeMonitor(self): """Monitor real-time state changes and report changed data via callbacks""" self.realTimeMonitorRunning = True info('Monitoring real-time state changes') nextTime = datetime.now() while not self.exiting.is_set(): try: if not self.exiting.wait(0.5): if datetime.now() > nextTime: nextTime = datetime.now() + timedelta( seconds=REAL_TIME_FREQUENCY) self.SendRealTimeData() except: exception('Monitoring real-time changes failed') debug('Monitoring real-time changes finished') self.realTimeMonitorRunning = False def SendRealTimeData(self): """Send real-time data via callback""" data = [] with self.realTimeMutex: if self.currentRealTimeData: for name, item in self.currentRealTimeData.items(): if cayennemqtt.SYS_GPIO in name: data.append(item) else: cayennemqtt.DataChannel.add_unique( data, cayennemqtt.DEV_SENSOR, name, value=item['value'], name=item['name'], type=item['type'], unit=item['unit']) try: cayennemqtt.DataChannel.add_unique( data, cayennemqtt.SYS_GPIO, item['args']['channel'], cayennemqtt.VALUE, item['value']) except: pass if name in self.queuedRealTimeData and self.queuedRealTimeData[ name]['value'] == item['value']: del self.queuedRealTimeData[name] self.currentRealTimeData = self.queuedRealTimeData self.queuedRealTimeData = {} if data: self.onDataChanged(data) def MonitorSensors(self): """Check sensor states for changes""" if self.exiting.is_set(): return self.currentSystemState += self.SensorsInfo() def MonitorPlugins(self): """Check plugin states for changes""" if self.exiting.is_set(): return self.currentSystemState += self.pluginManager.get_plugin_readings() def MonitorBus(self): """Check bus states for changes""" if self.exiting.is_set(): return self.currentSystemState += self.BusInfo() def MonitorSystemInformation(self): """Check system info for changes""" if self.exiting.is_set(): return self.currentSystemState += self.SystemInformation() def SystemInformation(self): """Return dict containing current system info, including CPU, RAM, storage and network info""" newSystemInfo = [] try: systemInfo = SystemInfo() newSystemInfo = systemInfo.getSystemInformation() # download_speed = self.downloadSpeed.getDownloadSpeed() # if download_speed: # cayennemqtt.DataChannel.add(newSystemInfo, cayennemqtt.SYS_NET, suffix=cayennemqtt.SPEEDTEST, value=download_speed, type='bw', unit='mbps') except Exception: exception('SystemInformation failed') return newSystemInfo def CallDeviceFunction(self, func, *args): """Call a function for a sensor/actuator device and format the result value type Args: func: Function to call args: Parameters to pass to the function Returns: True for success, False otherwise. """ result = func(*args) debug("result={}".format(result)) if result != None: if hasattr(func, "contentType"): if func.contentType != M_JSON: value_type = type(result) response = value_type(func.format % result) else: response = result else: response = result debug("response={}".format(response)) return response def BusInfo(self): """Return a dict with current bus info""" bus_info = [] gpio_state = self.gpio.wildcard() for key, value in gpio_state.items(): cayennemqtt.DataChannel.add(bus_info, cayennemqtt.SYS_GPIO, key, cayennemqtt.VALUE, value['value']) cayennemqtt.DataChannel.add(bus_info, cayennemqtt.SYS_GPIO, key, cayennemqtt.FUNCTION, value['function']) return bus_info def SensorsInfo(self): """Return a list with current sensor states for all enabled sensors""" manager.deviceDetector() devices = manager.getDeviceList() sensors_info = [] if devices is None: return sensors_info for device in devices: sensor = instance.deviceInstance(device['name']) if 'enabled' not in device or device['enabled'] == 1: sensor_types = { 'Temperature': { 'function': 'getCelsius', 'data_args': { 'type': 'temperature', 'unit': 'c' } }, 'Humidity': { 'function': 'getHumidityPercent', 'data_args': { 'type': 'humidity', 'unit': 'p' } }, 'Pressure': { 'function': 'getPascal', 'data_args': { 'type': 'pressure', 'unit': 'pa' } }, 'Luminosity': { 'function': 'getLux', 'data_args': { 'type': 'illuminance', 'unit': 'lux' } }, 'Distance': { 'function': 'getCentimeter', 'data_args': { 'type': 'prox', 'unit': 'cm' } }, 'MQSensor': { 'function': 'getMQ', 'data_args': { 'type': 'mq136', 'unit': 'ppm' } }, 'CO2Sensor': { 'function': 'readCO2', 'data_args': { 'type': 'co2', 'unit': 'ppm' } }, 'ServoMotor': { 'function': 'readAngle', 'data_args': { 'type': 'analog_actuator' } }, 'DigitalSensor': { 'function': 'read', 'data_args': { 'type': 'digital_sensor', 'unit': 'd' } }, 'DigitalActuator': { 'function': 'read', 'data_args': { 'type': 'digital_actuator', 'unit': 'd' } }, # 'AnalogSensor': {'function': 'readFloat', 'data_args': {'type': 'analog_sensor'}}, 'AnalogSensor': { 'function': 'readVolt', 'data_args': { 'type': 'analog_sensor' } }, 'AnalogActuator': { 'function': 'readFloat', 'data_args': { 'type': 'analog_actuator' } } } # extension_types = {'ADC': {'function': 'analogReadAllFloat'}, # 'DAC': {'function': 'analogReadAllFloat'}, # 'PWM': {'function': 'pwmWildcard'}, # 'GPIOPort': {'function': 'wildcard'}} for device_type in device['type']: try: display_name = device['description'] except: display_name = None # info("display_name:{}".format(display_name)) if device_type in sensor_types: try: sensor_type = sensor_types[device_type] # info("sensor_type:{}".format(sensor_type)) func = getattr(sensor, sensor_type['function']) # info("func:{}".format(func)) if len(device['type']) > 1: channel = '{}:{}'.format( device['name'], device_type.lower()) else: channel = device['name'] value = self.CallDeviceFunction(func) # info("value={}".format(value)) cayennemqtt.DataChannel.add( sensors_info, cayennemqtt.DEV_SENSOR, channel, value=value, name=display_name, **sensor_type['data_args']) if 'DigitalActuator' == device_type and value in ( 0, 1): manager.updateDeviceState( device['name'], value) except: exception( 'Failed to get sensor data: {} {}'.format( device_type, device['name'])) # else: # try: # extension_type = extension_types[device_type] # func = getattr(sensor, extension_type['function']) # values = self.CallDeviceFunction(func) # for pin, value in values.items(): # cayennemqtt.DataChannel.add(sensors_info, cayennemqtt.DEV_SENSOR, device['name'] + ':' + str(pin), cayennemqtt.VALUE, value, name=display_name) # except: # exception('Failed to get extension data: {} {}'.format(device_type, device['name'])) # info('Sensors info: {}'.format(sensors_info)) return sensors_info def AddSensor(self, name, description, device, args): """Add a new sensor/actuator Args: name: Name of sensor to add description: Sensor description device: Sensor device class args: Sensor specific args Returns: True for success, False otherwise. :param index: """ info('AddSensor: {}, {}, {}, {}'.format(name, description, device, args)) bVal = False try: sensorAdd = {} if name: sensorAdd['name'] = name if device: sensorAdd['device'] = device if args: sensorAdd['args'] = args if description: sensorAdd['description'] = description with self.sensorMutex: retValue = manager.addDeviceJSON(sensorAdd) self.InitCallbacks() info('Add device returned: {}'.format(retValue)) if retValue[0] == 200: bVal = True except Exception: exception('Error adding sensor') bVal = False return bVal def EditSensor(self, name, description, device, args): """Edit an existing sensor/actuator Args: name: Name of sensor to edit description: New sensor description device: New sensor device class args: New sensor specific args Returns: True for success, False otherwise. """ info('EditSensor: {}, {}, {}, {}'.format(name, description, device, args)) bVal = False try: sensorEdit = {} name = name sensorEdit['name'] = name sensorEdit['device'] = device sensorEdit['description'] = description sensorEdit['args'] = args with self.sensorMutex: retValue = manager.updateDevice(name, sensorEdit) self.InitCallbacks() info('Edit device returned: {}'.format(retValue)) if retValue[0] == 200: bVal = True except: exception("Edit sensor failed") bVal = False return bVal def RemoveSensor(self, name): """Remove an existing sensor/actuator Args: name: Name of sensor to remove Returns: True for success, False otherwise. """ bVal = False try: if self.pluginManager.is_plugin(name): return self.pluginManager.disable(name) sensorRemove = name try: sensor = instance.deviceInstance(sensorRemove) if hasattr(sensor, 'removeCallback'): sensor.removeCallback() except: pass with self.sensorMutex: retValue = manager.removeDevice(sensorRemove) info('Remove device returned: {}'.format(retValue)) if retValue[0] == 200: bVal = True except: exception("Remove sensor failed") bVal = False return bVal def EnableSensor(self, sensor, enable): """Enable a sensor/actuator Args: sensor: Hash composed from name and device class/type enable: 1 to enable, 0 to disable Returns: True for success, False otherwise. """ info('Enable sensor: ' + str(sensor) + ' ' + str(enable)) try: if sensor is None: return False if enable is None: return False with self.sensorMutex: if enable == 0: #add item to the list if sensor not in self.disabledSensors: DbManager.Insert(self.disabledSensorTable, sensor) self.disabledSensors[sensor] = 1 else: #remove item from the list if sensor in self.disabledSensors: DbManager.Delete(self.disabledSensorTable, sensor) del self.disabledSensors[sensor] #save list except Exception as ex: error('EnableSensor Failed with exception: ' + str(ex)) return False return True def GpioCommand(self, command, channel, value): """Execute onboard GPIO command Args: command: Type of command to execute channel: GPIO pin value: Value to use for writing data Returns: String containing command specific return value on success, or 'failure' on failure """ info('GpioCommand {}, channel {}, value {}'.format( command, channel, value)) result = 'failure' if command == 'function': old_state = self.gpio.digitalRead(channel) if value.lower() in ('in', 'input'): result = str(self.gpio.setFunctionString(channel, 'in')) elif value.lower() in ('out', 'output'): result = str(self.gpio.setFunctionString(channel, 'out')) new_state = self.gpio.digitalRead(channel) if new_state != old_state: self.OnGpioStateChange(channel, new_state) elif command in ('value', ''): return self.gpio.digitalWrite(channel, int(value)) debug('GPIO command failed') return result def SensorCommand(self, command, sensorId, channel, value): """Execute sensor/actuator command Args: command: Type of command to execute sensorId: Sensor id channel: Pin/channel on device, None if there is no channel value: Value to use for setting the sensor state Returns: Command specific return value on success, False on failure """ result = False info('SensorCommand: {}, sensor {}, channel {}, value {}'.format( command, sensorId, channel, value)) try: if self.pluginManager.is_plugin(sensorId, channel): return self.pluginManager.write_value(sensorId, channel, value) commands = { 'integer': { 'function': 'write', 'value_type': int }, 'value': { 'function': 'write', 'value_type': int }, 'function': { 'function': 'setFunctionString', 'value_type': str }, 'angle': { 'function': 'writeAngle', 'value_type': float }, 'float': { 'function': 'writeFloat', 'value_type': float }, 'volt': { 'function': 'writeVolt', 'value_type': float } } with self.sensorMutex: if sensorId in self.disabledSensors: info('Sensor disabled') return result sensor = instance.deviceInstance(sensorId) if not sensor: info('Sensor not found') return result if command in commands: device = instance.DEVICES[sensorId] info('Sensor found: {}'.format(device)) func = getattr(sensor, commands[command]['function']) value = commands[command]['value_type'](value) if channel: result = self.CallDeviceFunction( func, int(channel), value) else: result = self.CallDeviceFunction(func, value) if 'DigitalActuator' in device['type']: manager.updateDeviceState(sensorId, value) return result warn('Command not implemented: {}'.format(command)) return result except Exception: exception('SensorCommand failed') return result
def load_plugin_from_file(self, filename): """Loads a plugin from a specified plugin config file and adds it to the plugin list.""" try: info('Loading plugin: {}'.format(filename)) loaded = [] config = Config(filename) plugin_name = os.path.splitext(os.path.basename(filename))[0] info('Sections: {}'.format(config.sections())) inherited_from = set() for section in config.sections(): inherit = config.get(section, 'inherit', None) if inherit: inherited_from.add(inherit) for section in config.sections(): try: enabled = config.get(section, 'enabled', 'true').lower() == 'true' inherit = config.get(section, 'inherit', None) if enabled or section in inherited_from: plugin = { 'enabled': enabled, 'filename': filename, 'section': section, 'name': config.get(section, 'name', section), } try: plugin['channel'] = config.get(section, 'channel') plugin[ 'id'] = plugin_name + ':' + plugin['channel'] except NoOptionError: plugin['id'] = plugin_name + ':' + section inherit_items = {} if inherit in config.sections(): if inherit == section: raise ValueError( 'Section \'{}\' cannot inherit from itself' .format(section)) inherit_from = self.get_plugin(filename, inherit) inherit_items = { key: value for key, value in inherit_from.items() if key not in plugin.keys() } plugin.update(inherit_items) elif inherit: raise ValueError( 'Section \'{}\' cannot inherit from \'{}\'. Check spelling and section ordering.' .format(section, inherit)) self.override_plugin_value(config, section, 'module', plugin) self.override_plugin_value(config, section, 'class', plugin) if 'init_args' not in plugin: plugin['init_args'] = '{}' self.override_plugin_value(config, section, 'init_args', plugin) if not inherit_items or [ key for key in ('module', 'class', 'init_args') if inherit_items[key] is not plugin[key] ]: info('Creating instance of {} for {}'.format( plugin['class'], plugin['name'])) folder = os.path.dirname(filename) if folder not in sys.path: sys.path.append(folder) imported_module = importlib.import_module( plugin['module']) device_class = getattr(imported_module, plugin['class']) plugin['instance'] = device_class( **json.loads(plugin['init_args'])) self.override_plugin_value(config, section, 'read', plugin) if 'read_args' not in plugin: plugin['read_args'] = '{}' self.override_plugin_value(config, section, 'read_args', plugin) try: self.override_plugin_value(config, section, 'write', plugin) if 'write_args' not in plugin: plugin['write_args'] = '{}' self.override_plugin_value(config, section, 'write_args', plugin) except: pass try: self.override_plugin_value(config, section, 'set_function', plugin) except: pass try: self.override_plugin_value(config, section, 'register_callback', plugin) getattr(plugin['instance'], plugin['register_callback'])( lambda value, plugin=plugin: self. data_changed(value, plugin)) except: pass try: self.override_plugin_value(config, section, 'unregister_callback', plugin) except: pass self.plugins[plugin['id']] = plugin loaded.append(section) except Exception as e: error(e) except Exception as e: error(e) info('Loaded sections: {}'.format(loaded))
class SensorsClientTest(unittest.TestCase): @classmethod def setUpClass(cls): sevclient = CloudServerClient(host='192.168.8.107', port='1883', cayenneApiHost='192.168.8.107') cls.client = sensors.SensorsClient(sevclient) @classmethod def tearDownClass(cls): cls.client.StopMonitoring() del cls.client def OnDataChanged(self, sensor_data): # if len(sensor_data) < 5: # info('OnDataChanged: {}'.format(sensor_data)) # else: # info('OnDataChanged: {}'.format(len(sensor_data))) self.previousSystemData = self.currentSystemData self.currentSystemData = sensor_data if self.previousSystemData: self.done = True def testMonitor(self): debug('testMonitor') self.previousSystemData = None self.currentSystemData = None self.done = False SensorsClientTest.client.SetDataChanged(self.OnDataChanged) for i in range(35): sleep(1) if self.done: break info('Changed items: {}'.format([ x for x in self.currentSystemData if x not in self.previousSystemData ])) self.assertNotEqual(self.previousSystemData, self.currentSystemData) def testBusInfo(self): debug('testBusInfo') bus = { item['channel']: item['value'] for item in SensorsClientTest.client.BusInfo() } info('Bus info: {}'.format(bus)) for pin in GPIO().pins: self.assertIn('sys:gpio:{};function'.format(pin), bus) self.assertIn('sys:gpio:{};value'.format(pin), bus) def testSensorsInfo(self): debug('testSensorsInfo') sensors = SensorsClientTest.client.SensorsInfo() info('Sensors info: {}'.format(sensors)) for sensor in sensors: self.assertEqual('dev:', sensor['channel'][:4]) self.assertIn('value', sensor) def testSetFunction(self): debug('testSetFunciton') self.setChannelFunction(GPIO().pins[7], 'IN') self.setChannelFunction(GPIO().pins[7], 'OUT') def testSetValue(self): debug('testSetValue') self.setChannelFunction(GPIO().pins[7], 'OUT') self.setChannelValue(GPIO().pins[7], 1) self.setChannelValue(GPIO().pins[7], 0) def testSensors(self): debug('testSensors') #Test adding a sensor channel = GPIO().pins[8] testSensor = { 'description': 'Digital Input', 'device': 'DigitalSensor', 'args': { 'gpio': 'GPIO', 'invert': False, 'channel': channel }, 'name': 'testdevice' } SensorsClientTest.client.RemoveSensor( testSensor['name'] ) #Attempt to remove device if it already exists from a previous test compareKeys = ('args', 'description', 'device') retValue = SensorsClientTest.client.AddSensor( testSensor['name'], testSensor['description'], testSensor['device'], testSensor['args']) self.assertTrue(retValue) retrievedSensor = next(obj for obj in manager.getDeviceList() if obj['name'] == testSensor['name']) for key in compareKeys: self.assertEqual(testSensor[key], retrievedSensor[key]) #Test updating a sensor editedSensor = testSensor editedSensor['args']['channel'] = GPIO().pins[5] retValue = SensorsClientTest.client.EditSensor( editedSensor['name'], editedSensor['description'], editedSensor['device'], editedSensor['args']) self.assertTrue(retValue) retrievedSensor = next(obj for obj in manager.getDeviceList() if obj['name'] == editedSensor['name']) for key in compareKeys: self.assertEqual(editedSensor[key], retrievedSensor[key]) #Test removing a sensor retValue = SensorsClientTest.client.RemoveSensor(testSensor['name']) self.assertTrue(retValue) deviceNames = [device['name'] for device in manager.getDeviceList()] self.assertNotIn(testSensor['name'], deviceNames) def testSensorInfo(self): debug('testSensorInfo') self.config = Config(APP_SETTINGS) self.location = self.config.get('Agent', 'Location', "house0_room0_") actuator_channel = GPIO().pins[10] light_switch_channel = GPIO().pins[11] adcSensors = {} for sensor in adcSensors.values(): info('--------{} {} {}'.format(sensor['name'], sensor['description'], sensor['device'])) SensorsClientTest.client.AddSensor(sensor['name'], sensor['description'], sensor['device'], sensor['args']) sensors = { 'PCF8591': { 'description': 'PCF8591', 'index': 0, 'device': 'PCF8591', 'args': {}, 'name': 'adc' }, 'distance': { 'description': 'distance', 'index': 1, 'device': 'VL6180X', 'args': {}, 'name': self.location + 'distance' }, 'object_temperature': { 'description': 'ir_body_temperature', 'index': 2, 'device': 'MLX90614', 'args': { 'obj_temp': True }, 'name': self.location + 'ir_body' }, 'amb_temperature': { 'description': 'ir_climate_temperature', 'index': 3, 'device': 'MLX90614', 'args': { 'obj_temp': False }, 'name': self.location + 'ir_climate' }, 'luminosity': { 'description': 'luminosity', 'index': 4, 'device': 'GY30', 'args': {}, 'name': self.location + 'luminosity' }, 'co2': { 'description': 'co2', 'index': 5, 'device': 'CO2Sensor', 'args': { 'adc': 'adc', 'channel': 3 }, 'name': self.location + 'gas' }, 'h2s': { 'description': 'h2s', 'index': 6, 'device': 'MQSensor', 'args': { 'adc': 'adc', 'channel': 2 }, 'name': self.location + 'gas' }, 'nh3': { 'description': 'nh3', 'index': 6, 'device': 'MQSensor', 'args': { 'adc': 'adc', 'channel': 4 }, 'name': self.location + 'gas' }, 'climate': { 'description': 'climate', 'index': 7, 'device': 'BME280', 'args': { 'temperature': True, 'pressure': True, 'humidity': True }, 'name': self.location + 'climate' }, } for sensor in sensors.values(): # info("sensors:{}".format(sensor)) SensorsClientTest.client.RemoveSensor(sensor['name']) for sensor in sensors.values(): info('--------{} {} {}'.format(sensor['name'], sensor['description'], sensor['device'])) SensorsClientTest.client.AddSensor(sensor['name'], sensor['description'], sensor['device'], sensor['args']) # SensorsClientTest.client.SensorsInfo() #Test setting sensor values # self.setSensorValue(sensors['actuator'], 1) # self.setSensorValue(sensors['actuator'], 0) # self.setSensorValue(sensors['light_switch'], 1) # self.setSensorValue(sensors['light_switch'], 0) #Test getting analog value # channel = 'dev:{}'.format(sensors['MQ']['name']) # info(" channel -----> {} ".format(channel)) count = 0 while count < 1: info("new loop for SensorsInfo") sleep(5) for obj in SensorsClientTest.client.SensorsInfo(): info(obj) count = count + 1 # for obj in SensorsClientTest.client.SensorsInfo(): # info(obj) # for obj in SensorsClientTest.client.SensorsInfo(): # info(obj) # retrievedSensorInfo = next(obj for obj in SensorsClientTest.client.SensorsInfo() if obj['channel'] == channel) # # info(" retrievedSensorInfo -----> {} value={}".format(retrievedSensorInfo,retrievedSensorInfo['value'])) # # self.assertGreaterEqual(retrievedSensorInfo['value'], 0.0) # # self.assertLessEqual(retrievedSensorInfo['value'], 1.0) # for sensor in sensors.values(): # info(sensor['name']) # self.assertTrue(SensorsClientTest.client.RemoveSensor(sensor['name'])) def testSensorCallback(self): debug('testSensorCallback') self.previousSystemData = None self.currentSystemData = None self.done = False SensorsClientTest.client.SetDataChanged(self.OnDataChanged) actuator_channel = GPIO().pins[10] sensor_channel = GPIO().pins[11] sensors = { 'actuator': { 'description': 'Digital Output', 'device': 'DigitalActuator', 'args': { 'gpio': 'GPIO', 'invert': False, 'channel': actuator_channel }, 'name': 'test_actuator' }, 'sensor': { 'description': 'Digital Input', 'device': 'DigitalSensor', 'args': { 'gpio': 'GPIO', 'invert': False, 'channel': sensor_channel }, 'name': 'testdevice' } } for sensor in sensors.values(): SensorsClientTest.client.AddSensor(sensor['name'], sensor['description'], sensor['device'], sensor['args']) for i in range(35): sleep(1) if self.done: break info('Changed items: {}'.format([ x for x in self.currentSystemData if x not in self.previousSystemData ])) self.assertNotEqual(self.previousSystemData, self.currentSystemData) for sensor in sensors.values(): self.assertTrue( SensorsClientTest.client.RemoveSensor(sensor['name'])) def setSensorValue(self, sensor, value): SensorsClientTest.client.SensorCommand('integer', sensor['name'], None, value) channel = 'dev:{}'.format(sensor['name']) sensorInfo = next(obj for obj in SensorsClientTest.client.SensorsInfo() if obj['channel'] == channel) self.assertEqual(value, sensorInfo['value']) def setChannelFunction(self, channel, function): SensorsClientTest.client.GpioCommand('function', channel, function) bus = { item['channel']: item['value'] for item in SensorsClientTest.client.BusInfo() } self.assertEqual(function, bus['sys:gpio:{};function'.format(channel)]) def setChannelValue(self, channel, value): SensorsClientTest.client.GpioCommand('value', channel, value) bus = { item['channel']: item['value'] for item in SensorsClientTest.client.BusInfo() } self.assertEqual(value, bus['sys:gpio:{};value'.format(channel)])