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, 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, plugin_id, function, data_type): """Initializes the input/output. Arguments: plugin_id: Extension plugin ID in the format 'plugin_name:section', e.g. 'cayenne-pca9685:PCA9685' function: The IO function, 'in' or 'out' data_type: The Cayenne data type, e.g. 'digital_sensor' """ self.plugin_id = plugin_id self.plugin = None self.data_type = data_type.lower() self.function = function.lower() self.current_functions = {} self.read_args = {} self.write_args = {} self.plugin_manager = PluginManager() self.set_plugin()
class AnalogSensor(): def __init__(self, adc, channel): self.adcname = adc self.channel = toint(channel) self.adc = None self.pluginManager = PluginManager() self.setADCInstance() def __str__(self): return "AnalogSensor" def __family__(self): return "AnalogSensor" def setADCInstance(self): if not self.adc: self.adc = instance.deviceInstance(self.adcname) if not self.adc: self.adc = self.pluginManager.get_plugin_by_id(self.adcname) @response("%d") def read(self): info("********AnalogSensor*********") self.setADCInstance() try: value = self.adc.analogRead(self.channel) except: value = getattr(self.adc['instance'], self.adc['read'])(self.channel) return value @response("%.2f") def readFloat(self): info("********readFloat*********") self.setADCInstance() try: value = self.adc.analogReadFloat(self.channel) except: value = getattr(self.adc['instance'], self.adc['read'])(self.channel, 'float') return value @response("%.2f") def readVolt(self): info("********readVolt*********") self.setADCInstance() try: value = self.adc.analogReadVolt(self.channel) except: value = getattr(self.adc['instance'], self.adc['read'])(self.channel, 'volt') return value
class InputOutput(): """Reads data from and writes data to an input/output plugin.""" def __init__(self, plugin_id, function, data_type): """Initializes the input/output. Arguments: plugin_id: Extension plugin ID in the format 'plugin_name:section', e.g. 'cayenne-pca9685:PCA9685' function: The IO function, 'in' or 'out' data_type: The Cayenne data type, e.g. 'digital_sensor' """ self.plugin_id = plugin_id self.plugin = None self.data_type = data_type.lower() self.function = function.lower() self.current_functions = {} self.read_args = {} self.write_args = {} self.plugin_manager = PluginManager() self.set_plugin() def set_plugin(self): """Sets the plugin_id plugin.""" if not self.plugin: self.plugin = self.plugin_manager.get_plugin_by_id(self.plugin_id) try: self.read_args = self.plugin_manager.get_args(self.plugin, 'read_args') self.write_args = self.plugin_manager.get_args(self.plugin, 'write_args') except: pass def set_function(self, channel): """Sets the input/output function.""" try: if self.plugin and (channel not in self.current_functions or self.function != self.current_functions[channel]): function = getattr(self.plugin['instance'], self.plugin['set_function'])(channel, self.function).lower() self.current_functions[channel] = function if function == 'in': try: debug('Register callback for channel {}'.format(channel)) getattr(self.plugin['instance'], self.plugin['register_callback'])(channel, self.data_changed, data=channel) except: debug('Unable to register callback for channel {}'.format(channel)) pass except: debug('Error setting function') def value_to_tuple(self, value): """Converts value to tuple with the appropriate Cayenne data type.""" return (value, self.data_type) def read(self, channel, value_type=None): """Gets the data value for the channel as a tuple with the type.""" return self.value_to_tuple(self.read_value(channel, value_type)) def read_value(self, channel, value_type=None): """Read the data value on the specified channel.""" result = None self.set_plugin() if not self.plugin: error('Plugin {} is not loaded'.format(self.plugin_id)) return result self.set_function(channel) try: read_args = self.read_args if value_type: read_args['value_type'] = value_type result = getattr(self.plugin['instance'], self.plugin['read'])(channel, **read_args) except Exception as e: error('Error reading value from plugin {}, channel {}: {}'.format(self.plugin_id, channel, e)) return result def write(self, value, channel, value_type=None): """Write the digital value for the channel.""" return self.write_value(value, channel, value_type) def write_value(self, value, channel, value_type=None): """Write the data value on the specified channel.""" result = None self.set_plugin() if not self.plugin: error('Plugin {} is not loaded'.format(self.plugin_id)) return result self.set_function(channel) try: write_args = self.write_args if value_type: write_args['value_type'] = value_type result = getattr(self.plugin['instance'], self.plugin['write'])(channel, value, **write_args) except Exception as e: error('Error writing value to plugin {}, channel {}: {}'.format(self.plugin_id, channel, e)) return result def register_callback(self, callback): """Register a callback for data changes.""" info('Registering callback: {}'.format(callback)) self.callback = callback def unregister_callback(self): """Register a callback for data changes.""" self.callback = None def data_changed(self, channel, value): """Callback that is called when data has changed.""" if self.callback: self.callback(self.value_to_tuple(value))
class SensorsClient(): """Class for interfacing with sensors and actuators""" 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 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""" 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) 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 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': 'temp', 'unit': 'c'}}, 'Humidity': {'function': 'getHumidityPercent', 'data_args': {'type': 'rel_hum', 'unit': 'p'}}, 'Pressure': {'function': 'getPascal', 'data_args': {'type': 'bp', 'unit': 'pa'}}, 'Luminosity': {'function': 'getLux', 'data_args': {'type': 'lum', 'unit': 'lux'}}, 'Distance': {'function': 'getCentimeter', 'data_args': {'type': 'prox', 'unit': 'cm'}}, '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'}}, '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 if device_type in sensor_types: try: sensor_type = sensor_types[device_type] func = getattr(sensor, sensor_type['function']) if len(device['type']) > 1: channel = '{}:{}'.format(device['name'], device_type.lower()) else: channel = device['name'] value = self.CallDeviceFunction(func) 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. """ info('AddSensor: {}, {}, {}, {}'.format(name, description, device, args)) bVal = False try: sensorAdd = {} if name: sensorAdd['name'] = name if device: sensorAdd['device'] = device if args: sensorAdd['args'] = args if description: sensorAdd['description'] = description with self.sensorMutex: retValue = manager.addDeviceJSON(sensorAdd) self.InitCallbacks() info('Add device returned: {}'.format(retValue)) if retValue[0] == 200: bVal = True except Exception: exception('Error adding sensor') bVal = False return bVal def EditSensor(self, name, description, device, args): """Edit an existing sensor/actuator Args: name: Name of sensor to edit description: New sensor description device: New sensor device class args: New sensor specific args Returns: True for success, False otherwise. """ info('EditSensor: {}, {}, {}, {}'.format(name, description, device, args)) bVal = False try: sensorEdit = {} name = name sensorEdit['name'] = name sensorEdit['device'] = device sensorEdit['description'] = description sensorEdit['args'] = args with self.sensorMutex: retValue = manager.updateDevice(name, sensorEdit) self.InitCallbacks() info('Edit device returned: {}'.format(retValue)) if retValue[0] == 200: bVal = True except: exception("Edit sensor failed") bVal = False return bVal def RemoveSensor(self, name): """Remove an existing sensor/actuator Args: name: Name of sensor to remove Returns: True for success, False otherwise. """ bVal = False try: if self.pluginManager.is_plugin(name): return self.pluginManager.disable(name) sensorRemove = name try: sensor = instance.deviceInstance(sensorRemove) if hasattr(sensor, 'removeCallback'): sensor.removeCallback() except: pass with self.sensorMutex: retValue = manager.removeDevice(sensorRemove) info('Remove device returned: {}'.format(retValue)) if retValue[0] == 200: bVal = True except: exception("Remove sensor failed") bVal = False return bVal def EnableSensor(self, sensor, enable): """Enable a sensor/actuator Args: sensor: Hash composed from name and device class/type enable: 1 to enable, 0 to disable Returns: True for success, False otherwise. """ info('Enable sensor: ' + str(sensor) + ' ' + str(enable)) try: if sensor is None: return False if enable is None: return False with self.sensorMutex: if enable == 0: #add item to the list if sensor not in self.disabledSensors: DbManager.Insert(self.disabledSensorTable, sensor) self.disabledSensors[sensor] = 1 else: #remove item from the list if sensor in self.disabledSensors: DbManager.Delete(self.disabledSensorTable, sensor) del self.disabledSensors[sensor] #save list except Exception as ex: error('EnableSensor Failed with exception: ' + str(ex)) return False return True def GpioCommand(self, command, channel, value): """Execute onboard GPIO command Args: command: Type of command to execute channel: GPIO pin value: Value to use for writing data Returns: String containing command specific return value on success, or 'failure' on failure """ info('GpioCommand {}, channel {}, value {}'.format(command, channel, value)) result = 'failure' if command == 'function': old_state = self.gpio.digitalRead(channel) if value.lower() in ('in', 'input'): result = str(self.gpio.setFunctionString(channel, 'in')) elif value.lower() in ('out', 'output'): result = str(self.gpio.setFunctionString(channel, 'out')) new_state = self.gpio.digitalRead(channel) if new_state != old_state: self.OnGpioStateChange(channel, new_state) elif command in ('value', ''): return self.gpio.digitalWrite(channel, int(value)) debug('GPIO command failed') return result def SensorCommand(self, command, sensorId, channel, value): """Execute sensor/actuator command Args: command: Type of command to execute sensorId: Sensor id channel: Pin/channel on device, None if there is no channel value: Value to use for setting the sensor state Returns: Command specific return value on success, False on failure """ result = False info('SensorCommand: {}, sensor {}, channel {}, value {}'.format(command, sensorId, channel, value)) try: if self.pluginManager.is_plugin(sensorId, channel): return self.pluginManager.write_value(sensorId, channel, value) commands = {'integer': {'function': 'write', 'value_type': int}, 'value': {'function': 'write', 'value_type': int}, 'function': {'function': 'setFunctionString', 'value_type': str}, 'angle': {'function': 'writeAngle', 'value_type': float}, 'float': {'function': 'writeFloat', 'value_type': float}, 'volt': {'function': 'writeVolt', 'value_type': float}} with self.sensorMutex: if sensorId in self.disabledSensors: info('Sensor disabled') return result sensor = instance.deviceInstance(sensorId) if not sensor: info('Sensor not found') return result if command in commands: device = instance.DEVICES[sensorId] info('Sensor found: {}'.format(device)) func = getattr(sensor, commands[command]['function']) value = commands[command]['value_type'](value) if channel: result = self.CallDeviceFunction(func, int(channel), value) else: result = self.CallDeviceFunction(func, value) if 'DigitalActuator' in device['type']: manager.updateDeviceState(sensorId, value) return result warn('Command not implemented: {}'.format(command)) return result except Exception: exception('SensorCommand failed') return result
def __init__(self, adc, channel): self.adcname = adc self.channel = toint(channel) self.adc = None self.pluginManager = PluginManager() self.setADCInstance()