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 SchedulerTest(unittest.TestCase): def setUp(self): self.maxDiff = None self.test_client = TestClient() self.test_engine = SchedulerEngine(self.test_client, 'test') self.schedule_events = [] def tearDown(self): self.remove_schedules() self.test_engine.stop() def add_schedules(self, schedule_events): for event in schedule_events: self.test_engine.add_scheduled_event(event, True) self.schedule_events = self.schedule_events + schedule_events def remove_schedules(self, engine=None): scheduled_events = { event['id']: event for event in self.schedule_events if 'id' in event } for event in scheduled_events.values(): self.assertTrue(self.test_engine.remove_scheduled_event(event)) def check_schedules_added(self, expected): actual = self.test_engine.get_scheduled_events() self.assertCountEqual(expected, actual) def check_schedules_run(self, expected, skip_jobs=()): print('Pause to allow scheduled events to execute') expected_to_run = [ action for event in expected if event['title'] not in skip_jobs for action in event['actions'] ] for i in range(70): time.sleep(1) if len(expected_to_run) > 0 and len(expected_to_run) == len( self.test_client.actions_ran): break self.assertCountEqual(expected_to_run, self.test_client.actions_ran) def test_missing_id(self): start_date = datetime.datetime.strftime( datetime.datetime.utcnow() + datetime.timedelta(seconds=60), '%Y-%m-%dT%H:%M:%S.%fZ') missing_id_event = { 'title': 'no_id_job', 'actions': ['no_id_job_action'], 'config': { 'type': 'date', 'start_date': start_date } } self.assertFalse( self.test_engine.add_scheduled_event(missing_id_event, True)) self.assertFalse(self.test_engine.get_scheduled_events()) def test_overwrite_job(self): start_date = datetime.datetime.strftime( datetime.datetime.utcnow() + datetime.timedelta(seconds=60), '%Y-%m-%dT%H:%M:%S.%fZ') schedule_events = [{ 'id': 'overwrite_1', 'title': 'overwritten_job', 'actions': ['overwritten_job_action'], 'config': { 'type': 'date', 'start_date': start_date } }, { 'id': 'overwrite_1', 'title': 'date_job_readd_same_id', 'actions': ['date_job_readd_same_id_action'], 'config': { 'type': 'date', 'start_date': start_date } }] self.add_schedules(schedule_events) expected = [ event for event in schedule_events if 'id' in event and event['title'] != 'overwritten_job' ] self.check_schedules_added(expected) def test_current_schedules(self): start_date = datetime.datetime.strftime( datetime.datetime.utcnow() + datetime.timedelta(seconds=60), '%Y-%m-%dT%H:%M:%S.%fZ') now = datetime.datetime.strftime(datetime.datetime.utcnow(), '%Y-%m-%dT%H:%M:%S.%fZ') schedule_events = [{ 'id': 'current_1', 'title': 'date_job', 'actions': ['date_job_action'], 'config': { 'type': 'date', 'start_date': start_date } }, { 'id': 'current_2', 'title': 'daily_job', 'actions': ['daily_job_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 1, 'start_date': start_date } }, { 'id': 'current_3', 'title': 'every_3_days_job', 'actions': ['every_3_days_job_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 3, 'start_date': start_date } }, { 'id': 'current_4', 'title': 'now_date_job', 'actions': ['now_date_job_action'], 'config': { 'type': 'date', 'start_date': now } }, { 'id': 'current_5', 'title': 'weekly_job', 'actions': ['weekly_job_action'], 'config': { 'type': 'interval', 'unit': 'week', 'interval': 1, 'start_date': start_date } }, { 'id': 'current_6', 'title': 'bi-weekly_job', 'actions': ['weekly_job_action'], 'config': { 'type': 'interval', 'unit': 'week', 'interval': 2, 'start_date': start_date } }, { 'id': 'current_7', 'title': 'every_4_months_job', 'actions': ['every_4_months_job_action'], 'config': { 'type': 'interval', 'unit': 'month', 'interval': 4, 'start_date': start_date } }, { 'id': 'current_8', 'title': 'every_3_months_job', 'actions': ['every_3_months_job_action'], 'config': { 'type': 'interval', 'unit': 'month', 'interval': 3, 'start_date': now } }, { 'id': 'current_9', 'title': 'hourly_job', 'actions': ['hourly_job_action'], 'config': { 'type': 'interval', 'unit': 'hour', 'interval': 1, 'start_date': start_date } }] self.add_schedules(schedule_events) self.check_schedules_added(schedule_events) self.check_schedules_run(schedule_events) def test_past_schedules(self): next_minute = datetime.datetime.utcnow() + datetime.timedelta( seconds=60) passed_date = datetime.datetime.strftime( datetime.datetime.utcnow() - datetime.timedelta(seconds=120), '%Y-%m-%dT%H:%M:%S.%fZ') one_day_ago = datetime.datetime.strftime( next_minute - datetime.timedelta(days=1), '%Y-%m-%dT%H:%M:%S.%fZ') one_week_ago = datetime.datetime.strftime( next_minute - datetime.timedelta(days=7), '%Y-%m-%dT%H:%M:%S.%fZ') one_month_ago = datetime.datetime.strftime( schedule.month_delta(next_minute, -1), '%Y-%m-%dT%H:%M:%S.%fZ') one_year_ago = next_minute.replace(year=next_minute.year - 1) one_year_ago = datetime.datetime.strftime(one_year_ago, '%Y-%m-%dT%H:%M:%S.%fZ') schedule_events = [{ 'id': 'past_1', 'title': 'expired_date_job', 'actions': ['expired_date_job_action'], 'config': { 'type': 'date', 'start_date': passed_date } }, { 'id': 'past_2', 'title': 'daily_job_started_one_day_ago', 'actions': ['daily_job_started_one_day_ago_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 1, 'start_date': one_day_ago } }, { 'id': 'past_3', 'title': 'monthly_job_started_one_month_ago', 'actions': ['monthly_job_started_one_month_ago_action'], 'config': { 'type': 'interval', 'unit': 'month', 'interval': 1, 'start_date': one_month_ago } }, { 'id': 'past_4', 'title': 'yearly_job_started_one_year_ago', 'actions': ['yearly_job_started_one_year_ago_action'], 'config': { 'type': 'interval', 'unit': 'year', 'interval': 1, 'start_date': one_year_ago } }, { 'id': 'past_5', 'title': 'every_2_years_job_started_one_year_ago', 'actions': ['every_2_years_job_started_one_year_ago_action'], 'config': { 'type': 'interval', 'unit': 'year', 'interval': 2, 'start_date': one_year_ago } }, { 'id': 'past_6', 'title': 'weekly_job_started_one_week_ago', 'actions': ['weekly_job_started_one_week_ago_action'], 'config': { 'type': 'interval', 'unit': 'week', 'interval': 1, 'start_date': one_week_ago } }] self.add_schedules(schedule_events) self.check_schedules_added(schedule_events) self.check_schedules_run( schedule_events, ('expired_date_job', 'every_2_years_job_started_one_year_ago')) def test_future_schedules(self): one_day_from_now = datetime.datetime.strftime( datetime.datetime.utcnow() + datetime.timedelta(days=1), '%Y-%m-%dT%H:%M:%S.%fZ') end_of_month = datetime.datetime.strftime( datetime.datetime(2015, 1, 31), '%Y-%m-%dT%H:%M:%S.%fZ') future_month = datetime.datetime.strftime( datetime.datetime(2017, 12, 31), '%Y-%m-%dT%H:%M:%S.%fZ') future_year = datetime.datetime.strftime(datetime.datetime(2017, 1, 1), '%Y-%m-%dT%H:%M:%S.%fZ') schedule_events = [{ 'id': 'future_1', 'title': 'daily_job_starts_one_day_from_now', 'actions': ['daily_job_starts_one_day_from_now_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 1, 'start_date': one_day_from_now } }, { 'id': 'future_2', 'title': 'end_of_month_job', 'actions': ['end_of_month_job_action'], 'config': { 'type': 'interval', 'unit': 'month', 'interval': 1, 'start_date': end_of_month } }, { 'id': 'future_3', 'title': 'future_month_job', 'actions': ['future_month_job_action'], 'config': { 'type': 'interval', 'unit': 'month', 'interval': 1, 'start_date': future_month } }, { 'id': 'future_4', 'title': 'future_year_job', 'actions': ['future_year_job_action'], 'config': { 'type': 'interval', 'unit': 'month', 'interval': 1, 'start_date': future_year } }] self.add_schedules(schedule_events) self.check_schedules_added(schedule_events) skip_jobs = [event['title'] for event in schedule_events] self.check_schedules_run(schedule_events, skip_jobs) def test_reload(self): start_date = datetime.datetime.strftime( datetime.datetime.utcnow() + datetime.timedelta(seconds=60), '%Y-%m-%dT%H:%M:%S.%fZ') schedule_events = [{ 'id': 'reload_1', 'title': 'date_job', 'actions': ['date_job_action'], 'config': { 'type': 'date', 'start_date': start_date } }, { 'id': 'reload_2', 'title': 'daily_job', 'actions': ['daily_job_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 1, 'start_date': start_date } }] self.add_schedules(schedule_events) self.check_schedules_added(schedule_events) self.check_schedules_run(schedule_events) self.test_engine.stop() del self.test_engine del self.test_client self.test_client = TestClient() self.test_engine = SchedulerEngine(self.test_client, 'test') for event in schedule_events: if 'last_run' in event: del event['last_run'] self.check_schedules_added(schedule_events) self.check_schedules_run(schedule_events, ('date_job', 'daily_job')) def test_delayed_load(self): self.test_engine.stop() del self.test_engine del self.test_client now = datetime.datetime.utcnow() if (now.second > 35): print('Sleep until the minute rolls over') time.sleep(60 - now.second) now = datetime.datetime.strftime(datetime.datetime.utcnow(), '%Y-%m-%dT%H:%M:%S.%fZ') self.schedule_events = [{ 'id': 'delay_1', 'title': 'date_job', 'actions': ['date_job_action'], 'config': { 'type': 'date', 'start_date': now } }, { 'id': 'delay_2', 'title': 'daily_job', 'actions': ['daily_job_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 1, 'start_date': now } }, { 'id': 'delay_3', 'title': 'weekly_job', 'actions': ['weekly_job_action'], 'config': { 'type': 'interval', 'unit': 'week', 'interval': 1, 'start_date': now } }, { 'id': 'delay_4', 'title': 'monthly_job', 'actions': ['monthly_job_action'], 'config': { 'type': 'interval', 'unit': 'month', 'interval': 1, 'start_date': now } }, { 'id': 'delay_5', 'title': 'yearly_job', 'actions': ['yearly_job_action'], 'config': { 'type': 'interval', 'unit': 'year', 'interval': 1, 'start_date': now } }] for event in self.schedule_events: event_json = json.dumps(event) try: DbManager.Insert('scheduled_events', event['id'], event_json) except sqlite3.IntegrityError as e: DbManager.Update('scheduled_events', 'event = ?', event_json, 'id = ?', event['id']) print('Pause before loading scheduler') time.sleep(20) print('Starting scheduler, time is {}'.format( datetime.datetime.utcnow())) self.test_client = TestClient() self.test_engine = SchedulerEngine(self.test_client, 'test') self.check_schedules_run(self.schedule_events) def test_concurrent_updates(self): now = datetime.datetime.strftime(datetime.datetime.utcnow(), '%Y-%m-%dT%H:%M:%S.%fZ') schedule_events = [{ 'id': 'concurrent_1', 'title': 'date_job', 'actions': ['date_job_action'], 'config': { 'type': 'date', 'start_date': now } }, { 'id': 'concurrent_1', 'title': 'date_job_updated', 'actions': ['date_job_action'], 'config': { 'type': 'date', 'start_date': now } }, { 'id': 'concurrent_2', 'title': 'daily_job', 'actions': ['daily_job_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 1, 'start_date': now } }, { 'id': 'concurrent_2', 'title': 'daily_job_updated', 'actions': ['daily_job_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 1, 'start_date': now } }, { 'id': 'concurrent_3', 'title': 'weekly_job', 'actions': ['weekly_job_action'], 'config': { 'type': 'interval', 'unit': 'week', 'interval': 1, 'start_date': now } }, { 'id': 'concurrent_3', 'title': 'weekly_job_updated', 'actions': ['weekly_job_action'], 'config': { 'type': 'interval', 'unit': 'week', 'interval': 1, 'start_date': now } }, { 'id': 'concurrent_4', 'title': 'monthly_job', 'actions': ['monthly_job_action'], 'config': { 'type': 'interval', 'unit': 'month', 'interval': 1, 'start_date': now } }, { 'id': 'concurrent_4', 'title': 'monthly_job_updated', 'actions': ['monthly_job_action'], 'config': { 'type': 'interval', 'unit': 'month', 'interval': 1, 'start_date': now } }, { 'id': 'concurrent_5', 'title': 'yearly_job', 'actions': ['yearly_job_action'], 'config': { 'type': 'interval', 'unit': 'year', 'interval': 1, 'start_date': now } }, { 'id': 'concurrent_5', 'title': 'yearly_job_updated', 'actions': ['yearly_job_action'], 'config': { 'type': 'interval', 'unit': 'year', 'interval': 1, 'start_date': now } }] for event in schedule_events: threading.Thread(target=self.add_schedules, daemon=True, args=([event], )).start() #Only half the schedule_events should run since ones with the same id will overwrite previously added ones. Since we don't know what order that will take #we just make sure we only check that one of each action has run. run_events = { event['id']: event for event in schedule_events if 'id' in event } skip_jobs = [event['title'] for event in run_events.values()] self.check_schedules_run(schedule_events, skip_jobs) def test_update_schedules(self): start_date = datetime.datetime.strftime( datetime.datetime.utcnow() + datetime.timedelta(seconds=60), '%Y-%m-%dT%H:%M:%S.%fZ') schedule_events = [{ 'id': 'update_1', 'title': 'date_job', 'actions': ['date_job_action'], 'config': { 'type': 'date', 'start_date': start_date } }, { 'id': 'update_2', 'title': 'daily_job', 'actions': ['daily_job_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 1, 'start_date': start_date } }] self.add_schedules(schedule_events) update_schedule_events = [{ 'id': 'update_3', 'title': 'date_job_full_update', 'actions': ['date_job_full_update_action'], 'config': { 'type': 'date', 'start_date': start_date } }, { 'id': 'update_4', 'title': 'daily_job_full_update', 'actions': ['daily_job_full_update_action'], 'config': { 'type': 'interval', 'unit': 'day', 'interval': 1, 'start_date': start_date } }] self.assertTrue( self.test_engine.update_scheduled_events(update_schedule_events)) self.schedule_events = update_schedule_events self.check_schedules_run(update_schedule_events) def start_http_server(self): self.server = HTTPServer(('localhost', 8000), TestHandler) self.server.received = [] self.server.serve_forever() def test_http_notification(self): threading.Thread(target=self.start_http_server, daemon=True).start() now = datetime.datetime.strftime(datetime.datetime.utcnow(), '%Y-%m-%dT%H:%M:%S.%fZ') schedule_events = [{ 'id': 'http_1', 'title': 'date_get_job', 'actions': ['date_job_action'], 'http_push': { 'url': 'http://localhost:8000', 'method': 'GET', 'headers': { 'Content-Type': 'application/json' }, 'payload': { 'test': 'GET request' } }, 'config': { 'type': 'date', 'start_date': now } }, { 'id': 'http_2', 'title': 'date_post_job', 'actions': ['date_job_action'], 'http_push': { 'url': 'http://localhost:8000', 'method': 'POST', 'headers': { 'Content-Type': 'application/json' }, 'payload': { 'test': 'POST request' } }, 'config': { 'type': 'date', 'start_date': now } }, { 'id': 'http_3', 'title': 'date_put_job', 'actions': ['date_job_action'], 'http_push': { 'url': 'http://localhost:8000', 'method': 'PUT', 'headers': { 'Content-Type': 'application/json' }, 'payload': { 'test': 'PUT request' } }, 'config': { 'type': 'date', 'start_date': now } }, { 'id': 'http_4', 'title': 'date_delete_job', 'actions': ['date_job_action'], 'http_push': { 'url': 'http://localhost:8000', 'method': 'DELETE', 'headers': { 'Content-Type': 'application/json' }, 'payload': { 'test': 'DELETE request' } }, 'config': { 'type': 'date', 'start_date': now } }] self.add_schedules(schedule_events) self.check_schedules_added(schedule_events) self.check_schedules_run(schedule_events) self.assertEqual(4, len(self.server.received)) expected = [event['http_push']['payload'] for event in schedule_events] self.assertCountEqual(expected, self.server.received)
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