Mesh range testing


#1

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
    }
}

Local Build Argon using po-util with OpenThread
#2

Thanks for posting this info :satellite:

I’ve never testing a Yagi antenna before but you can clearly see they are very effective.


#3

HI,

If your interested in the wireless performance its worth paying attention to the anntena itself.

There is a chap Andreas Spiess, who has done a bit of study in to the issue, and discovered that even the anntena sold with devices can very often be usless for the task. A correctly selected anntena can make a huge difference to the devices performance.

His study was concerned with Lora, but the principle is the same. might be worth taking a peek at it-

Andreas Spiess: Anntena video


#4

Hi @rickkas7,

I did a range test with one Argon and one Xenon yesterday (both use the internal antenna only). The Argon was placed outside the window on the first floor (3.5m above ground). I was holding the Xenon in my hand and walked away from home. I had a clear line of sight 600m from home till I reached the nearby forest / hills.

At 600m away from home I had a RSSI of approx 88-92 dBm. I was using this code PR test/mesh/range.

The height of your antenna matters a lot (fresnel zone clearance)! See Fresnel Zone Calculator

Example:
Distance = 600m at 2.4 GHz -> Fresnel zone radius at midpoint r = 4.3m
A rule of thumb says, that one should have at least 60% of the fresnel zone radius clear -> that’s 2.6m

That’s why I placed my Argon 3.5 m above ground! (I’m aware that this might not always be possible in reality…:grinning:)

Figures for your tests above:
(Flat open area, antenna placed 1m above ground, frequency 2.4 GHz)

If 1m equals 60% of fresnel zone radius at midpoint, then the fresnel zone radius equals r = 1.66m.
-> At a distance of about 90m you will reach the limit (no more line of sight for radio waves), where attenuation will increase due to the fresnel zone!

So, by simply placing your antennas higher up above the ground you should be able to increase the range greatly. To reach 1km your antennas have to be at least 3.4m above ground / obstacles (60% of r = 5.6m)!

More information can be found here 3 Factors that Limit Range in RF Applications

BTW: I assume you tested with latest FW 0.8.0-rc.27 (FW 0.8.0-rc.25 used 0dBm instead of +8dBm), right?
BTW2: I love the yagi antenna, that’s a beauty!

Happy new year!


#5

Any idea when RSSI for the mesh communicatino will be added to the modular/cloud builds?


#6

Here is a good Paper going over how to get the most range from your devices and how different antennas and environment affect range. It gave me a better understanding of how it all works in a simple way.

@rocksetta May like this for his students since its explained in a simple way.

( http://ftp1.digi.com/support/images/XST-AN010a-MaximizingRange.pdf )


#7

@RWB, That URL isn’t working. :confused:


#8

http://ftp1.digi.com/support/images/XST-AN010a-MaximizingRange.pdf


#9

@rickkas7 can you point me in the right direction of how to do a monolithic build to get RSSI code like this running on an Xenon or Argon?

#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

#ifdef HAS_RSSI
int8_t rssi = otPlatRadioGetRssi(0);
snprintf(buf, sizeof(buf), “rssi %d”, (int)rssi);
display.println(buf);
#endif


High School Robotics Course using the Particle.io Mesh Devices Blog
#10

I do it using the local gcc-arm build chain:

cd firmware/main
make all PLATFORM=xenon MODULAR=n APPDIR="/path/to/source"

It should be possible from Particle Workbench but I believe you need to modify the tasks.json file because there isn’t a build task for building a monolithic binary.

If switching between modular and monolithic, probably do make clean all instead of just make all.


Is it possible for a mesh device to know which other mesh devices it is connected to directly?
#11

I made a better version of the display and gateway test firmware.

The data on the display is:

  • 151 the number of packets received
  • 94% the percentage of packets received
  • >-85 the RSSI for receiving the packet from the gateway to the display
  • <-84 the RSSI for receiving the packet on the gateway from the display

Now the display does a Mesh.publish once per second. The gateway receives this packet, notes the received RSSI, then publish back to the display the packet number and RSSI.

The display will display this information in both transmissions completed successfully.

Gateway firmware:

#include "Particle.h"

SerialLogHandler logHandler;

SYSTEM_THREAD(ENABLED);

#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

const unsigned long transmissionIntervalMs = 1000;

int lastId = 0;
int lastRssi = 0;
bool buttonClicked = false;
bool externalAntenna = false;
bool subscribed = false;
unsigned long lastReceive = 0;

void buttonHandler();
void selectAntenna(bool external);
void meshEventHandler(const char *event, const char *data);


void setup() {
	System.on(button_click, buttonHandler);
	pinMode(D7, OUTPUT);
}

void loop() {
	if (!subscribed && Mesh.ready()) {
		subscribed = true;
		Mesh.subscribe("distanceCheckToGW", meshEventHandler);
	}

	if (lastId != 0) {
		lastId = 0;

		char buf[64];
		snprintf(buf, sizeof(buf), "%d,%d", lastId, lastRssi);
		Mesh.publish("distanceCheckFromGW", 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
    }
}

void meshEventHandler(const char *event, const char *data) {
	lastReceive = millis();
	lastId = atoi(data);
#ifdef HAS_RSSI
	lastRssi = otPlatRadioGetRssi(0);
#endif
}



Display firmware:

#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 unsigned long displayUpdateIntervalMs = 500;

OledWingAdafruit display;

unsigned long lastSend = 0;
unsigned long lastReceive = 0;
int counter = 0;
int numReceived = 0;
bool displayNow = false;
bool subscribed = false;
bool buttonClicked = false;
bool externalAntenna = false;
int lastCounter = 0;
int lastRssi = 0;

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("distanceCheckFromGW", meshEventHandler);
	}

	if (millis() - lastSend >= transmissionIntervalMs) {
		lastSend = millis();

		char buf[64];
		snprintf(buf, sizeof(buf), "%d", ++counter);
		Mesh.publish("distanceCheckToGW", buf);
	}

	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();

	if (sscanf(data, "%d,%d", &lastCounter, &lastRssi) == 2) {
		numReceived++;
		displayNow = true;
	}
}


void displayUpdate() {
	display.clearDisplay();
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(0,0);

	char buf[64];

	if (millis() - lastReceive < 1250) {
		int pct = (counter > 0) ? (numReceived * 100 / counter) : 0;
		snprintf(buf, sizeof(buf), "%d %d%%", numReceived, pct);
		display.println(buf);

		buf[0] = 0;
#ifdef HAS_RSSI
		int8_t rssi = otPlatRadioGetRssi(0);
		snprintf(buf, sizeof(buf), ">%d <%d", (int)rssi, lastRssi);
		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
    }
}



#12

12 posts were merged into an existing topic: Local Build Argon using po-util with OpenThread