Showing posts with label laser. Show all posts
Showing posts with label laser. Show all posts

Thursday, August 24, 2017

ToF Laser to Monitor Cat Food Levels


 -----
We live in great times.  There was once a day when the only way to determine if the cat feeder needed more kibble was to actually look at it with your own eyes like some Neanderthal.  Thankfully technology and the magical world of IoT has changed all of that.
----
For this project we use a WiFi enable ESP8266 and a STMicro VL53L0X ToF Sensor mounted to a breakout board.  If you don't find a need to measure feline food consumption the project still provides Arduino code that can be extremely useful for your other IoT projects:
  • Logging data to a Google Drive Spreadsheet (via IFTTT)
  • Logging data to AT&T's M2X machine to machine servers (think nice graphs)
  • Sending SMSs to your mobile device (via IFTTT)
All of the above services are free but; of course, you will need to establish an account if you do not have on.
----
What's happening?
The ESP8266 runs in Arduino mode (source code below) in an endless loop.  Every hour it polls the VL53L0X ToF Sensor mounted on the lid of the cat food feeder.
-----
Since we know how many centimeters the food is from the ToF sensor at full and at empty we are able to scale those values and report/log "percent full" status.  Those values are posted to a Google Drive Spreadsheet and to AT&T's M2X machine to machine servers for logging.  If the food level is considered CRITICALLY LOW a SMS message goes out to our mobile device.  Just to increase the geek factor, CRITICALLY LOW alerts are also displayed on our Pebble watch.
----
The Google Drive Spreadsheet looks like this:
-----
Here is the bad ass AT&T M2X machine to machine server graph (note the increase after we filled the feeder):
-----
The IFTTT SMS alerts are sent if the rig determines food levels critically low:

-----
So..... How's it done?  You will need these:
Easy; hook it up like this:
 And it will look something like this:
-----
 Mount it to the cat feeder and you end up with this:

-----
Now all that is left is to copy/paste the code below into the Arduino IDE.  Upload it to the ESP8266 and your cats will never go hungry again!
-----
/*
 *  AUG2017
 *  STMicro VL53L0X ToF Sensor for
 *  Cat Food Level Monitoring
 *  WhiskeyTangoHotel.Com
 * 
 *  Logs % full values to:
 *    Google Drive (as an appending spreadsheet)
 *    AT&T M2X for historical graphing
 *    Send SMS to cell phone if level condition is RED/CRITICAL
 * 
 *  uC setting for Ardunio IDE
 *  NoderMCU 1.0 (ESP-12E Module), 80MHz, 921600, 4M (3M SPIFFS)
 * 
*/

// For the STMicro VL53L0X ToF Sensor
// I2C SDA to ESP8266 Pin D2.  SCL to ESP8266 Pin D1
#include "Adafruit_VL53L0X.h"   // Thanks again ADAFRUIT!!!
Adafruit_VL53L0X lox = Adafruit_VL53L0X();

// For the Wireless
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

// WiFi Connection Information
const char* ssid = "YourNetworkHere";    // PRIVATE: Enter your personal setup information.
const char* password = "YourNetworkPasswordHere"; // PRIVATE: Enter your personal setup information.
ESP8266WebServer server(80);

// IFTTT Information for WebHook widget
String MAKER_SECRET_KEY = "YourIFTTTCodeHere"; // PRIVATE: Enter your personal setup information. Your IFTTT Webhook key here
String TRIGGER_NAME_google_drive = "Cat_Food";  // this is the Maker IFTTT trigger name for google drive spreadsheet logging
String TRIGGER_NAME_M2X = "cat_food_mx";   // this is the Maker IFTTT trigger name for M2X logging
String TRIGGER_NAME_SMS = "CriticalFoodLevel_SMS";  // this is the Maker IFTTT trigger name to send SMS if low is level is CRITICAL.
const char* host = "maker.ifttt.com";
String url_google_drive;  // url that gets built for the IFTTT Webhook logging to google drive spreadsheet
String url_M2X;   // url that gets built for the IFTTT Webhook logging to AT&T M2X service
String url_SMS;   // url that gets built for the IFTTT Webhook sending SMS if food level is critical

