Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
![]() |
| |||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
| |||||
|
The Grand Green Grabber is a robotic grasping system designed with the goal of sampling plant species while attached to an Unmanned Aerial Vehicle. It is designed as a step toward solving a long standing problem in the area of plant research and understanding. How do you get testable materials from places that a person cannot readily go. Sure, you can rappel down a cliffside or climb trees to get into canopies, take a boat out to Islands or swim through the dense corn to to the center of the field. These things are all time consuming however. Many are also dangerous to varying degrees and require special training and knowledge. The ability to acquire samples from the air then can save time and money as well as reduce risk. Hence this grabber.
The basic design has two plates that clasp the target plant material along with a blade near the base that slices through the plant to create a clean cut along the base. One plate is moved by means of a screw up and down and it slides up to the static plate where the motor is located. The static plate also has a hole through it to allow the blade to pass. Among the sensors attached are a camera, a GPS system, and a range finder. These three provide information about the location. The camera gives a photo of the site and the initial state of the plant, The range finder helps give distance to target and helps to ensure that the grabber is positioned well for acquisition, and the GPS gives finer details on the relative location of the UAV. The whole system has a power down switch attached to put it into sleep mode or wake it up as necessary. This can be very useful for extending battery life and operational capacity in the field.
Youtube Video of project:
3D models found at:
GreenGrabber.cpp
C/C++/*
* Project myProject
* Author: Your Name
* Date:
* For comprehensive documentation and examples, please visit:
* https://docs.particle.io/firmware/best-practices/firmware-template/
*/
// Include Particle Device OS APIs
#include "Particle.h"
#include <JsonParserGeneratorRK.h>
#include "Adafruit_GPS.h"
#include "AdaFruit_VL53L0X.h"
#include "Stepper.h"
#include <Adafruit_MQTT.h>
#include "Adafruit_MQTT\Adafruit_MQTT_SPARK.h"
#include "Adafruit_MQTT\Adafruit_MQTT.h"
#include "credentials.h"
/************ Global State (you don't need to change this!) *** ***************/
TCPClient TheClient;
// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_SPARK mqtt(&TheClient,AIO_SERVER,AIO_SERVERPORT,AIO_USERNAME,AIO_KEY);
/****************************** Feeds ***************************************/
// Setup Feeds to publish or subscribe
Adafruit_MQTT_Publish GPSFeed = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/GPSCoordinates");
Adafruit_MQTT_Subscribe GreenGrabberControls = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/greengrabbercontrols");
Adafruit_MQTT_Publish TOFFeed = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/GrabberTOF");
Adafruit_MQTT_Subscribe GreenGrabberopen = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/greengrabberopen");
float pubValue;
int subValue;
unsigned int last, lastTime;
const int CLOSEBUTTON = D4;
const int SPR = 2048;
const int IN1 = D5;
const int IN2 = D6;
const int IN3 = D7;
const int IN4 = D10;
Stepper myStepper(SPR,IN1,IN3,IN2,IN4);
bool buttonPinState1;
bool virtualButtonState;
bool virtualButtonState2;
void createEventPayLoad(float latitude, float longitude);
int lastPublish;
const int RESETVALUE = 10000;
void getGPS(float *latitude, float *longitude, float *altitude, int *satellites);
Adafruit_GPS GPS(&Wire);
// Define Constants
const int TIMEZONE = -6;
const unsigned int UPDATE = 30000;
int OLED_RESET = -1;
// Declare Variables
float lat, lon, alt;
int sat;
unsigned int lastGPS;
void MQTT_connect();
bool MQTT_ping();
SYSTEM_MODE(AUTOMATIC);
// Run the application and system concurrently in separate threads
SYSTEM_THREAD(ENABLED);
Adafruit_VL53L0X lox = Adafruit_VL53L0X();
VL53L0X_RangingMeasurementData_t measure;
void setup() {
Serial.begin(115200);
// wait until serial port opens for native USB devices
while (! Serial) {
delay(1);
}
Serial.println("Adafruit VL53L0X test");
if (!lox.begin()) {
Serial.println(F("Failed to boot VL53L0X"));
while(1);
}
// power
Serial.println(F("VL53L0X API Simple Ranging example\n\n"));
myStepper.setSpeed(15);
pinMode(CLOSEBUTTON, OUTPUT);
// Connect to Internet but not Particle Cloud
WiFi.on();
WiFi.connect();
while(WiFi.connecting()) {
Serial.printf(".");
}
Serial.printf("\n\n");
// Setup MQTT subscriptions
mqtt.subscribe(&GreenGrabberControls);
mqtt.subscribe(&GreenGrabberopen);
//Iinitialization for the GPS signal
GPS.begin(0x10); // The I2C address to use is 0x10
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
GPS.sendCommand(PGCMD_ANTENNA);
delay(1000);
GPS.println(PMTK_Q_RELEASE);
}
void loop() {
// Get data from GSP unit (best if you do this continuously)
GPS.read();
if (GPS.newNMEAreceived()) {
if (!GPS.parse(GPS.lastNMEA())) {
return;
}
}
MQTT_connect();
MQTT_ping();
if (millis() - lastGPS > UPDATE) {
lastGPS = millis(); // reset the timer
getGPS(&lat,&lon,&alt,&sat);
//Serial.printf("\n=================================================================\n");
Serial.printf("Lat: %0.6f, Lon: %0.6f, Alt: %0.6f, Satellites: %i\n",lat, lon, alt, sat);
//Serial.printf("=================================================================\n\n");
}
Serial.print("Reading a measurement... ");
lox.rangingTest(&measure, false); // pass in 'true' to get debug data printout!
if (measure.RangeStatus != 4) { // phase failures have incorrect data
Serial.print("Distance (mm): "); Serial.println(measure.RangeMilliMeter);
} else {
Serial.println(" out of range ");
}
buttonPinState1 = digitalRead(CLOSEBUTTON);
if(buttonPinState1){
myStepper.step(-1000);
} else {
myStepper.step(0);
}
Serial.printf("The value of the button is %s \n", buttonPinState1 ? "true" : "false");
pubValue = random(100);
// this is our 'wait for incoming subscription packets' busy subloop
Adafruit_MQTT_Subscribe *subscription1;
while ((subscription1 = mqtt.readSubscription (200))) {
if (subscription1 == &GreenGrabberControls) {
virtualButtonState = atoi((char *)GreenGrabberControls.lastread);
Serial.printf("subValue is: %i \n", virtualButtonState);
}
}
if(virtualButtonState){
myStepper.step(-100);
} else {
myStepper.step(0);
}
Adafruit_MQTT_Subscribe *subscription2;
while ((subscription2 = mqtt.readSubscription (200))) {
if (subscription2 == &GreenGrabberopen) {
virtualButtonState2 = atoi((char *)GreenGrabberopen.lastread);
Serial.printf("subValue is: %i \n", virtualButtonState2);
}
}
if (millis() - lastPublish > RESETVALUE) {
lastPublish = millis();
TOFFeed.publish(measure.RangeMilliMeter);
}
if(virtualButtonState2){
myStepper.step(100);
} else {
myStepper.step(0);
}
// if((millis()-lastTime > 6000)) {
// if(mqtt.Update()) {
// GPSFeed.publish(lat);
// Serial.printf("Publishing %0.2f \n",pubValue);
//Serial.printf("Current random number is %i \n", num);
// }
// lastTime = millis();
//}
}
void MQTT_connect() {
int8_t ret;
// Return if already connected.
if (mqtt.connected()) {
return;
}
Serial.print("Connecting to MQTT... ");
while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
Serial.printf("Error Code %s\n",mqtt.connectErrorString(ret));
Serial.printf("Retrying MQTT connection in 5 seconds...\n");
mqtt.disconnect();
delay(5000); // wait 5 seconds and try again
}
Serial.printf("MQTT Connected!\n");
}
bool MQTT_ping() {
static unsigned int last;
bool pingStatus;
if ((millis()-last)>120000) {
Serial.printf("Pinging MQTT \n");
pingStatus = mqtt.ping();
if(!pingStatus) {
Serial.printf("Disconnecting \n");
mqtt.disconnect();
}
last = millis();
}
return pingStatus;
}
void getGPS(float *latitude, float *longitude, float *altitude, int *satellites){
int theHour;
theHour = GPS.hour + TIMEZONE;
if(theHour < 0) {
theHour = theHour + 24;
}
Serial.printf("Time: %02i:%02i:%02i:%03i\n",theHour, GPS.minute, GPS.seconds, GPS.milliseconds);
Serial.printf("Dates: %02i-%02i-20%02i\n", GPS.month, GPS.day, GPS.year);
Serial.printf("Fix: %i, Quality: %i",(int)GPS.fix,(int)GPS.fixquality);
if (GPS.fix) {
*latitude = GPS.latitudeDegrees;
*longitude = GPS.longitudeDegrees;
*altitude = GPS.altitude;
*satellites = (int)GPS.satellites;
createEventPayLoad(*latitude, *longitude);
}
}
void createEventPayLoad (float latitude, float longitude) {
JsonWriterStatic<256> jw;
{
JsonWriterAutoObject obj(&jw);
jw.insertKeyValue ("lat", latitude );
jw.insertKeyValue ("lon", longitude );
}
GPSFeed.publish(jw.getBuffer());
}
#include <Arduino.h>
#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h" //disable brownout problems
#include "soc/rtc_cntl_reg.h" //disable brownout problems
#include "esp_http_server.h"
//Replace with your network credentials
const char* ssid = "DDCIOT";
const char* password = "ddcIOT2020";
// other camera models listed in "camera_pins.h"
// #define CAMERA_MODEL_ESP32S3_EYE
// #include "camera_pins.h"
const int ONBOARD_LED = 2;
#include "credentials.h"
const char* mqttTopicSnap = "Tigress/feeds/GrabberCam";
const char* mqttTopicStream = "Tigress/feeds/GrabberStream";
const int MQTT_BUFFERSIZE = 50 * 1024;
const int MAX_PUBLISH = 50 * 1024; // Adafruit limit is 100 kB, not 50
// these are used for image publication to Adafruit dashboard
#include <PubSubClient.h>
#include <base64.h>
bool pin2On = false; // used for irregular LED flashing while attempting serial connection, as serial is often not up
const int PUBLISH_DELAY = 30000; // 5 minutes between publishing images
const int SERIAL_TIMEOUT = 0*1000; // 0 seconds to wait for serial connection - often absent
int lastTick = 0; // for timing
WiFiClient espClient;
PubSubClient client(espClient);
void mqtt_setup();
void callback(char* topic, byte* payload, unsigned int length);
char strOut[1024]; // for Adafruit text publications (1kB limit when Dashboard control history is on)
String buffer; // for Adafruit image publication (100kB limit when Dashboard control history is on)
#define PART_BOUNDARY "123456789000000000000987654321"
// This project was tested with the AI Thinker Model, M5STACK PSRAM Model and M5STACK WITHOUT PSRAM
#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM
// Not tested with this model
//#define CAMERA_MODEL_WROVER_KIT
#if defined(CAMERA_MODEL_WROVER_KIT)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 21
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 19
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 5
#define Y2_GPIO_NUM 4
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_M5STACK_WITHOUT_PSRAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 17
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_AI_THINKER)
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#else
#error "Camera model not selected"
#endif
void mqtt_setup();
void callback(char* topic, byte* payload, unsigned int length);
void setup() {
Serial.begin(9600);
pinMode(ONBOARD_LED, OUTPUT);
while(!Serial.available() && millis() - lastTick < SERIAL_TIMEOUT){
digitalWrite(ONBOARD_LED, pin2On = !pin2On);
delay(200 + 200 * (random()%2)); // random on/off delays of either 200 or 400 ms
Serial.begin(9600);
}
Serial.setDebugOutput(true);
Serial.println("Serial is up!");
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.frame_size = FRAMESIZE_VGA; // limited for 100 kB Adafruit limit for publishing
config.pixel_format = PIXFORMAT_JPEG;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_DRAM;
config.jpeg_quality = 10; // (0-63) higher numbers are lower quality
config.fb_count = 1;
// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
sensor_t * s = esp_camera_sensor_get();
// adjusted for spefic use in class
s->set_agc_gain(s, 15); // (1 - 31)
s->set_gain_ctrl(s, 1); // white balance gain control (0 or 1)
s->set_brightness(s, 2); // (-2 to 2) set to brightest
WiFi.begin(ssid, password);
WiFi.setSleep(false);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
}
// publish a pic, then wait
void loop() {
mqtt_setup(); // refresh to keep alive
static camera_fb_t *fb = NULL;
fb = esp_camera_fb_get();
if(!fb) {
Serial.println("Camera capture failed");
client.publish(mqttTopicStream, "Camera capture failed\0");
return;
}
// Adafruit insists on 64-bit encoded jpegs
buffer = base64::encode((uint8_t *)fb->buf, fb->len);
sprintf(strOut, "Got frame: %d x %d (%d/%d)\0", fb->width, fb->height, fb->len, buffer.length());
client.publish(mqttTopicStream, strOut);
Serial.printf("%s\n", strOut);
if (buffer.length() < MAX_PUBLISH) {
if (client.publish(mqttTopicSnap, buffer.c_str()))
{
Serial.print("Published Image to ");
Serial.print(mqttTopicSnap);
Serial.printf("\n");
client.publish(mqttTopicStream, "Published!\0");
Serial.printf("Published %d bytes (from %d)\n", buffer.length(), fb->len);
}
else
{
Serial.println("Error Publishing Image");
client.publish(mqttTopicStream, "Error publishing...\0");
}
} else {
client.publish(mqttTopicStream, "Over limit - We'll try to publish the next pic\0");
}
esp_camera_fb_return(fb);
buffer.clear();
delay(PUBLISH_DELAY); // use a delay here - time since last attempt ended, not started
}
//------------------ MQTT ----------------------------------
void mqtt_setup() {
// Adafruit publication limit is 100kB with Dashboard control history off
// Buffersize is set lower based on successful publish history
client.setBufferSize((uint16_t)MQTT_BUFFERSIZE);
client.setServer(AIO_SERVER, AIO_SERVERPORT);
client.setCallback(callback);
Serial.println("Connecting to MQTT");
while (!client.connected()) {
String clientId = "ESP32Client-";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str(), AIO_USERNAME, AIO_KEY)) {
Serial.println("connected");
} else {
Serial.print("failed with state ");
Serial.println(client.state());
delay(2000);
}
}
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived in topic: ");
Serial.println(topic);
String byteRead = "";
Serial.print("Message: ");
for (int i = 0; i < length; i++) {
byteRead += (char)payload[i];
}
Serial.println(byteRead);
}
Comments
Please log in or sign up to comment.