HowTo - Haxiot Gateway and Shield

Intro

Thought I would jot down my experience with the Haxiot Gateway and shield setup. Overall, the setup is very finicky and could benefit from a lot of improvement. It does work though, and if you follow these steps, you should be able to enjoy success with less pain.

Gotchas

Some gotchas to start off:

  • The Haxiot sketch example does not work.
  • You can’t read the nwkskey and appskey values after writing them - security feature.
  • The RN2903 is very slow, so it needs about 200ms of delay between each serial command, or you get errors.
  • With the free (non-commercial) account, you can only connect ABP mode.
  • RX1 is what is used for free accounts.
  • Use Ch0-7 on both gateway and shield.
  • Pay attention to packet format. If it doesn’t match, Cayenne will reject the data.
  • If your data is not an even set of hex characters, Haxiot adds a 0 in front.
  • When setting up your Gateway, you can change the Sample App name to myDevices to match the tutorial.
  • Note that to install the Gateway, you need a PC or other device that runs VirtualBox.
  • The Haxiot shield does not have a GPS, but it does have temperature and accelerometer data.

Links

Cayenne LoRa Payload Specification
Cayenne LoRa Tutorial
Haxiot Support Page
Haxiot Gateway Setup Guide

Downloads

Virtualbox Download
Virtualbox Extention Pack
Haxiot Gateway VM Image

HowTo

Gateway Setup

  1. Install the Haxiot Gateway per the Haxiot Gateway Setup Guide.
  2. Plug in the Gateway, connect to your computer, and start the VirtualBox image.
  3. Login to Loriot and register your Gateway MAC if you haven’t already per the guide.
  4. Select your Gateway, and verify the Status is Connected.
  5. Under Actions, Ping gateway and verify Gateway Pinged message is received.
  6. If you can’t Ping your gateway or it isn’t connected after a minute or so, try power cycling your gateway and resetting the VirtualBox VM.
  7. If you still don’t show Gateway connected, ensure you have a VirtualBox USB in your Device manager and that you don’t have outbound ports blocked. The VM needs to be able to access the cloud servers.
  8. Now go to Applications and select your App. Mine came up as Sample App, and I renamed it to say myDevices like the Cayenne LoRa Tutorial.
  9. Set your Data output to https with a post target of http://longrangeapi.mydevices.com/longrange/api/loriot/messages/add.
  10. You can also set it to Cayenne, but I’ve had intermittent success with this.

Shield Setup

  1. Connect shield to Arduino Uno or other compatible Arduino.
  2. Load the attached sketch below (note Wire and TimerOne libraries are required).
  3. From the Manage Devices button on your Application, Select the Device EUI.
  4. Under here you should be able to find the EUI, DevAddr, Appkey, NwkSKey, and AppSKey.
  5. If you don’t have these, generate them, and update your sketch.
  6. Load the sketch, and you should now see data in a minute or so in the Last Data box. Need to refresh browser.
  7. If you don’t, go back and check that you entered the keys properly in your sketch, or check out the serial monitor for errors.

Cayenne Dashboard

  1. For the dashboard, and Add a new device.
  2. Select LoRa (Beta), Loriot, Haxiot LoRaWAN Shield.
  3. Name it something nice.
  4. Load your Application ID from the Loriot page.
  5. For Loriot Token, click on Security Tokens under your Loriot app, and grab the Authentication token. If it doesn’t exist, generate it, and drop this into the Cayenne dialog.
  6. Note that this device does not move, but you can put in your address if you want the Dashboard to show your street map.
  7. Wait about a minute, and you should see accelerometer and temperature widgets along with a map.
  8. Note that the sketch is pretty dumb with the padding bytes function, so if you are receiving data on the Loriot dashboard but not seeing it in Cayenne, check out the serial monitor to see if you are not matching the payload expected.

That’s it. If you have problems, post it here so we can help get you going.

Cheers,

Craig

FYI - Sketch complements of Nik Kitson of Haxiot and tweaked a bit to make it groovy.

#include <SoftwareSerial.h>
#include <Wire.h> // for IIC communication
#include "TimerOne.h"

//************** Device parameters for registration *****************
////////////////////////////////////////////////////////////
char DEVICE_EUI[] = "your eui";
char DEVICE_ADDR[] = "your dev addr";  //4 bytes required
char APP_KEY[] = "your app key"; //16 bytes required
char NWK_SESSION_KEY[] = "your network session key"; //network session key
char APP_SESSION_KEY[] = "your app session key"; //network session key
////////////////////////////////////////////////////////////