// Define and set up some variables
float Range_inches;  // How far the sensor is from the food at time of reading.  Sensor is on roof of feeder.
float Min_level = 5.0;   // Distance in inches before low food alarm level.  Food is far from sensor on feeder roof
float Max_level = 0.5;   // Distance in inches from sensor for full feeder level.  Food is close to sensor on feeder roof
float Percent_full;  // How full in xx.x% is the food based on the Min/Max_levels defined above
float Caution_alarm = 35.0;  // at xx.x% food level is considered low.  String Status YELLOW
float Critical_alarm = 25.0;  // at xx.x% food level is considered critically low. String Status RED
String Status = "***_Starting_with_Caution_at:_" + String(Caution_alarm) + "%_and_CRITICAL_at:_" + String(Critical_alarm) + "%";  // Update to Out of Range, NORMAL, LOW, CRITICAL, etc. "spaces" will error IFTTT Webhook; use "_"
int Run_number;  // how many times the sensor has been read

// Output pins
const int led = 2;  // Blue on board LED is on PIN2 for this NoderMCU 1.0 ESP8266.  Blink it between reads

// Program control variables
int Seconds_between_posts = 60 * 60;  // how often to post the results of the sensor read. NOT EXACT due to post delays, Sensor reads, LED flashing, etc.
int logging = 1; // If 1 then log to cloud.  Any other value (0) turns it off.  ESP8266 "Start/Restart" message is always logged. 


void setup(void){  // This is run once.
  pinMode(led, OUTPUT);  // set up the onbaord LED pin as an output. 
  Serial.begin(115200);  // turn on the serial monitor for debug

  // wait until serial port opens for native USB devices
  while (! Serial) {
    delay(1);
  }

  // Is the ToF sensor connecting via I2C?
  Serial.println("STMicro VL53L0X test");
  if (!lox.begin()) {
    Serial.println(F("Failed to boot VL53L0X!!!"));
    while(1);
  }
  // power
  Serial.println(F("VL53L0X Passed... \n\n"));
 
  // Is the WiFi working?
  WiFi.begin(ssid, password);
  Serial.println("");
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print("Trying to connect to ");
    Serial.print(ssid);
    Serial.print(" on ");
    Serial.print(WiFi.localIP());
    Serial.println(".");
  }
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.println(WiFi.localIP());

  if (MDNS.begin("esp8266")) {
    Serial.println("MDNS responder started");
    Serial.println(" ");
  }

  // Use WiFiClient class to create TCP connections for WiFi logging
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");  // Boo!!!
    return;
  }

  server.begin();
  Serial.println("HTTP server started");  // Woo Hoo!!!

  // Write to Google Sheet via IFTTT Maker channel that the ESP8266 has started/restarted
  // Trigger the IFTTT Webhook Channel to update a Google sheet with the activity of the server starting/restarting
  // This can help log power outs, etc.  For first run we defined String Status for identify a startup condition.
  // Create the request for IFTTT google drive
  url_google_drive = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_google_drive + "/with/key/" + MAKER_SECRET_KEY + "?value1=" + String(Status);
  Serial.println("Status: " + Status);
  Serial.println(" ");
   
  // This sends the request to the IFTTT server
  client.print(String("POST ") + url_google_drive + " HTTP/1.1\r\n" +
  "Host: " + host + "\r\n" +
  "Connection: close\r\n\r\n"); 
  delay(500);  // Delay for web traffic; maybe not required. 
}

