Hardware components | ||||||
![]() |
| × | 2 | |||
| × | 1 | ||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 8 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
| |||||
![]() |
| |||||
![]() |
|
Tournament sportfishing is a major industry in South Florida. Professional fishing teams compete for multi-million dollar prizes and bragging rights among the community. The most competitive of the teams all have one thing in common, live bait. Live bait is the key to successful sportfishing and proper maintenance and cultivation of coveted baits is required to ensure success. Bait needs to be caught ahead of time and stored in pens over extended periods of time. The bait pens require constant fresh seawater flow to keep the bait alive. The pumps which supply this flow can become clogged reducing their performance or damaging the pump and killing the bait.
HowThe pump tender uses two Particle Photons to monitor and control bait pen water supply pumps. The first Photon monitors post pump pressure for evidence of restrictions both up- and down-stream of the supply pump. The pressure is published to Thingspeak (https://thingspeak.com/channels/362459) and monitored for unusual increases or decreases in pressure.
The data measured is also sent to the second Photon where the user is alerted through status lights if the system requires administration and OLED display of the pump pressure at time of alert. The user can decide to:
- Turn off the main pump
- Turn on an auxiliary pump and switch monitoring to new channel
- Do nothing
The sensor was calibrated using Labview to determine a voltage to pressure relationship to be used in the monitoring algorithm.
The primary photon will be mounted near the pump and contain a relay for pump switching. The photon will monitor pressure to determine system conditions. If water pressure drops or rises this indicates a pre- or post-pump restriction. In either situation an alert is triggered and sent to the cloud through Particle.Publish to the secondary photon where a Red LED is illuminated through a digital pin. Real-time pressure is displayed on the module through an OLED display. The pressure reading is filtered using an active filter which includes a LM358 Op-amp, an 100k ohm resistor and a 22k ohm resistor in series, and an 100 µF capacitor. This filter prevents erroneous alerts caused by rapid changes in pressure or signal noise. Alerts also trigger a text message notification through IFTTT and Android SMS to alert the user on the go.
The secondary photon will be mobile or tied to a computer or other 5V USB supply so the user can make real-time decisions when connected to the cloud. When the primary Photon triggers an alert, the secondary Photon receives a notification through Particle.Subscribe with the status of the pump pressure and the pressure reading at the time of the alert. The pressure reading is gathered using Particle.variable and displayed on the optional OLED. The user commands the control of the pump through Particle.Publish which triggers the control relay on the primary photon disconnecting power from the pump. A status led on the module indicates the pump control status and pressure status using visual report.
The system can be used to monitor and control as many pumps as the photon has switches. The code takes advantage of the Tinker app by maintaining the user interface which allows the user to read raw bit values and control digital and analog pins from the app. This project can be accomplished using a single Photon or using two for additional functionality and user interface features.
Pump Monitor Video//Including Libraries for OLED
#include <SparkFunMicroOLED.h>
#include <math.h>
#define publish_delay 600000//Delay between Thingspeak publish events (ms)
unsigned int lastPublish = 599750;//Initial publish time, set so user reset pushes most recent pressure reading
/* Include the SparkFunMicroOLED library
Hardware Connections:
This sketch was written specifically for the Photon Micro OLED Shield, which does all the wiring for you. If you have a Micro OLED breakout, use the following hardware setup:
MicroOLED ------------- Photon
GND ------------------- GND
VDD ------------------- 3.3V (VCC)
D1/MOSI ----------------- A5 (don't change)
D0/SCK ------------------ A3 (don't change)
D2
D/C ------------------- D6 (can be any digital pin)
RST ------------------- D7 (can be any digital pin)
CS ------------------- A2 (can be any digital pin)
*/
int tinkerDigitalRead(String pin);
int tinkerDigitalWrite(String command);
int tinkerAnalogRead(String pin);
int tinkerAnalogWrite(String command);
// -------------------------------------------
// Publish and Subscribe with Pressure Sensors
/* -------------------------------------------
Built from "Publish and Subscribe with Photoresistors" project example downloaded from Particle
Go find a buddy who also has a Particle device.
Each of you will pick a unique event name
(make it weird so that no one else will have it)
(no more that 63 ASCII characters, and no spaces)
In the following code, replace "pump_pressure" with your chosen name.
Replace "pump_control" with your buddy's chosen name.
Have your buddy do the same on his or her IDE.
Then, each of you should flash the code to your device.
Breaking the threshold pressure on one device will turn on the RGB LED on the second device.
But how does this magic work? Through the miracle of publish and subscribe.
We are going to Particle.publish a public event to the cloud.
That means that everyone can see you event and anyone can subscribe to it.
You and your buddy will both publish an event, and listen for each others events.
------------------------------------------*/
int led_red = D0; // RED LED Pin D0
int led_green = D1;//Green LED Pin D1
int led_blue = D2;//Blue LED Pin D2
int led_pump = D5;//Pump status indicator White LED Pin D5
//int boardLed = D7;//Board LED Pin D7
int pressure = A0;// Pressure sensor input
int power = A1;//Pressure Sensor Voltage(+) Pin
int normalValue;//Set a variable to call later
int highValue;//Set a variable to call later
int lowValue;//Set a variable to call later
int highThreshold;//Set a variable to call later
int lowThreshold;//Set a variable to call later
bool presHigh = true;//Set initial condition for pump pressure (normal)
MicroOLED oled;// Establish the OLED Display
void setup() {
pinMode(led_green,OUTPUT);// Our LED pins are output (lighting up the LEDs)
pinMode(led_blue,OUTPUT);//Our LED pins are output (lighting up the LEDs)
pinMode(led_red,OUTPUT);
pinMode(led_pump,OUTPUT);//Our LED pins are output (lighting up the LEDs)
// pinMode(boardLed,OUTPUT); // Our on-board LED is output as well
pinMode(pressure,INPUT); // Our pressure sensor pin is input (reading the pressure sensor)
//pinMode(power,INPUT); // The pin powering the pressure sensor is output (sending out consistent power)(notused)
digitalWrite(led_green,HIGH);// Turn on status light (Green)
digitalWrite(led_red,HIGH);// Ensure other LED is off
digitalWrite(led_blue,HIGH);// Ensure other LED is off
// Here we are going to subscribe to the pump control station event using Particle.subscribe
// Subscribe will listen for the event Turn_pump_off and, when it finds it, will run the function myHandler()
Particle.subscribe("Pump_control", myHandler);
// myHandler() is declared later in this app.
// Next, write the power of the photoresistor to be the maximum possible, which is 4095 in analog.(notused)
oled.begin(); // Initialize the OLED display
oled.clear(ALL); // Clear the display's internal memory
oled.display(); // Display what's in the buffer (splashscreen)
delay(250); // Delay 250 ms
oled.clear(PAGE); // Clear the buffer.
randomSeed(analogRead(A0));
Particle.function("digitalread", tinkerDigitalRead);
Particle.function("digitalwrite", tinkerDigitalWrite);
Particle.function("analogread", tinkerAnalogRead);
Particle.function("analogwrite", tinkerAnalogWrite);
// Pressure Sensor calibration.
normalValue = 385;//Measure pump pressure under normal operating conditions set the normal value
highValue = 450;//Measure pump pressure under abnormal operating conditions set the high value
lowValue = 300;//Measure pump pressure under abnormal operating conditions set the low value
highThreshold = (normalValue+highValue)/2;// Calculate a threshold for alert trigger
lowThreshold = (normalValue+lowValue)/2;// Calculate a threshold for alert trigger
}
void loop() {
if (Particle.connected() == false) {
Particle.connect();
}
gaugeDisplay();
delay(30000); //Delay alerts until pressure is normalized.
// This loop sends a publish when the pressure is high.
if (analogRead(pressure)<highThreshold&&analogRead(pressure)>lowThreshold) {// System must meet both requirements for normal publish
if (presHigh==true) {
Particle.publish("Pump_pressure","normal");
// digitalWrite(led_green, LOW);
// publish this public event
// Set the flag to reflect the current status of the beam.
presHigh=false;
}
}
else {
if (presHigh==false) {
// Same deal as before...
Particle.publish("Pump_pressure","abnormal");
presHigh=true;
}
}
// To blink the LED, when pressure is high
if (presHigh==true) {
digitalWrite(led_green, HIGH);
digitalWrite(led_red, HIGH);
digitalWrite(led_blue, LOW);
delay(500);
// Then we'll turn it off...
digitalWrite(led_green, LOW);
digitalWrite(led_red, HIGH);
digitalWrite(led_blue, HIGH);
delay(500);
digitalWrite(led_green, HIGH);
digitalWrite(led_red, LOW);// Running the RED LED last to leave the light red until user input
digitalWrite(led_blue, HIGH);
delay(500);
}
unsigned long now = millis(); // Publish the pressure data collected to thingspeak for plotting
if ((now - lastPublish) < publish_delay) {
return;
}
int value = (((analogRead(A0))*.26)+.5);
Particle.publish("thingSpeakWrite_A0", "{ \"1\": \"" + String(value) + "\", \"k\": \"ZB9COBBPARXH3UAJ\" }", 60, PRIVATE);//ZB9COBBPARXH3UAJ
lastPublish = now;
Particle.variable("Control", value);//ZB9COBBPARXH3UAJ
}
// Now for the myHandler function, which is called when the cloud tells us that our buddy's event is published.
void myHandler(const char *event, const char *data){
/* Particle.subscribe handlers are void functions, which means they don't return anything.
They take two variables-- the name of your event, and any data that goes along with your event.
In this case, the event will be "Pump_control" and the data will be "normal" or "high"
Since the input here is a char, we can't do
data=="normal"
or
data=="high"
chars just don't play that way. Instead we're going to strcmp(), which compares two chars.
If they are the same, strcmp will return 0.
*/
if (strcmp(data,"on")==0) {
// if pressure is normal, then turn your board LED off
// digitalWrite(boardLed,LOW); //Status light remains off
digitalWrite(led_green,LOW);
digitalWrite(led_red,HIGH);
digitalWrite(led_blue,HIGH);
// digitalWrite(led_pump,LOW);
delay(1000);
}
else if (strcmp(data,"off")==0) {
// if Pump pressure is high, turn your board LED on
// digitalWrite(boardLed,HIGH); // Status Light turns on
digitalWrite(led_red,LOW);
digitalWrite(led_green,HIGH);
digitalWrite(led_blue,HIGH);//Pump turns off
// digitalWrite(led_pump,HIGH);
delay(1000);
}
else{} // Really the data shouldn't be anything but those two listed above.
}
// Below is to display on OLED display
void gaugeDisplay()
{
for (int i=0; i<25; i++)
{
oled.clear(PAGE); // Clear the display
oled.setCursor(0, 0); // Set cursor to top-left
//oled.setFontType(0); // Smallest font
//oled.print("Pressure :"); // Print "A0"
//const char kPa=101.5;
oled.setFontType(3); // 7-segment font
oled.print(((analogRead(pressure)*.26)+.5)); // Print a0 reading P_out=0.26*V + 0.05
//oled.setCursor(0, 16); // Set cursor to top-middle-left
//oled.setFontType(0); // Repeat
//oled.print("A1:");
//oled.setFontType(2);
//oled.print(analogRead(A1));
//oled.setCursor(0, 32);
//oled.setFontType(0);
//oled.print("A7:");
//oled.setFontType(2);
//oled.print(analogRead(A7));
oled.display();
delay(100);
}
}
/*******************************************************************************
* Function Name : tinkerDigitalRead
* Description : Reads the digital value of a given pin
* Input : Pin
* Output : None.
* Return : Value of the pin (0 or 1) in INT type
Returns a negative number on failure
*******************************************************************************/
int tinkerDigitalRead(String pin)
{
//convert ascii to integer
int pinNumber = pin.charAt(1) - '0';
//Sanity check to see if the pin numbers are within limits
if (pinNumber< 0 || pinNumber >7) return -1;
if(pin.startsWith("D"))
{
pinMode(pinNumber, INPUT_PULLDOWN);
return digitalRead(pinNumber);
}
else if (pin.startsWith("A"))
{
pinMode(pinNumber+10, INPUT_PULLDOWN);
return digitalRead(pinNumber+10);
}
return -2;
}
/*******************************************************************************
* Function Name : tinkerDigitalWrite
* Description : Sets the specified pin HIGH or LOW
* Input : Pin and value
* Output : None.
* Return : 1 on success and a negative number on failure
*******************************************************************************/
int tinkerDigitalWrite(String command)
{
bool value = 0;
//convert ascii to integer
int pinNumber = command.charAt(1) - '0';
//Sanity check to see if the pin numbers are within limits
if (pinNumber< 0 || pinNumber >7) return -1;
if(command.substring(3,7) == "HIGH") value = 1;
else if(command.substring(3,6) == "LOW") value = 0;
else return -2;
if(command.startsWith("D"))
{
pinMode(pinNumber, OUTPUT);
digitalWrite(pinNumber, value);
return 1;
}
else if(command.startsWith("A"))
{
pinMode(pinNumber+10, OUTPUT);
digitalWrite(pinNumber+10, value);
return 1;
}
else return -3;
}
/*******************************************************************************
* Function Name : tinkerAnalogRead
* Description : Reads the analog value of a pin
* Input : Pin
* Output : None.
* Return : Returns the analog value in INT type (0 to 4095)
Returns a negative number on failure
*******************************************************************************/
int tinkerAnalogRead(String pin)
{
//convert ascii to integer
int pinNumber = pin.charAt(1) - '0';
//Sanity check to see if the pin numbers are within limits
if (pinNumber< 0 || pinNumber >7) return -1;
if(pin.startsWith("D"))
{
return -3;
}
else if (pin.startsWith("A0"))// Setting Calibrated output for Tinker App display
{
return (analogRead(pinNumber+10));
}
else if (pin.startsWith("A"))
{
return (analogRead(pinNumber+10));
}
return -2;
}
/*******************************************************************************
* Function Name : tinkerAnalogWrite
* Description : Writes an analog value (PWM) to the specified pin
* Input : Pin and Value (0 to 255)
* Output : None.
* Return : 1 on success and a negative number on failure
*******************************************************************************/
int tinkerAnalogWrite(String command)
{
//convert ascii to integer
int pinNumber = command.charAt(1) - '0';
//Sanity check to see if the pin numbers are within limits
if (pinNumber< 0 || pinNumber >7) return -1;
String value = command.substring(3);
if(command.startsWith("D"))
{
pinMode(pinNumber, OUTPUT);
analogWrite(pinNumber, value.toInt());
return 1;
}
else if(command.startsWith("A"))
{
pinMode(pinNumber+10, OUTPUT);
analogWrite(pinNumber+10, value.toInt());
return 1;
}
else return -2;
Particle.publish("Pump_Pressure", "150", 21600);
}
// This #include statement was automatically added by the Particle IDE.
#include <SparkFunMicroOLED.h>
#include <math.h>
//#define max_pres 160
//#define min_pres 70
#define publish_delay 600000//Delay between Thingspeak publish events (ms)
unsigned int lastPublish = 599750;//Initial publish time, set so user reset pushes most recent pressure reading
/* Include the SparkFunMicroOLED library
Hardware Connections:
This sketch was written specifically for the Photon Micro OLED Shield, which does all the wiring for you. If you have a Micro OLED breakout, use the following hardware setup:
MicroOLED ------------- Photon
GND ------------------- GND
VDD ------------------- 3.3V (VCC)
D1/MOSI ----------------- A5 (don't change)
D0/SCK ------------------ A3 (don't change)
D2
D/C ------------------- D6 (can be any digital pin)
RST ------------------- D7 (can be any digital pin)
CS ------------------- A2 (can be any digital pin)
*/
int Pump_off_led = D3; //Any Digital Pin connected to switch
int Pump_off = A0;
int Pump_on_led = D4;
int boardLed = D7; // lights led on doard
int led_green = D1; //4 pin LED - Pin 2 to pull up resistor
int led_red = D0;
int led_blue = D2;
int Threshold;
int presNormal;
double presRead;
int tinkerDigitalRead(String pin);
int tinkerDigitalWrite(String command);
int tinkerAnalogRead(String pin);
int tinkerAnalogWrite(String command);
int pressure(String command);
MicroOLED oled;// Establish the OLED Display
void setup() {
// Subscribe to the integration response event
Particle.subscribe("Pump_pressure", myHandler);
Particle.variable("Control", presRead);
//Particle.variable("presRead", &presRead, DOUBLE);
pinMode(led_green,OUTPUT);// Our LED pins are output (lighting up the LEDs)
pinMode(Pump_on_led,OUTPUT);
pinMode(Pump_off_led,OUTPUT);// 3 POSITION SWITCH FROM SPARK FUN KIT
pinMode(Pump_off,INPUT);
//pinMode(Pump_on,INPUT_PULLDOWN);//OTHER TERMINAL OF 3 POSITION SWITCH
pinMode(led_blue,OUTPUT);
pinMode(led_red,OUTPUT);
pinMode(boardLed,OUTPUT); // Our on-board LED is output as well
//pinMode(pressure,INPUT); // Our pressure sensor pin is input (reading the pressure sensor)
//pinMode(power,INPUT); // The pin powering the photoresistor is output (sending out consistent power)
digitalWrite(led_red,HIGH);
digitalWrite(led_green,HIGH);
digitalWrite(led_blue,HIGH);
digitalWrite(Pump_on_led,HIGH);
digitalWrite(Pump_off_led,HIGH);
oled.begin(); // Initialize the OLED display
oled.clear(ALL); // Clear the display's internal memory
oled.display(); // Display what's in the buffer (splashscreen)
delay(250); // Delay 250 ms
oled.clear(PAGE); // Clear the buffer.
randomSeed(analogRead(A0));
Particle.function("digitalread", tinkerDigitalRead);
Particle.function("digitalwrite", tinkerDigitalWrite);
Particle.function("analogread", tinkerAnalogRead);
Particle.function("analogwrite", tinkerAnalogWrite);
Threshold = 700;
bool presNormal = true;
}
void loop() {
gaugeDisplay();
if (analogRead(Pump_off)>Threshold) {
if (presNormal==true) {
Particle.publish("Pump_control","off");
digitalWrite(Pump_on_led, LOW);
digitalWrite(Pump_off_led, HIGH);
digitalWrite(led_red, HIGH);
delay(1000);
// publish this public event
// Set the flag to reflect the current status of the beam.
presNormal=false;
}
}
else {
if (presNormal==false) {
// Same deal as before...
//j=j+1;
//if (j==100) {
Particle.publish("Pump_control","on");
digitalWrite(Pump_on_led, HIGH);
digitalWrite(Pump_off_led, LOW);
digitalWrite(led_green, HIGH);
presNormal=true;
delay(1000);
//j=1; }
//else {
// }
}
}
}
void myHandler(const char *event, const char *data)
{
/* Particle.subscribe handlers are void functions, which means they don't return anything.
They take two variables-- the name of your event, and any data that goes along with your event.
In this case, the event will be "Pump_control" and the data will be "normal" or "high"
Since the input here is a char, we can't do
data=="normal"
or
data=="high"
chars just don't play that way. Instead we're going to strcmp(), which compares two chars.
If they are the same, strcmp will return 0.
*/
if (strcmp(data,"normal")==0) {
// if pressure is normal, then turn your board LED off
digitalWrite(boardLed,HIGH); //Status light remains green
digitalWrite(led_green,LOW);
digitalWrite(led_red,HIGH);
digitalWrite(led_blue,HIGH);
}
else if (strcmp(data,"abnormal")==0) {
// if Pump pressure is high, turn your board LED on
digitalWrite(boardLed,LOW); // Status Light turns RED
digitalWrite(led_red,LOW);
digitalWrite(led_green,HIGH);
digitalWrite(led_blue,HIGH);
}
}
//int presRead = Particle.variable();{}
/*int pressure(String controlstring)
{
if(controlstring.startsWith("data"))
{
controlstring = controlstring.substring(4, controlstring.length());
presRead = controlstring.toFloat();
//presRead = 100;
//settemp = min(settemp, maxsettemp);
//settemp = max(settemp, minsettemp);
//heaterenable = TRUE;
//circenable = TRUE;
//timeheatercheck = 0;
//return controlstring.toInt();
//settemp = 98.0;
}
else{}
}
*/
void gaugeDisplay()
{
for (int i=0; i<25; i++)
{
oled.clear(PAGE); // Clear the display
oled.setCursor(0, 0);
oled.setFontType(3); // 7-segment font
oled.print("100");// Set cursor to top-left
oled.display();
delay(250);
}
}
/*******************************************************************************
* Function Name : tinkerDigitalRead
* Description : Reads the digital value of a given pin
* Input : Pin
* Output : None.
* Return : Value of the pin (0 or 1) in INT type
Returns a negative number on failure
*******************************************************************************/
int tinkerDigitalRead(String pin)
{
//convert ascii to integer
int pinNumber = pin.charAt(1) - '0';
//Sanity check to see if the pin numbers are within limits
if (pinNumber< 0 || pinNumber >7) return -1;
if(pin.startsWith("D"))
{
pinMode(pinNumber, INPUT_PULLDOWN);
return digitalRead(pinNumber);
}
else if (pin.startsWith("A"))
{
pinMode(pinNumber+10, INPUT_PULLDOWN);
return digitalRead(pinNumber+10);
}
return -2;
}
/*******************************************************************************
* Function Name : tinkerDigitalWrite
* Description : Sets the specified pin HIGH or LOW
* Input : Pin and value
* Output : None.
* Return : 1 on success and a negative number on failure
*******************************************************************************/
int tinkerDigitalWrite(String command)
{
bool value = 0;
//convert ascii to integer
int pinNumber = command.charAt(1) - '0';
//Sanity check to see if the pin numbers are within limits
if (pinNumber< 0 || pinNumber >7) return -1;
if(command.substring(3,7) == "HIGH") value = 1;
else if(command.substring(3,6) == "LOW") value = 0;
else return -2;
if(command.startsWith("D"))
{
pinMode(pinNumber, OUTPUT);
digitalWrite(pinNumber, value);
return 1;
}
else if(command.startsWith("A"))
{
pinMode(pinNumber+10, OUTPUT);
digitalWrite(pinNumber+10, value);
return 1;
}
else return -3;
}
/*******************************************************************************
* Function Name : tinkerAnalogRead
* Description : Reads the analog value of a pin
* Input : Pin
* Output : None.
* Return : Returns the analog value in INT type (0 to 4095)
Returns a negative number on failure
*******************************************************************************/
int tinkerAnalogRead(String pin)
{
//convert ascii to integer
int pinNumber = pin.charAt(1) - '0';
//Sanity check to see if the pin numbers are within limits
if (pinNumber< 0 || pinNumber >7) return -1;
if(pin.startsWith("D"))
{
return -3;
}
else if (pin.startsWith("A"))
{
return analogRead(pinNumber+10);
}
return -2;
}
/*******************************************************************************
* Function Name : tinkerAnalogWrite
* Description : Writes an analog value (PWM) to the specified pin
* Input : Pin and Value (0 to 255)
* Output : None.
* Return : 1 on success and a negative number on failure
*******************************************************************************/
int tinkerAnalogWrite(String command)
{
//convert ascii to integer
int pinNumber = command.charAt(1) - '0';
//Sanity check to see if the pin numbers are within limits
if (pinNumber< 0 || pinNumber >7) return -1;
String value = command.substring(3);
if(command.startsWith("D"))
{
pinMode(pinNumber, OUTPUT);
analogWrite(pinNumber, value.toInt());
return 1;
}
else if(command.startsWith("A"))
{
pinMode(pinNumber+10, OUTPUT);
analogWrite(pinNumber+10, value.toInt());
return 1;
}
else return -2;
}
Comments
Please log in or sign up to comment.