Particle Bench Test / Demonstration

The following demonstrates testing of the primary functionality of the Particle Argon / Boron / Xenon by themselves, and interacting with the Particle Cloud.   The scope of functionalitty demonstrated includes:

  • Expose a variable through the Cloud so that it can be called with GET /v1/devices/{DEVICE_ID}/{VARIABLE}.
  • Expose a function through the Cloud so that it can be called with POST /v1/devices/{DEVICE_ID}/{FUNCTION}.
  • Publish an event through the Particle Device Cloud that will be forwarded to all registered listeners, and demonstrate that it is received by one device.  
  • Publish an event on the Mesh network and confirm it is received by another device on the Mesh network.  

 

Configure a Xenon and a Argon or Boron as illustrated below.  

Xenon


/* 
    particle.io Xenon
    device name: Xenon_A
    sketch filename: bench_OLED.ino
    
   Particle Xenon + AF 128x32 mono OLED

    Demonstrations:
      - Expose a variable through the Cloud so that it can be called 
        with GET /v1/devices/{DEVICE_ID}/{VARIABLE}.
      - Expose a function through the Cloud so that it can be called 
        with POST /v1/devices/{DEVICE_ID}/{FUNCTION}.
      - Publish an event to the Mesh network that will be 
        forwarded to all registered listeners (received by device 
        Argon_A and responded by blinking Argon_A's LED).
    
    A0  connected to a Partical variable "XenonA0" and reports the measured
		value to the Device Cloud console every 25 seconds. 
		The value of AnalogA0Val is also published to the Particle Cloud as 
		event "XenonAeventA0" so it can be accessed with a WebHook. 

    D5  Turns on/off in response to pushbutton.
		Blinks in response to Particle function "BlinkXenonLED".  

    D7  Pushbutton triggers an event published to the Particle Device Cloud.    
    
    The 128x32 monochrome OLED uses I2C (D0 & D1; 0x3C) D2,D3,D4
    
    D2,
    D3,
    D4  Pushbuttons on 128x32 mono OLED
        
*/

// AF 128x32 mono OLED   
// https://github.com/rickkas7/oled-wing-adafruit
#include "oled-wing-adafruit.h"
OledWingAdafruit display;
int lastDisplay = 0;


int pinLED = D5; 
int pinPushButtonPullDown = D7;
int buttonState = LOW;         
int lastButtonState = LOW;
int lastDebounceTime = 0;  // the last time the output pin was toggled
int debounceDelay = 50;    // The switch debounce time.  50 to 100 ms


#include "math.h"
int pinAnalogIn = A0;
double AnalogA0Val = 0.0;
// Set the value below equal to the number of bits for the ADC.
//  Argon, Boron, Xenon have 12-bit ADC
byte ADC_bits = 12; 
// vRef is the max analog input voltage (in mV)
float vRef = 3300.0;
// Timer for analogRead(pinADC)
// The min time to read one analog pin on Argon/Xenon/Boron is 10 us (100 kHz)
// 1000 us = 1 ms = 0.001 sec = 1 kHz
// 100 us = 0.1 ms = 0.0001 sec = 10 kHz
const unsigned long sampleWindow = 1000;  // sample window width in ms
unsigned long timerAlap = micros();  // timer
// Timer for Particle.Publish() of 
const unsigned long timerB = 25000;  // Publish value to Particle Cloud every 25000 ms = 25 sec
unsigned long timerBlap = millis();  // timer
// pinADC values in mV
float mVmax = 0.0;
float mVsum = 0.0;
unsigned long samples = 0;


// Cloud function "BlinkXenonLED" that blinks the Xenon LED on pinLED.
int iBlinkXenonLED(String sBlinkCmd);


