Contents

DashIO IoT Guide (Adafruit Bluefruit SPI Friend)

5 October 2021

So, what is DashIO? It is a quick effortless way to connect your IoT device to your phone. It allows easy setup of controls such as Dials, Text Boxes, Maps, Graphs, Notifications..., from your IoT device. You can define the look and layout of the controls on your phone from your IoT device. There are three methods to connect to your phone; Bluetooth Low Energy (BLE), TCP or MQTT. What's Dash then? Dash is a mqtt server with extra bits added in to allow you to send notifications, share your devices, and save your settings from your phone via the DashIO app.

This guide demonstractes how to make a BLE connection with an Adafruit Bluefruit SPI friend using the Arduino IDE.

Getting Started

For the big picture on DashIO, take a look at our website: dashio.io

For the DashIO arduino library: github.com/dashio-connect/arduino-dashio

Requirements

Grab an Adafruit Bluefruit SPI friend, Arduino IDE and follow this guide.

You will need to install the DashIO app on your pobile phone or tablet. And if you'd like to connect to one IoT device for free, setup a free DashIO account from within the DashIO app.

Install

DashIO

You will need to add the DashIO library into your project. Download the DashIO library from GitHub with the following link: https://github.com/dashio-connect/arduino-dashio

You will need to copy the following files from the arduino-dashio library into your project directory:

  • DashioBluefruitSPI.h
  • DashioBluefruitSPI.cpp
  • bluefruitConfig.g

BLE

You will need to install the Adafruit library for the Bluefruit SPI friend, which are available in the Arduino IDE Library Manager. Search for the library "Adafruit BluefruitLE nRF51" and install.

Guide

BLE Basics

Lets start with a simple BLE connection.

#include "DashioBluefruitSPI.h"

#define DEVICE_TYPE "BF_SPI_BLE_Type"
#define DEVICE_NAME "Jill Name"

DashioDevice dashioDevice(DEVICE_TYPE);
DashioBluefruit_BLE  ble_con(&dashioDevice, true);

void setup() {
    Serial.begin(115200);

    dashioDevice.name = DEVICE_NAME;
    ble_con.begin(true);
}

void loop() {
    ble_con.run();
}

This is about the fewest number of lines of code to get talking to the DashIO app. There is a lot happening under the hood to make this work. After the include we create a device with the device_type as its only attribute. We also create a BLE connection, with the newly created device being an attribute to the connection. The second attribute of the BLE connection enables the received and transmitted messages to be printed to the serial monitor.

In the setup function we assign the device name to the device and start the BLE connection ble_con.begin(true);. The device_ID is automagically obtained from the macAddress of the Bluefruit SPI Friend.

The "true" attribute of the ble_conn.begin(true); causes the Bluefruit SPI friend to perform a factory reset to make sure everything is in a known state. This can be set to "false" when you have finished your code.

This device is discoverable by the DashIO app. You can also discover your IoT device using a thiry party BLE scanner (e.g. BlueCap). The name of your IoT device will be a concatentation of "DashIO_" and the device_type.

If you like, you can assign your own device_ID by changing the BLE connection begin function call with the following:

dashioDevice.deviceID = "myUniqueDevieID";
ble_con.begin(true, false);

Lets add Dial control messages that are sent to the DashIO app every second. To do this we create a new task to provide a 1 second time tick and then send a Dial value message from the loop every second.

#include "DashioBluefruitSPI.h"

#define DEVICE_TYPE "BF_SPI_BLE_Type"
#define DEVICE_NAME "Jill Name"

DashioDevice dashioDevice(DEVICE_TYPE);
DashioBluefruit_BLE  ble_con(&dashioDevice, true);

void setup() {
    Serial.begin(115200);

    dashioDevice.name = DEVICE_NAME;
    ble_con.begin(true);
}

void loop() {
    ble_con.run();
    ble_con.sendMessage(dashioDevice.getDialMessage("D01", int(random(0, 100))));
    delay(1000);
}

The dashioDevice.getDialMessage("D01", int(random(0, 100))) creates the message with two parameters. The first parameter is the control_ID which identifies the specific Dial control in the DashIO app and the second parameter is simply the Dial value.

It would be nice to have the DashIO app automagically setup a new Device View and place your control on the new Device View. To do that we need to add a few more lines of code. Most importantly, we need to respond to messages coming in from the DashIO app. To do this we add a callback into the BLE connection with the setCallback function:

#include "DashioBluefruitSPI.h"

#define DEVICE_TYPE "BF_SPI_BLE_Type"
#define DEVICE_NAME "Jill Name"

DashioDevice dashioDevice(DEVICE_TYPE);
DashioBluefruit_BLE  ble_con(&dashioDevice, true);

void processConfig(ConnectionType connectionType) {
    String message((char *)0);
    message.reserve(2048);

    message += dashioDevice.getConfigMessage(DeviceCfg(1)); // One Device Views

    DialCfg first_dial_control("D01", "DV01", "Dial", {0.24, 0.14, 0.54, 0.26});
    message += dashioDevice.getConfigMessage(first_dial_control);

    DeviceViewCfg deviceView("DV01", "A Dial", "up", "0");
    message += dashioDevice.getConfigMessage(deviceView);

    ble_con.sendMessage(message);
}

