Particle Xenon Without Mesh

  • March 2020: Device OS release of 1.6 will be the final release to include Particle Mesh support
  • Dec 31, 2020: ability to set up new mesh networks, or add devices to existing mesh networks will be removed from the mobile app and cloud APIs

 

Xenon Non-Mesh Use Options

 

Flash Xenon via USB

The objective is to configure your Xenon to work as a standalone device that is NOT connected to, or dependent upon a Mesh network.  

  • Install the Particle CLI Installer for Windows
  • Run 'particle setup' in the Workbench CLI command line to start using Particle CLI
  • Put the Xenon into DFU mode
  • Make sure you select the COM port for your device in Workbench.
  • Issue the command 'particle serial inspect' while in listening mode. All report results should be 'PASS'.
  • Issue the command 'particle serial identify' while in listening mode.
  • While watching the project working folder, execute a Cloud Compile and then get the name of the '*.bin' file generated. Eg 'xenon_firmware-###.bin'. (filename also shown in the Workbench project window)
  • Put the Xenon in DFU mode and then in CLI execute the command 'particle flash --usb xenon_firmware-###.bin'.   If successful, Workbench will post "Flash success!" and the Xenon will reboot.  
  • If the flash was successful, the LED in the center of the Xenon will be pulsing white, and the D7 LED near the USB connection will be blinking blue.
  • It not necessary to mark 'setup done' on a standalone Xenon.
  • If you go into blinking blue at boot on a standalone Xenon, you may need to update the bootloader.   The command 'particle update' should do this for you.   Alternatively, you can manually update the bootloader with a specific version of the OS from the release site by issuing the command: 'particle flash --serial xenon-bootloader@1.4.1-rc.1.bin'.  

When Xenon is breathing white, it indicates the WiFi module is off.

If your Xenon is blinking dark blue, then you need to run the following code once to remove any registration to the Mesh network (good idea to do anyway):


#include "Particle.h"
#include "dct.h"

SYSTEM_MODE(SEMI_AUTOMATIC);

void setup() {
    const uint8_t val = 0x01;
    dct_write_app_data(&val, DCT_SETUP_DONE_OFFSET, 1);
    pinMode(D7, OUTPUT);
    digitalWrite(D7, HIGH);
}

void loop() {
}

https://community.particle.io/t/xenon-as-standalone-unit/48336/6

 

Useful Related Links

Particle Xenon setup instructions

Xenon Standalone

Using a Particle Xenon Offline

Particle YouTube video

 

Workbench (Visual Studio Code)

WorkBench COM Port

 

 

Solutions

Put your Xenon into Listening Mode

Press and hold the mode button until it blinks dark blue.  

 

Xenon + LoRa

Send a ISO 8601 date/time stamp plus floating point value (sensor data) from Xenon to Xenon/Argon/Boron via LoRa.   In addition to two Xenon/Argon/Boron devices, you will need the following LoRa hardware:

AF LoRa Radio FeatherWing - RFM95W 900 MHz

For the best range, add one of the following antenna options:

Labeled photo comparison of RP-SMA vs SMA connectors

 

The LoRa library will not work properly on a Xenon with Particle Device OS versions after 1.4.4 on a Xenon (but it works with Argon).  

 

Recommended LoRa FeatherWing GPIO Assigments
LoRa
FeatherWing
Argon
Boron
Xenon
IRQ (INT) D6 "A"
CS D5 "B"
RST D4 "C"

The above "IRQ", "CS", and "RST" references relate to the GPIO assignments when using the RadioHead library.  

 

Two code sketches are shown below, one for a Xenon that will be the "LoRa sender", and another for a Xenon/Argon/Boron "LoRa receiver" (they work reversed as well).   Two libraries will be needed:

 

LoRa_sender.ino