void setup() {

  pinMode(pinLED, OUTPUT);
  pinMode(pinPushButtonPullDown, INPUT);
  pinMode(pinAnalogIn, INPUT);

  // Publish the value of A0 as a Particle variable.
  // The frequency of publishing is managed by Particle. 
  Particle.variable("XenonA0",AnalogA0Val);

    // register the cloud function (funcKey, funcName)
  Particle.function("BlinkXenonA", iBlinkXenonLED);

  // Subscribe to Particle Cloud event "PushbuttonArgonA" sent from Argon_A
  // when the button is pressed on Argon_A.  Blinks the LED when the event occurs. 
  // NOTE: If you Particle.publish() with no PUBLIC or PRIVATE parameter, then it
  //       defaults to PRIVATE and then you must use ALL_DEVICES for Particle.subscribe().
  //       Particle.publish("sEventName", "sData", PUBLIC) => Particle.subscribe(sEventName, ALL_DEVICES)
  //       Particle.publish("sEventName", "sData", PRIVATE) => Particle.subscribe(sEventName, MY_DEVICES)
  Particle.subscribe("PushbuttonArgonA", PushbuttonArgonAEventHandler, MY_DEVICES);
 
  // Verify the LED works by blinking it. 
  blinkLED(pinLED);

   /* START AF FeatherWing OLED 128x32 mono */

  display.setup();
  display.clearDisplay();

  //display.setTextColor();  BLACK,BLUE,RED,GREEN,CYAN,MAGENTA,YELLOW,WHITE
  //display.setTextColor(text color, background color);
  //Sets the text color and background color the text will print on.
  //For monochrome (single-color) displays, colors are always specified as simply 1 (set) or 0 (clear).
  display.setTextColor(WHITE);
  
  // Turn off text wrapping
  display.setTextWrap(false);
  
  // display.setFont(const GFX font);
  // See font information at: https://learn.adafruit.com/adafruit-gfx-graphics-library?view=all
  
  display.clearDisplay();
  //  .setTextSize(2): 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
  display.setTextSize(2);   
  //display.setCursor(horizontal position, vertical position);
  display.setCursor(0,0);   // rows @ 0,16
  //display.print("0123456789"); 10x2 chars
  display.print("Xenon_A");
  display.setCursor(0,16);   // rows @ 0,16
  display.print("v20201213");
  display.display();
  lastDisplay = millis();
  
  /* END AF FeatherWing OLED 128x32 mono */
   
  timerBlap = millis(); // reset the timer
  timerAlap = micros(); // reset the timer
}   // setup()


void loop() {
    
  // Sample pinADC every sampleWindow microseconds
  if (timerAlap > micros() || timerBlap > millis()) {
    // micros() or millis() rollover
    mVmax = 0.0;
    mVsum = 0.0;
    samples = 0;
    timerAlap = micros();// reset the timer
    timerBlap = millis(); // reset the timer
  }
  if (micros() - timerAlap > sampleWindow) {  
    float mV = Get_mVfromADC(pinAnalogIn);
    if (mV > mVmax) {
      mVmax = mV;
    }
    mVsum = mVsum + mV;
    samples++;
    timerAlap = micros(); // reset the timer
  } else if (millis() - timerBlap > timerB) {  
    // Report value of AnalogA0Val and mVmax every timerB milliseconds
    //blinkLED(pinLED);
    AnalogA0Val = mVsum/float(samples);
    display.clearDisplay();
    display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
    display.setCursor(0,0);   // rows @ 0,16
    display.print("A0=");
    display.setCursor(37,0);
    display.print(AnalogA0Val);
    display.display();
    lastDisplay = millis();
    String sAnalogA0Val =  String(AnalogA0Val, DEC);
    bool bResult;
    bResult = Particle.publish("xenonAeventA0", sAnalogA0Val, PRIVATE);
    if (bResult != 1) {
        // event did not publish
        digitalWrite(pinLED, HIGH);
    }
    mVmax = 0.0;
    mVsum = 0.0;
    samples = 0;
    timerAlap = micros();// reset the timer
    timerBlap = millis(); // reset the timer
  }

    display.loop();
    if (display.pressedA()) {
        // OLED button A on D2 pressed
    	display.clearDisplay();
    	display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
    	display.setCursor(0,0);   // rows @ 0,16
    	//display.print("012345678901234567890");  21 chars horizontally
    	display.println("Button A");
    	display.setCursor(0,16);
    	display.println("A0=");
    	display.setCursor(37,16);
    	display.println(AnalogA0Val);
    	display.display();
    	lastDisplay = millis();
    }
    
    if (display.pressedB()) {
        // OLED button B on D3 pressed
    	display.clearDisplay();
    	display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
    	display.setCursor(0,0);   // rows @ 0,16
    	display.println("Button B");
        display.setCursor(0,16);
        //Time.format(Time.now(), "Now it's %I:%M%p."); // Now it's 03:21AM.
        display.print(Time.format(Time.now(), "%I:%M%p"));
    	display.display();
    	lastDisplay = millis();
    }
    
    if (display.pressedC()) {
        // OLED button C on D4 pressed
    	display.clearDisplay();
    	display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
    	display.setCursor(0,0);   // rows @ 0,16
    	display.println("Button C");
        display.setCursor(0,16);
        display.print("v ");
        display.setCursor(21,16);
        display.print(System.version().c_str());
    	display.display();
    	lastDisplay = millis();
    }
    // Clear the display every 30 seconds
    if (lastDisplay > millis())  lastDisplay = millis();
    if (millis() - lastDisplay > 30000) { 
      display.clearDisplay();
      display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
      display.setCursor(0,0);   // rows @ 0,16
      //display.print("0123456789"); 10x2 chars
      display.print("Device:");
      display.setCursor(0,16);
      display.print("Zenon_A");
      display.display();
      lastDisplay = millis();
    }

  // Detect a change in the pushbutton state.
  // This logic is for a button using a pulldown resistor. 
  buttonState = digitalRead(pinPushButtonPullDown); 
  // if millis() or timer wraps around, reset it
  if (lastDebounceTime > millis())  lastDebounceTime = millis();
  if (buttonState != lastButtonState) { 
    // Multiple changes in the buttonState can occur when a pushbutton is 
    // pressed, or a switch is toggled. Use a debounce timer to only react
    // to a change in button state after an interval of debounceDelay. 
    lastButtonState = buttonState;
    //  Check if enough time has passed to evaluate another pushbutton press.
    if ((millis() - lastDebounceTime) > debounceDelay) {
      lastDebounceTime = millis();
      if (buttonState == HIGH) {
        digitalWrite(pinLED, HIGH);
        // Note that a Mesh.publish() cannot be simulated by the Particle Event Console,
        // and no events published by a device will be received by the Particle Event Console. 
        bool success;
        success = Mesh.publish("PushbuttonXenonA", "Pushbutton on Xenon_A");
        // Note that success == FALSE executes at least once. 
        // Therefore, it is better to check for success == TRUE
        if (success == 0) {
          // event published successfully
          digitalWrite(pinLED, LOW);
        } else {
          // Mesh.publish() error (leave the LED on)
          display.clearDisplay();
          display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
          display.setCursor(0,0);   // rows @ 0,16
          display.print("ERROR #");
          display.setCursor(97,0);
          display.print(success);   // error code number
          display.setCursor(0,24);
          display.setTextSize(1);   // 20 chars wide x 4 tall; each character is 6 pixels wide x 8 tall
          //display.print("012345678901234567890");  21 chars horizontally
          display.print("Mesh.publish()");
          display.display();
          lastDisplay = millis();
        } // success
      } // buttonState
    } // millis()
  } // buttonState != lastButtonState

}   // loop()