//************** ACCELEROMETER THRESHOLDS*******************************
#define xThreshold 0.5
#define yThreshold 0.5
#define zThreshold 0.5

//**************** TEMPERATURE SAMPLE TIME *******************************
#define TEMP_SAMPLE_TIME 100   //sample time for temperature


//******************** Channel Status *******************
//set bit channel on = 1, channel off = 0
//bank 0 = channels 0-7, bank 1 = channels 8-15, bank 2 = channels 16-23, bank 3 = channels 24-31, bank 4 = channels 32-39,
//bank 5 = channels 40-47, bank 6 = channels 48-55, bank 7 = channels 56-63, bank 8 = channels 64-71,
//MSB = highest channel number in bank. Example Bank 2 = b11000000. Channels 23 and  22 are on

byte channelBank[] = {B11111111, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B11111111}; 


#define accAddress 0x1D    //right justified accelerometer address
#define tmpAddress 0x48     //right justified TMP112 address
#define I2C_WRITE Wire.write 
#define I2C_READ Wire.read

/******************** ACCELEROMETER Data ************/
// adresss of accelerometer
#define adress_acc    0X1D // MMA8653FC and MMA8652FC

// adress of registers for MMA8653FC
#define ctrl_reg1     0x2A
#define ctrl_reg2     0x2B
#define ctrl_reg3     0x2C
#define ctrl_reg4     0x2D
#define ctrl_reg5     0x2E
#define int_source    0x0C
#define status_       0x00
#define f_setup       0x09
#define out_x_msb     0x01
#define out_y_msb     0x03
#define out_z_msb     0x05
#define sysmod        0x0B
#define xyz_data_cfg  0x0E
#define ff_mt_cfg     0x15
#define ff_mt_src     0x16
#define ff_mt_ths     0x17
#define ff_mt_count   0x18

//************** set resolution of accelerometer *************************
//#define resolution   0.0038;        //resolution for 2g
//#define resolution   0.0078;        //resolution for 4g
#define resolution     0.0156;        //resolution for 8g

//************** set range of accelerometer *************************
//#define range         0x00  //2G full scale
//#define range         0x01  //4G full scale
#define range         0x02  //8G full scale

//************** configure accelerometer *************************
//#define accConfig      0x00    // Output data rate at 800Hz, no auto wake, no auto scale adjust, no fast read mode
//#define accConfig      0x21    // Output data rate at 200Hz, no auto wake, no auto scale adjust, no fast read mode
#define accConfig      0x41    // Output data rate at 50Hz, no auto wake, no auto scale adjust, no fast read mode
//#define accConfig      0x71    // Output data rate at 1.5Hz, no auto wake, no auto scale adjust, no fast read mode



//****************** sensor variables **************************
float axeXnow ;
float axeYnow ;
float axeZnow ;
float axeXprev;
float axeYprev;
float axeZprev;
float tempC;

int printTemp;
int printXaxis;
int printYaxis;
int printZaxis;
char charVal[4][5];
long tempCounter;

//*******************************************************************************************************
SoftwareSerial loraSerial(4, 5); // RX, TX   ** Set to 10, 11 for Mega2560 boards

void setup() {

delay(1000);                //startup delay - gives Lora module time to reset if cold start
Serial.begin(57600);        //terminal serial port
loraSerial.begin(57600);      
Wire.begin();                              // join i2c bus
ACC_INIT();                                //initialize accelerometer

getConfig();                           //get module information
setChannelStatus();                        //configure channels to be on or off

configureABP();                          //configure Access By Personalization (ABP)
//configureOTAA();                         //configure Over The Air Activation (OTAA)

Timer1.initialize(500000);         // initialize timer1, and set to 0.5 second period
Timer1.attachInterrupt(systemTimer);    //set ISR

I2C_READ_ACC(0x01);   //get initial readings
axeXprev = axeXnow;
axeYprev = axeYnow;
axeZprev = axeZnow;

tempCounter = 0;
}

