Skip to main content

Setup

Modules and Peripherals Setup

Let's first define the setup function within the sketch. The following is required:

  • Log: To log messages and debug information
  • LedCtrl: To modify the LEDs
  • LTE: To connect to the network
  • MqttClient: To connect to the MQTT broker
  • ECC608: To fetch the thing name (the identifier) of the board. The thing name is used so that the MQTT broker can recognize the particular board that is used
  • MCP9808: The temperature sensor on the board
  • VEML3328: The light sensor on the board
  • ArduinoJSON: Used to decode the MQTT messages.

The SW0 button is also configured for input so that it can be used to send a heartbeat.

#include <log.h>
#include <led_ctrl.h>
#include <lte.h>
#include <mqtt_client.h>
#include <ecc608.h>
#include <mcp9808.h>
#include <ArduinoJson.h>
#include <veml3328.h>

// These are predefined topics which are enabled in the Microchip AWS sandbox
const char MQTT_SUB_TOPIC_FMT[] PROGMEM = "$aws/things/%s/shadow/update/delta";
const char MQTT_PUB_TOPIC_FMT[] PROGMEM = "%s/sensors";

static char mqtt_sub_topic[128];
static char mqtt_pub_topic[128];

void setup() {
Log.begin();

LedCtrl.begin();

// Does a little animation of the LEDs
LedCtrl.startupCycle();

// Enable interrupts
sei();

if (Mcp9808.begin()) {
Log.error(F("Could not initialize the temperature sensor"));

while (1) {}
}

if (Veml3328.begin()) {
Log.error(F("Could not initialize the light sensor"));

while (1) {}
}

ECC608.begin();

// Find the thing ID and set the publish and subscription topics
uint8_t thing_name[128];
size_t thing_name_len = sizeof(thing_name);

const ATCA_STATUS status =
ECC608.readProvisionItem(AWS_THINGNAME, thing_name, &thing_name_len);

if (status != ATCA_SUCCESS) {
Log.error(F("Could not retrieve thing name from the ECC"));
Log.error(F("Unable to initialize the MQTT topics. Stopping..."));
LedCtrl.on(Led::ERROR);
return;
}

Log.infof(F("Board name: %s\r\n"), thing_name);

sprintf_P(mqtt_sub_topic, MQTT_SUB_TOPIC_FMT, thing_name);
sprintf_P(mqtt_pub_topic, MQTT_PUB_TOPIC_FMT, thing_name);

Log.info(
F("Will now connect to the operator. If the board hasn't previously "
"connected to the operator/network, establishing the "
"connection the first time might take some time."));
connectLTE();

}

The connectLTE() function is not defined yet, but that will follow shortly. First let's dive into some of the state machine setup:

State Machine Setup

To have a clear definition of the states within the state machine there is a need to have some structures which define them. This is a particularly good use case for an enum. One could just label the states from 0 to 3 with some integer, where 0 is not connected for example, but that quickly increases the chance for bugs and is far less readable.

typedef enum {
NOT_CONNECTED,
CONNECTED_TO_NETWORK,
CONNECTED_TO_BROKER,
STREAMING_DATA
} State;

static State state = NOT_CONNECTED;

The events also need to be defined in some way. A simple way for this is to use the individual bits of some integer, where a one signifies that the event has been raised. The strength of this is that it is compact, and can support multiple events being raised at the same time (for example if bit 5 and bit 7 are raised, then both the send heartbeat event and the start publishing data event is raised). That being said, this requires bitwise operations, where one has to be careful. Checking that the broker-connected event was raised can be done by event_flags & BROKER_CONN_FLAG.

#define NETWORK_CONN_FLAG                 (1 << 0)
#define NETWORK_DISCONN_FLAG (1 << 1)
#define BROKER_CONN_FLAG (1 << 2)
#define BROKER_DISCONN_FLAG (1 << 3)
#define SEND_HEARTBEAT_FLAG (1 << 4)
#define STOP_PUBLISHING_SENSOR_DATA_FLAG (1 << 5)
#define START_PUBLISHING_SENSOR_DATA_FLAG (1 << 6)
#define SEND_SENSOR_DATA_FLAG (1 << 7)


static volatile uint16_t event_flags = 0;

LTE setup

With this being set up, one can continue with the functions for LTE. The callback for the LTE module are registered so that the state machine can know when the LTE module got disconnected. Note that in this callback the event_flags are OR-ed in so that they don't overwrite any existing events raised. Also, note that these functions have to be defined above the setup() function.

void disconnectedFromNetwork(void) { event_flags |= NETWORK_DISCONN_FLAG; }

void connectLTE() {

Lte.onDisconnect(disconnectedFromNetwork);

Lte.begin();
event_flags |= NETWORK_CONN_FLAG;
}

MQTT Setup

For MQTT, some care has to be taken for the implementation. The system can receive multiple messages within a short time interval, and this has to be taken into account. In the sandbox, this is solved by keeping a circular buffer of the message identifiers which has arrived.

// We allow 32 messages in flight
#define RECEIVE_MESSAGE_ID_BUFFER_SIZE 32
#define RECEIVE_MESSAGE_ID_BUFFER_MASK (RECEIVE_MESSAGE_ID_BUFFER_SIZE - 1)

static uint32_t received_message_identifiers[RECEIVE_MESSAGE_ID_BUFFER_SIZE];
static volatile uint8_t received_message_identifiers_head = 0;
static volatile uint8_t received_message_identifiers_tail = 0;

Convenience functions for MQTT and callbacks are also needed so that they can be called within the main state machine logic. As with LTE, callbacks are registered for the MqttClient. These are called when the connection is made, a message is received and the client got disconnected. In the callback for the received message the circular buffer is utilized to keep track of the incoming messages.

void disconnectedFromBroker(void) { event_flags |= BROKER_DISCONN_FLAG; }

void receivedMessage(__attribute__((unused)) const char* topic,
__attribute__((unused)) const uint16_t msg_length,
const int32_t msg_id) {

received_message_identifiers_head = (received_message_identifiers_head + 1) & RECEIVE_MESSAGE_ID_BUFFER_MASK;
received_message_identifiers[received_message_identifiers_head] = msg_id;
}

void connectMqtt() {
MqttClient.onConnectionStatusChange(connectedToBroker,
disconnectedFromBroker);
MqttClient.onReceive(receivedMessage);

while (!MqttClient.beginAWS()) {
delay(1000);
}
}

Button Setup

For the system to send a heartbeat when the SW0 button is pressed through interrupts, we have to attach an interrupt to the pin. The SW0 button is at PD2. If it was pressed, the send heartbeat event is raised.

void sendHeartbeatInterrupt(void) {
if (PORTD.INTFLAGS & PIN2_bm) {
event_flags |= SEND_HEARTBEAT_FLAG;
PORTD.INTFLAGS = PIN2_bm;
}
}

// Has to be added to setup():
pinConfigure(PIN_PD2, PIN_DIR_INPUT);
attachInterrupt(PIN_PD2, sendHeartbeatInterrupt, FALLING);