void loop(void){
  // Loop forever.  Read the sensor and post based on the delay values set above.
  // The blue onboard LED will blink between ToF reads.
 
  // Read the ToF.  Distance in mm  returned. 
  for (int x = 0; x < 10; x++) { // Quick toggle blue on  board LED to show measurement being taken.
    digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED. Shows program is running
    delay(100);    }  // endfor quick toogle Blue LED
  VL53L0X_RangingMeasurementData_t measure;

  Serial.println("-------------------------------------");
  Serial.println("Reading a measurement... ");
  lox.rangingTest(&measure, false);
  // Convert to inches because the USA, for some reason, doesn't want to adopt the metric system...
  Range_inches = measure.RangeMilliMeter/25.4;
  Run_number = Run_number + 1;

  // Scale and normalize the Min Max levels to 0% to 100%.  Clip the range in case of a the Max fill limit was exceeded or a misread.
  Percent_full = ((Range_inches - Min_level) / (Max_level - Min_level)) * 100.0;
  if (Percent_full > 100) {
    Percent_full = 100.00;
  }
  if (Percent_full < 0) {
    Percent_full = 0.0;
  }

  //Serial.println(String(Range_inches));  // Debug use
     
  // Is the ToF Sensor reading 'anything' for a distance?
  if (Range_inches > 100 ) {  // Something's weird.  Ranging error.  The ToF sensor is NOT over 100 inches fron the food. EVER!!!
      Status = "Run:" + String(Run_number) + "__ERROR:_***_Out_of_Range_***";
  } else {  // ToF made a successful reading so log the food level   
    if (Percent_full >= Caution_alarm) {  // above CAUTION LEVEL, All's good
      Status = "Run:" + String(Run_number) + "___GREEN---GOOD";
    } // endif GREEN---GOOD
 
    if (Percent_full < Caution_alarm && Percent_full > Critical_alarm) {  //  CAUTION Zone, YELLOW---REFILL_SOON
      Status = "Run:" + String(Run_number) + "___~~~YELLOW---REFILL_SOON~~~";
    } // end if YELLOW---REFILL_SOON"
 
    if (Percent_full <= Critical_alarm) {  //  CAUTION Zone, RED---REFILL_ASAP"
      Status = "Run:" + String(Run_number)+ "___!!!_RED---REFILL_ASAP_!!!";
      if (logging == 1) { // is logging turned on? Maninly for debug...  Typically would be set = 1
        // Set up IFTTT Webhook Channel to send the SMS. 
        // Use WiFiClient class to create TCP connections for IFTT SMS
        WiFiClient client;
        const int httpPort = 80;
        if (!client.connect(host, httpPort)) {
          Serial.println("connection failed");
          return;
        }     
        url_SMS = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_SMS + "/with/key/" + MAKER_SECRET_KEY+ "?value1=" + String(Percent_full);
        Serial.println("Critical Level: Sending SMS with payload:.");
        Serial.println(url_SMS);
        Serial.println(" ");
        client.print(String("POST ") + url_SMS + " HTTP/1.1\r\n" +
        "Host: " + host + "\r\n" +
        "Connection: close\r\n\r\n");
        //Serial.println("GOOGLE DRIVE URL:");
        //Serial.println(url_google_drive);
        delay(500);   // pause for webservices   
      }         
    } // endif RED---REFILL_ASAP
     
  }

  // Serial print to the monitor for debug
  Serial.println(String(Range_inches) + " inches down / " + String(Percent_full) + "% full");
  Serial.println("Status: " + Status);
  // Create the request for IFTTT Google Drive and M2X updates
  url_google_drive = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_google_drive + "/with/key/" + MAKER_SECRET_KEY + "?value1=" + String(Percent_full) + "%" + "&value2=" + Status;
  url_M2X = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_M2X + "/with/key/" + MAKER_SECRET_KEY + "?value1=" + String(Percent_full);
 
  // This sends the request to the IFTTT server.
  //Serial.println("Requesting URL...");   // debug
  //Serial.println(url);   // debug
  if (logging == 1) { // is logging turned on? Maninly for debug...  Typically would be set = 1
    //Serial.println("Logging is ON."); 
    // Set up IFTTT Webhook Channel to update a Google sheet with the activity. 
    // Use WiFiClient class to create TCP connections for IFTT Webhook logging
    WiFiClient client;
    const int httpPort = 80;
    if (!client.connect(host, httpPort)) {
      Serial.println("connection failed");
      return;
    }     
    client.print(String("POST ") + url_google_drive + " HTTP/1.1\r\n" +
    "Host: " + host + "\r\n" +
    "Connection: close\r\n\r\n");
    Serial.println(" ");
    Serial.println("IFTTT url payload to Google Drive:");
    Serial.println(url_google_drive);
    Serial.println(" ");
    delay(500);   // pause for webservices
 
    // Set up IFTTT Maker Channel to update a AT&T M2X Server with the activity. 
    // Use WiFiClient class to create TCP connections for IFTT Webhook logging
    //WiFiClient client;
    //const int httpPort = 80;
    if (!client.connect(host, httpPort)) {
      Serial.println("connection failed");
      return;     }     
    client.print(String("POST ") + url_M2X + " HTTP/1.1\r\n" +
    "Host: " + host + "\r\n" +
    "Connection: close\r\n\r\n");
    Serial.println("IFTTT url payload for M2X:");
    Serial.println(url_M2X);
   
  } else {
     Serial.println("Logging is OFF.");
  } // endif/else logging 
 
  for (int x = 0; x < Seconds_between_posts; x++) { // Delay for next measurement.
    digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED. Shows program is running
    delay(1000);   } // endfor delay for read measurement
}
-----
Thanks and check out our other stuff!!!

