These are unofficial numbers, only tested once by me. Your results may vary.
The Boron gateway sends out a mesh publish once per second. The Xenon display shows when it receives a packet and the RSSI. The display also changes to indicate the number of packets missed. When testing the range I listed the maximum range at an RSSI of -89, which is about as low as you can go before you start losing packets.
Tests
Built-In antenna (both sides)
I tested with the Boron mounted about 33" from the ground (about 1 meter, table height). I held the Xenon in my hand, but faced the antennas toward each other, with my body not in the way.
The range was 179’ (54 meters). Note that this is nearly optimal conditions: outside, line-of-sight, with little nearby RF interference. You should consider this the best-case range, not something you’re likely to achieve regularly.
16" duck on Boron, built-in on Xenon
This 16" magnetic base duck antenna works really well. Sure, it’s big, but the performance is impressive, and it’s omni-directional.
The range was 330’ (100 meters), which is really impressive. And since only the gateway used the large antenna, it’s quite practical.
This antenna costs about $20, but it includes the u.FL to RP-SMA cable and several different SMA adapters.
16" duck on both sides
If you need greater distance, putting the 16" duck antenna on both sides is the way to go.
I got 620’ (187 meters) of range!
Yagi on both sides
This option isn’t really practical unless you want to span a very long distance between mesh nodes. But if you have two fixed points and a places put big antennas, this is pretty amazing:
1320’ (400 meters, or 1/4 mile)
This antenna, a 18 dBi 2.4GHz wi-fi high gain antenna, is about 25" long. It costs about $20 and you’d need two of them, so it’s really only practical if you need to go a very long distance. You also need u.FL to RP-SMA adapters, however the one from the duck antenna work perfectly.
Gateway Firmware
This is the gateway firmware.
Tapping the MODE button toggles between internal and external antenna. A long (1 second) blink of the blue D7 LED indicates external antenna. A short blink indicates the internal antenna. The default is internal.
#include "Particle.h"
SerialLogHandler logHandler;
SYSTEM_THREAD(ENABLED);
const unsigned long transmissionIntervalMs = 1000;
const char *meshEventName = "distanceCheck";
unsigned long lastTransmissionMs = 0;
int counter = 0;
bool buttonClicked = false;
bool externalAntenna = false;
void buttonHandler();
void selectAntenna(bool external);
void setup() {
System.on(button_click, buttonHandler);
pinMode(D7, OUTPUT);
}
void loop() {
if (millis() - lastTransmissionMs >= transmissionIntervalMs) {
lastTransmissionMs = millis();
char buf[32];
snprintf(buf, sizeof(buf), "%d", ++counter);
Mesh.publish(meshEventName, buf);
}
if (buttonClicked) {
buttonClicked = false;
externalAntenna = !externalAntenna;
selectAntenna(externalAntenna);
digitalWrite(D7, HIGH);
delay(externalAntenna ? 1000 : 50);
digitalWrite(D7, LOW);
}
}
void buttonHandler() {
buttonClicked = true;
}
void selectAntenna(bool external) {
if (external) {
#if (PLATFORM_ID == PLATFORM_ARGON)
digitalWrite(ANTSW1, 1);
digitalWrite(ANTSW2, 0);
#elif (PLATFORM_ID == PLATFORM_BORON)
digitalWrite(ANTSW1, 0);
#else
digitalWrite(ANTSW1, 0);
digitalWrite(ANTSW2, 1);
#endif
} else {
#if (PLATFORM_ID == PLATFORM_ARGON)
digitalWrite(ANTSW1, 0);
digitalWrite(ANTSW2, 1);
#elif (PLATFORM_ID == PLATFORM_BORON)
digitalWrite(ANTSW1, 1);
#else
digitalWrite(ANTSW1, 1);
digitalWrite(ANTSW2, 0);
#endif
}
}
Display Firmware
While modular user firmware can’t get the RSSI yet, it’s easily available to monolithic. In order to get the RSSI you’ll need to use the gcc-arm local build chain or Particle Workbench.
Tapping the MODE button toggles between internal and external antenna. The display briefly will indicate which antenna is selected. The default is internal.
You can build it with the cloud compilers (modular), but the RSSI won’t display.
#include "Particle.h"
// dependencies.oled-wing-adafruit=0.0.5
// https://github.com/rickkas7/oled-wing-adafruit
#include "oled-wing-adafruit.h"
#if MODULE_FUNCTION == 3
// This is a monolithic build, so we have RSSI functions available
#define HAS_RSSI
#include "ot_api.h"
extern "C" {
int8_t otPlatRadioGetRssi(otInstance *aInstance);
};
#endif
SerialLogHandler logHandler;
SYSTEM_THREAD(ENABLED);
const unsigned long transmissionIntervalMs = 1000;
const char *meshEventName = "distanceCheck";
const unsigned long displayUpdateIntervalMs = 500;
OledWingAdafruit display;
unsigned long lastReceive = 0;
int numReceived = 0;
bool displayNow = false;
bool subscribed = false;
bool buttonClicked = false;
bool externalAntenna = false;
unsigned long lastDisplayUpdate = 0;
void meshEventHandler(const char *event, const char *data);
void displayUpdate();
void buttonHandler();
void selectAntenna(bool external);
void setup() {
System.on(button_click, buttonHandler);
display.setup();
display.clearDisplay();
display.display();
}
void buttonHandler() {
buttonClicked = true;
}
void loop() {
display.loop();
if (!subscribed && Mesh.ready()) {
subscribed = true;
Mesh.subscribe(meshEventName, meshEventHandler);
}
if (((millis() - lastDisplayUpdate) >= displayUpdateIntervalMs) || displayNow) {
lastDisplayUpdate = millis();
displayNow = false;
displayUpdate();
}
if (buttonClicked) {
buttonClicked = false;
externalAntenna = !externalAntenna;
selectAntenna(externalAntenna);
Log.info("button pressed externalAntenna=%d", externalAntenna);
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println(externalAntenna ? "EXTERNAL" : "INTERNAL");
display.println("ANTENNA");
display.display();
}
}
void meshEventHandler(const char *event, const char *data) {
lastReceive = millis();
numReceived++;
displayNow = true;
}
void displayUpdate() {
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(0,0);
char buf[64];
if (millis() - lastReceive < 1250) {
snprintf(buf, sizeof(buf), "%d rcvd", numReceived);
display.println(buf);
#ifdef HAS_RSSI
int8_t rssi = otPlatRadioGetRssi(0);
snprintf(buf, sizeof(buf), "rssi %d", (int)rssi);
display.println(buf);
#endif
}
else {
size_t missed = (millis() - lastReceive) / 1000;
snprintf(buf, sizeof(buf), "missed %u", missed);
display.println(buf);
}
display.display();
}
void selectAntenna(bool external) {
if (external) {
#if (PLATFORM_ID == PLATFORM_ARGON)
digitalWrite(ANTSW1, 1);
digitalWrite(ANTSW2, 0);
#elif (PLATFORM_ID == PLATFORM_BORON)
digitalWrite(ANTSW1, 0);
#else
digitalWrite(ANTSW1, 0);
digitalWrite(ANTSW2, 1);
#endif
} else {
#if (PLATFORM_ID == PLATFORM_ARGON)
digitalWrite(ANTSW1, 0);
digitalWrite(ANTSW2, 1);
#elif (PLATFORM_ID == PLATFORM_BORON)
digitalWrite(ANTSW1, 1);
#else
digitalWrite(ANTSW1, 1);
digitalWrite(ANTSW2, 0);
#endif
}
}