Lehmitz
Published © MIT

The Grand Green Grabber

Obtaining plants can be difficult so this project aims to build a drone arm that can be flown to these locations to acquire them.

IntermediateFull instructions providedOver 2 days61
The Grand Green Grabber

Things used in this project

Hardware components

Photon 2
Particle Photon 2
×1
Time-of-Flight (ToF) VL53L0X Laser Ranging Unit (MCP4725/)
M5Stack Time-of-Flight (ToF) VL53L0X Laser Ranging Unit (MCP4725/)
×1
Rohs Stetp Motor
×1
Yellow Button
×1
Adafruit Stemma GPS
×1

Software apps and online services

Adafruit.io
Visual Studio 2017
Microsoft Visual Studio 2017
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Super Glue

Story

Read more

Custom parts and enclosures

Static Plate

Mobile Plate

Breadboard Box

Schematics

Functions Flowchart for Green Grabber

Green Grabber Fritzing Diagram

This is the basic outline for the wiring layout on the main breadboard for this project. Note that the camera is effectively self contained and so, while included, does not have any direct connection with the rest of the components.

Green Grabber Fritzing Diagram

Fritizing Board Diagram for Green Grabber

Wiring Schematic for Green Grabber

Code

GreenGrabber.cpp

C/C++
This is the code for the GreenGrabber UAV arm. It runs most of the sensors and systems. Absent is the Camera which has its own independent code.
/* 
 * 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());
}

Green Grabber Camera

C/C++
This is the code for the camera on the Green Grabber.
#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);
}

Credits

Lehmitz

Lehmitz

3 projects • 5 followers

Comments