Skip to content
This repository was archived by the owner on Apr 5, 2024. It is now read-only.

IIO-only device support #14

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
76 changes: 60 additions & 16 deletions idetect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand All @@ -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
Expand All @@ -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])
Expand All @@ -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)))
Expand All @@ -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))
Expand Down Expand Up @@ -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
4 changes: 2 additions & 2 deletions information.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
11 changes: 5 additions & 6 deletions reading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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)
Expand Down
37 changes: 19 additions & 18 deletions sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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"
Expand All @@ -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)
40 changes: 20 additions & 20 deletions transformers.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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":
Expand All @@ -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