void loop() {

  
  
  while (loraSerial.available()) {
    Serial.write(loraSerial.read());
  }

  if (tempCounter >= TEMP_SAMPLE_TIME)   //send data every x time
  {
     getTemperature();
     I2C_READ_ACC(0x01);
     sendAllData();
     tempCounter = 0;
  }
  delay(50);
  I2C_READ_ACC(0x01);
#if 0 
  if ((abs(axeXprev-axeXnow))>xThreshold || (abs(axeYprev-axeYnow))>yThreshold || (abs(axeZprev-axeZnow))>zThreshold)  //send data if any axis exceeds threshold
  {
     getTemperature();
     sendAllData();
     axeXprev = axeXnow;
     axeYprev = axeYnow;
     axeZprev = axeZnow;
  }
#endif
}


//*********************************************************************************************
//***************************** FUNCTIONS *****************************************************
//*********************************************************************************************

void sendAllData(void)
{
  Serial.println();
  Serial.println("****************************************");
  Serial.print("Temperature (deg C): ");Serial.println(tempC);
  Serial.print("X Axis Now: ");Serial.println(axeXnow);
  Serial.print("Y Axis Now: ");Serial.println(axeYnow);
  Serial.print("Z Axis Now: ");Serial.println(axeZnow);
  Serial.print("X Axis Previous: ");Serial.println(axeXprev);
  Serial.print("Y Axis Previous: ");Serial.println(axeYprev);
  Serial.print("Z Axis Previous: ");Serial.println(axeZprev);
  Serial.println();

  formatData();
  loraSerial.print("mac tx uncnf 10 ");loraSerial.print("0167");loraSerial.print(charVal[0]);
  loraSerial.print("0271");loraSerial.print(charVal[1]);loraSerial.print(charVal[2]);loraSerial.println(charVal[3]);
  
  Serial.print("mac tx uncnf 10 ");Serial.print("0167");Serial.print(charVal[0]);
  Serial.print("0471");Serial.print(charVal[1]);Serial.print(charVal[2]);Serial.println(charVal[3]);
  
  waitCommandResponse();

}

void systemTimer(void)
{
  tempCounter++;
  
}


//************************ format data for lora tx **********************************
//modified for Cayenne Lora
void formatData(void)
{
  int len;
   
  printTemp = int(tempC*10);
  String stringT = String(printTemp,HEX);
  len = stringT.length();
  stringT.toCharArray(charVal[0],5);
  leadingZero(0,len);
  
  printXaxis = int(axeXnow * 1000);
  String stringX = String(printXaxis,HEX);
  len = stringX.length();
  stringX.toCharArray(charVal[1],5);
  leadingZero(1,len);

  printYaxis = int(axeYnow * 1000);
  String stringY = String(printYaxis,HEX);
  len = stringY.length();
  stringY.toCharArray(charVal[2],5);
  leadingZero(2,len);

  printZaxis = int(axeZnow * 1000);
  String stringZ = String(printZaxis,HEX);
  len = stringZ.length();
  stringZ.toCharArray(charVal[3],5);
  leadingZero(3,len);

}
//********************* Add leading zeros to hex value ********************************
void leadingZero(byte dataID, byte lenStr)
{
  charVal[dataID][4]='\0';
  
  if (lenStr==3)
  {
    charVal[dataID][3]=charVal[dataID][2];
    charVal[dataID][2]=charVal[dataID][1];
    charVal[dataID][1]=charVal[dataID][0];
    charVal[dataID][0]='0';
  }
  else if (lenStr==2)
  {    
    charVal[dataID][3]=charVal[dataID][1];
    charVal[dataID][2]=charVal[dataID][0];
    charVal[dataID][1]='0';
    charVal[dataID][0]='0';
  }
  else if (lenStr==1)
  {
    charVal[dataID][3]=charVal[dataID][0];
    charVal[dataID][2]='0';
    charVal[dataID][1]='0';
    charVal[dataID][0]='0';
  }
}
//****************** Write to I2C Slave Registers *********************************************

void I2C_SEND(unsigned char REG_ADDRESS, unsigned  char DATA)  //SEND data to MMA7660
{
  
  Wire.beginTransmission(adress_acc);
  Wire.write(REG_ADDRESS);
  Wire.write(DATA);
  Wire.endTransmission();
}

