This commit is contained in:
brentru 2020-10-22 10:52:17 -04:00
parent 4fd6b01843
commit 61ff68f7ad
5 changed files with 1202 additions and 0 deletions

755
src/Wippersnapper.cpp Normal file
View file

@ -0,0 +1,755 @@
/*!
* @file Wippersnapper.cpp
*
* @mainpage Adafruit Wippersnapper Wrapper
*
* @section intro_sec Introduction
*
* This is the documentation for Adafruit's Wippersnapper wrapper for the
* Arduino platform. It is designed specifically to work with the
* Adafruit IO+ Wippersnapper IoT platform.
*
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* @section dependencies Dependencies
*
* This library depends on <a href="https://github.com/adafruit/Adafruit_Sensor">
* Adafruit_Sensor</a> being present on your system. Please make sure you have
* installed the latest version before using this library.
*
* @section author Author
*
* Written by Brent Rubell for Adafruit Industries.
*
* @section license License
*
* BSD license, all text here must be included in any redistribution.
*
*/
#include "Wippersnapper.h"
ws_board_status_t _boardStatus; // TODO: move to header
uint16_t Wippersnapper::bufSize;
uint8_t Wippersnapper::_buffer[128];
char Wippersnapper:: _value[45];
Timer<16U, &millis, char *> Wippersnapper::t_timer;
Wippersnapper::pinInfo Wippersnapper::ws_pinInfo;
char Wippersnapper::timerPin[3];
/**************************************************************************/
/*!
@brief Instantiates the Wippersnapper client object.
@param aio_username
Adafruit IO Username.
@param aio_key
Adafruit IO Active Key.
*/
/**************************************************************************/
Wippersnapper::Wippersnapper(const char *aio_username, const char *aio_key) {
_mqtt = 0; // MQTT Client object
_username = aio_username;
_key = aio_key;
// TODO: Remove!
_deviceId = "myDevice"; // Adafruit IO+ device name
_hw_vid = 0; // Hardware's usb vendor id
_hw_pid = 0; // Hardware's usb product id
// Reserved MQTT Topics //
_topic_description = 0;
_topic_description_status = 0;
_topic_signals_in = 0;
_topic_signals_out = 0;
//_init();
}
/**************************************************************************/
/*!
@brief Wippersnapper destructor
*/
/**************************************************************************/
Wippersnapper::~Wippersnapper() {
// re-allocate topics
free(_topic_description);
free(_topic_signals_in);
free(_topic_signals_out);
}
/**************************************************************************/
/*!
@brief This function is the core of the createSignalRequest encoding process.
It handles the top-level pb_field_t array manually, in order to encode
a correct field tag before the message. The pointer to MsgType_fields
array is used as an unique identifier for the message type.
*/
/**************************************************************************/
bool Wippersnapper::encode_unionmessage(pb_ostream_t *stream, const pb_msgdesc_t *messagetype, void *message)
{
pb_field_iter_t iter;
if (!pb_field_iter_begin(&iter, signal_v1_CreateSignalRequest_fields, message))
return false;
do
{
if (iter.submsg_desc == messagetype)
{
if (!pb_encode_tag_for_field(stream, &iter))
return false;
return pb_encode_submessage(stream, messagetype, message);
}
} while (pb_field_iter_next(&iter));
return false;
}
/****************************************************************************/
/*!
@brief ISR which reads the value of a digital pin and updates pinInfo.
*/
/****************************************************************************/
bool Wippersnapper::cbDigitalRead(char *pinName) {
BC_DEBUG_PRINT("cbDigitalRead(");BC_DEBUG_PRINT(pinName);BC_DEBUG_PRINTLN(")");
// Read and store pinName into struct.
ws_pinInfo.pinValue = digitalRead((unsigned)atoi(pinName));
// debug TODO remove
BC_DEBUG_PRINT("Pin Values: "); BC_DEBUG_PRINT(ws_pinInfo.pinValue);
BC_DEBUG_PRINT(" "); BC_DEBUG_PRINTLN(ws_pinInfo.prvPinValue);
return true; // repeat every xMS
}
/**************************************************************************/
/*!
@brief Returns if succcessfully sent pin event to MQTT broker,
otherwise false;
*/
/**************************************************************************/
bool Wippersnapper::sendPinEvent() {
uint8_t buffer[128]; // message buffer, TODO: Make this a shared buffer
bool status = false;
// create output stream for buffer
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
//pb_ostream_t stream = {0};
// Create PinEventRequest message type
pin_v1_PinEventRequest msg;
// Encode payload
strcpy(msg.pin_name, "D"); // TODO: hotfix for broker to identify in desc., remove
strcat(msg.pin_name, timerPin);
itoa(ws_pinInfo.pinValue, msg.pin_value, 10);
// Encode PinEventRequest message
status = encode_unionmessage(&stream, pin_v1_PinEventRequest_fields, &msg);
if (!status)
{
Serial.println("Encoding failed!");
return false;
}
Serial.print("Encoded size is: ");Serial.println(stream.bytes_written);
_mqtt->publish(_topic_signals_in, buffer, stream.bytes_written, 0);
return true;
}
/**************************************************************************/
/*!
@brief Executes pin events from the broker
*/
/**************************************************************************/
// Process pin events from the broker
bool Wippersnapper::pinEvent() {
// strip "D" or "A" from "circuitpython-style" pin_name
char* pinName = signalMessage.payload.pin_event.pin_name + 1;
// Set pin value
BC_DEBUG_PRINT("Setting ")BC_DEBUG_PRINT(atoi(pinName));
BC_DEBUG_PRINT(" to ");BC_DEBUG_PRINTLN(atoi(signalMessage.payload.pin_event.pin_value));
digitalWrite(atoi(pinName), atoi(signalMessage.payload.pin_event.pin_value));
return true;
}
/**************************************************************************/
/*!
@brief Configures a pin's mode, direction, pull and period.
*/
/**************************************************************************/
bool Wippersnapper::pinConfig()
{
BC_DEBUG_PRINT("Pin Name: ");BC_DEBUG_PRINTLN(signalMessage.payload.pin_config.pin_name);
BC_DEBUG_PRINT("Mode: ");BC_DEBUG_PRINTLN(signalMessage.payload.pin_config.mode);
BC_DEBUG_PRINT("Direction : ");BC_DEBUG_PRINTLN(signalMessage.payload.pin_config.direction);
BC_DEBUG_PRINT("Pull enabled: ");BC_DEBUG_PRINTLN(signalMessage.payload.pin_config.pull);
ws_pinInfo.PinNameFull = signalMessage.payload.pin_config.pin_name;
// strip "D" or "A" from "circuitpython-style" pin_name
char* pinName = signalMessage.payload.pin_config.pin_name + 1;
ws_pinInfo.pinName = pinName;
// TODO: Check for pullup, configure!
// Configure pin mode and direction
switch(signalMessage.payload.pin_config.mode) {
case pin_v1_ConfigurePinRequest_Mode_MODE_ANALOG:
if (signalMessage.payload.pin_config.direction == pin_v1_ConfigurePinRequest_Direction_DIRECTION_INPUT) {
BC_DEBUG_PRINTLN("* Configuring Analog input pin.");
} else {
BC_DEBUG_PRINTLN("* Configuring Analog output pin.");
}
break;
case pin_v1_ConfigurePinRequest_Mode_MODE_DIGITAL:
// TODO: _INPUT is incorrect, should be 0x0 not 0x1
if (signalMessage.payload.pin_config.direction == 0) {
BC_DEBUG_PRINTLN("* Configuring digital input pin");
long timerMs = signalMessage.payload.pin_config.period;
BC_DEBUG_PRINTLN(timerMs);
strcpy(timerPin, pinName);
auto task = t_timer.every(timerMs, cbDigitalRead, timerPin);
}
BC_DEBUG_PRINTLN("Configuring digital pin direction");
pinMode(atoi(ws_pinInfo.pinName), signalMessage.payload.pin_config.direction);
break;
default:
BC_DEBUG_PRINTLN("Unable to obtain pin configuration from message.")
return false;
break;
}
// TODO: Replace this with a return from within the switch case's calling methods
return true;
}
/**************************************************************************/
/*!
@brief Executes when signal topic receives a new message and copies
payload and payload length.
*/
/**************************************************************************/
void Wippersnapper::cbSignalTopic(char *data, uint16_t len) {
BC_DEBUG_PRINTLN("cbSignalTopic()");
memcpy(_buffer, data, len);
bufSize = len;
}
/**************************************************************************/
/*!
@brief Decodes a signal buffer protobuf message.
NOTE: Should be executed in-order after a new _buffer is recieved.
*/
/**************************************************************************/
bool Wippersnapper::decodeSignalMessage() {
// create a stream which reads from buffer
pb_istream_t stream = pb_istream_from_buffer(_buffer, bufSize);
// decode the message
bool status;
status = pb_decode(&stream, signal_v1_CreateSignalRequest_fields, &signalMessage);
if (!status) {
BC_DEBUG_PRINTLN("Unable to decode signal message");
return false;
}
return true;
}
/**************************************************************************/
/*!
@brief Calls a function handler provided a signal message's payload
type.
*/
/**************************************************************************/
bool Wippersnapper::executeSignalMessageEvent() {
// Executes signal message event based on payload type
switch(signalMessage.which_payload) {
case signal_v1_CreateSignalRequest_pin_config_tag:
Serial.println("DEBUG: Pin config callback");
pinConfig();
break;
case signal_v1_CreateSignalRequest_pin_event_tag:
Serial.println("DEBUG: Pin event callback");
pinEvent();
break;
case signal_v1_CreateSignalRequest_sensor_config_tag:
Serial.println("DEBUG: Sensor config callback");
break;
case signal_v1_CreateSignalRequest_sensor_event_tag:
Serial.println("DEBUG: Sensor event callback");
break;
case signal_v1_CreateSignalRequest_location_request_tag:
Serial.println("DEBUG: Location request callback");
break;
default:
return false;
break;
}
return true;
}
/**************************************************************************/
/*!
@brief Executes when the broker publishes a response to the
client's board description message.
*/
/**************************************************************************/
void cbDescriptionStatus(char *data, uint16_t len) {
BC_DEBUG_PRINTLN("cbDescriptionStatus()");
uint8_t buffer[len];
memcpy(buffer, data, len);
// init. CreateDescriptionResponse message
description_v1_CreateDescriptionResponse message = description_v1_CreateDescriptionResponse_init_zero;
// create input stream for buffer
pb_istream_t stream = pb_istream_from_buffer(buffer, len);
// decode the stream
bool status;
status = pb_decode(&stream, description_v1_CreateDescriptionResponse_fields, &message);
if (!status) {
BC_DEBUG_PRINTLN("Error decoding description status message!");
}
// set board status
switch (message.response) {
case description_v1_CreateDescriptionResponse_Response_RESPONSE_OK:
_boardStatus = BC_BOARD_DEF_OK;
break;
case description_v1_CreateDescriptionResponse_Response_RESPONSE_VID_NOT_FOUND:
_boardStatus = BC_BOARD_DEF_INAVLID_VID;
break;
case description_v1_CreateDescriptionResponse_Response_RESPONSE_PID_NOT_FOUND:
_boardStatus = BC_BOARD_DEF_INVALID_PID;
break;
case description_v1_CreateDescriptionResponse_Response_RESPONSE_UNSPECIFIED:
_boardStatus = BC_BOARD_DEF_UNSPECIFIED;
break;
default:
_boardStatus = BC_BOARD_DEF_UNSPECIFIED;
}
BC_DEBUG_PRINT("Board def. response: ")
BC_DEBUG_PRINTLN(_boardStatus);
}
/**************************************************************************/
/*!
@brief Generates Wippersnapper feeds.
*/
/**************************************************************************/
void Wippersnapper::generate_feeds() {
// Check and set network iface UID
BC_DEBUG_PRINT(_boardId);
setUID();
for (int i = 0; i < sizeof(_uid); i++) {
BC_DEBUG_PRINTLN(_uid[i]);
}
// TODO: Simplify this
// Strip the bottom 3 bytes from the UID
uint8_t uid[3];
BC_DEBUG_PRINTLN("Bottom 3 UID");
for (int i = 5; i > 2; i--) {
uid[6-1-i] = _uid[i];
}
// free space
delete [] _uid;
BC_DEBUG_PRINTLN("uid2");
for (int i = 0; i < 3; i++) {
BC_DEBUG_PRINTLN(uid[i]);
}
// Assign board type, defined at compile-time
_boardId = BOARD_ID;
// Assign board type info
// TODO: This should move somewhere else...
_hw_vid = USB_VID;
_hw_pid = USB_PID;
// dynamically allocate memory for reserved topics
_topic_description = (char *)malloc(sizeof(char) * strlen(_username) + strlen(TOPIC_DESCRIPTION) + 1);
// TODO: These should be built using the UID
_topic_description_status = (char *)malloc(sizeof(char) * strlen(_username) + strlen(TOPIC_SIGNALS) + strlen("/devices/") + strlen("/status") + 1);
_topic_signals_in = (char *)malloc(sizeof(char) * strlen(_deviceId) + strlen(TOPIC_SIGNALS) + strlen("/devices/in") + 1);
_topic_signals_out = (char *)malloc(sizeof(char) * strlen(_deviceId) + strlen(TOPIC_SIGNALS) + strlen("/devices/out") + 1);
// build description topic
if (_topic_description) {
strcpy(_topic_description, _username);
strcat(_topic_description, TOPIC_DESCRIPTION);
}
// build description status topic
if (_topic_description_status) {
strcpy(_topic_description_status, "devices/");
strcat(_topic_description_status, _deviceId);
strcat(_topic_description_status, TOPIC_DESCRIPTION);
strcat(_topic_description_status, "/status");
}
// build signals incoming topic
if (_topic_signals_in) {
strcpy(_topic_signals_in, "devices/");
strcat(_topic_signals_in, _deviceId);
strcat(_topic_signals_in, TOPIC_SIGNALS);
strcat(_topic_signals_in, "in");
}
// build signals outgoing topic
if (_topic_signals_out) {
strcpy(_topic_signals_out, "devices/");
strcat(_topic_signals_out, _deviceId);
strcat(_topic_signals_out, TOPIC_SIGNALS);
strcat(_topic_signals_out, "out");
}
}
/**************************************************************************/
/*!
@brief Connects to Adafruit IO+ Wippersnapper broker.
*/
/**************************************************************************/
void Wippersnapper::connect() {
BC_DEBUG_PRINTLN("::connect()");
_status = BC_IDLE;
_boardStatus = BC_BOARD_DEF_IDLE;
BC_DEBUG_PRINTLN("Generating WS Feeds...");
generate_feeds();
// TODO: Print out feeds it generated
// Subscription to listen to commands from the server
_topic_signals_out_sub = new Adafruit_MQTT_Subscribe(_mqtt, _topic_signals_out);
_topic_signals_out_sub->setCallback(cbSignalTopic);
//_mqtt->subscribe(_topic_signals_out_sub);
// Publish to outgoing commands channel, server listens to this sub-topic
_topic_signals_in_pub = new Adafruit_MQTT_Publish(_mqtt, _topic_signals_in);
// Create a subscription to the description status response topic
_topic_description_sub = new Adafruit_MQTT_Subscribe(_mqtt, _topic_description_status);
// set callback
_topic_description_sub->setCallback(cbDescriptionStatus);
// subscribe
//_mqtt->subscribe(_topic_description_sub);
BC_DEBUG_PRINT("Connecting to Wippersnapper.");
// Connect network interface
_connect();
// Wait for connection to broker
while (status() < BC_CONNECTED) {
BC_DEBUG_PRINT(".");
delay(500);
}
BC_DEBUG_PRINTLN("Connected!");
// Send hardware description to broker
if (!sendGetHardwareDescription()){
// TODO: get error types back from function instead of bool, verify against resp.
BC_DEBUG_PRINTLN("Hardware description process failed!");
}
}
/**************************************************************************/
/*!
@brief Disconnects from Adafruit IO+ Wippersnapper.
*/
/**************************************************************************/
void Wippersnapper::disconnect() {
_disconnect();
}
/**************************************************************************/
/*!
@brief Checks and handles network interface connection.
*/
/**************************************************************************/
ws_status_t Wippersnapper::checkNetworkConnection(uint32_t timeStart) {
if (status() < BC_NET_CONNECTED) {
BC_DEBUG_PRINTLN("connection failed, reconnecting...");
unsigned long startRetry = millis();
while (status() < BC_CONNECTED) { // return an error on timeout
if (millis() - startRetry > 5000) {
return status();
}
delay(500);
}
if (!sendGetHardwareDescription()){
// TODO: get error types back from function instead of bool, verify against resp.
return status();
}
}
return status();
}
/**************************************************************************/
/*!
@brief Checks and handles connection to MQTT broker.
*/
/**************************************************************************/
ws_status_t Wippersnapper::checkMQTTConnection(uint32_t timeStart) {
while(mqttStatus() != BC_CONNECTED && millis() - timeStart < 60000) {
}
if (mqttStatus() != BC_CONNECTED) {
return status();
}
return status();
}
/**************************************************************************/
/*!
@brief Pings MQTT broker to keep connection alive.
*/
/**************************************************************************/
void Wippersnapper::ping() {
if (millis() > (_prv_ping + 60000)) {
_mqtt->ping();
_prv_ping = millis();
}
}
/**************************************************************************/
/*!
@brief Processes incoming commands and handles network connection.
*/
/**************************************************************************/
ws_status_t Wippersnapper::run() {
uint32_t timeStart = millis();
// increment software timer
t_timer.tick();
// Check network connection
checkNetworkConnection(timeStart); // TODO: handle this better
// Check and handle MQTT connection
checkMQTTConnection(timeStart); // TODO: handle this better
// Ping broker if keepalive elapsed
ping();
// Process all incoming packets from Wippersnapper MQTT Broker
_mqtt->processPackets(500);
// Handle incoming signal message
int n; // TODO: decl. in .h instead
// TODO: Sizeof may not work, possibly use bufSize instead
n = memcmp(_buffer, _buffer_state, sizeof(_buffer));
if (! n == 0) {
BC_DEBUG_PRINTLN("New data in message buffer");
// Decode signal message
if (! decodeSignalMessage()) {
return status();
}
if (! executeSignalMessageEvent()) {
BC_DEBUG_PRINTLN("Err: Event failed to execute.");
}
// update _buffer_state with contents of new message
// TODO: Sizeof may not work, possibly use bufSize instead
memcpy(_buffer_state, _buffer, sizeof(_buffer));
}
// Send updated pin value to broker
if ( ws_pinInfo.pinValue != ws_pinInfo.prvPinValue ) {
BC_DEBUG_PRINT("Pin Values: "); BC_DEBUG_PRINT(ws_pinInfo.pinValue);
BC_DEBUG_PRINT(" "); BC_DEBUG_PRINT(ws_pinInfo.prvPinValue);
sendPinEvent();
ws_pinInfo.prvPinValue = ws_pinInfo.pinValue;
}
return status();
}
/**************************************************************************/
/*!
@brief Sends board description message to Wippersnapper
*/
/**************************************************************************/
bool Wippersnapper::sendBoardDescription() {
BC_DEBUG_PRINT("Publishing board description...");
uint8_t buffer[128]; // message stored in this buffer
size_t message_length;
bool status;
// initialize message definition
description_v1_CreateDescriptionRequest message = description_v1_CreateDescriptionRequest_init_zero;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// fill message fields
strcpy(message.machine_name, _deviceId);
message.usb_vid = USB_VID;
message.usb_pid = USB_PID;
// encode message
status = pb_encode(&stream, description_v1_CreateDescriptionRequest_fields, &message);
message_length = stream.bytes_written;
// verify message
if (!status) {
BC_DEBUG_PRINTLN("encoding description message failed!");
//printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return false;
}
// publish message
_mqtt->publish(_topic_description, buffer, message_length, 0);
BC_DEBUG_PRINTLN("Published!");
_boardStatus = BC_BOARD_DEF_SENT;
return true;
}
/***************************************************************************/
/*!
@brief Sends board description message and verifies broker's response
*/
/***************************************************************************/
bool Wippersnapper::sendGetHardwareDescription(){
// Send hardware characteristics to broker
if (!sendBoardDescription()) {
_boardStatus = BC_BOARD_DEF_SEND_FAILED;
BC_DEBUG_PRINTLN("Unable to send board description to broker");
return false;
}
// Verify broker responds OK
BC_DEBUG_PRINTLN("Verifying board definition response")
while (getBoardStatus() != BC_BOARD_DEF_OK) {
BC_DEBUG_PRINT(".");
// TODO: needs a retry+timeout loop here!!
_mqtt->processPackets(500); // run a processing loop
}
return true;
}
/**************************************************************************/
/*!
@brief Returns the network status.
@return Wippersnapper network status.
*/
/**************************************************************************/
ws_status_t Wippersnapper::status() {
ws_status_t net_status = networkStatus();
// if we aren't connected, return network status
if (net_status != BC_NET_CONNECTED) {
_status = net_status;
return _status;
}
// check mqtt status and return
_status = mqttStatus();
return _status;
}
/**************************************************************************/
/*!
@brief Returns the board definition status
@return Wippersnapper board definition status
*/
/**************************************************************************/
ws_board_status_t Wippersnapper::getBoardStatus() {
return _boardStatus;
}
/**************************************************************************/
/*!
@brief Checks connection status with Adafruit IO's MQTT broker.
@return True if connected, otherwise False.
*/
/**************************************************************************/
ws_status_t Wippersnapper::mqttStatus() {
// if the connection failed,
// return so we don't hammer IO
if (_status == BC_CONNECT_FAILED) {
BC_DEBUG_PRINT("mqttStatus() failed to connect");
BC_DEBUG_PRINTLN(_mqtt->connectErrorString(_status));
return _status;
}
if (_mqtt->connected())
return BC_CONNECTED;
// prevent fast reconnect attempts, except for the first time through
if (_last_mqtt_connect == 0 ||
millis() - _last_mqtt_connect > 60000) {
_last_mqtt_connect = millis();
switch (_mqtt->connect(_username, _key)) {
case 0:
return BC_CONNECTED;
case 1: // invalid mqtt protocol
case 2: // client id rejected
case 4: // malformed user/pass
case 5: // unauthorized
return BC_CONNECT_FAILED;
case 3: // mqtt service unavailable
case 6: // throttled
case 7: // banned -> all MQTT bans are temporary, so eventual retry is
// permitted
// delay to prevent fast reconnects and fast returns (backward
// compatibility)
delay(60000);
return BC_DISCONNECTED;
default:
return BC_DISCONNECTED;
}
}
return BC_DISCONNECTED;
}
/**************************************************************************/
/*!
@brief Provide status explanation strings.
@return A pointer to the status string, _status. _status is the BC status
value
*/
/**************************************************************************/
const __FlashStringHelper *Wippersnapper::statusText() {
switch (_status) {
// CONNECTING
case BC_IDLE:
return F("Idle. Waiting for connect to be called...");
case BC_NET_DISCONNECTED:
return F("Network disconnected.");
case BC_DISCONNECTED:
return F("Disconnected from Wippersnapper.");
// FAILURE
case BC_NET_CONNECT_FAILED:
return F("Network connection failed.");
case BC_CONNECT_FAILED:
return F("Wippersnapper connection failed.");
case BC_FINGERPRINT_INVALID:
return F("Wippersnapper SSL fingerprint verification failed.");
case BC_AUTH_FAILED:
return F("Wippersnapper authentication failed.");
// SUCCESS
case BC_NET_CONNECTED:
return F("Network connected.");
case BC_CONNECTED:
return F("Wippersnapper connected.");
case BC_CONNECTED_INSECURE:
return F("Wippersnapper connected. **THIS CONNECTION IS INSECURE** SSL/TLS "
"not supported for this platform.");
case BC_FINGERPRINT_UNSUPPORTED:
return F("Wippersnapper connected over SSL/TLS. Fingerprint verification "
"unsupported.");
case BC_FINGERPRINT_VALID:
return F("Wippersnapper connected over SSL/TLS. Fingerprint valid.");
default:
return F("Unknown status code");
}
}

