Skip to content

Instantly share code, notes, and snippets.

@SyncChannel
Created March 17, 2016 01:16
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save SyncChannel/8e10c30f136dd7c765f8 to your computer and use it in GitHub Desktop.
Save SyncChannel/8e10c30f136dd7c765f8 to your computer and use it in GitHub Desktop.
LoRa FeatherWing IOX Adafruit IO Gateway Example Program
/* LoRa FeatherWing IOX Adafruit IO Gateway Example Program
* By: Dan Watson | syncchannel.blogspot.com
* Date: 3-12-2016
* Version: 0.1 Initial Release
*
* Example Adafruit IO Gateway Program for the LoRa FeatherWing IOX for Adafruit Feather
* Tested with HUZZAH ESP8266
*
* This program configures the Feather as a LoRa receiver and Adafruit IO Wi-Fi Gateway.
* It is intended as a companion to the LoRA FeatherWing IOX Beacon Example Program.
* It can receive the beacon messages and publish them to Adafruit IO using MQTT.
*
* This program is adapted from the RFM95W_Nexus library created by Gerben den Hartog
* It has been adapted from his code to work specifically on Adafruit Feather with
* the LoRa FeatherWing. Credit is given to Gerben for is useful library and work!
* Check out his full github repo if you are interested in LoRaWAN.
*
* This program also uses the Adafruit MCP-23008 library, which can be downloaded in
* the Arduino Library Manager. See these pages for more info:
* https://github.com/adafruit/Adafruit-MCP23008-library
* https://www.adafruit.com/products/593
*
* The Adafruit MQTT and MQTT_Client libraries & example code are also used:
* https://learn.adafruit.com/adafruit-feather-huzzah-esp8266/overview
* https://learn.adafruit.com/adafruit-io/mqtt-api
*
*/
// SPI is used for the RFM module. I2C is used for the MCP-23008.
// Numerous includes are required for Adafruit MQTT library
#include <SPI.h>
#include <Wire.h>
#include <ESP8266WiFi.h>
#include "Adafruit_MCP23008.h"
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
// Adafruit IO and Wi-Fi Access Point info
#define WLAN_SSID "your ssid here"
#define WLAN_PASS "your password here"
#define AIO_SERVER "io.adafruit.com"
#define AIO_SERVERPORT 1883
#define AIO_USERNAME "your Adafruit IO user name here"
#define AIO_KEY "your Adafruit IO private key here"
/************ Global State (you don't need to change this!) ******************/
// Create an ESP8266 WiFiClient class to connect to the MQTT server.
WiFiClient client;
// Store the MQTT server, username, and password in flash memory.
// This is required for using the Adafruit MQTT library.
const char MQTT_SERVER[] PROGMEM = AIO_SERVER;
const char MQTT_USERNAME[] PROGMEM = AIO_USERNAME;
const char MQTT_PASSWORD[] PROGMEM = AIO_KEY;
// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, AIO_SERVERPORT, MQTT_USERNAME, MQTT_PASSWORD);
/****************************** Feeds ***************************************/
// *** These are just examples! Change these to your own feed names. ***
const char TEMP_FEED[] PROGMEM = AIO_USERNAME "/feeds/LoRaIOXtemp";
Adafruit_MQTT_Publish tempfeed = Adafruit_MQTT_Publish(&mqtt, TEMP_FEED);
const char BATT_FEED[] PROGMEM = AIO_USERNAME "/feeds/LoRaIOXbatt";
Adafruit_MQTT_Publish battfeed = Adafruit_MQTT_Publish(&mqtt, BATT_FEED);
const char TEMP_FEED2[] PROGMEM = AIO_USERNAME "/feeds/LoRaIOXtemp2";
Adafruit_MQTT_Publish tempfeed2 = Adafruit_MQTT_Publish(&mqtt, TEMP_FEED2);
const char BATT_FEED2[] PROGMEM = AIO_USERNAME "/feeds/LoRaIOXbatt2";
Adafruit_MQTT_Publish battfeed2 = Adafruit_MQTT_Publish(&mqtt, BATT_FEED2);
/****************************************************************************/
// Define GPIO pin descriptions
#define LED 0
// Define I/O expander connections to RFM module. These are hard wired.
#define DIO0 5
#define DIO1 6
#define DIO2 7
#define DIO3 3
#define DIO4 4
#define DIO5 0
#define CS 2
#define RFM_RESET 1
// Set this true to invert the LED outputs (for HUZZAH ESP8266) (optional)
#define LEDINVERT true
// Set frequency of RFM module
// Available frequencies depend on which RFM module you have
#define RFM_FREQ_MHZ 915000000
// Define the length of the user message payload in bytes
#define PACKAGE_LENGTH 10
// The Adafruit MCP-23008 library is very easy to use. To access a pin on the RFM module,
// use the standard digitalRead and digitalWrite commands with "iox." in front.
Adafruit_MCP23008 iox;
// Counter to help keep Adafruit IO connection alive
uint32_t connectCounter = 0;
void setup()
{
Serial.begin(9600);
Serial.println("LoRa FeatherWing IOX - Adafruit IO Gateway");
//Initialize SPI
SPI.begin();
SPI.beginTransaction(SPISettings(4000000,MSBFIRST,SPI_MODE0));
iox.begin(); // Default address
//Initialize I/O pins on MCP-23008
iox.pinMode(DIO0,INPUT);
iox.pinMode(DIO5,INPUT);
iox.pinMode(CS,OUTPUT);
// Initialize normal GPIO on the Feather
pinMode(LED,OUTPUT);
// Pull chip select high for now
iox.digitalWrite(CS,HIGH);
// Minimum of 100ms recommended to allow RFM module to start up
delay(200);
//Initialize the RFM module
RFM_Init();
Serial.println("Initialized RFM module.");
// Connect to WiFi access point.
Serial.println(); Serial.println();
Serial.print("Connecting to ");
Serial.println(WLAN_SSID);
WiFi.begin(WLAN_SSID, WLAN_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected");
Serial.println("IP address: "); Serial.println(WiFi.localIP());
Serial.println("");
MQTT_connect();
// Flash LED on Feather to notify user that setup is complete
for (int i = 0; i <= 3; i++)
{
digitalWrite(LED, HIGH^LEDINVERT);
delay(100);
digitalWrite(LED, LOW^LEDINVERT);
delay(200);
}
}
void loop()
{
//Check if the RFM has received a package
if(iox.digitalRead(DIO0) == HIGH)
{
digitalWrite(LED, HIGH^LEDINVERT);
//Get package from RFM
unsigned char RFM_Package[PACKAGE_LENGTH];
RFM_ReadPackage(RFM_Package);
if (RFM_Package[0] == 0xD7 && RFM_Package[1] == 0xD7) // Valid message
{
//Send report over serial
Serial.print("Got message from Node 0x");
Serial.println(RFM_Package[2], HEX);
Serial.print("Raw Message: [ ");
for(int i = 0; i < PACKAGE_LENGTH; i++)
{
if (RFM_Package[i] < 0x10)
Serial.print("0");
Serial.print(RFM_Package[i], HEX);
Serial.print(" ");
}
Serial.println("]");
if (RFM_Package[3] == 0x01) // This is a temperature report
{
float temp = (RFM_Package[4] << 8) | RFM_Package[5];
temp = (temp*3.22 - 400) / 19.5; // Temp in degrees C
float batt = (RFM_Package[6] << 8) | RFM_Package[7];
batt = batt*6.44; // *3.22 to convert to mV, *2 due to divider
batt = batt/1000; // Convert to volts
uint16_t messageCounter = (RFM_Package[8] << 8) | RFM_Package[9];
Serial.print("Temp: ");
Serial.print(temp);
Serial.print("*C | Batt: ");
Serial.print(batt);
Serial.print("V | Msg Count: ");
Serial.println(messageCounter);
bool publishResults = true;
if (RFM_Package[2] == 0x20) // We check Node ID to determine which feeds to send the data to
{
char pubTemp[7];
dtostrf(temp, 6, 2, pubTemp); // Don't send raw doubles. They will not display correctly
if (!tempfeed.publish(pubTemp)) // when graphed in Adafruit IO (currently)
publishResults = false;
delay(10);
char pubBatt[6];
dtostrf(batt, 4, 2, pubBatt);
if (!battfeed.publish(pubBatt))
publishResults = false;
delay(10);
} else if (RFM_Package[2] == 0x21) { // Different node ID, upload to different feeds
char pubTemp[7];
dtostrf(temp, 6, 2, pubTemp);
if (!tempfeed2.publish(pubTemp))
publishResults = false;
delay(10);
char pubBatt[6];
dtostrf(batt, 4, 2, pubBatt);
if (!battfeed2.publish(pubBatt))
publishResults = false;
delay(10);
}
if (publishResults)
{
Serial.println("Data published to Adafruit IO.");
} else {
Serial.println("FAILED to publish data to Adafruit IO!");
}
}
} else { // You get here if the message header was not 0xD7 0xD7. Likely due to corrupted package
Serial.println("Invalid Message Received!");
}
Serial.println("");
delay(50);
digitalWrite(LED, LOW^LEDINVERT);
}
else if (millis() > connectCounter) // Check connection status every 1 second, re-connect as necessary
{
connectCounter = millis() + 1000;
MQTT_connect();
}
}
/*
* Function to initialize the RFM module.
* It is highly recommended that you read the datasheet for the module, there are lots of
* settings you can adjust to improve the wireless link parameters for your specific usage
* http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf
*/
void RFM_Init()
{
//Set RFM module in sleep mode to change to LoRa mode
RFM_Write(0x01,0x00);
//Set LoRa mode
RFM_Write(0x01,0x80);
//Set RFM module in standby mode to change more settings
RFM_Write(0x01,0x81);
while(iox.digitalRead(DIO5) == LOW)
{
//Wait for mode ready
}
// Set carrier frequency
// RFM_FREQ_MHZ / 61.035 Hz = 3 byte value to load
unsigned long freqHolder = RFM_FREQ_MHZ / 61.035; // round down
uint8_t freqLSB = freqHolder;
uint8_t freq2B = (freqHolder >> 8);
uint8_t freqMSB = (freqHolder >> 16);
RFM_Write(0x06,freqMSB);
RFM_Write(0x07,freq2B);
RFM_Write(0x08,freqLSB);
//Set maximum transmit power settings
RFM_Write(0x09,0xFF);
//Set bandwith 250 kHz, Coding rate = 4/8, Implicit header mode
RFM_Write(0x1D,0x89);
//Spreading factor 11, PayloadCRC on
RFM_Write(0x1E,0xB4);
//*NOTE: If you decide to change to Spreading Factor 6, you also need to set the following
//*two registers by uncommenting these lines
//RFM_Write(0x31,0xC5);
//RFM_Write(0x37,0x0C);
//Preamble length 0x0018 + 4 = 28
RFM_Write(0x20,0x00);
RFM_Write(0x21,0x18);
//Payload length
RFM_Write(0x22,PACKAGE_LENGTH);
//Set RFM in continues receive
RFM_Write(0x01,0x85);
while(iox.digitalRead(DIO5) == LOW)
{
//Wait for mode ready
}
}
/* This function is used to read a value from a specific register of the RFM module
* Arguments: RFM_Adress Adress of the register to read
* Return: The value of the register is returned
*/
unsigned char RFM_Read(unsigned char RFM_Address)
{
unsigned char RFM_Data;
//Set CS pin low to start SPI communication
iox.digitalWrite(CS,LOW);
//Send Address
SPI.transfer(RFM_Address);
//Send 0x00 to receive the answer from the RFM
RFM_Data = SPI.transfer(0x00);
//Set CS high to end communication
iox.digitalWrite(CS,HIGH);
//Return received data
return RFM_Data;
}
/* This function is used to write a value to a specific register of the RFM module
* Arguments: RFM_Adress Adress of the register to be written
* RFM_Data Data that will be written
*/
void RFM_Write(unsigned char RFM_Address, unsigned char RFM_Data)
{
//Set CS pin low to start communication
iox.digitalWrite(CS,LOW);
//Send Addres with MSB 1 to make it a write command
SPI.transfer(RFM_Address | 0x80);
//Send Data
SPI.transfer(RFM_Data);
//Set CS pin high to end communication
iox.digitalWrite(CS,HIGH);
}
/* This function is used to send a message payload
* Arguments: *RFM_Package Pointer to the array with the data to send
*/
void RFM_SendPackage(unsigned char *RFM_Package)
{
//Switch RFM to standby
RFM_Write(0x01,0x81);
while(iox.digitalRead(DIO5) == LOW)
{
//Wait for mode ready
}
//Set SPI pointer to Tx base address
RFM_Write(0x0D,0x80);
//Switch DIO to TxDone
RFM_Write(0x40,0x40);
//Write payload in to the FIFO
for(int i = 0; i < PACKAGE_LENGTH; i++)
{
RFM_Write(0x00,*RFM_Package);
RFM_Package++;
}
//Switch RFM to TX
RFM_Write(0x01,0x83);
while(iox.digitalRead(DIO0) == LOW)
{
//Wait for Tx Done
}
//Clear interrupt
RFM_Write(0x12,0x08);
//Set DIO0 to RxDone
RFM_Write(0x40,0x00);
//Set RFM in continues receive
RFM_Write(0x01,0x85);
while(iox.digitalRead(DIO5) == LOW)
{
//Wait for mode ready
}
}
/* This function is used to receive a message/payload from the RFM module
* Arguments: *RFM_Package Pointer to the array with the data to send
*/
void RFM_ReadPackage(unsigned char *RFM_Package)
{
unsigned char RFM_Interrupt;
unsigned char RFM_PackageLocation;
//Get interrupt register
RFM_Interrupt = RFM_Read(0x12);
//Switch RFM to Standby
RFM_Write(0x01,0x81);
while(iox.digitalRead(DIO5) == LOW)
{
//Wait for mode ready
}
//Clear interrupt register
RFM_Write(0x12,0x60);
//Get package location
RFM_PackageLocation = RFM_Read(0x10);
//Set SPI pointer to Packagelocation
RFM_Write(0x0D,RFM_PackageLocation);
//Get message
for(int i = 0; i < PACKAGE_LENGTH; i++)
{
*RFM_Package = RFM_Read(0x00);
RFM_Package++;
}
//Switch RFM to receive
RFM_Write(0x01,0x85);
while(iox.digitalRead(DIO5) == LOW)
{
//Wait for mode ready
}
}
// Function to connect and reconnect as necessary to the MQTT server.
// Should be called in the loop function and it will take care if connecting.
void MQTT_connect()
{
int8_t ret;
// Stop if already connected.
if (mqtt.connected()) {
return;
}
Serial.print("Connecting to MQTT... ");
while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
Serial.println(mqtt.connectErrorString(ret));
Serial.println("Retrying MQTT connection in 5 seconds...");
mqtt.disconnect();
delay(5000); // wait 5 seconds
}
Serial.println("MQTT Connected!");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment