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:
Configure a Xenon and a Argon or Boron as illustrated below.
/*
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()
/*
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()
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.