Expanding the capabilities of the WipperSnapper Arduino library requires you to have some familiarity with C++. The example below involves creating and editing some code that helps WipperSnapper interface with an I2C sensor.

If you get stuck or need help, post up on the #help-with-adafruit-io channel in the Adafruit Discord server and we'll try to assist!

Get Setup

This guide assumes you've installed the Arduino IDE and have successfully programmed a WipperSnapper-compatible development board with it. 

Start by making a local fork of the WipperSnapper_Arduino repository to your GitHub account. Next, you'll need to clone this forked repository to your computer's Arduino library folder (we have a guide on how to locate this folder here)

Open up the Arduino IDE. Then, open the WipperSnapper demo sketch located in File->Tools->Adafruit WipperSnapper->Wippersnapper_demo.

Since the WipperSnapper library was installed manually and not from the Arduino Library Manager, it's required dependencies will not be automatically installed. 

Open the library.properties file for this library. Underneath the depends field is a list of all the libraries required. You'll need to manually install each dependency using the Arduino Library Manager (Sketch->Include Libraries->Manage Libraries).

Once you've manually installed the dependencies, select the board you're using and click "Verify".

If you successfully compiled the sketch, you're ready to add a new component to Adafruit IO WipperSnapper.

Adding an I2C Component Driver

As an example, we'll be creating a new WipperSnapper I2C component for the MCP9808 sensor. You will need to modify the instructions below to reflect the sensor you're adding.

Before digging into the code, find and install an Arduino library that already contains driver code for your sensor. Adafruit WipperSnapper interfaces with existing libraries, low-level sensor code within the I2C drivers is not accepted. Low-level code should already be wrapped into an Arduino library which is listed on the Arduino Library Manager.

The sensor used here is an Adafruit MCP9808 Breakout. The Arduino library for this is listed on the Arduino Library Manager and publicly available on GitHub.

During this process, only files within the Adafruit_Wippersnapper_Arduino/src/components/i2c directory will be modified. Open your file browser, or code editor, to this folder.

Next, create a new file under the Adafruit_Wippersnapper_Arduino/src/components/i2c/drivers directory named WipperSnapper_I2C_Driver_MCP9808.h. This file will contain the code that tells WipperSnapper how to communicate with the MCP9808 sensor.

Next is to fill it with code that references the MCP9808 Arduino library linked above. To begin, start with the following template.

#ifndef WipperSnapper_I2C_Driver_MCP9808_H
#define WipperSnapper_I2C_Driver_MCP9808_H

#include "WipperSnapper_I2C_Driver.h"
#include <Adafruit_MCP9808.h>

/**************************************************************************/
/*!
    @brief  Class that provides a driver interface for a MCP9808 sensor.
*/
/**************************************************************************/
class WipperSnapper_I2C_Driver_MCP9808 : public WipperSnapper_I2C_Driver {
public:
  /*******************************************************************************/
  /*!
      @brief    Constructor for a MCP9808 sensor.
      @param    i2c
                The I2C interface.
      @param    sensorAddress
                7-bit device address.
  */
  /*******************************************************************************/
  WipperSnapper_I2C_Driver_MCP9808(TwoWire *i2c, uint16_t sensorAddress)
      : WipperSnapper_I2C_Driver(i2c, sensorAddress) {
    _i2c = i2c;
    _sensorAddress = sensorAddress;
  }

  /*******************************************************************************/
  /*!
      @brief    Destructor for an MCP9808 sensor.
  */
  /*******************************************************************************/
  ~WipperSnapper_I2C_Driver_MCP9808() {
    // Called when a MCP9808 component is deleted.
    delete _mcp9808;
  }

  /*******************************************************************************/
  /*!
      @brief    Initializes the MCP9808 sensor and begins I2C.
      @returns  True if initialized successfully, False otherwise.
  */
  /*******************************************************************************/
  bool begin() {
    // Place initialization code here!
  }


protected:
  Adafruit_MCP9808 *_mcp9808; ///< Pointer to MCP9808 temperature sensor object
};

#endif // WipperSnapper_I2C_Driver_MCP9808

At the top of the code are include guards which prevent the compiler from including multiple instances of this class. 

Next are #includes which include WipperSnapper_I2C_Driver.h, the base class containing generic implementations for the I2C_Driver, and the MCP9808 sensor driver - Adafruit_MCP9808.h. You will want to include the correct library for your I2C sensor here.