193
src/Wippersnapper.h Normal file
View file

@ -0,0 +1,193 @@
/*!
* @file BlinkaConnect.h
*
* This is the documentation for Adafruit's BlinkaConnect wrapper for the
* Arduino platform. It is designed specifically to work with the
* Adafruit IO+ BlinkaConnect IoT platform.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* Written by Brent Rubell for Adafruit Industries.
*
* BSD license, all text here must be included in any redistribution.
*
*/
#ifndef WIPPERSNAPPER_H
#define WIPPERSNAPPER_H
// Nanopb
#include <nanopb/pb_common.h>
#include <nanopb/pb.h>
#include <nanopb/pb_encode.h>
#include <nanopb/pb_decode.h>
// Protocol buffer messages
#include <proto/description.pb.h> // description.proto
#include <proto/signal.pb.h> // signal.proto
// External libraries
#include "Adafruit_MQTT.h"
#include "Arduino.h"
#include <arduino-timer.h>
// Internal libraries
#include "Wippersnapper_Boards.h"
// Reserved Wippersnapper topics
#define TOPIC_DESCRIPTION "/info/" ///< Device description topic
#define TOPIC_SIGNALS "/signals/" ///< Device signals topic
#define BC_PRINTER Serial ///< Where debug messages will be printed
#define BC_DEBUG
// Define actual debug output functions when necessary.
#ifdef BC_DEBUG
#define BC_DEBUG_PRINT(...) \
{ BC_PRINTER.print(__VA_ARGS__); } ///< Prints debug output.
#define BC_DEBUG_PRINTLN(...) \
{ BC_PRINTER.println(__VA_ARGS__); } ///< Prints line from debug output.
#else
#define BC_DEBUG_PRINT(...) \
{} ///< Prints debug output
#define BC_DEBUG_PRINTLN(...) \
{} ///< Prints line from debug output.
#endif
// Adafruit IO Status States
typedef enum {
BC_IDLE = 0, // Waiting for connection establishement
BC_NET_DISCONNECTED = 1, // Network disconnected
BC_DISCONNECTED = 2, // Disconnected from Adafruit IO
BC_FINGERPRINT_UNKOWN = 3, // Unknown BC_SSL_FINGERPRINT
BC_NET_CONNECT_FAILED = 10, // Failed to connect to network
BC_CONNECT_FAILED = 11, // Failed to connect to Adafruit IO
BC_FINGERPRINT_INVALID = 12, // Unknown BC_SSL_FINGERPRINT
BC_AUTH_FAILED = 13, // Invalid Adafruit IO login credentials provided.
BC_SSID_INVALID =
14, // SSID is "" or otherwise invalid, connection not attempted
BC_NET_CONNECTED = 20, // Connected to Adafruit IO
BC_CONNECTED = 21, // Connected to network
BC_CONNECTED_INSECURE = 22, // Insecurely (non-SSL) connected to network
BC_FINGERPRINT_UNSUPPORTED = 23, // Unsupported BC_SSL_FINGERPRINT
BC_FINGERPRINT_VALID = 24, // Valid BC_SSL_FINGERPRINT
BC_BOARD_DESC_INVALID = 25 // Unable to send board description
} ws_status_t;
// Wippersnapper board definition status
typedef enum {
BC_BOARD_DEF_IDLE,
BC_BOARD_DEF_SEND_FAILED,
BC_BOARD_DEF_SENT,
BC_BOARD_DEF_OK,
BC_BOARD_DEF_INAVLID_VID,
BC_BOARD_DEF_INVALID_PID,
BC_BOARD_DEF_UNSPECIFIED
} ws_board_status_t;
class Wippersnapper {
public:
Wippersnapper(const char *aio_username, const char *aio_key);
virtual ~Wippersnapper();
void connect();
virtual void _connect() = 0;
void disconnect();
virtual void _disconnect() = 0;
void generate_feeds(); // Generate device-specific WS feeds
virtual void setUID() = 0;
const __FlashStringHelper *statusText();
virtual ws_status_t networkStatus() = 0;
ws_status_t status();
ws_status_t mqttStatus();
ws_board_status_t getBoardStatus();
bool sendBoardDescription();
bool sendGetHardwareDescription();
ws_status_t checkNetworkConnection(uint32_t timeStart);
ws_status_t checkMQTTConnection(uint32_t timeStart);
void ping();
ws_status_t run();
bool decodeSignalMessage();
bool executeSignalMessageEvent();
bool pinConfig();
bool pinEvent();
bool sendPinEvent();
static bool cbDigitalRead(char *pinName);
static void cbSignalTopic(char *data, uint16_t len);
const char *_username; /*!< Adafruit IO Username. */
const char *_key; /*!< Adafruit IO Key. */
static uint16_t bufSize;
static uint8_t _buffer[128]; /*!< Shared buffer to save callback payload */
uint8_t _buffer_state[128]; /*!< Holds previous contents of static _buffer */
// Protobuf structs
signal_v1_CreateSignalRequest signalMessage = signal_v1_CreateSignalRequest_init_zero;
// Protobuf helpers
bool encode_unionmessage(pb_ostream_t *stream, const pb_msgdesc_t *messagetype, void *message);
struct pinInfo {
char *pinName;
char *PinNameFull;
int pinValue;
int prvPinValue; // holds prv. pin state
};
static pinInfo ws_pinInfo;
static Timer<16, millis, char *> t_timer;
static char timerPin[3];
private:
void _init();
protected:
ws_status_t _status = BC_IDLE; /*!< Adafruit IO connection status */
uint32_t _last_mqtt_connect = 0; /*!< Previous time when client connected to
Adafruit IO, in milliseconds */
uint32_t _prv_ping = 0;
Adafruit_MQTT *_mqtt; /*!< Reference to Adafruit_MQTT, _mqtt. */
// PoC Server
//const char *_mqtt_broker = "2.tcp.ngrok.io"; /*!< MQTT Broker URL */
// uint16_t _mqtt_port = 18653; /*!< MQTT Broker URL */
// Staging Server
const char *_mqtt_broker = "io.adafruit.us"; /*!< MQTT Broker URL */
uint16_t _mqtt_port = 1883; /*!< MQTT Broker URL */
const char *_deviceId; /*!< Adafruit IO+ device identifier string */
const char *_boardId; /*!< Adafruit IO+ board string */
uint16_t _hw_vid; /*!< USB vendor identifer */
uint16_t _hw_pid; /*!< USB product identifier */
uint8_t _uid[6];
// MQTT topics
char *_topic_description; /*!< MQTT topic for the device description */
char *_topic_description_status; /*!< MQTT subtopic carrying the description status resp. from the broker */
char *_topic_signals_in; /*!< Device -> Server communication channel */
char *_topic_signals_out; /*!< Server -> Device communication channel */
Adafruit_MQTT_Subscribe *_topic_description_sub;
Adafruit_MQTT_Publish *_topic_signals_in_pub;
Adafruit_MQTT_Subscribe *_topic_signals_out_sub;
Adafruit_MQTT_Subscribe *_subscription;
static char _value[45]; /*!< Data to send back to Wippersnapper, max. IO data len */
static char _prv_value[45]; /*!< Data to send back to Wippersnapper, max. IO data len */
};
#endif // ADAFRUIT_WIPPERSNAPPER_H