void PushbuttonArgonAEventHandler(const char *event, const char *data) {
  // Event handler for event "PushbuttonArgonA"
  //blinkLED(pinLED);
  display.clearDisplay();
  display.setTextWrap(false);
  display.setTextSize(1);   // 20 chars wide x 4 tall; each character is 6 pixels wide x 8 tall; 
  display.setCursor(0,0);   // rows @ 0,8,16,24
  display.print("Argon_A pushbutton");
  display.setCursor(0,24);   // rows @ 0,8,16,24
  display.print("data=");
  display.setCursor(31,24);   // rows @ 0,8,16,24; col=Len("data=")*6+1
  display.print(data);
  display.display();
  display.setTextWrap(true);
  lastDisplay = millis();
} //PushbuttonArgonAEventHandler()


// Cloud function "BlinkXenonLED" that blinks the Xenon LED on pinLED.
// This function gets called by a POST request
// curl https://api.particle.io/v1/devices/XENHAB925CN8WBJ/BlinkXenonA \
//     -d access_token=123412341234 \
//     -d "args=once"
int iBlinkXenonLED(String sBlinkCmd) {
  if(sBlinkCmd == "once") {
    blinkLED(pinLED);
    delay(10);
    blinkLED(pinLED);
    delay(10);
    blinkLED(pinLED);
    display.clearDisplay();
    display.setTextWrap(false);
    display.setTextSize(1);   // 20 chars wide x 4 tall; each character is 6 pixels wide x 8 tall; 
    display.setCursor(0,0);   // rows @ 0,8,16,24
    display.print("Cloud function:");
    display.setCursor(0,8);   // rows @ 0,8,16,24
    display.print("'BlinkZenonLED'");
    display.setCursor(0,24);   // rows @ 0,8,16,24
    display.print("args=");
    display.setCursor(31,24);  // rows @ 0,8,16,24; col=Len("args=")*6+1
    display.print(sBlinkCmd);
    display.display();
    display.setTextWrap(true);
    lastDisplay = millis();
    return 1;
  }
  else return -1;
} //  iBlinkXenonLED()


