diff --git a/README.md b/README.md index 2fd55de..41c65bf 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,16 @@ This command searches the running kernel for all the drivers it includes and pri | ADS1015 | Texas Instruments ADS1015 ADC | ti-ads1015 | 0x48 - 0x4B | Yes, NOT working | | TSL4531 | TAOS TSL4531 ambient light sensors | tsl4531 | 0x29 | Not tested | | VEML6070 | VEML6070 UV A light sensor | veml6070 | 0x38, 0x39 | Yes, works | +| DHT11 | DHT11/DHT22/AM2302 | dht11 | N/A | Yes, works (with overlay) | -By default, the block searches for sensors on SMBus number 1 (/dev/i2c-1) however you can set the bus number (an integer value) using the `BUS_NUMBER` service variable. +_"With overlay" means that the device tree overlay has to be enabled at boot time. This is done using the "Define DT overlays" option in the device configuration page on balenaCloud."_ +_Devices with no address are not available on the SMbus and can only be detected in `IIO*` modes_ + +There are different sensor search modes currently supported, and they are set using the `DETECT_SENSORS` service variable: +- `I2C` (default) - Searches only the SMBus + By default, the block searches for sensors on SMBus number 1 (/dev/i2c-1) however you can set the bus number (an integer value) using the `BUS_NUMBER` service variable. +- `IIO_STRICT` - Skips the SMBus search and compares the names of all available iio devices to a list of supported ones. +- `IIO` - Accepts every available iio device, even unverified ones. ### Publishing Data diff --git a/idetect.py b/idetect.py index a977482..ec17a67 100644 --- a/idetect.py +++ b/idetect.py @@ -5,11 +5,12 @@ import errno import os import iio -import json import time +import iio +from typing import Set, Dict # Decimal address values -devices = { +i2c_devices: Dict[int,str] = { 12: "ad5446", 13: "ad5446", 14: "ad5446", @@ -38,12 +39,18 @@ 119: "multiple" } +# List of supported iio devices (superset of i2c devices) +iio_device_names: Set[str] = { + "dht11", # Also applies to the DHT22 + *set(i2c_devices.values()) +} + del_drivers = { "ad5446.c": "ad5446.c", "apds9960": "apds9960", "bme680": "bme680-i2c", "bmp280": "bmp280-i2c", -"dht11": "dht11", +#"dht11": "dht11", "hdc100": "hdc100", "htu21": "htu21", "mcp320x": "mcp320x", @@ -76,7 +83,7 @@ def read_chip_id(bus, device, loc): return chip_id -def detect_iio_sensors(): +def detect_i2c_sensors(): bus_number = int(os.getenv('BUS_NUMBER', '1')) # default 1 indicates /dev/i2c-1 bus = SMBus(bus_number) device_count = 0 @@ -111,7 +118,7 @@ def detect_iio_sensors(): print("Found device (via i2cdetect) at 0x40") if device_count > 0: # We want to remove any existing devices if they are present - # First unintatntiate the i2c bus + # First uninstantiate the i2c bus print("======== Removing existing devices from the i2c bus... ========") for device in active: print("Deleting device found at {0}.".format(hex(device))) @@ -123,7 +130,7 @@ def detect_iio_sensors(): print("======== Unloading any existing modules... ========") # Next unload any present devices using modprobe -rv output = subprocess.check_output("lsmod").decode() # TODO: replace check_output with run variant - d = [] + active_modules = set() i = 0 for line in output.split('\n'): i = i + 1 @@ -136,13 +143,10 @@ def detect_iio_sensors(): mod_name = lsmod_module[0][0:find_underscore] # strip underscore and everything following else: mod_name = lsmod_module[0] - d.append(mod_name) - # remove duplicates from list - dd = [] - [dd.append(x) for x in d if x not in dd] + active_modules.add(mod_name) # find in dict #print("Currently loaded modules: {0}".format(dd)) - for x in dd: + for x in active_modules: if x in del_drivers: print("Unloading module {0} as {1}.".format(x, del_drivers[x])) subprocess.run(["modprobe", "-r", x]) @@ -152,7 +156,7 @@ def detect_iio_sensors(): #print("Keys: {0}".format(devices.keys())) new_active = [] for x in active: - if x in devices.keys(): + if x in i2c_devices.keys(): new_active.append(x) else: print("Device at {0} not in known supported drivers.".format(hex(x))) @@ -165,10 +169,10 @@ def detect_iio_sensors(): subprocess.run(["modprobe", "industrialio"]) new_active_count = 0 for device in new_active: - if devices[device] != "multiple": - print("Loading device {0} on address {1}.".format(devices[device], hex(device))) - subprocess.run(["modprobe", devices[device]]) - new_device = "echo {0} {1} > /sys/bus/i2c/devices/i2c-1/new_device".format(devices[device], hex(device)) + if i2c_devices[device] != "multiple": + print("Loading device {0} on address {1}.".format(i2c_devices[device], hex(device))) + subprocess.run(["modprobe", i2c_devices[device]]) + new_device = "echo {0} {1} > /sys/bus/i2c/devices/i2c-1/new_device".format(i2c_devices[device], hex(device)) os_out = os.system(new_device) if os_out > 0: print("New device {0} exit code: {1}".format(hex(device), os_out)) @@ -221,3 +225,43 @@ def detect_iio_sensors(): bus = None return 0 +def detect_iio_sensors( + strict: bool, + context: iio.Context, + supported_devices: Set[str] = iio_device_names +) -> int: + """Fetches number of iio (supported) devices inside the iio context. + + Args: + strict (bool): Return only the number of detected devices whose names are in supported_devices + context (iio.Context): iio Context + supported_devices(Set[str]): Names of supported devices + + Returns: + int: Nr of (supported) iio devices detected. + """ + + nr_devices = 0 + + if strict: + for device in context.devices: + if device.name.split('@')[0] in supported_devices: + nr_devices += 1 + else: + nr_devices = len(context.devices) + + return nr_devices + +def detect_sensors(context: iio.Context) -> int: + detection_mode = os.getenv("DETECT_SENSORS", "I2C") + print("Sensor detection mode: {0}".format(detection_mode)) + + if detection_mode == "I2C": + return detect_i2c_sensors() + elif detection_mode == "IIO_STRICT": + return detect_iio_sensors(True, context) + elif detection_mode == "IIO": + return detect_iio_sensors(False, context) + else: + print("Unknown value for env variable DETECT_SENSORS: {0}".format(detection_mode)) + return 0 diff --git a/information.py b/information.py index f7bb6b6..4cbf000 100644 --- a/information.py +++ b/information.py @@ -26,8 +26,8 @@ def __init__(self, context): """ self.context = context - def write_information(self): - """Write the information about the current context.""" + def print_information(self): + """Print the information about the current context.""" self._context_info() def _context_info(self): diff --git a/reading.py b/reading.py index 572b2d7..8aa291c 100644 --- a/reading.py +++ b/reading.py @@ -12,7 +12,7 @@ class Reading: """Class for retrieving readings from devices.""" - def __init__(self, context): + def __init__(self, context: iio.Context): """ Class constructor. Args: @@ -69,7 +69,7 @@ def write_reading(self): return reading - def _device_read(self, dev): + def _device_read(self, dev: iio.Device): reads = {} for channel in dev.channels: @@ -87,7 +87,7 @@ def _device_read(self, dev): return reads @staticmethod - def _channel_attribute_value(channel, channel_attr): + def _channel_attribute_value(channel: iio.Channel, channel_attr: str): v = 0 try: v = channel.attrs[channel_attr].value @@ -97,10 +97,9 @@ def _channel_attribute_value(channel, channel_attr): return v class IIO_READER: - data = None - + def __init__(self): - print("initializing reading") + print("Initializing IIO reader...") def get_readings(self, context): reading = Reading(context) diff --git a/sensor.py b/sensor.py index db1e8b6..91ddd2d 100644 --- a/sensor.py +++ b/sensor.py @@ -29,34 +29,33 @@ def mqtt_detect(): try: r = requests.get(url).json() - except Exception as e: - print("Error looking for MQTT service: {0}".format(str(e))) - return False - else: services = r[app_name]['services'].keys() + print("Supervisor response: {0}".format(str(r))) if "mqtt" in services: return True else: return False - + except Exception as e: + print("Error looking for MQTT service: {0}".format(str(e))) + return False class balenaSense(): readfrom = 'unset' def __init__(self): print("Initializing sensors...") + self.context = _create_context() + self.sensor = IIO_READER() + # Print the iio info + information = Information(self.context) + information.print_information() + # First, use iio to detect supported sensors - self.device_count = idetect.detect_iio_sensors() + self.device_count = idetect.detect_sensors(self.context) if self.device_count > 0: self.readfrom = "iio_sensors" - self.context = _create_context() - self.sensor = IIO_READER() - # Print the iio info - information = Information(self.context) - information.write_information() - # More sensor types can be added here # make sure to change the value of self.readfrom @@ -103,6 +102,8 @@ def background_web(server_socket): use_httpserver = os.getenv('ALWAYS_USE_HTTPSERVER', 0) publish_interval = os.getenv('MQTT_PUB_INTERVAL', '8') publish_topic = os.getenv('MQTT_PUB_TOPIC', 'sensors') + mqtt_client = None + balenasense = None try: interval = float(publish_interval) @@ -116,21 +117,21 @@ def background_web(server_socket): enable_httpserver = "False" - if mqtt_detect() and mqtt_address == "none": + if mqtt_address == "none" and mqtt_detect(): mqtt_address = "mqtt" if mqtt_address != "none": print("Starting mqtt client, publishing to {0}:1883".format(mqtt_address)) print("Using MQTT publish interval: {0} sec(s)".format(interval)) - client = mqtt.Client() + mqtt_client = mqtt.Client() try: - client.connect(mqtt_address, 1883, 60) + mqtt_client.connect(mqtt_address, 1883, 60) except Exception as e: print("Error connecting to mqtt. ({0})".format(str(e))) mqtt_address = "none" enable_httpserver = "True" else: - client.loop_start() + mqtt_client.loop_start() balenasense = balenaSense() else: enable_httpserver = "True" @@ -150,6 +151,6 @@ def background_web(server_socket): t.start() while True: - if mqtt_address != "none": - client.publish(publish_topic, json.dumps(balenasense.sample())) + if mqtt_client is not None and balenasense is not None: + mqtt_client.publish(publish_topic, json.dumps(balenasense.sample())) time.sleep(interval) diff --git a/transformers.py b/transformers.py index f2253a2..9dbc671 100644 --- a/transformers.py +++ b/transformers.py @@ -1,6 +1,6 @@ import os -def device_transform(device_name, fields): +def device_transform(device_name: str, fields: dict) -> dict: # Create a dict copy to work on... # So we're not iterating a changing dict @@ -12,11 +12,11 @@ def device_transform(device_name, fields): if field == "humidityrelative": new_fields["humidity"] = new_fields.pop("humidityrelative") elif field == "temp": - x = fields[field] + val = fields[field] if os.getenv('TEMP_UNIT', 'C') == 'F': - new_fields[field]= ((x/1000) * 1.8) + 32 + new_fields[field]= ((val/1000) * 1.8) + 32 else: - new_fields[field] = x/1000 + new_fields[field] = val/1000 new_fields["temperature"] = new_fields.pop("temp") elif device_name == "bme280": @@ -26,43 +26,43 @@ def device_transform(device_name, fields): new_fields[field] = fields[field]/1000 new_fields["humidity"] = new_fields.pop("humidityrelative") elif field == "temp": - x = fields[field] + val = fields[field] if os.getenv('TEMP_UNIT', 'C') == 'F': - new_fields[field]= ((x/1000) * 1.8) + 32 + new_fields[field]= ((val/1000) * 1.8) + 32 else: - new_fields[field] = x/1000 + new_fields[field] = val/1000 new_fields["temperature"] = new_fields.pop("temp") elif field == "pressure": - x = fields[field] - new_fields[field] = x * 10 + val = fields[field] + new_fields[field] = val * 10 elif device_name == "bmp280": print("Transforming {0} value(s)...".format(device_name)) for field in fields: if field == "temp": - x = fields[field] + val = fields[field] if os.getenv('TEMP_UNIT', 'C') == 'F': - new_fields[field] = ((x/1000) * 1.8) + 32 + new_fields[field] = ((val/1000) * 1.8) + 32 else: - new_fields[field] = x/1000 + new_fields[field] = val/1000 new_fields["temperature"] = new_fields.pop("temp") elif field == "pressure": - x = fields[field] - new_fields[field] = x * 10 + val = fields[field] + new_fields[field] = val * 10 - elif device_name == "htu21": + elif device_name == "htu21" or "dht11" in device_name: print("Transforming {0} value(s)...".format(device_name)) for field in fields: if field == "temp": - x = fields[field] + val = fields[field] if os.getenv('TEMP_UNIT', 'C') == 'F': - new_fields[field]= ((x/1000) * 1.8) + 32 + new_fields[field]= ((val/1000) * 1.8) + 32 else: - new_fields[field] = x/1000 + new_fields[field] = val/1000 new_fields["temperature"] = new_fields.pop("temp") if field == "humidityrelative": - x = fields[field] - new_fields[field] = x/1000 + val = fields[field] + new_fields[field] = val/1000 new_fields["humidity"] = new_fields.pop("humidityrelative") return new_fields