Sunday, February 17, 2013

SpiroGraph 3D Laser Project [with Arduino]

Objective:  Reflect a laser beam off mirrors mounted on three fans to display a 'wild' 3D spirograph type pattern on any surface.  Provide fan speed control (manual or automatic) to adjust the displayed patterns.  Full electrical schematic and source code follows.
-----
If you are not interested in the build and just want to see the result, below is a short video.  If you want to really "trip" out you can watch the 20 minute version.  If you turn up the volume you can hear the fans speeds changing.




-----
If you read through the site, you can see I have been using other micro controllers (MSP430, PIC, PICAXE, Freescale Freedom, etc.)  The Arduino platform is certainly one of the most popular and I wanted to give it a try.

I approached this as an Arduino tutorial project because it combines a lot of basics that go into many projects.  I would recommend it to anyone interested in learning the Arduino because:
  • It's cool (maybe even relaxing) to watch.  Be the envy of your friends...
  • The parts are cheap; many probably already in your kit.
  • Demonstrates reading multiple ADC (Analog to Digital) voltage inputs.
  • Demonstrates multiple PWM (Pulse Width Modulation) outputs to vary LED brightness and control motor speeds.
  • Demonstrates random number generation with the Arduino.
  • Demonstrates using arrays for both variables and pin I/O assignments.
  • Demonstrates the Arduino "mapvalue" scaling function.
  • Demonstrates output of Arduino debug values to the PC screen.
  • Demonstrates multiple voltages being used for a project (12VDC, 5VDC, and 3.3VDC).
  • Demonstrates other program control stuff, etc....
If you are trying to learn the Arduino (or really, any other micro controller) this project beats the hell out of just a "Hello World" blinking LED.  The effect is guaranteed to impress your friends at the next Rave Party.
----
For the project you will need a few items:
  • Arduino (I used the Nano pictured above; $9USD shipped)
  • Laser Diode (check eBay for red ones that sell for ~$1.50USD shipped)
  • Three DC fans or motors (I rescued three 24VDC instrument cooling fans)
  • Three small mirrors and double sided tape to attach then to each fan center
  • Three 10K pots (used to control the fan speed independently)
  • Three LEDs and three 330 ohm resistors
  • AC/DC Power adaptor (I rescued an 18VDC wall wart)
  • LM7805 voltage regulator to tame the output from the wall wart to 5VDC
  • L78L33 voltage regulator (provides a 3.3VDC for the laser diode)
  • TC4469 Quad Motor Driver to provide controlled power to the fans
-----
First thing we need to do is arrange the three fan motors in a box pattern.  I fixed them together with yellow zip ties.  The "wall" on the far left is just a fan housing and where we will mount the red laser diode.  There is a better pic of that later.
-----
Here is a pic of the red laser diode mounted on a thick wire.  You can also see one of the small mirrors mounted to one of the fan's center with double sided tape.  
-----
The red laser beam is aimed so that it reflects off each mirror as it spins, and finally, on onto a wall, etc.  The path of the laser on the spinning mirrors is kinda like this:
-----
Next thing you will need to do connect up a bunch of wires per the schematic below.  Since there is voltage on the project that is higher that the Arduino, the LEDs, or the laser can handle pay special attention when connecting the voltage regulators (LM7805 and L78L33) and components or you will "cook" something.  Also, don't try to skimp out and go without the TC4469 to drive the fans.  The Arduino can't source enough current to drive the fans.  Motor drivers are common in projects so this is a good time learn how to use them anyway.   Click on the schematic to make to bigger.
-----
After connecting everything up, the mess will look something like this:
-----
We still need to program the Arduino Nano to control the project; read the POT locations, adjust the fan speeds and LED brightness, speed change delays, etc.  Simply copy and paste the source code below into the Arduino IDE (Integrated Development Enviroment) installed on your PC.  Then download the source code "sketch" into the Nano.  If this step seems daunting check out this page on the official Arduino site.
/*
 **************************************
 ***** www.WhiskeyTangoHotel.Com  *****
 **************************************
 Project Name: Spyrograph Laser (3 axis)

 Start Date:  Feb 2013

 Program Rev History and Notes: 
 Project controls 3 * 24VDC fans (with mirrors attached to the center).  A laser is
 shined onto the mirror.  A '3D' pattern is drawn on the wall with the laser.

 If a control POT is full up, the fans speed is random.
 If a control POT is full down, the fan turns off.
 Else the control POT varies the fan speed manually.

 ***************************************
 */