//****************************** Read I2C Slave register *******************************************
void I2C_READ_REG(int ctrlreg_address) //READ number data from i2c slave ctrl-reg register and return the result in a vector
{
  unsigned char REG_ADDRESS;
  int i=0; 
  Wire.beginTransmission(adress_acc); //=ST + (Device Adress+W(0)) + wait for ACK
  Wire.write(ctrlreg_address);  // register to read
  Wire.endTransmission();
  Wire.requestFrom(adress_acc,1); // read a number of byte and store them in write received
}

//****************** Accelerometer Initialization ************************************
 void ACC_INIT()
{
    I2C_SEND(ctrl_reg1 ,0X00); // standby to be able to configure
    delay(10);
    I2C_SEND(f_setup ,B01100000); // set FIFO mode - circular mode 32 byte watermark
    delay(5);
    I2C_SEND(xyz_data_cfg ,range); // set full range
    delay(1);
    I2C_SEND(ctrl_reg1 ,accConfig); // configure accelerometer
    delay(1);
}
//************************** Read Acceleromter Registers ************************************

void I2C_READ_ACC(int ctrlreg_address) //READ number data from i2c slave ctrl-reg register and return the result in a vector
{
  byte REG_ADDRESS[7];
  int accel[4];
  int i=0; 
  Wire.beginTransmission(adress_acc); //=ST + (Device Adress+W(0)) + wait for ACK
  Wire.write(ctrlreg_address);  // store the register to read in the buffer of the wire library
  Wire.endTransmission(); // actually send the data on the bus -note: returns 0 if transmission OK-
  Wire.requestFrom(adress_acc,7); // read a number of byte and store them in wire.read (note: by nature, this is called an "auto-increment register adress")

  for(i=0; i<7; i++) // 7 because on datasheet p.19 if FREAD=0, on auto-increment, the adress is shifted
  // according to the datasheet, because it's shifted, outZlsb are in adress 0x00
  // so we start reading from 0x00, forget the 0x01 which is now "status" and make the adapation by ourselves
  //this gives:
  //0 = status
  //1= X_MSB
  //2= X_LSB
  //3= Y_MSB
  //4= Y_LSB
  //5= Z_MSB
  // 6= Z_LSB
  {
  REG_ADDRESS[i]=Wire.read(); //each time you read the write.read it gives you the next byte stored. The couter is reset on requestForm
}


// MMA8653FC gives the result in 10bits. 8bits are in _MSB, and 2 are in _LSB
// this part is used to concatenate both, and then put a sign on it (the most significant bit is giving the sign)
// the explanations are on p.14 of the 'application notes' given by freescale.
      for (i=1;i<7;i=i+2)
      {
      accel[0] = (REG_ADDRESS[i+1]|((int)REG_ADDRESS[i]<<8))>>6; // X
        if (accel[0]>0x01FF) {accel[1]=(((~accel[0])+1)-0xFC00);} // note: with signed int, this code is optional
        else {accel[1]=accel[0];} // note: with signed int, this code is optional
            switch(i){
            case 1: axeXnow=accel[1]*resolution;
                              break;
            case 3: axeYnow=accel[1]*resolution;
                              break;
             case 5: axeZnow=accel[1]*resolution;
                              break;
                }
       }

}

//******************** Temperature Sensor (TMP112) Reading *****************************
void getTemperature(void)
{
  long tempSum;
  int tempReadingIn[2];
  Wire.requestFrom(tmpAddress,2); 
  tempReadingIn[0] = Wire.read();
  tempReadingIn[1] = Wire.read();

  //it's a 12bit int, using two's compliment for negative
  tempSum = ((tempReadingIn[0] << 8) | tempReadingIn[1]) >> 4; 
  tempC = tempSum*0.0625;
}

//********************* configure ABP ************************************************
void configureABP(void)
{
  loraSerial.print("mac set deveui ");loraSerial.println(DEVICE_EUI);
  Serial.print("mac set deveui ");Serial.println(DEVICE_EUI);
  waitCommandResponse();
  
  //loraSerial.print("mac set appeui ");loraSerial.println(APP_EUI);
  //Serial.print("mac set appeui ");Serial.println(APP_EUI);
  //waitCommandResponse();
  
  loraSerial.print("mac set appkey ");loraSerial.println(APP_KEY);
  Serial.print("mac set appkey ");Serial.println(APP_KEY);
  waitCommandResponse();
  loraSerial.print("mac set devaddr ");loraSerial.println(DEVICE_ADDR);
  Serial.print("mac set devaddr ");Serial.println(DEVICE_ADDR);
  waitCommandResponse();
  loraSerial.print("mac set nwkskey ");loraSerial.println(NWK_SESSION_KEY);
  Serial.print("mac set nwkskey ");Serial.println(NWK_SESSION_KEY);
  waitCommandResponse();
  loraSerial.print("mac set appskey ");loraSerial.println(APP_SESSION_KEY);
  Serial.print("mac set appskey ");Serial.println(APP_SESSION_KEY);
  waitCommandResponse();
  loraSerial.println("mac save");
  Serial.println("mac save");
  waitCommandResponse();
  loraSerial.println("mac join abp");
  Serial.println("mac join abp");
  waitCommandResponse();
}

