RoadTest: Particle Mesh Wi-Fi Bundle + Grove Sensor Kit
Author: BigG
Creation date:
Evaluation Type: Development Boards & Tools
Did you receive all parts the manufacturer stated would be included in the package?: True
What other parts do you consider comparable to this product?: There is none really. Electric Imp would be similar to Particle.io, for a managed cloud service with integrated devices but they do not offer devices with Mesh networking capability.
What were the biggest problems encountered?: Controlling my desire to run ahead with complicated design applications before learning to walk and understand the system. Building a Mesh application that works well is more complicated than a simple BLE or WiFi network. Mess it up and it can be tricky to recover, but thankfully you can recover with the help of troubleshooting guides, documentation and online support.
Detailed Review:
It's fast, it's flashy, it's fairly flexible, it's only occasionally frustrating, but more importantly it's fun to use. A fantastic job all-round.
And here's why...
Firstly, I want to thank Particle.io and Element14 for giving me the chance to review the Particle Mesh Wi-Fi bundle with the help of a Grove Starter Kit for Particle Mesh.
This Particle Mesh Wi-Fi bundle comes with three mesh-ready kits.
Firstly, there is the Argon kit. This kit contains a combo board, matching the Adafruit Feather form factor, which includes an Espressif ESP32-D0WD 2.4G Wi-Fi coprocessor plus a Nordic Semiconductor nRF52840 SoC. The board acts as the Mesh Network gateway by connecting to your local area Wi-Fi network for Internet access and manages your local Particle Mesh network. Also included in the kit is a breadboard, a USB cable and some components (2 x resistors, an LED and a Light Sensor).
Then there are the two Xenon kits. Each kit contains a Nordic Semiconductor nRF52840 SoC based board (matching the Adafruit Feather form factor) that acts as the Mesh Network end point or network repeater. The kit also includes a breadboard, a USB cable and some components.
The bundle also includes a JTAG & SWD compatible GNU Debugger and a Featherwing Tripler.
Then, for the road test we were also provided with a Seeed Studio Grove Starter Kit for Particle Mesh. This kit includes a Grove interface shield for Particle boards and seven, err, surely this should say eight different Grove components, namely: an Ultrasonic Ranger [1], a Rotary Angle sensor [2], a light sensor [3], a momentary push button [4], a buzzer [5], a DHT11 temperature and humidity sensor [6], a RGB led [7] and a 4-digit 7-segment display [8].
So, before I start my review, let me explain my road testing approach.
I am someone who has never used any of the Particle.io development kits before. I have plenty of experience using Arduino with SeeedStudio's Grove components and I'm very familiar with the Electric Imp managed cloud PaaS approach with its own IoT connected integral hardware.
So, for this review a key aim of mine was to evaluate the degree of effort it takes to get familiar with the Particle Mesh Kit's application development process. As such, I followed a number of steps, namely:
For those who like to see the unboxing and the setup process, I documented my getting started experience in this blog.
Let me begin by saying that compared to all other development board setup processes, this was a breath of fresh air. They really have set themselves apart, in my opinion, as I found that their Getting Started guided approach was very good at getting me set up quickly and with minimal confusion, despite the fact that we are talking about connected products.
I found that the online setup instructions, provided on setup.particle.io, were clear and concise with the right mix of graphics and text, although it did get a little confusing when the website leads you on to use the Android app on your phone to compete the setup process.
That being said, I found the above setup page (also mentioned in my blog) was a sort of cul de sac, which caused me to hesitate as I was not sure whether to close the webpage or leave it open while using the app. I found that if you did close you lost out on what happens next. As such, it needs more text to tell the user to leave it open etc. or explain to the user that there are other options too for setup, such as the CLI option.
Yes, the CLI option is somewhat hidden as it's not explain in the setup documentation. It is in the documentation and a tutorial is provided, so I suppose more correctly, I should say that it is not exposed to the new user from the outset. With CLI you can pretty much do everything once your devices are plugged into a serial port on your computer. Mind you there are subtle differences that you need to look out for, such as, for example, when you complete the mobile phone-based setup, the setup is marked as done while with CLI you have to do this manually by way of instruction (or it can be done in the code). Anyway, for the purposes of this road test, I left CLI alone, although I did get it set up on my computer.
Back to the particle.io app.
Ok, so I discovered the hard way that you have to use an Android cellular phone with Bluetooth, rather than an Android tablet with Bluetooth, to set up the Argon and Xenon mesh devices. Maybe they will change this in the future, as the assumption that you need a cellular connection on your Android device, even though cellular is only needed for the Boron, seemed unnecessarily restrictive for the Argon & Xenon setup routines. Nevertheless, despite this restriction, their latest app (version 3.0.1) worked well on my old Android 5.1 phone, unlike some other Bluetooth Mesh apps I've tried downloading before.
Then when you complete the setup on the phone there was no reminder to return to the website, or if there was, I missed it. Mind you, I think this has changed and you now have this link given to you on the app: setup.particle.io?start-building which takes you to the “hello IoT world” page for the device you've just set up and this allows you to animate the LED on the device. Note that if have not setup a device the above link just defaults to the login screen – so it's smart enough!
Now, when you complete the setup routine you should get to this page.
I thought this was brilliant (when I got there in the end). It gave me all the options I needed to take the next step. The only thing missing here, in my opinion, is the obvious URL printed in bold (for my brain to remember), which is docs.particle.io.
Once again, compared to other websites and their documentation for development boards, I found this portal to be excellent. They really have surpassed themselves in terms of content and structure for helping the new user learn and develop their knowledge and understanding about the products and the firmware development process.
The only thing that caused a little confusion, on my part, was the fact that there are two content “funnels”. You have the content structure for the top menu and then you have a content structure for the Prototyping hardware icons. They don't appear to one and the same.
So for example, if you clicked on the Argon board icon and then you clicked on, say the Quickstart menu option you do not get taken to the Quickstart page for Argon (you had to use the link in the page to do that - as highlighted below). This was somewhat confusing at first.
However, once you realise that menu and icons are mostly separate content funnels, then all is good.
I'm sure they must've discussed this before (pros and cons) as there would've been merit in using the Argon icon click-through page i.e. https://docs.particle.io/argon/ as the customer's starting point for the Argon (and similarly for Xenon's with https://docs.particle.io/xenon/ ) because it provides a very good introduction for the product and has all the correct quickstart and documentation links too.
Then in terms of other learning material, there is this self-guide workshop (Mesh-101), which uses the Grove Starter Kit for Particle Mesh. This workshop material provides step by step instructions for a range of different projects. It is well worth looking at, even if you do not have the Grove Starter Kit.
Using the Particle Mesh Platform diagram, as found here (https://www.particle.io/mesh/), we can see that the Particle.io is one of very few end-to-end platforms out there.
I'll now briefly look at the different elements that make up this cloud-connected mesh platform.
For this road test we were given the Argon as our gateway option. For those looking for a cellular option, there is the Boron.
For me, these are the most notable features that define the Particle Mesh-ready hardware:
All hardware includes the Nordic Semiconductor nRF52840 SoC. This SoC is a 32-bit ARM® Cortex™-M4 CPU with floating point unit running at 64 MHz. It has protocol support for Bluetooth 5, Thread, Zigbee & ANT. It even includes an NFC-A Tag capability and the Particle boards include a uFl antenna connector for this purpose too.
The Argon board also includes an Espressif ESP32 coprocessor to handle the WiFi connectivity. It is not clear if they are not using the single core option or the dual core. If they plan to keep using the dual core, it would be nice to open up the spare core for more edge type processing power.
One of the interesting hardware elements for me was the onboard Status LED and how it is used, by way of flashing sequences and colour modes, to indicate different device modes. The troubleshooting guide provides all the information about what these mean. Here is the reference for the Argon board: https://docs.particle.io/tutorials/device-os/led/argon/
I also created a short video showing some of the different flashing status modes available on the Argon.
And I was able to capture a failure mode too, where the Xenons refused to reconnect to the Mesh network, even though it was flashing cyan, indicating some sort of attempt at a cloud connection. Unfortunately, I did not have time to try out The Debugger to see if that offered any insight.
Image source: https://docs.particle.io/assets/files/Session2-IntroducingParticleMesh.pdf
As stated on the Particle.io website, Particle Mesh is built on OpenThread, an open mesh networking standard released by Nest.
According to https://openthread.io/ website, OpenThread implements all Thread networking layers (IPv6, 6LoWPAN, IEEE 802.15.4 with MAC security, Mesh Link Establishment, Mesh Routing) and device roles, as well as Border Router support.
From a user's or even a developer's perspective, I found that you don't need to do anything different when it comes to how the mesh assigns these different roles etc.
Here, I carried out two very basic tests to quickly ascertain connectivity distance.
The first test used one Xenon, which subscribed to the LED state event published by the Argon. The Argon published the LED blink state every 1 second. I then carried the Xenon around the house to see if it stopped blinking. Then only time I noticed an obvious change was when I was standing outside the front of the house behind a tree some 20 metres away. This was rather impressive, in my opinion, as the Argon was upstairs at the back of house on my desk beside the computer.
The second test used two Xenons. Both Xenons were flashed with the same firmware as per the first test. In this case I simply placed the first Xenon at the front window and then walked outside away from house to see how far I could get before I stopped getting an LED change of state. In this case I was some 75 metres away, line of site, before a noticeable change was observed on the 2nd Xenon. Here too, I was rather impressed.
Image source: https://docs.particle.io/workshops/mesh-101-workshop/a-lap-around-particle/
Particle.io can also be described as a full-stack IoT device platform and the IoT Device Cloud is the cornerstone or hub which monitors your devices and allows you to control your devices according to your application function and variable definitions. The IoT Device Cloud also allows you to securely upload your firmware onto your devices.
Once you are logged into Particle.io you get to see you devices through the Cloud Console. Within the console is not only a wealth of information but also the opportunity to control your devices remotely (or centrally). As highlighted in my blog, the SIGNAL and PING functions are handy to check that your devices are still "alive" or connected. Of course, note that if your Xenon's are in deep sleep mode these functions will not work as devices need to be awake to review these commands.
Because there is so much here, the simplest way to show what's available to via video demo.
The code can be edited via the web IDE:
// ----------------------------------------- // Function and Variable with Photoresistors // ----------------------------------------- // In this example, we're going to register a Particle.variable() with the cloud so that we can read brightness levels from the photoresistor. // We'll also register a Particle.function so that we can turn the LED on and off remotely. // We're going to start by declaring which pins everything is plugged into. int led = D0; // This is where your LED is plugged in. The other side goes to a resistor connected to GND. int photoresistor = A0; // This is where your photoresistor is plugged in. The other side goes to the "power" pin (below). int power = A5; // This is the other end of your photoresistor. The other side is plugged into the "photoresistor" pin (above). // The reason we have plugged one side into an analog pin instead of to "power" is because we want a very steady voltage to be sent to the photoresistor. // That way, when we read the value from the other side of the photoresistor, we can accurately calculate a voltage drop. int analogvalue; // Here we are declaring the integer variable analogvalue, which we will use later to store the value of the photoresistor. // Next we go into the setup function. void setup() { // First, declare all of our pins. This lets our device know which ones will be used for outputting voltage, and which ones will read incoming voltage. pinMode(led,OUTPUT); // Our LED pin is output (lighting up the LED) pinMode(photoresistor,INPUT); // Our photoresistor pin is input (reading the photoresistor) pinMode(power,OUTPUT); // The pin powering the photoresistor is output (sending out consistent power) // Next, write the power of the photoresistor to be the maximum possible, so that we can use this for power. digitalWrite(power,HIGH); // We are going to declare a Particle.variable() here so that we can access the value of the photoresistor from the cloud. Particle.variable("analogvalue", &analogvalue, INT); // This is saying that when we ask the cloud for "analogvalue", this will reference the variable analogvalue in this app, which is an integer variable. // We are also going to declare a Particle.function so that we can turn the LED on and off from the cloud. Particle.function("led",ledToggle); // This is saying that when we ask the cloud for the function "led", it will employ the function ledToggle() from this app. Particle.publishVitals(15); // Publish vitals every 15 seconds, indefinitely } // Next is the loop function... void loop() { // check to see what the value of the photoresistor is and store it in the int variable analogvalue analogvalue = analogRead(photoresistor); } // Finally, we will write out our ledToggle function, which is referenced by the Particle.function() called "led" int ledToggle(String command) { if (command=="on") { digitalWrite(led,HIGH); return 1; } else if (command=="off") { digitalWrite(led,LOW); return 0; } else { return -1; } }
Overall, I found the console to be very useful and is very nicely designed. The only thing I did not like, is that none of the event data persists, even for a couple minutes, which means that if you switch pages you lose the data.
From a Mesh perspective, I found that they still need to optimise how the Xenon device connectivity is shown. I had a couple of experiences where the Xenon devices were shown to be connected even though the gateway (Argon device) was disconnected. I believe that the Particle.io team continues to develop the console to make this better.
Particle.io have some nice ready made off-the-shelf integration options for you. They even tutorials on how to set these up:
Google Cloud Platform: https://docs.particle.io/tutorials/integrations/google-cloud-platform/
Microsoft Azure Hub: https://docs.particle.io/tutorials/integrations/azure-iot-hub/
Google Maps: https://docs.particle.io/tutorials/integrations/google-maps/
InfluxData Platform: https://docs.particle.io/tutorials/integrations/influxdata/
IFTTT Channel: https://docs.particle.io/tutorials/integrations/ifttt/
However, in my opinion, this is where Particle.io have let themselves down slightly (maybe it is just not prioritised at the moment). Yes, you have the pre-defined integrations like Google Cloud Platform, Microsoft Azure, InfluxData (database) IFTTT, or you can set up POST, GET, PUSH requests via webhooks, but these are template driven and I cannot see options for conditional error and exception handling. There is no ability to apply “smarts” which you could do through a micro-service or serverless function, for example, which would be my preference.
So, in my opinion, this is small limitation on integration capability as it does not allow the user to use the power of the cloud for computational work or for handling of complex algorithms, for example (although I recognise Google probably would do this better).
Still, in my experience with other services, I've found it less problematic and more robust to have the ability to develop your own smart serverless functions, using say nodejs or c++ or Python, or even Wiring/Processing. So, even though I did not test, I have my reservations about the value some a high level Node-Red rules engine versus a code engine.
Thus if you are looking to expand an application with cloud functionality you would have to push your Particle.io data to another service (like Google Cloud or maybe PubNub, for example), in order to compute the data and/or handle data queries and then you would have to poll this web service via a webhook to retrieve the answer. As such, I see this as the less optimal scenario... but maybe my perspective will change as I grow more comfortable with the Particle.io methodology. Who knows.
When it comes to trying out some examples for Particle.io devices you are never short changed. There is plenty available online.
I'll start with the official guides provided. The hardware examples provided for Argon and Xenon (pretty much and the same) are very nicely explained:
https://docs.particle.io/tutorials/hardware-projects/hardware-examples/argon/
https://docs.particle.io/tutorials/hardware-projects/hardware-examples/xenon/
These examples allow you to progress from the very basic LED blinking to learning how to publish and subscribe to data events. There is also a “Tinker” example giving you visibility and control of all your GPIO's.
The examples are also replicated on the Web IDE:
It does not long to progress through these examples. I decided for my first project to take the Web-Connected LED example and enhance it by including some of the sensors from Grove Starter Kit. I also wanted to test how easy it was to port code from my Arduino IDE onto the Particle Web IDE. It only took a couple of minutes and it worked pretty well. Here is the video documenting the process:
For my second project, I wanted to get a little more elaborate to test the mesh networking functionality. Here is the setup:
There are three elements that make up this project, namely:
At the last minute, I also included a Slack integration so that I get a Slack notification when the temperature and humidity data is read. This was a very straight forward process. To get this to work you have to set some customisation for the payload:
Here is the mechanics behind the design, where rotation speed determines when the temperature and humidity data is read:
And here is the raw video footage (hopefully it captures all):
Argon Code as follows:
// This #include statement was automatically added by the Particle IDE. #include <TM1637Display.h> // ----------------------------------- // ARGON: Mesh Gateway and Motor Speed Controller over Mesh // 4-digit display used to indicate speed settings // ----------------------------------- const int LEDBRD = D7; // This is the built in LED // Module connection pins (Digital Pins) const int CLKPIN = D2; const int DIOPIN = D3; const int BUZPIN = D4; const int BTNPIN = D18; // Module connection pins (Analog Pins) const int DIALPIN = A0; const int LONGPRESS = 1500; // The amount of time (in milliseconds) between message display const int DISPLAY_DELAY = 1500; // The amount of time (in milliseconds) between periodic time interrupt const int TIMER_DELAY = 1000; // Some preset "words" for the 4-digit display const uint8_t SEG_DONE[] = { SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, // d SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O SEG_C | SEG_E | SEG_G, // n SEG_A | SEG_D | SEG_E | SEG_F | SEG_G // E }; const uint8_t SEG_STOP[] = { SEG_A | SEG_C | SEG_D | SEG_F | SEG_G, // S SEG_D | SEG_E | SEG_F | SEG_G, // t SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O SEG_A | SEG_B | SEG_E | SEG_F | SEG_G // P }; const uint8_t SEG_COOL[] = { SEG_A | SEG_D | SEG_E | SEG_F, // C SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O SEG_D | SEG_E | SEG_F // L }; unsigned long t_angle = 0, t_now = 0, t_btn = 0; int remoteCntrl = 0; unsigned int prevAngleValue = 0; uint8_t data[] = { 0xff, 0xff, 0xff, 0xff }; uint8_t blank[] = { 0x00, 0x00, 0x00, 0x00 }; uint8_t motorSpeed = 0, i = 0, count = 0; bool ConfigMode = false; TM1637Display display(CLKPIN, DIOPIN); int motorToggle(String command); // Last time, we only needed to declare pins in the setup function. // This time, we are also going to register our Particle function void setup() { // Here's the pin configuration, same as last time pinMode(LEDBRD, OUTPUT); pinMode(BUZPIN, OUTPUT); pinMode(BTNPIN, INPUT_PULLUP); // We are also going to declare a Particle.function so that we can turn the MOTOR on and off from the cloud. Particle.function("motor", motorToggle); // This is saying that when we ask the cloud for the function "led", it will employ the function motorToggle() from this app. // For good measure, let's also make sure board LED and buzzer is off when we start: digitalWrite(LEDBRD, LOW); digitalWrite(BUZPIN, LOW); display.setBrightness(0x0f); //display.setSegments(data); delay(DISPLAY_DELAY); // Done! display.setSegments(SEG_DONE); delay(DISPLAY_DELAY); display.setSegments(SEG_COOL); delay(2*DISPLAY_DELAY); } /* Last time, we wanted to continously blink the LED on and off Since we're waiting for input through the cloud this time, we don't actually need to put anything in the loop */ void loop() { // Nothing to do here int BtnState = digitalRead(BTNPIN); if (BtnState || remoteCntrl) { if (!t_btn) { if (ConfigMode) { // Send alert to the cloud Particle.publish("motor/power", "on", PRIVATE); tone(BUZPIN, 3600, 250); delay(500); tone(BUZPIN, 3600, 250); delay(500); tone(BUZPIN, 3600, 250); delay(500); // Publish event to Mesh delay(100); Mesh.publish("motor/speed", String(motorSpeed)); // Show the value on the 4-digit Display display.showNumberDec(motorSpeed, false); ConfigMode = false; //timer.start(); } else t_btn = millis(); } else { if ((millis() - t_btn) > LONGPRESS) { // We are now in config mode - stop the timer interrupt //timer.stop(); ConfigMode = true; t_btn = 0; // Send alert to the cloud Particle.publish("motor/power", "off", PRIVATE); // Publish event to Mesh delay(100); Mesh.publish("motor/speed", "0"); // We clear the screen and blink the set range display.setSegments(blank); delay(500); tone(BUZPIN, 3400, DISPLAY_DELAY); display.setSegments(SEG_STOP); delay(DISPLAY_DELAY); //display.setSegments(dataclear); delay(500); } } remoteCntrl = 0; } else if (!BtnState && !remoteCntrl) t_btn = 0; if (!ConfigMode) { if ((millis() - t_angle) > TIMER_DELAY) { // Calculate the gap between t_angle = millis(); motorSpeed_handler(); } } } // We're going to have a super cool function now that gets called when a matching API request is sent // This is the motorToggle function we registered to the "led" Particle.function earlier. int motorToggle(String command) { /* Particle.functions always take a string as an argument and return an integer. Since we can pass a string, it means that we can give the program commands on how the function should be used. In this case, telling the function "on" will turn the LED on and telling it "off" will turn the LED off. Then, the function returns a value to us to let us know what happened. In this case, it will return 1 for the LEDs turning on, 0 for the LEDs turning off, and -1 if we received a totally bogus command that didn't do anything to the LEDs. */ if (command == "on") { digitalWrite(LEDBRD, LOW); if (ConfigMode) { remoteCntrl = 1; t_btn = 0; } return 1; } else if (command == "off") { digitalWrite(LEDBRD, HIGH); if (!ConfigMode) { remoteCntrl = 1; t_btn = 1; } return 0; } else { return -1; } } void motorSpeed_handler() { unsigned int AngleVal = analogRead(DIALPIN); delay(1); AngleVal += analogRead(DIALPIN); AngleVal = AngleVal/2; // We now map this value to determine motorSpeed motorSpeed = map(AngleVal, 0, 4095, 0, 100); if (abs(AngleVal - prevAngleValue) > 30) { Mesh.publish("motor/speed", String(motorSpeed)); // Show the value on the 4-digit Display display.showNumberDec(motorSpeed, false); prevAngleValue = AngleVal; } }
The Xenon #1 Code:
// This #include statement was automatically added by the Particle IDE. #include <Stepper.h> // ----------------------------------- // Mesh Motor Controller // ----------------------------------- const int MOTORLED = D6; // Controlled by the console (remote function) - used to detect motor speed const int BRDLED = D7; // Responds based on light sensor reading const int photosensor = A0; // This is where your photoresistor or phototransistor is plugged in. The other side goes to the "power" pin (below). const int photoPWR = D18; // This is to provide power to photoresistor const int IN1step = D3; const int IN2step = D4; const int IN3step = D5; const int IN4step = D8; const int LIGHTTHRESHOLDVALUE = 750; const int SAMPLEINTERVAL = 1000; const int STEPSPERREVOLUTION = 2048; // the number of steps per revolution as per your motor spec unsigned long t_sample = 0; int LightSensorValue; // Here we are declaring the integer variable analogvalue, int LightThresholdValue; int prevThresholdValue; int motorSpeed = 0; int MeshMotorSpeed = 0; bool MonitorMovement = false; Stepper myStepper(STEPSPERREVOLUTION, IN1step, IN2step, IN3step, IN4step); void setup() { Serial.begin(115200); // Here's the pin configuration, same as last time pinMode(MOTORLED, OUTPUT); pinMode(BRDLED, OUTPUT); pinMode(photoPWR, OUTPUT); // For good measure, let's also make sure both LEDs are off when we start: digitalWrite(MOTORLED, LOW); digitalWrite(BRDLED, LOW); digitalWrite(photoPWR, LOW); Mesh.subscribe("motor/speed", motorSpeedHandler); // We are also going to declare a Particle.function so that we can turn the LED on and off from the cloud. Particle.function("MotorSpeedLed", MotorLedToggle); } // Last time, we wanted to continously blink the LED on and off // Since we're waiting for input through the cloud this time, // we don't actually need to put anything in the loop void loop() { if (motorSpeed) { myStepper.step(1); digitalWrite(BRDLED, LOW); } else { myStepper.step(0); digitalWrite(BRDLED, HIGH); } if ((millis() - t_sample) > SAMPLEINTERVAL) { t_sample = millis(); if (MonitorMovement) { LightSensorValue = analogRead(photosensor); Serial.printlnf("Light Value=%d", LightSensorValue); if (LightSensorValue < LIGHTTHRESHOLDVALUE) { LightThresholdValue = 0; } else { LightThresholdValue = 1; } if (LightThresholdValue != prevThresholdValue) { Particle.publish("Light", "Threshold:"+String(LightThresholdValue)+", Value:"+String(LightSensorValue), PRIVATE ); } prevThresholdValue = LightThresholdValue; } } } // We're going to have a super cool function now that gets called when a matching API request is sent // This is the ledToggle function we registered to the "led" Particle.function earlier. int MotorLedToggle(String command) { /* Particle.functions always take a string as an argument and return an integer. Since we can pass a string, it means that we can give the program commands on how the function should be used. In this case, telling the function "on" will turn the LED on and telling it "off" will turn the LED off. Then, the function returns a value to us to let us know what happened. In this case, it will return 1 for the LEDs turning on, 0 for the LEDs turning off, and -1 if we received a totally bogus command that didn't do anything to the LEDs. */ if (command=="on") { digitalWrite(MOTORLED, HIGH); digitalWrite(photoPWR, HIGH); MonitorMovement = true; return 1; } else if (command=="off") { digitalWrite(MOTORLED, LOW); digitalWrite(photoPWR, LOW); MonitorMovement = false; return 0; } else { return -1; } } void motorSpeedHandler(const char *event, const char *data) { //Particle.publish("Mesh Event", String(event)+" - "+String(data)); MeshMotorSpeed = String(data).toInt(); motorSpeed = MeshMotorSpeed/10; // Now set the speed of the motor myStepper.setSpeed(motorSpeed); }
And finally, the Xenon #2 Code:
// This #include statement was automatically added by the Particle IDE. #include <Grove_ChainableLED.h> // This #include statement was automatically added by the Particle IDE. #include <Grove-Ultrasonic-Ranger.h> // This #include statement was automatically added by the Particle IDE. #include <Grove_Temperature_And_Humidity_Sensor.h> // ----------------------------------------- // Xenon: UltraSonic Sensor + DHT11 Temperature and Humidity Sensor + Chainable RGB LED // ----------------------------------------- // In this example, we're going to register a Particle.variable() with the cloud so that we can read temp and RH values from DHT11 // We also subscribe to a MESH motor speed notification // We also publish Temp and Humidity Values // RGB is used to indicate with these reading are taken // We're going to start by declaring which pins everything is plugged into. const int RGBCLKPIN = D2; const int RGBDATPIN = D3; const int DHT11PIN = D4; // This is where your DHT11 yellow wire is plugged in. const int UDISTPIN = D5; // This is where your Ultrasonic Sensor yellow wire is plugged in. const unsigned int SAMPLERATE = 500; // This is the sample rate in milliseconds to measure distance const unsigned int LEDTEMPDISPTIMEOUT = 5000; // This is how long the RGB stays on for before turning off const unsigned int OBJECTGAPDIST = 15; // THis is the threshold distance for Ulstrasonic sensor const float SENSORGAP = 20.0; // We assume gap between DHT11 and ultrasonic sensor is 20cm const double CIRCUM = 62.8318530718; // Drum Circumference in centimetres const int NUM_LEDS = 1; const int TEMPHIGH = 25; const int TEMPLOW = 18; unsigned long t_sample = 0; unsigned long t_dht11 = 0; unsigned long t_led = 0; // Drum Surface Speed unsigned long SampleTimeDelay = 0; // Store the Ultrasonic Distance value as long long RangeInCentimeters; String DHTdata = "{null}"; String DHTdelay = "{null}"; // Store the temperature and humidity value as float float h = 0.0; float t = 0.0; // We store these values as ints (10 x float) to give 1 decimal int Tempvalue = 0; int RHvalue = 0; // So we assume our DHT sensor is 1 metre away from Ultrasonic sensor // The motor speed is given to us as RPM. // We assume radius of coveyor rotating is 10 centimetres int ReadingDelayTime = 0; bool ReadDHTvalsTrigger = false; DHT dht(DHT11PIN); Ultrasonic ultrasonic(UDISTPIN); ChainableLED leds(RGBCLKPIN, RGBDATPIN, NUM_LEDS); // Next we go into the setup function. void setup() { Serial.begin(115200); // We are going to declare a Particle.variable() here so that we can access the Temperatuer value of the DHT11 Sensor from the cloud. Particle.variable("DHT_Delay", &DHTdelay, STRING); Mesh.subscribe("motor/speed", motorSpeedHandler); dht.begin(); leds.init(); leds.setColorRGB(0, 0, 0, 0); // Delay a short while to settle sensors delay(1000); } // Next is the loop function... void loop() { if ((millis() - t_sample) > SAMPLERATE) { t_sample = millis(); RangeInCentimeters = ultrasonic.MeasureInCentimeters(); // two measurements should keep an interval if (RangeInCentimeters < OBJECTGAPDIST) { // We will publish temperature and humidity value readings depending on motor speed if (!ReadDHTvalsTrigger && SampleTimeDelay) { Serial.print(RangeInCentimeters); //0~400cm Serial.println(" cm"); ReadDHTvalsTrigger = true; t_dht11 = millis(); t_led = 0; leds.setColorRGB(0, 0, 0, 255); } } } if (ReadDHTvalsTrigger && SampleTimeDelay) { if ((millis() - t_dht11) > SampleTimeDelay) { // We take some measurements //Read Humidity h = dht.getHumidity(); // We round up for integer value RHvalue = int((h + 0.05) * 10); // Read temperature as Celsius t = dht.getTempCelcius(); // We round up for integer value Tempvalue = int((t + 0.05) * 10); int nTemp = Tempvalue*10; nTemp = nTemp > TEMPHIGH*100 ? TEMPHIGH*100 : (nTemp < TEMPLOW*100 ? TEMPLOW*100 : nTemp); nTemp = map(nTemp, TEMPLOW*100, TEMPHIGH*100, 0, 1023); ledLight(nTemp); Serial.printlnf("Temp x 10 (C) = %d \tRH x 10 (%) = %d", Tempvalue, RHvalue); DHTdata = "{Temp:" + String(float(Tempvalue)/10.0,1) + "C, RH:" + String(float(RHvalue)/10.0,1) + "%}"; Particle.publish("DHT11", DHTdata, PRIVATE); ReadDHTvalsTrigger = false; } } // Handle when rotation stops if (!SampleTimeDelay) { ReadDHTvalsTrigger = false; t_dht11 = 0; } if (t_led) { if ((millis() - t_led) > LEDTEMPDISPTIMEOUT) { t_led = 0; if (SampleTimeDelay) { leds.setColorRGB(0, 0, 10, 10); } else { leds.setColorRGB(0, 0, 0, 0); } } } } void motorSpeedHandler(const char *event, const char *data) { //Particle.publish("Mesh Event", String(event)+" - "+String(data)); int MeshMotorSpeed = String(data).toInt(); // We divide by 10 to get the RPM MeshMotorSpeed /= 10; if (MeshMotorSpeed) { SampleTimeDelay = 10*int(100.0 * (SENSORGAP / (CIRCUM * float(MeshMotorSpeed)/60.0))); leds.setColorRGB(0, 0, 10, 10); } else { SampleTimeDelay = 0; leds.setColorRGB(0, 0, 0, 0); } Serial.printlnf("motorSpeed (RPM) = %d \tTimeDelay (ms) = %d", MeshMotorSpeed, SampleTimeDelay); DHTdelay = "{" + Time.timeStr() + ", RPM:" + String(MeshMotorSpeed) + ", TimeDelay:" + String(SampleTimeDelay) + "msec}"; } void ledLight(int dta) // light led { dta = dta/4; // 0 - 255 byte colorR = byte(dta); byte colorG = 255-byte(dta); byte colorB = 0; leds.setColorRGB(0, colorR, colorG, colorB); t_led = millis(); }
Of course, if you are looking beyond what you see here and on the Particle.io website then Hackster.io (sister company of Element14) have a Particle channel, where plenty of demo projects are presented by the community: https://www.hackster.io/particle
I have barely got to grips with all the different elements that make up Particle.io's end to end IoT Development platform but from what I have experienced so far, I am more than happy to say that I think the Mesh Networking kit is brilliant. I really like it.
For me, the key benefit of developing an application with this kit was the speed by which you can get things up and running. Flexibility in designed in too as you are not constrained to just the web IDE, you can use your own browser-based IDE (64bit option only though for Windows) and then there is the CLI too.
As a new user, I was guided along nicely through their quick start guide. The documentation and their website is very polished. The only reason I did not give them a perfect 10 for all aspects was the fact that when I did encounter a networking or mesh error it was unclear as to the reason for this error. I suspect though that this relates either to the RTOS or simply down to poor programming skill. I am sure others will find this is the nature of developing mesh networks - they can be tricky. They are more complicated to debug etc.
Anyhow, I certainly plan to use this system going forward. Well done Particle.
Top Comments
Good road test report.
DAB
Thanks DAB.
Ah OK, that video. The two Xenons shown were never in sync to start with as neither one would connect with the cloud for some reason.
From what I understand, when the Xenons start with fast flashing green…