The constructor (WipperSnapper_I2C_Driver_MCP9808()) and destructor (~WipperSnapper_I2C_Driver_MCP9808()) are called when the component is either initialized or deleted via the WipperSnapper website. The constructor is passed the I2C object and the sensor address from the new component form.

At the bottom, underneath protected, you'll need to create a pointer (_mcp9808) to a MCP9808 sensor object. 

Next, within begin(), initialize the sensor and begin I2C communication. This can be copied from an example Arduino sketch's setup() function.

At this point, your code should look like the following.

#ifndef WipperSnapper_I2C_Driver_MCP9808_H
#define WipperSnapper_I2C_Driver_MCP9808_H

#include "WipperSnapper_I2C_Driver.h"
#include <Adafruit_MCP9808.h>

/**************************************************************************/
/*!
    @brief  Class that provides a driver interface for a MCP9808 sensor.
*/
/**************************************************************************/
class WipperSnapper_I2C_Driver_MCP9808 : public WipperSnapper_I2C_Driver {
public:
  /*******************************************************************************/
  /*!
      @brief    Constructor for a MCP9808 sensor.
      @param    i2c
                The I2C interface.
      @param    sensorAddress
                7-bit device address.
  */
  /*******************************************************************************/
  WipperSnapper_I2C_Driver_MCP9808(TwoWire *i2c, uint16_t sensorAddress)
      : WipperSnapper_I2C_Driver(i2c, sensorAddress) {
    _i2c = i2c;
    _sensorAddress = sensorAddress;
  }

  /*******************************************************************************/
  /*!
      @brief    Destructor for an MCP9808 sensor.
  */
  /*******************************************************************************/
  ~WipperSnapper_I2C_Driver_MCP9808() {
    // Called when a MCP9808 component is deleted.
    delete _mcp9808;
  }

  /*******************************************************************************/
  /*!
      @brief    Initializes the MCP9808 sensor and begins I2C.
      @returns  True if initialized successfully, False otherwise.
  */
  /*******************************************************************************/
  bool begin() {
    _mcp9808 = new Adafruit_MCP9808();
    bool isInit = _mcp9808->begin((uint8_t)_sensorAddress, _i2c);
    return isInit;
  }


protected:
  Adafruit_MCP9808 *_mcp9808; ///< Pointer to MCP9808 temperature sensor object
};

#endif // WipperSnapper_I2C_Driver_MCP9808

Getting the Temperature

The MCP9808 contains an ambient temperature sensor. A function needs to be added to read the value of the ambient temperature sensor, called getEventAmbientTemperature().

The WipperSnapper I2C component utilizes the Adafruit Unified Sensor Driver to handle reading sensors. This driver reduces all data to a single sensors_event_t structure and uses standardized SI units for every type of sensor.

Within the getEventAmbientTemperature function, read from the MCP9808 sensor (_mcp9808->readTempC();) and set the sensors_event_t struct's temperature field to the SI value of the temperature sensor (tempEvent->temperature = _mcp9808->readTempC());

#ifndef WipperSnapper_I2C_Driver_MCP9808_H
#define WipperSnapper_I2C_Driver_MCP9808_H

#include "WipperSnapper_I2C_Driver.h"
#include <Adafruit_MCP9808.h>

class WipperSnapper_I2C_Driver_MCP9808 : public WipperSnapper_I2C_Driver {
public:
  WipperSnapper_I2C_Driver_MCP9808(TwoWire *_i2c, uint16_t sensorAddress) : WipperSnapper_I2C_Driver(_i2c, sensorAddress) {
    // Called when a MCP9808 component is created
    setI2CAddress(sensorAddress); // sets the driver's I2C address
    _mcp9808 = new Adafruit_MCP9808();
    _isInitialized = _mcp9808->begin();
  }

  ~WipperSnapper_I2C_Driver_MCP9808() {
      // Called when a MCP9808 component is deleted.
      delete _mcp9808;
  }

  bool getEventAmbientTemperature(sensors_event_t *tempEvent) {
    tempEvent->temperature = _mcp9808->readTempC();
    return true;
  }

protected:
  Adafruit_MCP9808 *_mcp9808; ///< Pointer to MCP9808 temperature sensor object
};

#endif // WipperSnapper_I2C_Driver_MCP9808

Wait - My I2C breakout has more than one sensor!