float Get_mVfromADC(byte AnalogPin) {
  // Reads the ADC for the pin AnalogPin and returns the voltage in millivolts.
  // Works with any ADC bit size, provided ADC_bits is set to the correct value.
  // *** Uses Aref, so you MUST feed a reference voltage to Aref.

  // The standard analogRead() function takes about 112 us for the Uno, Nano, Mega, 
  // and other AVR chips (8 kHz).  It takes 425 us for SAMD21 chips (Arduino Zero).
  // A special library analogReadFast() can decrease the analog read time to 21 us.  
  //   https://www.avdweb.nl/arduino/adc-dac/fast-10-bit-adc
  //   https://github.com/avandalen/avdweb_AnalogReadFast
  // Other techniques that modify the ADC prescaler can achieve 30 kHz. 
  
  // A note about 1023 & 1024:
  //  10 bit ADC has 2^10 = 1024 values on a scale from 0 to 1023.
  //  The correct ADC conversion is 1024 for a 10 bit ADC.
  //  The maximum ADC value is 1023 
  
  // read the value from the sensor:
  int ADCval = analogRead(AnalogPin);  
  //  Voltage at pin in milliVolts = (reading from ADC) * (vRef/pow(2,ADC_bits)) 
  //float mV = ADCval * (vRef / ( (float)pow(2,ADC_bits) ));
  float mV = ADCval * (vRef / (float(pow(2,ADC_bits))));
  //float mV = ADCval * (vRef / pow(2,ADC_bits));
  return mV;
} // Get_mVfromADC()


void blinkLED(byte ledPIN){
  //  consumes 300 ms.
  for(int i = 5; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(30);
    digitalWrite(ledPIN, LOW);
    delay(30);
  }    
} // blinkLED()

 

Argon / Boron

  • Subscribe to an event published by device Xenon_A, blinking the LED in response.


/* 
    particle.io Argon
    device name: Argon_A
    
    - Subscribe to a Mesh event published by device Xenon_A, 
      blinking the LED in response. 
    - Publish an event to the Particle Cloud that will be 
      forwarded to all registered listeners.
    - Publish the value of an analog input to the Particle Cloud, 
      allowing it to be retrieved by a call with: 
      GET /v1/devices/{DEVICE_ID}/{VARIABLE}.
    
    A0  Output from the photo resistor is published to the Device Cloud
        as the Particle variable "ArgonA0" every 25 seconds.

    D5  LED turns on when pushbutton is pressed, and blinks in response
        to an event published by device Xenon_A (pushbutton on Xenon_A).

    D7  Pushbutton triggers an event "PushbuttonArgonA" published to the 
        Particle Mesh network and is received by device Xenon_A.

*/

int pinLEDWhite = D5; 
int pinPushButtonPullUp = D6;
int buttonState = HIGH;         
int lastButtonState = HIGH;
int lastDebounceTime = 0;  // the last time the output pin was toggled
int debounceDelay = 50;    // The switch debounce time.  50 to 100 ms
int pinAnalogIn = A0;
double AnalogA0Val = 0.0;

Timer timerA0(25000, ReadA0);


void setup() {

  pinMode(pinLEDWhite, OUTPUT);
  pinMode(pinPushButtonPullUp, INPUT);
  pinMode(pinAnalogIn, INPUT);

  // Read pinAnalogIn every 25 seconds.
  timerA0.start();
  // Publish the value of A0 as a Particle variable.
  // The frequency of publishing is managed by Particle. 
  Particle.variable("ArgonA0",AnalogA0Val);

  // Subscribe to Particle Cloud event "PushbuttonXenonA" sent from Xenon_A
  // when the button is pressed on Xenon_A.  Blinks the LED when the event occurs. 
  Mesh.subscribe("PushbuttonXenonA", PushbuttonXenonAEventHandler);

  // Verify the LED works by blinking it. 
  blinkLED(pinLEDWhite);

}   // setup()


