Beispiel #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()
Beispiel #2
0
    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()
Beispiel #3
0
 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.connected = False
     self.exiting = Event()
Beispiel #4
0
 def __init__(self):
     """Initialize the bus and sensor info and start monitoring sensor states"""
     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")
     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()
Beispiel #5
0
    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
Beispiel #6
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
Beispiel #7
0
def Test():
    from myDevices.utils.config import Config
    testDownload = DownloadSpeed(Config('/etc/myDevices/AppSettings.ini'))
    speed = testDownload.getDownloadSpeed()
    print('Download speed 1: ' + str(speed))
    sleep(20)
    speed = testDownload.getDownloadSpeed()
    print('Download speed 2: ' + str(speed))
    sleep(20)
    speed = testDownload.getDownloadSpeed()
    print('Download speed 3: ' + str(speed))
Beispiel #8
0
 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
Beispiel #9
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)
Beispiel #10
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
Beispiel #11
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)
Beispiel #12
0
    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
Beispiel #13
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))
 def setUp(self):
     setDebug()
     self.config = Config('/etc/myDevices/AppSettings.ini')
     self.updater = Updater(self.config)
Beispiel #15
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)])
Beispiel #16
0
import sys
from myDevices.utils.config import Config
from myDevices.utils.logger import setInfo, info, error
from myDevices.plugins.manager import PLUGIN_FOLDER

if __name__ == '__main__':
    # Run the code to disable a plugin in a script so it can be called via sudo
    setInfo()
    info(sys.argv)
    if len(sys.argv) != 3:
        error('Plugin not disabled, invalid arguments')
        sys.exit(1)
    filename = sys.argv[1]
    if filename.startswith(PLUGIN_FOLDER) and filename.endswith('.plugin'):
        section = sys.argv[2]
        config = Config(filename)
        if section in config.sections():
            config.set(section, 'enabled', 'false')
        else:
            error('Section \'{}\' not found in {}'.format(section, filename))
            sys.exit(1)
    else:
        sys.exit(1)