void processIncomingMessage(MessageData *messageData) {
    switch (messageData->control) {
    case config:
        processConfig(messageData->connectionType);
        break;
    }
}

void setup() {
    Serial.begin(115200);

    dashioDevice.name = DEVICE_NAME;
    ble_con.setCallback(&processIncomingMessage);
    ble_con.begin(true);
}

void loop() {
    ble_con.run();
    ble_con.sendMessage(dashioDevice.getDialMessage("D01", int(random(0, 100))));
    delay(1000);
}

The processIncomingMessage callback checks the message type and calls the appropriate function. At this stage we are only interested in CONFIG messages and the processConfig function is called appropriately. This part of the code creates quite long messages, so we allocate enough space for the message, 2048 bytes in this case. We create and send three config response messages:

  1. Device: lets the DashIO app know there is a device with one Device View.
  2. Dial: includes the control_ID of the Dial, the control_ID of the Device View it will be displayed on, the title and the position of the dial on the Device View.
  3. DeviceView: includes the control_ID, title, icon name and colour of the Device View.

The next piece of the puzzle to consider is how we receive data from the DashIO app. Lets add a Knob and connect it to the Dial. We do this by adding the Knob to the processIncomingMessage function and send the Knob value to the Dial in the processConfig function. And remember to remove the timer and the horrible delay from the loop:

#include "DashioBluefruitSPI.h"

#define DEVICE_TYPE "BF_SPI_BLE_Type"
#define DEVICE_NAME "Jill Name"

DashioDevice dashioDevice(DEVICE_TYPE);
DashioBluefruit_BLE  ble_con(&dashioDevice, true);

void processConfig(ConnectionType connectionType) {
    String message((char *)0);
    message.reserve(2048);

    message += dashioDevice.getConfigMessage(DeviceCfg(1)); // One Device Views

    DialCfg first_dial_control("D01", "DV01", "Dial", {0.24, 0.14, 0.54, 0.26});
    message += dashioDevice.getConfigMessage(first_dial_control);

    KnobCfg aKnob("KB01", "DV01", "Knob", {0.24, 0.42, 0.54, 0.26});
    message += dashioDevice.getConfigMessage(aKnob);

    DeviceViewCfg deviceView("DV01", "Dial & Knob", "up", "black");
    message += dashioDevice.getConfigMessage(deviceView);
    
    ble_con.sendMessage(message);
}

void processIncomingMessage(MessageData *messageData) {
    switch (messageData->control) {
    case config:
        processConfig(messageData->connectionType);
        break;
    case knob:
        if (messageData->idStr == "KB01") {
            String message = dashioDevice.getDialMessage("D01", (int)messageData->payloadStr.toFloat());
            ble_con.sendMessage(message);
        }
        break;
    }
}

void setup() {
    Serial.begin(115200);

    dashioDevice.name = DEVICE_NAME;
    ble_con.setCallback(&processIncomingMessage);
    ble_con.begin(true);
}

void loop() {
    ble_con.run();
}

Finally, we should respond to the STATUS message from the DashIO app. STATUS messages allows the IoT device to send initial conditions for each control to the DashIO app as soon as a connection becomes active. Once again, we do this from the processIncomingMessage function and our complete code looks like this:

#include "DashioBluefruitSPI.h"

#define DEVICE_TYPE "BF_SPI_BLE_Type"
#define DEVICE_NAME "Jill Name"

DashioDevice dashioDevice(DEVICE_TYPE);
DashioBluefruit_BLE  ble_con(&dashioDevice, true);

int dialValue = 0;

void processConfig(ConnectionType connectionType) {
    String message((char *)0);
    message.reserve(2048);

    message += dashioDevice.getConfigMessage(DeviceCfg(1)); // One Device Views

    DialCfg first_dial_control("D01", "DV01", "Dial", {0.24, 0.14, 0.54, 0.26});
    message += dashioDevice.getConfigMessage(first_dial_control);

    KnobCfg aKnob("KB01", "DV01", "Knob", {0.24, 0.42, 0.54, 0.26});
    message += dashioDevice.getConfigMessage(aKnob);

    DeviceViewCfg deviceView("DV01", "Dial & Knob", "up", "black");
    message += dashioDevice.getConfigMessage(deviceView);
    
    ble_con.sendMessage(message);
}

void processStatus(ConnectionType connectionType) {
    String message((char *)0);
    message.reserve(1024);

    message = dashioDevice.getKnobMessage("KB01", dialValue);
    message += dashioDevice.getDialMessage("D01", dialValue);

    ble_con.sendMessage(message);
}

void processIncomingMessage(MessageData *messageData) {
    switch (messageData->control) {
    case status:
        processStatus(messageData->connectionType);
        break;
    case config:
        processConfig(messageData->connectionType);
        break;
    case knob:
        if (messageData->idStr == "KB01") {
              dialValue = messageData->payloadStr.toFloat()
            String message = dashioDevice.getDialMessage("D01", dialValue);
            ble_con.sendMessage(message);
        }
        break;
    }
}

void setup() {
    Serial.begin(115200);

    dashioDevice.name = DEVICE_NAME;
    ble_con.setCallback(&processIncomingMessage);
    ble_con.begin(true);
}

void loop() {
    ble_con.run();
}

This is just the beginning and there is a lot more we can do. Take a look at the examples in the library to see more details.