/*
 * Project:     LoRa_sender.ino
 * Description: Xenon + LoRa sender
 * Author:      Mark Kiehl / Mechatronic Solutions LLC
 * Date:        March 2020
 * 
 * Adafruit LoRa RFM95W 900 MHz FeatherWing + Xenon/Argon/Boron
 * 
 * Sends a message via LoRa to another Xenon/Argon/Boron.
 * The message consists of a ISO 8601 date/time stamp and a floating
 * point value (simulated sensor value). 
 * For this demo, the original message is acknowledged by sending
 * it back to the sender. 
 * The LED on D7 will turn on when a message is send, and then
 * off after the reply is received from the LoRa receiver.
 * The LED will blink S-O-S if an error occurs initializing the
 * LoRa drivers. 
 * The receiver should run LoRa_receiver.ino
 * LoRa messages are NOT encrypted.
 * 
 * WARNING:  Works with Particle device OS v1.4.4 or older (not 1.5.0). 
 *           Device OS 1.5.0 causes Xenon to lockup after 2nd execution of rf95.send().
 */

#include "Particle.h"
#if (PLATFORM_ID == PLATFORM_XENON)
SYSTEM_MODE(MANUAL);
#endif

//////////////////////////////////////////////////////////////////////////////
// 10000 ms = 10 sec = 0.1 Hz 
// 1000 ms = 1 sec = 1 Hz
// 100 ms = 0.1 sec = 10 Hz
// 10 ms = 0.01 sec = 100 Hz
unsigned long timerInterval = random(1000,5000);  
unsigned long timerLast = 0;  // timer
//////////////////////////////////////////////////////////////////////////////
//  LoRa 900 MHz Radio
//  LoRa 900 MHz Radio
//  RadioHead library for LoRa Radio
//  Add library "RF9X-RK" and "CryptoLW-RK" to "lib" folder.
//  https://github.com/rickkas7/RF9X-RK
//  https://github.com/rickkas7/CryptoLW-RK
#include <RH_RF95.h>
// Argon/Boron/Xenon: A=D6, B=D5, C=D4, D=D3, E=D2, F=D14
#define RFM95_INT D6  // D6="A"
#define RFM95_CS  D5  // D5="B"
#define RFM95_RST D4  // D4="C"
// Define frequency (set later)
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
//////////////////////////////////////////////////////////////////////////////
byte cData[31]; // 16+1+13+1=31 "20200216T150644Z;+6.534755E+06"
unsigned long successCount = 0;
boolean bStopOnError = false;
//////////////////////////////////////////////////////////////////////////////


void setup() {
  pinMode(D7, OUTPUT);

  Serial.begin();
  waitFor(Serial.isConnected, 30000);
  delay(1000);
  Serial.printlnf("System version: %s", (const char*)System.version());
  if (PLATFORM_ID == PLATFORM_XENON)
    Serial.printlnf("Free RAM %d", System.freeMemory());

  //////////////////////////////////////////////////////////////////////////////
  // LoRa
  pinMode(RFM95_RST, OUTPUT);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  // manual reset  (pull low for 100 us)
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);
  while (!rf95.init()) {
    while (1) blinkERR(D7);
  }
  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
  // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
  if (!rf95.setFrequency(RF95_FREQ)) {
    while (1) blinkERR(D7);
  }
  // The default transmitter power is 13dBm, using PA_BOOST.
  // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then 
  // you can set transmitter powers from 5 to 23 dBm:
  rf95.setTxPower(23, false);
  //////////////////////////////////////////////////////////////////////////////

  Serial.println("Setup complete");
} // setup()