Beispiel #17
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
Beispiel #18
0
class CayenneMQTTClient:
    """Cayenne MQTT Client class.
    
    This is the main client class for connecting to Cayenne and sending and receiving data.
    
    Standard usage:
    * Set on_message callback, if you are receiving data.
    * Connect to Cayenne using the begin() function.
    * Call loop() at intervals (or loop_forever() once) to perform message processing.
    * Send data to Cayenne using write functions: virtualWrite(), celsiusWrite(), etc.
    * Receive and process data from Cayenne in the on_message callback.

    The on_message callback can be used by creating a function and assigning it to CayenneMQTTClient.on_message member.
    The callback function should have the following signature: on_message(topic, message)
    If it exists this callback is used as the default message handler.
    """
    client = None
    root_topic = ""
    connected = False
    on_message = None

    config = Config(APP_SETTINGS)

    mqtt_dis_prefix = config.get('Agent', 'MQTT_DIS_PREFIX', "homeassistant")

    def begin(self,
              username,
              password,
              clientid,
              hostname='mqtt.mydevices.com',
              port=8883):
        """Initializes the client and connects to Cayenne.
        
        username is the Cayenne username.
        password is the Cayenne password.
        clientid is the Cayennne client ID for the device.
        hostname is the MQTT broker hostname.
        port is the MQTT broker port.
        """

        # self.root_topic = 'v1/{}/things/{}'.format(username, clientid)
        self.root_topic = '{}'.format(self.mqtt_dis_prefix)
        self.client = mqtt.Client(client_id=clientid,
                                  clean_session=True,
                                  userdata=self)
        self.client.on_connect = self.connect_callback
        self.client.on_disconnect = self.disconnect_callback
        self.client.on_message = self.message_callback
        self.client.username_pw_set(username, password)
        if port != 1883:
            self.client.tls_set(ca_certs='/etc/ssl/certs/ca-certificates.crt',
                                tls_version=PROTOCOL_TLSv1_2)
        self.client.connect(hostname, port, 60)
        info('Connecting to {}:{}'.format(hostname, port))

    def connect_callback(self, client, userdata, flags, rc):
        """The callback for when the client connects to the server.

        client is the client instance for this callback.
        userdata is the private user data as set in Client() or userdata_set().
        flags are the response flags sent by the broker.
        rc is the connection result.
        """
        if rc != 0:
            # MQTT broker error codes
            broker_errors = {
                1: 'unacceptable protocol version',
                2: 'identifier rejected',
                3: 'server unavailable',
                4: 'bad user name or password',
                5: 'not authorized',
            }
            raise Exception("Connection failed, " +
                            broker_errors.get(rc, "result code " + str(rc)))
        else:
            info("Connected with result code " + str(rc))
            self.connected = True
            # Subscribing in on_connect() means that if we lose the connection and
            # reconnect then subscriptions will be renewed.
            client.subscribe(self.get_topic_string(COMMAND_TOPIC, True))
            client.subscribe(self.get_topic_string(COMMAND_JSON_TOPIC, False))

    def disconnect_callback(self, client, userdata, rc):
        """The callback for when the client disconnects from the server.

        client is the client instance for this callback.
        userdata is the private user data as set in Client() or userdata_set().
        rc is the connection result.
        """
        info("Disconnected with result code " + str(rc))
        self.connected = False
        reconnected = False
        while not reconnected:
            try:
                self.client.reconnect()
                reconnected = True
            except:
                print("Reconnect failed, retrying")
                time.sleep(5)

    def transform_command(self, command, payload=[], channel=[]):
        """Transform a command message into an object.

        command is the command object that will be transformed in place.
        payload is an optional list of payload data items.
        channel is an optional list containing channel and suffix data.
        """
        if not payload:
            command['payload'] = command.pop('value')
            channel = command['channel'].split('/')[-1].split(';')
        else:
            if len(payload) > 1:
                command['cmdId'] = payload[0]
                command['payload'] = payload[1]
            else:
                command['payload'] = payload[0]
        command['channel'] = channel[0]
        if len(channel) > 1:
            command['suffix'] = channel[1]

    def message_callback(self, client, userdata, msg):
        """The callback for when a message is received from the server.

        client is the client instance for this callback.
        userdata is the private user data as set in Client() or userdata_set().
        msg is the received message.
        """
        try:
            message = {}
            if msg.topic[-len(COMMAND_JSON_TOPIC):] == COMMAND_JSON_TOPIC:
                message = loads(msg.payload.decode())
                self.transform_command(message)
            else:
                self.transform_command(message,
                                       msg.payload.decode().split(','),
                                       msg.topic.split('/')[-1].split(';'))
            debug('message_callback: {}'.format(message))
            if self.on_message:
                self.on_message(message)
        except:
            exception('Error processing message: {} {}'.format(
                msg.topic, str(msg.payload)))

    def get_topic_string(self, topic, append_wildcard=False):
        """Return a topic string.
        
        topic: the topic substring
        append_wildcard: if True append the single level topics wildcard (+)"""
        if append_wildcard:
            return '{}/{}/+'.format(self.root_topic, topic)
        else:
            return '{}/{}'.format(self.root_topic, topic)

    def disconnect(self):
        """Disconnect from Cayenne.
        """
        self.client.disconnect()

    def loop(self, timeout=1.0):
        """Process Cayenne messages.
        
        This should be called regularly to ensure Cayenne messages are sent and received.
        
        timeout: The time in seconds to wait for incoming/outgoing network
          traffic before timing out and returning.
        """
        self.client.loop(timeout)

    def loop_start(self):
        """This is part of the threaded client interface. Call this once to
        start a new thread to process network traffic. This provides an
        alternative to repeatedly calling loop() yourself.
        """
        self.client.loop_start()

    def loop_stop(self):
        """This is part of the threaded client interface. Call this once to
        stop the network thread previously created with loop_start(). This call
        will block until the network thread finishes.
        """
        self.client.loop_stop()

    def publish_packet(self, topic, packet, qos=0, retain=False):
        """Publish a packet.
        
        topic: topic substring.
        packet: JSON packet to publish.
        qos: quality of service level to use.
        retain: if True, the message will be set as the "last known good"/retained message for the topic.
        """
        debug('Publish to {}'.format(self.get_topic_string(topic)))
        self.client.publish(self.get_topic_string(topic), packet, qos, retain)

    def publish_response(self, msg_id, error_message=None):
        """Send a command response to Cayenne.
        
        This should be sent when a command message has been received.
        msg_id is the ID of the message received.
        error_message is the error message to send. This should be set to None if there is no error.
        """
        topic = self.get_topic_string(COMMAND_RESPONSE_TOPIC)
        if error_message:
            payload = "error,%s=%s" % (msg_id, error_message)
        else:
            payload = "ok,%s" % (msg_id)
        self.client.publish(topic, payload)
from myDevices.cloud.updater import Updater
from myDevices.utils.config import Config, APP_SETTINGS
from myDevices.utils.logger import setInfo

if __name__ == '__main__':
    # Run the actual update check in a script so it can be called via sudo
    setInfo()
    config = Config(APP_SETTINGS)
    updater = Updater(config)
    updater.DoUpdateCheck()
Beispiel #20
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))
Beispiel #21
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