WipperSnapper uses the Adafruit Unified Sensor Driver to handle reading sensors and enforces standardized SI units for every type of sensor. In WipperSnapper_I2C_Driver.h, there are functions for each predefined sensor type within the Adafruit Unified Sensor Driver.

For each sensor value you'd like to read - the driver code should implement the Arduino Library's function for obtaining the value within a new getEventSensorType() function and pack it into a sensors_event_t field. 

NOTE - If you're adding a sensor that does not yet have a predefined sensor_type, please add a new issue to Adafruit_Sensor.

Add the Sensor Driver to the I2C Component

With the sensor driver WipperSnapper_I2C_Driver_MCP9808.h complete,  add it to the I2C Component (components/i2c/WipperSnapper_I2C.*).

Open components/i2c/WipperSnapper_I2C.h in a code editor. At the top of the file, include the MCP9808 driver just created.

...
#include "drivers/WipperSnapper_I2C_Driver_DPS310.h"
#include "drivers/WipperSnapper_I2C_Driver_SCD30.h"
#include "drivers/WipperSnapper_I2C_Driver_MCP9808.h"

Towards the bottom of this file, add a new pointer to the MCP9808 class:

..
  WipperSnapper_I2C_Driver_SCD30   *_scd30 = nullptr;
  WipperSnapper_I2C_Driver_BME280  *_bme280 = nullptr;
  WipperSnapper_I2C_Driver_MCP9808 *_mcp9808 = nullptr;
 ..

Finally, add code to components/i2c/WipperSnapper_I2C.cpp in order to detect and initialize the MCP9808 sensor.

bool WipperSnapper_Component_I2C::initI2CDevice(
    wippersnapper_i2c_v1_I2CDeviceInitRequest *msgDeviceInitReq) {
  ...
  } else if (strcmp("mcp9808", msgDeviceInitReq->i2c_device_name) == 0) {
    _mcp9808 = new WipperSnapper_I2C_Driver_MCP9808(this->_i2c, i2cAddress);
    if (!_mcp9808->begin()) {
      WS_DEBUG_PRINTLN("ERROR: Failed to initialize MCP9808!");
      _busStatusResponse =
          wippersnapper_i2c_v1_BusResponse_BUS_RESPONSE_DEVICE_INIT_FAIL;
      return false;
    }
    _scd30->configureDriver(msgDeviceInitReq);
    drivers.push_back(_mcp9808);
    WS_DEBUG_PRINTLN("MCP9808 Initialized Successfully!");
  }
  ...
}

Testing your new Sensor

In the Arduino IDE, open File->Examples->Adafruit_WipperSnapper_Arduino->Wippersnapper_demo and compile the sketch.

If the sketch compiles with no errors, upload the sketch to your development board.

Open the Arduino Serial Monitor at 115200 baud to monitor the WipperSnapper session.

After the board successfully connects to WipperSnapper, you should see the following debug output stating the application is running.

On the WipperSnapper device page, click + New Component.

As part of our review process, we'll make your component appear under the component picker as "in development". This allows you to test the component before giving us (Adafruit) final approval to make it live.

On your Adafruit IO Device page, open the component picker. Then, click the "Show Dev" checkbox.

On the Arduino Serial monitor, you should see an I2C Scan command being executed on the Feather.

On the WipperSnapper website, configure your component and click Create Component.

On the Arduino Serial Monitor, you should see the MCP9808 sensor initialize. After 30 seconds, it should read the temperature value and PUBLISH it to WipperSnapper.

You should see the new value appear on your WipperSnapper device page.

Almost done - let's finally add a pull request to the Adafruit WipperSnapper library so others can use this sensor.

Create a Pull Request to WipperSnapper Arduino

First, read over the Doxygen page on this guide and run Doxygen on your code. Then, format your code using clang-format.

Once you've run Doxygen and linted using clang-format, you're ready to submit a pull request adding the sensor to the WipperSnapper Arduino library.

Commit and push your files to a branch on local your fork of Adafruit_WipperSnapper_Arduino and open a new pull request on the Adafruit_WipperSnapper_Arduino repository.

Adafruit has an example of a "perfect" pull request here.

Once reviewed and accepted, Adafruit will include support for your sensor in the latest version of the Adafruit IO Wippersnapper library. We will also remove the "in-development" flag from the component to make it live on Adafruit IO.

This guide was first published on Mar 10, 2022. It was last updated on Mar 10, 2022.

This page (Adding an I2C Component Driver) was last updated on Feb 03, 2022.

Text editor powered by tinymce.