void loop() {

  // Send a message at a random interval between 500 and 5000 ms
  //if (timerLast > millis())  timerLast = millis();
  if ((millis() - timerLast) > timerInterval) {
    digitalWrite(D7, HIGH);

    // Create a message to send with random content.
    // "20200216T150644Z;++6.534755E+06"
    BuildSimulatedSerialSensorData(cData, sizeof(cData));    
    
    Serial.printlnf("rf95.send().. '%s'", cData);
    // Send a message to rf95_server
    rf95.send(cData, sizeof(cData));  

    delay(10);
    // Wait up to 0.1 seconds for the transmitter to complete transmitting
    rf95.waitPacketSent(100);
    
    // Now wait for a reply
    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
    uint8_t len = sizeof(buf);
    if (rf95.waitAvailableTimeout(1000)) { 
      // Should be a reply message for us now   
      if (rf95.recv(buf, &len)) {
        digitalWrite(D7, LOW); 
       // Compare the received data buff to the data sent arrToEncrypt
        int matches = 0;
        for(unsigned int i=0; i<sizeof(cData); i++){
          if(buf[i]==cData[i]){     
            matches++;  
          } 
        }
        if (matches == sizeof(cData)) {
          successCount++;
          Serial.printlnf("successCount = %d", successCount);
          digitalWrite(D7, LOW); 
        } else {
          // The receiver didn't send back the same message content as
          // what was sent by this sender. 
          if (bStopOnError == true) 
            while (1) blinkERR(D7);
        }
      } else {
        // message confirmation from receiver failed!
      }
    } else {
      // reply timeout
    }       

    timerInterval = random(1000,5000);  
    timerLast = millis();
  } // timer
} // loop()


void blinkERR(byte ledPIN){
  // S-O-S
  const int S = 150, O = 300;
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(O);
    digitalWrite(ledPIN, LOW);
    delay(O);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
} // blinkERR()

//////////////////////////////////////////////////////////////////////////////


void BuildSimulatedSerialSensorData(byte *arr, int len) {
  //  Populates arr with a simulated serial data string with
  //  a ISO 8601 date time stamp and a sensor value (float).
  //  "20200216T150644Z;+6.534755E+06"

  for (int i=0; i<len; i++) {
    arr[i] = 0x20;  // space character
  }
  
  // Get a random ISO8601 date time string
  byte dateTimeIso8601[16];
  BuildIso8601DateTimeRandom(dateTimeIso8601, sizeof(dateTimeIso8601)); 
  int iLastPos = 0;
  for (unsigned int i=0; i<sizeof(dateTimeIso8601); i++) {
    arr[i] = char(dateTimeIso8601[i]);
    iLastPos++;
  }
  iLastPos--;
  //Serial.print("iLastPos = "); Serial.println(iLastPos);
  
  // Add the ";"
  arr[iLastPos+1] = ';';
  iLastPos++;
  //Serial.print("iLastPos = "); Serial.println(iLastPos);

  // Get a random floating point value as a formatted string
  // to scientific notation.
  byte cDouble[14];
  BuildDoubleValRandom(cDouble, sizeof(cDouble));
  int f = iLastPos + 1;
  for (unsigned int i=0; i<sizeof(cDouble); i++) {
    arr[f] = char(cDouble[i]);
    f++;
    iLastPos++;
  }
  arr[iLastPos] = 0x00; // null character
} // BuildSimulatedSerialSensorData()

void BuildDoubleValRandom(byte *arr, unsigned int len) {
  // Populates arr with a string representation of a 
  // random floating point number formatted in scientific
  // notation with at least 7 significant digits.
  // +6.534755E+06"
  // 
  for (unsigned int i=0; i<len; i++) {
    arr[i] = 0x20;  // space character
  }
  long myLong = random(100000000, 999999999);
  long divisor;
  int expnt = random(2,6);
  switch (expnt) {
    case 2:
      divisor = 100;
      break;
    case 3:
      divisor = 1000;
      break;
    case 4:
      divisor = 10000;
      break;
    case 5:
      divisor = 100000;
    default:
      divisor = 10;
  }
  float f = (float)myLong / divisor;
  int sign = random(10);
  if (sign <= 5) {
    f = f * (-1.0);
  }
  //Serial.print("f = "); Serial.println(f, 6);
  char cDbl[len];
  sprintf(cDbl, "%+E", f);  
  //Serial.print("cDbl = '"); Serial.print(cDbl); Serial.println("'");
  //char cDbl[sigFig+8];
  //dtostre(f, cDbl, sigFig, 2);   // 2 = show + sign
  // cDbl = '-1.0002e+05'
  //Serial.print("cDbl = '"); Serial.print(cDbl); Serial.println("'");
  for (unsigned int i=0; i<strlen(cDbl); i++) {
    arr[i] = cDbl[i];
  } 
  arr[len-1] = 0x00;  // null character
} // BuildDoubleValRandom()


