Ejemplo n.º 1
0
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()
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
 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)
Ejemplo n.º 5
0
 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
Ejemplo n.º 6
0
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))
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
 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))
Ejemplo n.º 10
0
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)])