#if 0 //not supported on free account
//************************ configure OTAA ************************************
void configureOTAA(void)
{
  loraSerial.print("mac set deveui ");loraSerial.println(DEVICE_EUI);
  Serial.print("mac set deveui ");Serial.println(DEVICE_EUI);
  waitCommandResponse();
  loraSerial.print("mac set appeui ");loraSerial.println(APP_EUI);
  Serial.print("mac set appeui ");Serial.println(APP_EUI);
  waitCommandResponse();
  loraSerial.print("mac set appkey ");loraSerial.println(APP_KEY);
  Serial.print("mac set appkey ");Serial.println(APP_KEY);
  waitCommandResponse();
  loraSerial.println("mac save");
  Serial.println("mac save");
  waitCommandResponse();
  loraSerial.println("mac join otaa");
  Serial.println("mac join otaa");
   waitCommandResponse();
}
#endif

//********************* configure channels on or off ******************************
void setChannelStatus()
{
  byte channelNumber=0;
  for (int x=0; x<9; x++)
  {
    for (int b=0; b<8; b++)
    {
      if (bitRead(channelBank[x],b))
      {
        loraSerial.print("mac set ch status ");loraSerial.print(channelNumber);loraSerial.println(" on");
        Serial.print("mac set ch status ");Serial.print(channelNumber);Serial.println(" on");
      }
      else
      {
        loraSerial.print("mac set ch status ");loraSerial.print(channelNumber);loraSerial.println(" off");
        Serial.print("mac set ch status ");Serial.print(channelNumber);Serial.println(" off");
      }
    channelNumber++;
    waitCommandResponse();
    }
  }
  loraSerial.println("mac set adr off");
  Serial.println("mac set adr off");
  waitCommandResponse();
  loraSerial.println("mac set pwridx 5");
  Serial.println("Set Tx power");
  waitCommandResponse();
  //loraSerial.println("mac set dr 4");
  //Serial.println("Set Data Rate 4");
  //waitCommandResponse();
  loraSerial.println("mac save");
  delay(200);
}

//******************** wait for response from module *************************************
void waitCommandResponse(void)
{
  unsigned long currentTime,startTime;
  startTime = millis(); 
  while (loraSerial.available() == 0)
  {
    currentTime = millis();
    if (currentTime > (startTime + 3000))       //exit wait routine if nor response in 3 seconds
    {
      Serial.println("No Response from Lora Module");
      return;
    }
  }
 
  while (loraSerial.available()) 
    {
      Serial.write(loraSerial.read());
    }
}

//************************ get configuration information from module *********************
void getConfig(void)
{
  loraSerial.println("sys get ver");      //get module version
  Serial.print("Module Version: ");
  waitCommandResponse();
  Serial.println();
  
  loraSerial.println("sys get hweui");      //get module version
  Serial.print("EUI Node Address: ");
  waitCommandResponse();
  Serial.println();
}
2 Likes

Thanks for posting this Craig! Your hours of trouble shooting will save everyone lots of time. I wonder if this would be a good post for the library? What do you think?

Benny,

I originally considered it, but it really isn’t a super example of good coding practice. It’s something cobbled together to test hardware.

I think it would be good with a bit more error checking and create some better structure around the Cayenne payloads. Sometimes we are getting eroneous packets because the byte stuffing isn’t working all the time - for instance.

Thought I would post it anyway though as yeah, many hours went into figuring this out and Cayenne should be easy peasy.

Cheers,

Craig

@hcortes here is the post for using the Haxiot shield :slight_smile:

1 Like

I’ll try to give this a try in the next couple days. Definitely an easy to follow howto!