View file

@ -0,0 +1,36 @@
/*!
* @file Wippersnapper_Boards.h
*
* This file determines board type at compile-time
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* Written by Brent Rubell for Adafruit Industries.
*
* BSD license, all text here must be included in any redistribution.
*
*/
#ifndef ADAFRUIT_WIPPERSNAPPER_BOARDS_H
#define ADAFRUIT_WIPPERSNAPPER_BOARDS_H
#if defined(ADAFRUIT_PYPORTAL)
#define USB_VID 0x239A
#define USB_PID 0x8036
#define BOARD_ID "adafruit_pyportal_m4"
#elif defined(ADAFRUIT_METRO_M4_AIRLIFT_LITE)
#define USB_VID 0x239A
#define USB_PID 0x8038
#define BOARD_ID "adafruit_metro_m4_airliftlite"
#elif defined(ADAFRUIT_METRO_M4_EXPRESS)
#define USB_VID 0x239A
#define USB_PID 0x8021
#define BOARD_ID "adafruit_metro_m4"
#else
#warning "Board not identified within Wippersnapper_Boards.h!"
#endif
#endif // ADAFRUIT_WIPPERSNAPPER_BOARDS_H

View file

@ -0,0 +1,28 @@
/*!
* @file Wippersnapper_Networking.h
*
* This file includes network interfaces at compile-time.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* Written by Brent Rubell for Adafruit Industries.
*
* BSD license, all text here must be included in any redistribution.
*
*/
#ifndef WIPPERSNAPPER_NETWORKING_H
#define WIPPERSNAPPER_NETWORKING_H
#if defined(ADAFRUIT_METRO_M4_AIRLIFT_LITE) || \
defined(ADAFRUIT_PYPORTAL) || defined(ADAFRUIT_METRO_M4_EXPRESS) || \
defined(USE_AIRLIFT)
#include "network_interfaces/Wippersnapper_AIRLIFT.h"
typedef Wippersnapper_AIRLIFT Wippersnapper_WiFi;
#else
#warning "Must define network interface in config.h!"
#endif
#endif // WIPPERSNAPPER_NETWORKING_H