void BuildIso8601DateTimeRandom(byte *arr, unsigned int len) {
  // Updates arr with characters representing a random
  // ISO8601 date time formatted as: 20200216T150644Z
  for (unsigned int i=0; i<len; i++) {
    arr[i] = 0x20;  // space character
  }
  unsigned int iLastPos = 0;
  unsigned long iYearMonthDay = 20200000;
  byte iMonth = random(1, 13);
  iYearMonthDay += iMonth * 100;
  byte iDay = random(1, 29);
  iYearMonthDay += iDay;
  char cYearMonthDay[9];
  sprintf(cYearMonthDay, "%lu", iYearMonthDay);
  //Serial.print("\ncYearMonthDay = '"); Serial.print(cYearMonthDay); Serial.println("'");
  for (unsigned int i=0; i<sizeof(cYearMonthDay); i++) {
    arr[i] = cYearMonthDay[i];
  }
  iLastPos = strlen(cYearMonthDay)-1;
  //Serial.print("iLastPos = "); Serial.println(iLastPos);
  // Add the "T"
  arr[iLastPos+1] = 'T';
  iLastPos = iLastPos + 1;  
  // hour
  byte iTime = random(1, 24);
  char cTime[3];
  sprintf(cTime, "%02u", iTime);
  //Serial.print("cTime = '"); Serial.print(cTime); Serial.println("'");
  unsigned int f = 0;
  for (unsigned int i=iLastPos+1; i<iLastPos+sizeof(cTime); i++) {
    //Serial.print(i); Serial.print(", "); Serial.println(cTime[f]);
    arr[i] = cTime[f];
    f++;
  }
  iLastPos = iLastPos + 2;
  //Serial.print("iLastPos = "); Serial.println(iLastPos);
  // minute
  iTime = random(1, 59);
  sprintf(cTime, "%02u", iTime);
  //Serial.print("cTime = '"); Serial.print(cTime); Serial.println("'");
  f = 0;
  for (unsigned int i=iLastPos+1; i<iLastPos+sizeof(cTime); i++) {
    //Serial.print(i); Serial.print(", "); Serial.println(cTime[f]);
    arr[i] = cTime[f];
    f++;
  }
  iLastPos = iLastPos + 2;
  //Serial.print("iLastPos = "); Serial.println(iLastPos);
  // second
  iTime = random(1, 59);
  sprintf(cTime, "%02u", iTime);
  //Serial.print("cTime = '"); Serial.print(cTime); Serial.println("'");
  f = 0;
  for (unsigned int i=iLastPos+1; i<iLastPos+sizeof(cTime); i++) {
    //Serial.print(i); Serial.print(", "); Serial.println(cTime[f]);
    arr[i] = cTime[f];
    f++;
  }
  iLastPos = iLastPos + 2;
  //Serial.print("iLastPos = "); Serial.println(iLastPos);
  // Add the "Z"
  arr[iLastPos+1] = 'Z';
  iLastPos = iLastPos + 1;
  //Serial.print("iLastPos = "); Serial.println(iLastPos);
} // BuildIso8601DateTimeRandom

 

LoRa_receiver.ino

/*
 * Project:     LoRa_receiver.ino
 * Description: Argon + LoRa receiver
 * Author:      Mark Kiehl / Mechatronic Solutions LLC
 * Date:        March 2020
 * 
 * Adafruit LoRa RFM95W 900 MHz FeatherWing + Xenon/Argon/Boron
 * 
 * Receives a message via LoRa from another Xenon/Argon/Boron.
 * The message consists of a ISO 8601 date/time stamp and a floating
 * point value (simulated sensor value). 
 * For this demo, the original message is acknowledged by sending
 * it back to the sender. 
 * The LED on D7 will turn on when a message is received, and then
 * off after the reply is sent to the LoRa sender.
 * The LED will blink S-O-S if an error occurs initializing the
 * LoRa drivers. 
 * The sender should run LoRa_sender.ino
 * LoRa messages are NOT encrypted.
 * 
 * Tested with Particle Device OS v1.5.2 on Argon.
 * WARNING:  May require Particle device OS 1.4.4 for Xenon.
 */

