Skip to main content

ESDK Gets New Expansion Capabilities

ESDK Ecosystem Expansion (EEA) board

ESDK Ecosystem Expansion (EEA) board enables prototyping with third-party sensors.

In this article, we’ll be taking a look at the new Environmental Sensor Development Kit (ESDK) EEA v0.1 module features and interfaces, and then adding software support for a new sensor interfaced via this, by writing a simple plugin module.

The EEA Board

The ESDK-EEA board features four popular interfaces commonly encountered by makers and hobbyists, including a Sparkfun Qwiic connector, a Digilent I2C Pmod, and two Seeedstudio Grove connectors (one GPIO and one I2C).

EEA Board Interfaces

Selectable 3.3V or 5V Vcc voltages are provided for the Grove connectors by means of a three-pin header and pin jumper — on the GPIO interface, this additionally sets the GPIO voltage levels, thanks to an on-board bidirectional level translator. The GPIO interface is additionally protected by a TVS diode to minimise the impact of ESD events on the user side.

Close up of the board and chip

The I2C interfaces on the Pmod, Qwiic and Grove connectors are connected to the rest of the sensor chain through the use of an NXP I2C bus repeater that supports hot swapping, and thus avoids corruption of any transactions taking place on the sensor bus.

Using a bus repeater also provides some additional ESD protection for the sensor bus, meaning that accidental transients/overvoltage events on the user side should not damage other sensor modules and the Raspberry Pi.

I2C pull-up resistors are provided on the user connector side, meaning that sensor boards that do not provide any will work.

Making Use of the EEA

To accompany the ESDK-EEA board, we’ve implemented an (at present experimental) plug-in system that allows users to write Python classes that provide an interface to custom sensors.

Implementing a Plugin System

Implementing plugins took some thinking; Python provides a built-in library called “imp” that provides access to the import system internals and allows for module importing on-the-fly. This gave us a starting point to work from, as we knew that we’d need to be able to load modules ad-hoc.

for filename in os.listdir(self.pluginFullPath):
            modulename, extension = os.path.splitext(filename)
            if extension == '.py':
                file, path, descr = imp.find_module(modulename, [self.pluginFullPath])
                if file:
                    try:
                        self.logger.debug("Found plugin module: {}".format(file.name))
                        module = imp.load_module(modulename, file, path, descr)
                        self.pluginsModuleList.append(module)

Importing a module is as easy as finding the module using `imp.find_module()` with a module name and directory provided, which returns a file name, path and a description. All three of these (and the module name) then get passed to `imp.load_module()` which returns a module object.

for pluginModule in self.pluginsModuleList:
            for name, obj in inspect.getmembers(pluginModule):
                if inspect.isclass(obj):
                    self.logger.debug("Created plugin class {}".format(obj))
                    self.plugins.append(obj())

Loaded modules are then added to a list, to be iterated over to discover classes using functions from the built-in “inspect” module. The first step is to iterate over each member of the module using a `for` loop and the `inspect.getmembers()` function, and then a check is performed to find the class member — once found, the object is instantiated and added to another list for later use.

for plugin in self.plugins:
                pluginName = plugin.__class__.__name__
                self.logger.debug("Trying to read plugin {}".format(pluginName))
                try:
                    data = plugin.readSensors()
                    if data != -1:
                        self.sensorData.update(data)

The `readAllModules()` function was then expanded to support reading sensor data from plugins. The code is functionally equivalent to the code that reads from the built-in sensor modules, where the only difference is the `readSensors()` function is called on the list of plugin objects.

Using the Plugin System

The plugin handling is implemented in the MAIN module of the DesignSpark.ESDK library, and consists of one additional function called `loadPlugins`, which attempts to import Python files from a specified directory.

Plugin directory configuration is provided when the ModMAIN class is instantiated by specifying the argument `pluginDir` — if this is not provided, the library will look for plugins in a sub-directory called `plugins/` in the current working directory.

Only a call to `loadPlugins()` needs to be made, as reading from plugins is implemented in `readAllModules()`.

Plugins should contain only one class, and at the minimum a function within the class called `readSensors()`, which returns a dictionary containing specific values plus sensor data. A short, descriptive sensor name should be given, and the same applies for the “sensor” key value — for example, the ESDK-CO2 board has a “sensor” value of “CO20.2” which is the board name and hardware revision.

{
        "pir": {
                "motion": "1",
                "sensor": "pirplugin0.1"
        }
}

Sensor data should be included in the innermost dictionary alongside the “sensor” key/value pair, and multiple sensor data key/value pairs can be included. For example, the ESDK-THV board returns key/value pairs for the following data points: “temperature”, “humidity”, “vocIndex”.

Should no data be available, a value of -1 should be returned in place of a dictionary. The `readAllModules()` function specifically looks for this value and then skips over reading the sensor data.

import RPi.GPIO as GPIO

GPIO1 = 20

class PIR:
        def __init__(self):
                GPIO.setmode(GPIO.BCM)
                GPIO.setup(GPIO1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

        def readSensors(self):
                pirState = GPIO.input(GPIO1)
                return {"pir": {"motion": str(pirState), "sensor": "pirplugin0.1"}}

The above example demonstrates reading from a Seeedstudio PIR sensor (188-7098) , utilising a GPIO input on the ESDK-EEA Grove connector.

As can be seen, the example PIR plugin is successfully loaded by a test application and returns sensor data.

To Finish

In this article we’ve taken a look at the ESDK-EEA expansion module, and how this enables the usage of the vast array of existing Pmod, Grove, and Qwiic sensors within the ESDK ecosystem. The newly implemented plugin system backs this up by allowing for users to develop their own Python code to handle reading sensors that then plug into the existing Air Quality application. Other applications that utilise the ESDK hardware and Python library can also make use of the EEA module and plugin support to expand their functionality.

Design files for the EEA board have been released on GitHub, software support will be released in the next release of the DesignSpark.ESDK library and documentation is available here.

Engineer of mechanical and electronic things by day, and a designer of rather amusing, rather terrible electric "vehicles" by night.