def main(argv): """Main entry point for starting the agent client""" global pidfile configfile = None logfile = None i = 1 setInfo() while i < len(argv): if argv[i] in ["-c", "-C", "--config-file"]: configfile = argv[i + 1] i += 1 elif argv[i] in ["-l", "-L", "--log-file"]: logfile = argv[i + 1] i += 1 elif argv[i] in ["-h", "-H", "--help"]: displayHelp() elif argv[i] in ["-d", "--debug"]: setDebug() elif argv[i] in ["-P", "--pidfile"]: pidfile = argv[i + 1] i += 1 i += 1 if configfile == None: configfile = '/etc/myDevices/Network.ini' writePidToFile(pidfile) logToFile(logfile) config = Config(configfile) HOST = config.get('CONFIG', 'ServerAddress', 'mqtt.mydevices.com') PORT = config.getInt('CONFIG', 'ServerPort', 8883) CayenneApiHost = config.get('CONFIG', 'CayenneApi', 'https://api.mydevices.com') global client client = CloudServerClient(HOST, PORT, CayenneApiHost) client.Start()
def __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 __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()
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()
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 RetrieveUpdate(self): try: info('Checking update version') debug(UPDATE_URL + ' ' + UPDATE_CFG) retValue = self.DownloadFile(UPDATE_URL, UPDATE_CFG) if retValue is False: error('failed to download update file') return retValue updateConfig = Config(UPDATE_CFG) try: self.newVersion = updateConfig.get('UPDATES', 'Version') self.downloadUrl = updateConfig.get('UPDATES', 'Url') except: error('Updater missing: update version or Url') info('Updater retrieve update success') return True except: error('Updater retrieve update failure') return False
def 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))
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 getMessageBody(self, inviteCode): body = {'id': inviteCode} hardware = Hardware() if hardware.Serial and hardware.isRaspberryPi(): body['type'] = 'rpi' body['hardware_id'] = hardware.Serial else: hardware_id = hardware.getMac() if hardware_id: body['type'] = 'mac' body['hardware_id'] = hardware_id try: system_data = [] cayennemqtt.DataChannel.add(system_data, cayennemqtt.SYS_HARDWARE_MAKE, value=hardware.getManufacturer(), type='string', unit='utf8') cayennemqtt.DataChannel.add(system_data, cayennemqtt.SYS_HARDWARE_MODEL, value=hardware.getModel(), type='string', unit='utf8') config = Config(APP_SETTINGS) cayennemqtt.DataChannel.add(system_data, cayennemqtt.AGENT_VERSION, value=config.get( 'Agent', 'Version', __version__)) system_info = SystemInfo() capacity_data = system_info.getMemoryInfo((cayennemqtt.CAPACITY, )) capacity_data += system_info.getDiskInfo((cayennemqtt.CAPACITY, )) for item in capacity_data: system_data.append(item) body['properties'] = {} body['properties']['pinmap'] = NativeGPIO().MAPPING if system_data: body['properties']['sysinfo'] = system_data except: exception('Error getting system info') return json.dumps(body)
def RetrieveUpdate(self): try: info('Checking update version') debug('Retrieve update config: {} {}'.format( UPDATE_URL, UPDATE_CFG)) retValue = self.DownloadFile(UPDATE_URL, UPDATE_CFG) if retValue is False: error('Failed to download update file') return retValue updateConfig = Config(UPDATE_CFG) try: self.newVersion = updateConfig.get('UPDATES', 'Version') except: error('Updater missing version') # try: # self.downloadUrl = updateConfig.get('UPDATES','Url') # except: # error('Updater missing url') info('Updater retrieve update success') return True except: error('Updater retrieve update failure') return False
def main(argv): global pidfile configfile = None scriptfile = None logfile = None isDebug = False i = 1 setInfo() while i < len(argv): if argv[i] in ["-c", "-C", "--config-file"]: configfile = argv[i + 1] i += 1 elif argv[i] in ["-l", "-L", "--log-file"]: logfile = argv[i + 1] i += 1 elif argv[i] in ["-h", "-H", "--help"]: displayHelp() elif argv[i] in ["-d", "--debug"]: setDebug() elif argv[i] in ["-P", "--pidfile"]: pidfile = argv[i + 1] i += 1 i += 1 if configfile == None: configfile = '/etc/myDevices/Network.ini' writePidToFile(pidfile) logToFile(logfile) # SET HOST AND PORT config = Config(configfile) HOST = config.get('CONFIG', 'ServerAddress', 'cloud.mydevices.com') PORT = config.getInt('CONFIG', 'ServerPort', 8181) CayenneApiHost = config.get('CONFIG', 'CayenneApi', 'https://api.mydevices.com') # CREATE SOCKET global client client = CloudServerClient(HOST, PORT, CayenneApiHost)
def 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
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)
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)])
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)
class CloudServerClient: """Class to connect to the server and send and receive data""" def __init__(self, host, port, cayenneApiHost): """Initialize the client configuration""" self.HOST = host self.PORT = port self.CayenneApiHost = cayenneApiHost self.config = Config(APP_SETTINGS) self.networkConfig = Config(NETWORK_SETTINGS) self.username = self.config.get('Agent', 'Username', None) self.password = self.config.get('Agent', 'Password', None) self.clientId = self.config.get('Agent', 'ClientID', None) self.location = self.config.get('Agent', 'Location', "house0@room0@") self.mqtt_dis_prefix = self.config.get('Agent', 'MQTT_DIS_PREFIX', "homeassistant") self.connected = False self.exiting = Event() self.sensorsClient = None def __del__(self): """Delete the client""" self.Destroy() def Start(self): """Connect to server and start background threads""" try: self.installDate = None try: self.installDate = self.config.get('Agent', 'InstallDate', fallback=None) except: pass if not self.installDate: self.installDate = int(time()) self.config.set('Agent', 'InstallDate', self.installDate) if not self.username and not self.password and not self.clientId: self.CheckSubscription() if not self.Connect(): error('Error starting agent') return self.schedulerEngine = SchedulerEngine(self, 'client_scheduler') self.readQueue = Queue() self.writeQueue = Queue() self.hardware = Hardware() self.oSInfo = OSInfo() self.count = 10000 self.buff = bytearray(self.count) self.writerThread = WriterThread('writer', self) self.writerThread.start() self.processorThread = ProcessorThread('processor', self) self.processorThread.start() self.systemInfo = [] TimerThread(self.SendSystemInfo, 300) self.sensorsClient = sensors.SensorsClient(self) self.sensorsClient.SetDataChanged(self.OnDataChanged) # TimerThread(self.SendSystemState, 30, 5) self.updater = Updater(self.config) self.updater.start() events = self.schedulerEngine.get_scheduled_events() self.EnqueuePacket(events, cayennemqtt.JOBS_TOPIC) # self.sentHistoryData = {} # self.historySendFails = 0 # self.historyThread = Thread(target=self.SendHistoryData) # self.historyThread.setDaemon(True) # self.historyThread.start() except Exception as e: exception('Initialize error: ' + str(e)) def Destroy(self): """Destroy client and stop client threads""" info('Shutting down client') self.exiting.set() if hasattr(self, 'sensorsClient'): self.sensorsClient.StopMonitoring() if hasattr(self, 'schedulerEngine'): self.schedulerEngine.stop() if hasattr(self, 'updater'): self.updater.stop() if hasattr(self, 'writerThread'): self.writerThread.stop() if hasattr(self, 'processorThread'): self.processorThread.stop() ThreadPool.Shutdown() self.Disconnect() info('Client shut down') def OnDataChanged(self, data): """Enqueue a packet containing changed system data to send to the server""" try: if len(data) > 15: items = [ { item['channel']: item['value'] } for item in data if not item['channel'].startswith(cayennemqtt.SYS_GPIO) ] info('Send changed data: {} + {}'.format( items, cayennemqtt.SYS_GPIO)) else: info('Send changed data: {}'.format([{ item['channel']: item['value'] } for item in data])) # items = {} # gpio_items = {} # for item in data: # if not item['channel'].startswith(cayennemqtt.SYS_GPIO): # items[item['channel']] = item['value'] # else: # channel = item['channel'].replace(cayennemqtt.SYS_GPIO + ':', '').split(';') # if not channel[0] in gpio_items: # gpio_items[channel[0]] = str(item['value']) # else: # gpio_items[channel[0]] += ',' + str(item['value']) # info('Send changed data: {}, {}: {}'.format(items, cayennemqtt.SYS_GPIO, gpio_items)) except: info('Send changed data') pass self.EnqueuePacket(data) def SendSystemInfo(self): """Enqueue a packet containing system info to send to the server""" try: currentSystemInfo = [] cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.SYS_OS_NAME, value=self.oSInfo.ID) cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.SYS_OS_VERSION, value=self.oSInfo.VERSION_ID) cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.AGENT_VERSION, value=self.config.get( 'Agent', 'Version', __version__)) cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.SYS_POWER_RESET, value=0) cayennemqtt.DataChannel.add(currentSystemInfo, cayennemqtt.SYS_POWER_HALT, value=0) config = SystemConfig.getConfig() if config: channel_map = { 'I2C': cayennemqtt.SYS_I2C, 'SPI': cayennemqtt.SYS_SPI, 'Serial': cayennemqtt.SYS_UART, 'OneWire': cayennemqtt.SYS_ONEWIRE, 'DeviceTree': cayennemqtt.SYS_DEVICETREE } for key, channel in channel_map.items(): try: cayennemqtt.DataChannel.add(currentSystemInfo, channel, value=config[key]) except: pass if currentSystemInfo != self.systemInfo: data = currentSystemInfo if self.systemInfo: data = [x for x in data if x not in self.systemInfo] if data: self.systemInfo = currentSystemInfo info('Send system info: {}'.format([{ item['channel']: item['value'] } for item in data])) self.EnqueuePacket(data) except Exception: exception('SendSystemInfo unexpected error') def CheckSubscription(self): """Check that an invite code is valid""" inviteCode = self.config.get('Agent', 'InviteCode', fallback=None) if not inviteCode: error('No invite code found in {}'.format(self.config.path)) print( 'Please input an invite code. This can be retrieved from the Cayenne dashboard by adding a new Raspberry Pi device.\n' 'The invite code will be part of the script name shown there: rpi_[invitecode].sh.' ) inviteCode = input('Invite code: ') if inviteCode: self.config.set('Agent', 'InviteCode', inviteCode) else: print('No invite code set, exiting.') raise SystemExit inviteCode = self.config.get('Agent', 'InviteCode') cayenneApiClient = CayenneApiClient(self.CayenneApiHost) credentials = cayenneApiClient.loginDevice(inviteCode) if credentials == None: error( 'Registration failed for invite code {}, closing the process'. format(inviteCode)) raise SystemExit else: info('Registration succeeded for invite code {}, credentials = {}'. format(inviteCode, credentials)) self.config.set('Agent', 'Initialized', 'true') try: self.username = credentials['mqtt']['username'] self.password = credentials['mqtt']['password'] self.clientId = credentials['mqtt']['clientId'] self.config.set('Agent', 'Username', self.username) self.config.set('Agent', 'Password', self.password) self.config.set('Agent', 'ClientID', self.clientId) except: exception('Invalid credentials, closing the process') raise SystemExit def Connect(self): """Connect to the server""" self.connected = False count = 0 while self.connected == False and count < 30 and not self.exiting.is_set( ): try: self.mqttClient = cayennemqtt.CayenneMQTTClient() self.mqttClient.on_message = self.OnMessage self.mqttClient.begin(self.username, self.password, self.clientId, self.HOST, self.PORT) self.mqttClient.loop_start() self.connected = True except OSError as oserror: Daemon.OnFailure('cloud', oserror.errno) error('Connect failed: ' + str(self.HOST) + ':' + str(self.PORT) + ' Error:' + str(oserror)) if self.exiting.wait(30): # If we are exiting return immediately return self.connected count += 1 return self.connected def Disconnect(self): """Disconnect from the server""" Daemon.Reset('cloud') try: if hasattr(self, 'mqttClient'): self.mqttClient.loop_stop() info('myDevices cloud disconnected') except: exception('Error stopping client') def Restart(self): """Restart the server connection""" if not self.exiting.is_set(): debug('Restarting cycle...') sleep(1) self.Disconnect() self.Connect() def CheckJson(self, message): """Check if a JSON message is valid""" try: test = loads(message) except ValueError: return False return True def OnMessage(self, message): """Add message from the server to the queue""" info('OnMessage: {}'.format(message)) self.readQueue.put(message) def RunAction(self, action): """Run a specified action""" debug('RunAction: {}'.format(action)) result = True command = action.copy() self.mqttClient.transform_command(command) result = self.ExecuteMessage(command) return result def ProcessMessage(self): """Process a message from the server""" try: messageObject = self.readQueue.get(False) if not messageObject: return False except Empty: return False self.ExecuteMessage(messageObject) def ExecuteMessage(self, message): """Execute an action described in a message object Returns: True if action was executed, False otherwise.""" result = False if not message: return result channel = message['channel'] info('ExecuteMessage: {}'.format(message)) if channel in (cayennemqtt.SYS_POWER_RESET, cayennemqtt.SYS_POWER_HALT): result = self.ProcessPowerCommand(message) elif channel.startswith(cayennemqtt.DEV_SENSOR): result = self.ProcessSensorCommand(message) elif channel.startswith(cayennemqtt.SYS_GPIO): result = self.ProcessGpioCommand(message) elif channel == cayennemqtt.AGENT_DEVICES: result = self.ProcessDeviceCommand(message) elif channel in (cayennemqtt.SYS_I2C, cayennemqtt.SYS_SPI, cayennemqtt.SYS_UART, cayennemqtt.SYS_ONEWIRE): result = self.ProcessConfigCommand(message) elif channel == cayennemqtt.AGENT_MANAGE: result = self.ProcessAgentCommand(message) elif channel == cayennemqtt.AGENT_SCHEDULER: result = self.ProcessSchedulerCommand(message) else: info('Unknown message') return result def ProcessPowerCommand(self, message): """Process command to reboot/shutdown the system Returns: True if command was processed, False otherwise.""" error_message = None try: self.EnqueueCommandResponse(message, error_message) commands = { cayennemqtt.SYS_POWER_RESET: 'sudo shutdown -r now', cayennemqtt.SYS_POWER_HALT: 'sudo shutdown -h now' } if int(message['payload']) == 1: debug('Processing power command') data = [] cayennemqtt.DataChannel.add(data, message['channel'], value=1) self.EnqueuePacket(data) self.writeQueue.join() info('Calling execute: {}'.format( commands[message['channel']])) output, result = executeCommand(commands[message['channel']]) debug('ProcessPowerCommand: {}, result: {}, output: {}'.format( message, result, output)) if result != 0: error_message = 'Error executing shutdown command' except Exception as ex: error_message = '{}: {}'.format(type(ex).__name__, ex) if error_message: error(error_message) data = [] cayennemqtt.DataChannel.add(data, message['channel'], value=0) self.EnqueuePacket(data) raise ExecuteMessageError(error_message) return error_message == None def ProcessAgentCommand(self, message): """Process command to manage the agent state Returns: True if command was processed, False otherwise.""" error = None try: if message['suffix'] == 'uninstall': output, result = executeCommand( 'sudo -n /etc/myDevices/uninstall/uninstall.sh', disablePipe=True) debug('ProcessAgentCommand: {}, result: {}, output: {}'.format( message, result, output)) if result != 0: error = 'Error uninstalling agent' # elif message['suffix'] == 'config': # for key, value in message['payload'].items(): # if value is None: # info('Remove config item: {}'.format(key)) # self.config.remove('Agent', key) # else: # info('Set config item: {} {}'.format(key, value)) # self.config.set('Agent', key, value) else: error = 'Unknown agent command: {}'.format(message['suffix']) except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) if error: raise ExecuteMessageError(error) return error == None def ProcessConfigCommand(self, message): """Process system configuration command Returns: True if command was processed, False otherwise.""" error = None try: value = 1 - int( message['payload'] ) #Invert the value since the config script uses 0 for enable and 1 for disable command_id = { cayennemqtt.SYS_I2C: 11, cayennemqtt.SYS_SPI: 12, cayennemqtt.SYS_UART: 13, cayennemqtt.SYS_ONEWIRE: 19 } result, output = SystemConfig.ExecuteConfigCommand( command_id[message['channel']], value) debug('ProcessConfigCommand: {}, result: {}, output: {}'.format( message, result, output)) if result != 0: error = 'Error executing config command' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def ProcessGpioCommand(self, message): """Process GPIO command Returns: True if command was processed, False otherwise.""" error = None try: channel = int(message['channel'].replace( cayennemqtt.SYS_GPIO + ':', '')) result = self.sensorsClient.GpioCommand( message.get('suffix', 'value'), channel, message['payload']) debug('ProcessGpioCommand result: {}'.format(result)) if result == 'failure': error = 'GPIO command failed' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def ProcessSensorCommand(self, message): """Process sensor command Returns: True if command was processed, False otherwise.""" error = None try: sensor_info = message['channel'].replace( cayennemqtt.DEV_SENSOR + ':', '').split(':') sensor = sensor_info[0] channel = None if len(sensor_info) > 1: channel = sensor_info[1] result = self.sensorsClient.SensorCommand( message.get('suffix', 'value'), sensor, channel, message['payload']) debug('ProcessSensorCommand result: {}'.format(result)) if result is False: error = 'Sensor command failed' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def ProcessDeviceCommand(self, message): """Process a device command to add/edit/remove a sensor Returns: True if command was processed, False otherwise.""" error = None try: payload = message['payload'] info('ProcessDeviceCommand payload: {}'.format(payload)) if message['suffix'] == 'add': result = self.sensorsClient.AddSensor(payload['sensorId'], payload['description'], payload['class'], payload['args']) elif message['suffix'] == 'edit': result = self.sensorsClient.EditSensor(payload['sensorId'], payload['description'], payload['class'], payload['args']) elif message['suffix'] == 'delete': result = self.sensorsClient.RemoveSensor(payload['sensorId']) else: error = 'Unknown device command: {}'.format(message['suffix']) debug('ProcessDeviceCommand result: {}'.format(result)) if result is False: error = 'Device command failed' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def ProcessSchedulerCommand(self, message): """Process command to add/edit/remove a scheduled action Returns: True if command was processed, False otherwise.""" error = None try: if message['suffix'] == 'add': result = self.schedulerEngine.add_scheduled_event( message['payload'], True) elif message['suffix'] == 'edit': result = self.schedulerEngine.update_scheduled_event( message['payload']) elif message['suffix'] == 'delete': result = self.schedulerEngine.remove_scheduled_event( message['payload']) elif message['suffix'] == 'get': events = self.schedulerEngine.get_scheduled_events() self.EnqueuePacket(events, cayennemqtt.JOBS_TOPIC) else: error = 'Unknown schedule command: {}'.format( message['suffix']) debug('ProcessSchedulerCommand result: {}'.format(result)) if result is False: error = 'Schedule command failed' except Exception as ex: error = '{}: {}'.format(type(ex).__name__, ex) self.EnqueueCommandResponse(message, error) return error == None def EnqueueCommandResponse(self, message, error): """Send response after processing a command message""" if not 'cmdId' in message: # If there is no command id we assume this is a scheduled command and don't send a response message. return debug('EnqueueCommandResponse error: {}'.format(error)) if error: response = 'error,{}={}'.format(message['cmdId'], error) else: response = 'ok,{}'.format(message['cmdId']) info(response) self.EnqueuePacket(response, cayennemqtt.COMMAND_RESPONSE_TOPIC) def EnqueuePacket(self, message, topic=cayennemqtt.DATA_TOPIC): """Enqueue a message packet to send to the server""" packet = (topic, message) self.writeQueue.put(packet) def DequeuePacket(self): """Dequeue a message packet to send to the server""" packet = (None, None) try: packet = self.writeQueue.get(False) except Empty: pass return packet
class 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()
def load_plugin_from_file(self, filename): """Loads a plugin from a specified plugin config file and adds it to the plugin list.""" try: info('Loading plugin: {}'.format(filename)) loaded = [] config = Config(filename) plugin_name = os.path.splitext(os.path.basename(filename))[0] info('Sections: {}'.format(config.sections())) inherited_from = set() for section in config.sections(): inherit = config.get(section, 'inherit', None) if inherit: inherited_from.add(inherit) for section in config.sections(): try: enabled = config.get(section, 'enabled', 'true').lower() == 'true' inherit = config.get(section, 'inherit', None) if enabled or section in inherited_from: plugin = { 'enabled': enabled, 'filename': filename, 'section': section, 'name': config.get(section, 'name', section), } try: plugin['channel'] = config.get(section, 'channel') plugin[ 'id'] = plugin_name + ':' + plugin['channel'] except NoOptionError: plugin['id'] = plugin_name + ':' + section inherit_items = {} if inherit in config.sections(): if inherit == section: raise ValueError( 'Section \'{}\' cannot inherit from itself' .format(section)) inherit_from = self.get_plugin(filename, inherit) inherit_items = { key: value for key, value in inherit_from.items() if key not in plugin.keys() } plugin.update(inherit_items) elif inherit: raise ValueError( 'Section \'{}\' cannot inherit from \'{}\'. Check spelling and section ordering.' .format(section, inherit)) self.override_plugin_value(config, section, 'module', plugin) self.override_plugin_value(config, section, 'class', plugin) if 'init_args' not in plugin: plugin['init_args'] = '{}' self.override_plugin_value(config, section, 'init_args', plugin) if not inherit_items or [ key for key in ('module', 'class', 'init_args') if inherit_items[key] is not plugin[key] ]: info('Creating instance of {} for {}'.format( plugin['class'], plugin['name'])) folder = os.path.dirname(filename) if folder not in sys.path: sys.path.append(folder) imported_module = importlib.import_module( plugin['module']) device_class = getattr(imported_module, plugin['class']) plugin['instance'] = device_class( **json.loads(plugin['init_args'])) self.override_plugin_value(config, section, 'read', plugin) if 'read_args' not in plugin: plugin['read_args'] = '{}' self.override_plugin_value(config, section, 'read_args', plugin) try: self.override_plugin_value(config, section, 'write', plugin) if 'write_args' not in plugin: plugin['write_args'] = '{}' self.override_plugin_value(config, section, 'write_args', plugin) except: pass try: self.override_plugin_value(config, section, 'set_function', plugin) except: pass try: self.override_plugin_value(config, section, 'register_callback', plugin) getattr(plugin['instance'], plugin['register_callback'])( lambda value, plugin=plugin: self. data_changed(value, plugin)) except: pass try: self.override_plugin_value(config, section, 'unregister_callback', plugin) except: pass self.plugins[plugin['id']] = plugin loaded.append(section) except Exception as e: error(e) except Exception as e: error(e) info('Loaded sections: {}'.format(loaded))
class 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