#include "Particle.h"
#if (PLATFORM_ID == PLATFORM_XENON)
SYSTEM_MODE(MANUAL);
#endif

//////////////////////////////////////////////////////////////////////////////
//  LoRa 900 MHz Radio
//  RadioHead library for LoRa Radio
//  Add library "RF9X-RK" and "CryptoLW-RK" to "lib" folder.
//  https://github.com/rickkas7/RF9X-RK
//  https://github.com/rickkas7/CryptoLW-RK
#include <RH_RF95.h>
// Argon/Boron/Xenon: A=D6, B=D5, C=D4, D=D3, E=D2, F=D14
#define RFM95_INT D6  // D6="A"
#define RFM95_CS  D5  // D5="B"
#define RFM95_RST D4  // D4="C"
// Define frequency (set later)
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
//////////////////////////////////////////////////////////////////////////////
unsigned long successCount = 0;
boolean bStopOnError = false;


void setup() {
  pinMode(D7, OUTPUT);

  Serial.begin();
  waitFor(Serial.isConnected, 30000);
  Serial.printlnf("System version: %s", System.version().c_str());

  pinMode(RFM95_RST, OUTPUT);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  // manual reset
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);

  while (!rf95.init()) {
    while (1) blinkERR(D7);
  }
  Serial.println("rf95.init() successful");
  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
  // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
  if (!rf95.setFrequency(RF95_FREQ)) {
    while (1) blinkERR(D7);
  }
  // The default transmitter power is 13dBm, using PA_BOOST.
  // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then 
  // you can set transmitter powers from 5 to 23 dBm:
  rf95.setTxPower(23, false);
  //////////////////////////////////////////////////////////////////////////////

  Serial.println("Setup complete");
} // setup()

// loop() runs over and over again, as quickly as it can execute.
void loop() {

  if (rf95.available()) {
    // Should be a message for us now
    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
    uint8_t len = sizeof(buf);
    if (rf95.recv(buf, &len))     {
      digitalWrite(D7, HIGH);
      // Signal strength ranges from -15 to -100.  Smaller (closer to zero) the better. 
      Serial.printlnf("Received: '%s'", buf);
      // Send a reply (send the data sent back to the Sender)
      rf95.send(buf, sizeof(buf));
      rf95.waitPacketSent();
      successCount++;
      Serial.printlnf("successCount = %d", successCount);
      digitalWrite(D7, LOW);
    } else {
      if (bStopOnError == true) 
        while (1) blinkERR(D7);
    }
  }
   

} // loop()


void blinkERR(byte ledPIN){
  // S-O-S
  const int S = 150, O = 300;
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(O);
    digitalWrite(ledPIN, LOW);
    delay(O);
  }    
  delay(200);
  for(int i = 3; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(S);
    digitalWrite(ledPIN, LOW);
    delay(S);
  }    
  delay(200);
} // blinkERR()

//////////////////////////////////////////////////////////////////////////////

 

Links

library, RF9X-RK 34. This is a port of the RadioHead Packet Radio Library 7 version 1.89 for RF95/96/97/98 LoRa compatible radios.   delete the missing #include "RF9X_RK.h"

Port of the RadioHead RF95/96/97/98 LoRa Packet radio driver for Particle

Arduino LoRa
For Particle: IRQ -> E -> D2; CS -> A -> D6; RST -> F -> A5;

https://github.com/rickkas7/RF9X-RK

 


Links

Xenon datasheet

Particle Workbench tutorial

 

Particle Bluetooth API

How to Use Particle's Powerful Bluetooth API On Your Xenon, Boron or Argon

The Ultimate Guide

Particle tutorial on Gen 3 using Bluetooth or NFC

My Particle BLE Notes

 

More AF FeatherWing products from Adafruit and from Particle

 


Do you need help developing or customizing a IoT product for your needs?   Send me an email requesting a free one hour phone / web share consultation.  

 

The information presented on this website is for the author's use only.   Use of this information by anyone other than the author is offered as guidelines and non-professional advice only.   No liability is assumed by the author or this web site.