void loop() {
  
  // Detect a change in the pushbutton state.
  // This logic is for a button using a pullup resistor. 
  buttonState = digitalRead(pinPushButtonPullUp); 
  // if millis() or timer wraps around, reset it
  if (lastDebounceTime > millis())  lastDebounceTime = millis();
  if (buttonState != lastButtonState) { 
    // Multiple changes in the buttonState can occur when a pushbutton is 
    // pressed, or a switch is toggled. Use a debounce timer to only react
    // to a change in button state after an interval of debounceDelay. 
    lastButtonState = buttonState;
    //  Check if enough time has passed to evaluate another pushbutton press.
    if ((millis() - lastDebounceTime) > debounceDelay) {
      lastDebounceTime = millis();
      if (buttonState == LOW) {
        digitalWrite(pinLEDWhite, HIGH);
        bool bResult;
        // NOTE: If you Particle.publish() with no PUBLIC or PRIVATE parameter, then it
        //       defaults to PRIVATE and then you must use ALL_DEVICES for Particle.subscribe().
        //       Particle.publish("sEventName", "sData", PUBLIC) => Particle.subscribe(sEventName, ALL_DEVICES)
        //       Particle.publish("sEventName", "sData", PRIVATE) => Particle.subscribe(sEventName, MY_DEVICES)
        bResult = Particle.publish("PushbuttonArgonA", "Pushbutton on Argon_A", PRIVATE);
        if (!bResult) {
          // event publish did not work
          digitalWrite(pinLEDWhite, HIGH);
        }
      } else {
          digitalWrite(pinLEDWhite, LOW);
      } // buttonState
    } // millis()
  } // buttonState != lastButtonState

}   // loop()


void PushbuttonXenonAEventHandler(const char *event, const char *data) {
  // Event handler for event "PushbuttonXenonA"
  blinkLED(pinLEDWhite);
} //PushbuttonXenonAEventHandler()


// Create a timer and update AnalogA0Val every 25 seconds.
void ReadA0() {
  AnalogA0Val = analogRead(pinAnalogIn);
} // ReadA0()


void blinkLED(byte ledPIN){
  //  consumes 300 ms.
  for(int i = 5; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(30);
    digitalWrite(ledPIN, LOW);
    delay(30);
  }    
} // blinkLED()

Testing

  • Place both devices Zenon_A and Argon_A in close visual proximity to you and wherever you have access to the Particle Cloud IDE and Particle Console, and power them up.  
  • Go to the Particle Console Events page and manually trigger an event "PushbuttonArgonA" and observe device Zenon_A (it's LED should blink).  

  • While observing the Particle Console Events page, press the pushbutton on device Argon_A and confirm that an event named "PushbuttonArgonA" is received in the Device Cloud.  

    Press the pushbutton on device Argon_A again, and this time confirm that the LED on the device Zenon_A blinks in response to the published event.  
  • Briefly press the pushbutton on device Zenon_A.   You should observe the LED on Zenon_A illuminate while the button is pressed.   At the same time, watch the device Argon_A to see if it responds to the pushbutton by blinking it's LED.   Note that you cannot see (or simulate) this Mesh.publish() event on the Particle Console Events page because they are private between devices within the Mesh network.  
  • At the Particle Device Console, go to the page for Argon_A and click the 'Get' button at the lower right of the page under the section 'VARIABLES' to read a value for the published variable 'ArgonA0' published by device Argon_A.  

    The value for 'ArgonA0' is the value assigned by the timer function 'TimeA0' that executes the function 'ReadA0' every 25 seconds.   This is the digitalRead() value for the photo resistor output connected to analog input A0.  
  • At the Particle Device Console, go to the page for Zenon_A and click the 'Get' button at the lower right of the page under the section 'VARIABLES' to read a value for the published variable 'ZenonA0' published by device Zenon_A.  

    This value is the reading for the photo transistor connected to analog input A0.  
  • At the Particle Device Console, go to the page for Zenon_A and in the right panel under "FUNCTIONS", enter the string "once" (without double quotes), and click the 'CALL' key while observing the device Zenon_A.   You should see the LED blink in response to the function call, and the value of "1" should be returned to the Particle Device Console.  

Conclusions

  • The combination of the Particle Cloud, Mesh Network, and the devices Argon, Boron, and Xenon make a powererful and easy to use set of IoT tools.  
  • Each IoT device may communicate privately to other devices on the Mesh network, without generating unecessary data communcations over the internet.  
  • The Particle language extensions make it very easy to publish data to the Particle Cloud, and locally within the Mesh network.   API's are included to allow web hooks that may interact with the IoT devices through GET and POST calls.   The Particle Console provides tools to verify the functionality of these GET and POST calls, without the need for the developer to create a web page.  

 


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.