View file

@ -0,0 +1,190 @@
/*!
* @file Wippersnapper_AIRLIFT.h
*
* This is a driver for using the Adafruit AirLift
* ESP32 Co-Processor with Wippersnapper.
*
* The ESP32 uses SPI to communicate. Three lines (CS, ACK, RST) are required
* to communicate with the ESP32.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* Written by Brent Rubell for Adafruit Industries.
*
* MIT license, all text here must be included in any redistribution.
*
*/
#ifndef WIPPERSNAPPER_AIRLIFT_H
#define WIPPERSNAPPER_AIRLIFT_H
#include "Wippersnapper.h"
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#include "Arduino.h"
#include "SPI.h"
#include "WiFiNINA.h"
#define NINAFWVER "1.0.0" /*!< min. nina-fw version compatible with this library. */
/****************************************************************************/
/*!
@brief Class for interacting with AirLift Co-Processors.
*/
/****************************************************************************/
class Wippersnapper_AIRLIFT : public Wippersnapper {
public:
/**************************************************************************/
/*!
@brief Initializes the Adafruit IO class for AirLift devices.
@param deviceId
The Adafruit IO+ device identifier, TODO
@param ssid
The WiFi network's SSID.
@param ssidPassword
The WiFi network's password.
@param ssPin
The ESP32's S.S. pin.
@param ackPin
The ESP32's ACK pin.
@param rstPin
The ESP32's RST pin.
@param gpio0Pin
The ESP32's gpio0 pin.
@param wifi
a SPIClass
*/
/**************************************************************************/
Wippersnapper_AIRLIFT(const char *aio_user, const char *aio_key, const char *ssid,
const char *ssidPassword, int ssPin, int ackPin, int rstPin,
int gpio0Pin, SPIClass *wifi): Wippersnapper(aio_user, aio_key) {
_wifi = wifi;
_ssPin = ssPin;
_ackPin = ackPin;
_rstPin = rstPin;
_gpio0Pin = gpio0Pin;
_ssid = ssid;
_pass = ssidPassword;
_mqtt_client = new WiFiClient;
_mqtt = new Adafruit_MQTT_Client(_mqtt_client, _mqtt_broker, _mqtt_port);
}
/**************************************************************************/
/*!
@brief Destructor for the Adafruit IO AirLift class.
*/
/**************************************************************************/
~Wippersnapper_AIRLIFT() {
if (_mqtt)
delete _mqtt;
}
/********************************************************/
/*!
@brief Checks the version of an ESP32 module against
NINAFWVER. Raises an error if the firmware needs to be
upgraded.
*/
/********************************************************/
void firmwareCheck() {
_fv = WiFi.firmwareVersion();
if (_fv < NINAFWVER) {
BC_DEBUG_PRINTLN("Please upgrade the firmware on the ESP module to the latest version.");
}
}
/********************************************************/
/*!
@brief Gets the AirLift interface's MAC Address.
*/
/********************************************************/
void setUID() {
WiFi.macAddress(mac);
memcpy(_uid, mac, sizeof(mac));
}
/********************************************************/
/*!
@brief Returns the network status of an ESP32 module.
@return ws_status_t
*/
/********************************************************/
ws_status_t networkStatus() {
switch (WiFi.status()) {
case WL_CONNECTED:
return BC_NET_CONNECTED;
case WL_CONNECT_FAILED:
return BC_NET_CONNECT_FAILED;
case WL_IDLE_STATUS:
return BC_IDLE;
default:
return BC_NET_DISCONNECTED;
}
}
/*******************************************************************/
/*!
@brief Returns the type of network connection used by Wippersnapper
@return AIRLIFT
*/
/*******************************************************************/
const char *connectionType() { return "AIRLIFT"; }
protected:
const char *_ssid;
const char *_pass;
String _fv = "0.0.0";
uint8_t mac[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
int _ssPin, _ackPin, _rstPin, _gpio0Pin = -1;
WiFiClient *_mqtt_client;
SPIClass *_wifi;
/**************************************************************************/
/*!
@brief Establishes a connection with the wireless network.
*/
/**************************************************************************/
void _connect() {
if (strlen(_ssid) == 0) {
_status = BC_SSID_INVALID;
} else {
// setup ESP32 pins
if (_ssPin != -1) {
WiFi.setPins(_ssPin, _ackPin, _rstPin, _gpio0Pin, _wifi);
}
// validate up-to-date nina-fw version
firmwareCheck();
// disconnect from possible previous connection
_disconnect();
// check for esp32 module
if (WiFi.status() == WL_NO_MODULE) {
BC_DEBUG_PRINT("No ESP32 module detected!");
return;
}
WiFi.begin(_ssid, _pass);
_status = BC_NET_DISCONNECTED;
}
}
/**************************************************************************/
/*!
@brief Disconnects from the wireless network.
*/
/**************************************************************************/
void _disconnect() {
WiFi.disconnect();
delay(500);
}
};
#endif //WIPPERSNAPPER_AIRLIFT_H