// Array starts at VAL 0.  PWM outputs for mirror motors on D9, 10, 11
int Mirror[] = {
  9, 10, 11};

// PWM outpts for LED status monitors,  They mimic the fan speed
int Led[] = {
  3, 5, 6};

// Analog pins.  Read Pots that control mirror motors
int Pot[] = {
  1, 2, 3};

int potvalue[3];  // Store the value of the Pot[] (this value will be 0-1023)
int mapvalue[3];  // We take the potvalue and rescale it for PWM outputs

int DelayVal = 2000;      // how fast for the PWM randon speed hold in mSecs
int KnobBuffer = 10;      // how much of the top end or bottom end of the pot to ignore for random or off fan
int FullSpin = 0;         // always 0. We want to hit mirrors with full power at program start
int MaxSpin = 0;          // 0 is full blast. largest val for PWM on mirros during run mode
int MinSpin = 200;        // 255 is off.  lowest speed/PWM for mirrors

void setup()
{
  //Serial.begin(9600);  // Comment in final version, just for debug...

  for (int i = 0; i<=2; i++) { 
    pinMode(Mirror[i], OUTPUT);      // sets the digital pins as output
    pinMode(Mirror[i], OUTPUT);
    pinMode(Mirror[i], OUTPUT);
  }

  for (int i = 0; i<=2; i++) {  
    pinMode(Led[i], OUTPUT);      // sets the digital pins as output
    pinMode(Led[i], OUTPUT);
    pinMode(Led[i], OUTPUT);
  }

  randomSeed(analogRead(0));    // Pin 0 is connected to nothing and will read 'noise' to generate a random seed

  //Spin the fan up full Speed to start and
  //Blink the LEDs as a self test
  for (int i = 0; i<=2; i++) {  
    digitalWrite(Mirror[i],0); // 0 (full low PWM applies full power the the fans)
    digitalWrite(Mirror[i],0);
    digitalWrite(Mirror[i],0);
  }

  for (int i = 0; i <= 50; i++) {  // Blink the LEDs
    digitalWrite(Led[0], 0);       // 0 turns the LED on
    digitalWrite(Led[1], 0);
    digitalWrite(Led[2], 0);
    delay(20);

    digitalWrite(Led[0], 255);     // 1 turns the LED off
    digitalWrite(Led[1], 255);
    digitalWrite(Led[2], 255);
    delay(20);
  } // endSelf Test Loop

}   //end Setup()

void loop()
{
  // Use Array values in a 'for loop' to Read the POTs and control the LEDs and Fans

  for (int i = 0; i<=2; i++) {  
    potvalue[i] = analogRead(Pot[i]);    // read the position of one of the three POTs
    
    //the mapvalue function will rescale the potvalues (0 to 1023) to a range for PWM output (255 to 0)
    mapvalue[i] = map(potvalue[i], 0, 1023, MinSpin, MaxSpin);   // on LOW (0) from the Arduino would turn fan full on due to TC4469 inverted input.

    if (mapvalue[i] <= KnobBuffer) {   // is pot near full up position randomize that mirror speed
      mapvalue[i] = random(MaxSpin, MinSpin);  
      delay(DelayVal);  // if we are random speeding the mirror then delay to allow for the speed adjustment to settle
    }

    if (mapvalue[i] >= MinSpin) {  // if POT is full down then
      mapvalue[i] = 255;           // turn off this fan (255 is off due to TC4469 inverted input.
    }

    analogWrite(Mirror[i],mapvalue[i]);   // Spin the fan to the selected or calulated speed.  0 = full; 255 = off
    analogWrite(Led[i], mapvalue[i]);     // LED brightness mimics fan speed.  0 = bright; 255 = off

    /* Serial.prints below are for debug.  Remove in final version.
    Serial.print(mapvalue[i]);
    Serial.print("   ");
    delay(500);
    */

    } //end control array for loop

    //Serial.println();  //Serial.print for debug.  Remove in final version.

}  // end void()  end of program code
-----
After you clean up all the wiring, the finished goods will tidy up nicely:



-----
If all goes well (which it will after you sort through your wiring errors, etc) you will be rewarded with your own laser light show.  Time to get out that Pink Floyd album and enjoy your work. 




-----
If you're still with me, thanks or checking out the build page.  This is a great project because it has a high visual effect; a 'wow' factor.  Good luck.
-----
Link back: Hack A Day
Link back: Hacked Gadgets
-----