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!!!

micro:bit Tilt Angle Controls Servo Motor


https://makecode.microbit.org/#

The micro:bit is a simple, rugged, easy to program microcontroller aimed at the education market and you get a lot for your money.  The unit is programmed with a browser interface.  That means no huge development environments to install on your hardrive making the time from unboxing to "Hello World" literally minutes.  The simplest way to push code into the micro:bit is with the JavaScript Blocks editor.  However, Java and Python can also be used.

With three mouse drags we created a program to have a servo motor track the tilt angle of the micro:bit.  It doesn't get much easier.
-----
https://makecode.microbit.org/#


-----
If you are wanting to introduce someone to the magic of programming, basic electronics, and blinky lights this is a low cost/low risk way to make it happen.
-----

Sunday, June 11, 2017

Measuring Fidget Spinner RPM via TEK MDO4000 Oscilloscope


-----
How fast can a Fidget Spinner spin?  Well, it depends on what spins it.  Let's use compressed air.


That sounds fast, it's actually scary to hold!!!  But how fast?
-----
To make the measurement we used a simple LED as a sensor.  A bright light shines down onto the LED.  The arms of the Fidget Spinner block the light as they move over the LED creating electrical pulses. 
-----
The sensor LED is connected to a Tektronix MDO4104C to measure the pulses.  The scope (along with a few thousand other features) allows an engineer to quickly study waveform measurements with cursors.  After a little math we get our answer; well over 10,000 RPM.
That's fast!
-----

Monday, May 22, 2017

Tweeting Cat Door (now with Rasperry PI)

-----
It is hard to believe, but it was over five years ago that we created a Tweeting CatDoor using the Electric Imp.  Basic hobby IoT was much harder back then.  The Electric Imp, being a commercial grade product, was rock solid.  It only needed to reboot twice during it's entire service.  That said, a lightening strike may have gotten the best of it.
----
Basic hobby IoT is easier today thanks to project sites like HackaDay, etc.

A look into our spare parts box prompted some changes:  The Electric Imp gets replaced by a Raspberry Pi ZERO and WiFi dongle (we didn't have a spare ZERO-W laying around).  The original magnetic reed switch implementation goes solid state and gets replaced with a A3144EUA Hall Effect Sensor.  The concept is still the same as the original; a magnet is placed on the door and when Zena or Kelso use the door a magnet swings past the stationary sensor triggering a GPIO event.

As shown in the wiring diagram above, the build is simple.  The python code straightforward.

For the python code below you will need you create your own Twitter App and establish:
      - Consumer Key (API Key) and Consumer Secret (API Secret)
      - Access Token and Access Token Secret
These Keys and Tokens are, of course, changed in the python code below for obvious reasons.  Also, to save space the 'database' of tweets listed in the code is reduced.
-----
# May 2017 / WhiskeyTangoHotel.Com
# Raspberry PI ZERO for tweeting Cat Door activity
# MagSwitch is model A3144EUA Hall Effect sensor with magnet next to it
# Activate tweet loop whenever the Hall Effect Sensor changes state

import random
import time

# Set up Auth and token ID for the twitter API
from twython import Twython
# fill in your 4 keys in following variables
C_key = "ckckckckckblaghblagckckckckck"
C_secret = "cscscscscsblaghblaghblaghcscscscscs"
A_token = "atatatatatblaghblaghblagatatatatat"
A_secret = "asasasasasblaghblaghblagasasasasas"

localtime = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
Current_day = time.strftime("%d", time.localtime()) # what day of the month is it
New_day_dawned = Current_day # 1st run after midnight send status tweet to verify program is running

###  PROGRAM VAR SETTINGS ###

# Flood control (in seconds) for debounce and not sending to many tweets
Flood_control = 180 # seconds

# Really tweet (YES) or just test/debug (NO)?
Tweet = "YES"   # YES or NO in ALL UPPERCASE

# Track how many times the door has been triggered
Counter = 0

# Set up Hall Effect Sensor
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD) # to use Raspberry Pi board pin numbers
MagSwitch = 8  # Pin from signal of hall effect sensor
#Set pin to input and set pull-up resistor to hold the pin is high
GPIO.setup(MagSwitch, GPIO.IN, pull_up_down=GPIO.PUD_UP)
print ' '
print "Program starts with Flood control of " + str(Flood_control) + " seconds."
print "Tweet set to " + Tweet
print " "

localtime = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
print "RasPI started on " + localtime + ".  Begin the mission!!!"
MagSwitch_Current = GPIO.input(MagSwitch)
#print "MagSwitch value is: " + str(MagSwitch_Current)
print ' '
if (Tweet == "YES"):
    myTweet = Twython(C_key,C_secret,A_token,A_secret)
    localtime = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
    myTweet.update_status(status="RasPI started at " + localtime + ".  Begin the mission!!!")
  
time.sleep(1)  # Whew... let's rest a bit..    Why?  Dunno...

while True:    # program runs 'forever'
    # First check if this is a new day.  If yes, tweet program is still active
    localtime = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
    Current_day = time.strftime("%d", time.localtime())
    if Current_day != New_day_dawned:
        print "A new day has dawned. It is " + localtime + "."
        print ' '
        if (Tweet == "YES"):
            myTweet = Twython(C_key,C_secret,A_token,A_secret)
            localtime = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
            myTweet.update_status(status="Hello, Twitter. Let's make the best of " + localtime)
        New_day_dawned = Current_day
                  
    # Now check to see if Sensor has changed state.  If so then Let's tweet
    if (GPIO.input(MagSwitch) != MagSwitch_Current):
        print ' '
        localtime = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        print "Triggered at: " + localtime
        print '-------------------------------'
      
        Counter = Counter + 1
          
        # Load the CatQuotes; then Tweet it
        CatQuote = [
        "ZERO", #this is zero and should never be called.  just here to make counting easier 'cuz i'm stupid
        "Tweet Quote 1",
        "Tweet Quote 2",
        "Tweet Quote 3",
        "Tweet Quote 4",
        "Tweet Quote 5",
        "Tweet Quote etc...",        ]
      
        Number_of_quotes =  6 - 1
        random.seed() # seed off the CPU clock
        r = random.random()              
        r = int((r * Number_of_quotes) + 1)

        print str(Counter) + ": CatQuote("+str(r)+"): " + CatQuote[r]
        print "Flood control set to " + str(Flood_control) + " seconds."
        print "Tweet set to: " + Tweet
        if (Tweet == "YES"):
            myTweet = Twython(C_key,C_secret,A_token,A_secret)
            myTweet.update_status(status=CatQuote[r])
      
        time.sleep(Flood_control)  # pause for debounce, re-read HE sensor, keeps from tweeting all the time.
      
        # Door etc has settled after the Flood Control.  Reset and wait for next toggle
        MagSwitch_Current = GPIO.input(MagSwitch)
      
        print "Flood Control PASSED."
        print " "
-----
Good luck if you decide to build your own tweeting RasPI.

Monday, March 6, 2017

Remote Control the Alexa Echo from Anywhere in the World

-----
Seems to be a common request:  "How do I control my Amazon Echo remotely?"  As to why this needs to be done is somewhat of a mystery, but evidently; done it must be...

First off; this is a hack.  Even hackers being the hacker types they are will think it's a hack and those hackers will be right.
-----
Take a look at the diagram above.  Basically what is happening is a Raspberry PI with a speaker plugged into it is commanding the Amazon Echo via a text-to-speech program.
  • The user logs into their home network via OpenVPN from anywhere in the World.
  • Navigate via VNC, etc. to a RasPI with a speaker placed next to an Amazon Echo.
  • From the RasPI command line use the 'festival' text-to-speech program to speak instructions to the Amazon Echo.
  • Amazon Echo obeys the command.
-----
Here's a quick video demo:
 -----
Others have done the hard work, but you will need to install some software:
  • If you don't have OpenVPN running, PiVPN makes this really easy on the Raspberry PI (and other platforms).  This *could* be optional if you only wanted to use the hack from inside your LAN or opened up a PORT on your router.  If you do decide to open up a PORT on your router for the whole wide world be aware of the risks.
  • Install the text-to-speech program 'festival' on the Raspberry PI with the command:
$ sudo apt-get install festival festival-freebsoft-utils 
  • Plug a speaker into the Raspberry PI and place it next to the Amazon Echo.
  • Log into that Raspberry PI and issue a 'festival' text-to-speech command to control the Amazon Echo.  For example:

$ echo  "Alexa, what time is it" | festival --tts
 or

$ echo  "Alexa, play Super Freak" | festival --tts
  •  Success! World domination!
-----

Friday, January 6, 2017

ESP8266 WiFi Garage Door Opener from any Web Browser

The 'brain' is the ESP8266 uC.  It is available with on board WiFi and plenty of I/O for smaller projects.  All this for well under $10USD with programming options for NodeMCU, MicroPython, and the Arduino IDE.
-----
There seems to be an unstoppable drive in the hacker DIY community for web based garage door openers and we were compelled to respond.  The garage door opener we have opens/shuts from a push button switch that basically creates a short to connect two terminals on the garage door opening unit.  That allows easy implementation because all that is required is a ESP8266 controlled relay wired across those two terminals to create a switch closure.

In addition to activating the door any activity is logged to a Google Sheet via the IFTTT.com Maker Channel.  This is handy to track all activation usage and ESP8266 server restarts.
-----
The main components are the ESP8266, a relay module, a BS170 N-Channel MOSFET.
 
-----
Simple.  Connect the 'stuff' as shown in the schematic:
and it will look something like this:
----
Use the Arduino IDE to load the source code below into the ESP8266 then wire the Normally Open (NO) side of relay you are controlling to the two terminals on the garage door opener that active the motor when 'shorted' together.

A few comments on the application:
  • Control works from Android, iPhone, PC, etc.  Basically any browser.  In the source code below if a device can open "http://192.168.0.28/long_confusing_URL_to_activate_relay" it will activate the garage door.
  • There is a "TEST" URL in the source code (http://192.168.0.28/) that confirms the ESP8266 is online but does not activate the door.
  • Set a static IP for the ESP8266 in your WiFi router.  Otherwise it may be assigned a different local IP if the ESP8266 or WiFi router is restarted.
  • Use long/complex URLs.  That way those that are connected to your router don't have a 'obvious' URL to activate the rig or one they can remember if you demo it.
  • We only wanted control of the door when connected to the host WiFi router locally (LAN) and not from anyplace on the planet.  If you want extended control to the WWW open a port on your router, but be aware of the concerns. We wanted to limit use only to those authorized to connect the WiFi router locally (LAN).  Plus, we didn't want to risk accidentally activating the door from a remote location.
  • The source code has separate IFTTT.com Maker Channel triggers to log events.  We could use one Maker Channel trigger and just pass different GETPOST variables.  However, creating multiple Maker Channel triggers would easily allow usage tracking on individuals by assigning each one a unique trigger name. (/ZenaActivate, /KelsoActivate, etc...)
  • Any time the door is activated or the ESP8266 is restarted (power outage, etc) a Google Sheet is updated to log the event as shown below. 
 
-----
The Arduino IDE source code is:

/*
 *  Garage Door Opener via Web Browser and log to IFTTT.com
 *  ESP8266 NodeMCU Board using Arduino IDE
 *  WhiskyTangoHotel.Com    DEC2106
 * 
 *  Blue on board LED is active LOW on GPIO2 (D4 on silkscreen)
 *  Relay to control Garage Door is active HIGH on GPIO5 (D1 on silkscreen)
 * 
 *  Opening 192.168.0.28/long_confusing_URL_to_activate_relay is called.  Every effort is made to keep the relay off 
 *  so the door does not close/activate by accident.
 * 
 *  A 'test' message is display on browser to see if server is up by calling root at:.
 *  192.168.0.28/  This WILL NOT ACTIVATE THE RELAY.  Only tests the server
 * 
 *  The 'meat' is at 192.168.0.28/long_confusing_URL_to_activate_relay. This will send a msg to the browser AND open/close the door.
 * 
 */

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

// WiFi Information
const char* ssid = "YOUR-SSID";
const char* password = "ASCII-PASSCODE-FOR-YOUR-SSID";

// IFTTT Information
String MAKER_SECRET_KEY = "xxx-yyy-zzz-123-456-789"; // your maker key here
String TRIGGER_NAME = "IFTTT_Maker_name_to_activate_relay";  // this is the Maker IFTTT trigger name for relay activation
const char* host = "maker.ifttt.com";

ESP8266WebServer server(80);

// Output pins
const int led = 2;  // Blue on board LED
const int relay = 5;  // Relay control line

int randNumber;  // Random# generated just to show a change in the screen.  Help to verify updated page call.

void handleRoot() {
  // This is called if 192.168.0.28/ is requested.  The root.
  // The 'meat' is at /long_confusing_URL_to_activate_relay.
  // This is just here to test the ESP8266 connectivity of the WiFi network without moving the relay
  // Show a message and flash the on board LED.
  randNumber = random(1, 10000);  // Random number just to show a change on the webpage at reload.
  server.send(200, "text/plain", "Testing ESP8266.  Response is: " + String(randNumber));
  digitalWrite(led, 0);  // Active LOW.  Turn On board LED On
  delay(2000);
  digitalWrite(led, 1);  // Active LOW.  Turn On board LED Off
}

void handleNotFound(){
  digitalWrite(led, 1);  // Keep the LED off.
  digitalWrite(relay, 0);  // Keep Relay OFF
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void setup(void){
  pinMode(led, OUTPUT);
  pinMode(relay, OUTPUT);
  digitalWrite(led, 1);  // LED Off
  digitalWrite(relay, 0);  // on power to relay
  Serial.begin(115200);  // serial prints to PC for debug use only
  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.println("");
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.println(WiFi.localIP());

  if (MDNS.begin("esp8266")) {
    Serial.println("MDNS responder started");
 
  // Write to Google Sheet via IFTTT Maker channel that the ESP8266 has started/restarted

  // Now we trigger the IFTTT Maker Channel to update a Google sheet with the activity of the server starting/restarting
  // This can help log power outs, etc. 
  // Use WiFiClient class to create TCP connections
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }
 
  // Create the request for IFTTT GARAGE_trigger_serverstart.  This can help log power outs, etc.
  String url = "https://maker.ifttt.com/trigger/GARAGE_trigger_serverstart/with/key/" + MAKER_SECRET_KEY;
  Serial.print("Requesting URL: ");
  Serial.println(url);
 
  // This sends the request to the IFTTT server
  client.print(String("POST ") + url + " HTTP/1.1\r\n" +
  "Host: " + host + "\r\n" +
  "Connection: close\r\n\r\n");
 
  delay(500);  // Delay to for web traffic; maybe not required.
  }

  server.on("/", handleRoot);

  server.on("/long_confusing_URL_to_activate_relay", [](){
    // This is called when 192.168.0.28/long_confusing_URL_to_activate_relay is called
    randNumber = random(1, 10000);  // Random number just to show a change on the webpage at reload.
    server.send(200, "text/plain", "Relay activated @ESP8266.  Code: " + String(randNumber));
    digitalWrite(led, 0);  // Active LOW.  Turn On board LED On
    digitalWrite(relay, 1);  // Relay ON

    // Know we trigger the IFTTT Maker Channel to update a Google sheet with the activity. 
    // Use WiFiClient class to create TCP connections
    WiFiClient client;
    const int httpPort = 80;
    if (!client.connect(host, httpPort)) {
      Serial.println("connection failed");
      return;
    }
 
    // Create the request for IFTTT
    String url = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME + "/with/key/" + MAKER_SECRET_KEY;
    Serial.print("Requesting URL: ");
    Serial.println(url);
 
    // This sends the request to the IFTTT server
    client.print(String("POST ") + url + " HTTP/1.1\r\n" +
    "Host: " + host + "\r\n" +
    "Connection: close\r\n\r\n");
   
    delay(2000);  // Delay to keep the relay closed.
    digitalWrite(led, 1);  // Active LOW.  Turn On board LED Off
    digitalWrite(relay, 0);  //Relay OFF
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void){
  server.handleClient();
}
-----

Sunday, December 18, 2016

Low Cost Bi-Directional Level Shift Module Characterization (TE291)

-----
From time to time the level shift module pictured above has come in handy.  They do a great job shifting 3.3VDC to 5VDC logic or 5VDC logic to 3.3VDC logic.  The cost is about $1USD and hookup is simple.  Of course they are designed for low speed digital signals but, we wondered how the module would handle higher speeds.
----
The Bench Setup:
Keithley 2230G-30-1 power supply for the 3.3VDC and 5VDC power.









Tektronix AFG3252C function generator for the square wave (3.3V and 5V logic) stimulus.










Tektronix MDO4104C oscilloscope to capture the input/output signals.

-----
The Result:
Performance was very good, especially considering these module are often used in the 100KHz and below range.  Leveling from 5V to 3.3V had better results.  The signals start looking ridiculous over 1MHz. Take a look at the scope shots below.
-----
3.3V Level Shifted to 5V at 10KHz, 100KHz, 500KHz, and 1MHz
(Yellow = Input; Blue = Output)




-----
 5V Level Shifted to 3.3V at 10KHz, 100KHz, 500KHz, and 1MHz
(Yellow = Input; Blue = Output)




 -----

Thursday, December 1, 2016

Portable ESP8266 WiFi Sniffer (Arduino IDE)

-----
The ESP8266 modules are so cheap who could resist experimenting with one.  For about $8USD a few things you get are 9 GPIOs, I2C/SPI support, an ADC, and on board WiFi or other goodies.  In addition it's all programmable in the Arduino IDE that is familiar to many and has a good user support network.

This example application shows a quick and easy way to get a portable 'WarDriver' with the WiFi ESP8266 and an OLED display.

No resistors, etc. needed; connect it up like this:
https://www.adafruit.com/product/2821https://www.amazon.com/Diymall-Yellow-Serial-Arduino-Display/dp/B00O2LLT30/ref=sr_1_1?ie=UTF8&qid=1480612090&sr=8-1&keywords=diymall+oled

    Pinout Connections
ESP8266                  OLED
3VDC <<==+==>> Vcc
  GND <<==+==>> GND
 SCL(5) <<==+==>> SCL
 SDA(4) <<==+==>> SDA



Take a look at the source code below for the links on installing ESP8266 capability to the Arduino IDE.  Chances are if you are reading this you already have the Arduino IDE installed; just make sure you are running at least Rev 1.6.8.  Then upload the source code to the ESP8266 and your up.
-----
Here is a sample of the rig running in a random parking lot a fair distance from an apartment complex.  Eight networks were found; all encrypted.  The OLED displays the number of networks, SSID name, signal strength (dBm), and if the network is OPEN or Encrypted.
One thing that was a surprise is how many cars have OPEN WiFi running.  Also, pretty much every long haul 18 wheeler heading down the interstate is a rolling WiFi hotspot, but most (not all) are Encrypted.
-----
Here is the source code to push into the ESP8266 via the Arduino IDE:

/*
 * WhiskeyTangoHotel.Com /  NOV2016
 * 'WarDriver' ESP8266 Adafruit HUZZAH w/ WiFi and 32 line OLED
 *   
 *   Scan WiFi networks leverages from:
 *   https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/WiFiScan/WiFiScan.ino
 *   
 *   OLED Driver: Thanks, adafruit.com
 *   
 *   Compile for 80MHz with Arduino IDE
 *      Arduino IDE 1.6.8 or greater
 *      http://www.arduino.cc/en/Main/Software
 *      
 *      ESP8266 Board Package
 *      Enter http://arduino.esp8266.com/stable/package_esp8266com_index.json into Additional Board Manage
 *      Restart, select your ESP8266 from the Tools->Board dropdown
 *   
 */
#include "ESP8266WiFi.h"  // API for the ESP8266 WiFi

// Setup the 32 line OLED 
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET LED_BUILTIN  // 4
Adafruit_SSD1306 display(OLED_RESET);

#if (SSD1306_LCDHEIGHT != 32)  // change to 64 for larger OLED. See error trap next line.
#error("Height incorrect, please fix Adafruit_SSD1306.h!"); 
#endif

int screen_roll_delay = 800; // How long to leave Network info on OLED. Delay is executed four times (for LED blink)

void setup() {
  // Setup and test writes to the OLED and Serial Monitor (ESP8266 expects Serial Monitor at 115200 baud)
  
  // Some more OLED setup
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();    // Clear the buffer.
  display.display();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  
  display.setCursor(0,0);
  display.println("2.4GHz WiFi Scanner");
  Serial.begin(115200); // Display to serial monitor as well as OLED
  Serial.println("Setup begins....");

  // Set WiFi to station mode and disconnect from an AP if it was previously connected
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(100);

  Serial.println("Setup completes!!!");
  display.println("------------------");
  display.println("WhiskeyTangoHotel");
  display.println("      .Com");
  display.display();
  delay(5000);  // Welcome/Test screen delay.
}  // end void setup

void loop() {
  Serial.println("Scan starts...");

  // update OLED
  display.clearDisplay();
  display.display();
  display.setCursor(0,0);
  display.println("Scanning...");
  display.display();

  // WiFi.scanNetworks will return the number of networks found as variable "n"
  digitalWrite(0, HIGH);  // On board LED ON
  int n = WiFi.scanNetworks();
  Serial.println(" and completes!!!");
  
  if (n == 0) {  // No WiFi found.  Update the Serial Monitor and the OLED
    Serial.println("No WiFi found!!!");

    // update OLED
    display.clearDisplay();
    display.display();
    display.setCursor(0,0);
    display.println("Scanning...");
    display.println("No WiFi found!!!");
    display.display();
    digitalWrite(0, LOW);  // On board LED OFF
  }      // endif n=0 (no wifi found)
  else  // wifi found
  {
    Serial.print(n);
    Serial.println(" Networks found:");
    Serial.println("----------------");

    for (int i = 0; i < n; ++i)
    {
      digitalWrite(0, HIGH);  // On board LED ON
      
      // Print SSID and RSSI for each network found to Serial Monitor. Show SSID, Signal strenght, and OPEN or Encrypted
      Serial.print(i + 1);
      Serial.print(": ");
      Serial.print(WiFi.SSID(i));
      Serial.print(": ");
      Serial.print(WiFi.RSSI(i));
      Serial.print("dBm | ");
      Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE)?"Not Encrypted":"Encrypted");

      //update OLED with found WiFi. Show SSID, Signal strenght, and OPEN or Encrypted
      display.clearDisplay();
      display.display();
      display.setCursor(0,0);
      display.print("Network ");
      display.print(i+1);
      display.print(" of ");
      display.println(n);
      display.display();
      display.println(WiFi.SSID(i));
      display.println("-------------------");
      display.print(WiFi.RSSI(i));
      display.print("dBm | ");
      display.println((WiFi.encryptionType(i) == ENC_TYPE_NONE)?"OPEN":"Encrypted");
      display.display();

      delay(screen_roll_delay);  // Little delay to allow time to read OLED. Flash the on board LED just for fun.
      digitalWrite(0, LOW);   // On board LED OFF
      delay(screen_roll_delay); 
      digitalWrite(0, HIGH);   // On board LED ON
      delay(screen_roll_delay);  
      digitalWrite(0, LOW);   // On board LED OFF
      delay(screen_roll_delay); 
    }  // end for/next loop for n# of wifi networks found
  }   // endif wifi found (n was <> 0)
  Serial.println("");
}  // end void loop (endless)
-----
Thanks for the visit.

Tuesday, October 25, 2016

Prime Numbers in a Box

-----
How many times have you needed the next prime number in a sequence and, like some animal, had to go to a printed table to look it up.  Well, those days are over.
----
A prime number is any positive whole number that can only get evenly divided only by 1 and itself.  Primes are used in many applications; a popular use being for encryption and cryptography.  Demonstrated here is another use for prime numbers.  That is making use of an older/slower Raspberry PI and a few parts to nerd up the decor of any room.

A few Raspberry PI skills learned will be:
     - writing to text files
     - reading from text files
     - driving a low cost I2C LCD display
     - driving a relay via a transistor
     - simple graceful shutdown method for the RasPI with a button and a JST connector.
-----
 
The project has an entertaining audio effect if you are into numbers.  Primes go on forever and ever; infinitely large.  The smallest numerical difference between two primes is 2 (example: 7-5=2).  What is interesting is the distance (difference) between two consecutive primes stays relatively low as the primes become very large.  Press a button and Primes in a Box gives a audible (relay click) signal for each non prime as it waits to display the next found prime.  If you enjoy mathematics you may find this oddly relaxing.
-----
The python source is pretty straight forward.  On button press a pointer to a file containing the first few million primes is indexed and displayed on the LCD.  A 5VDC relay clicks to represent the non primes in between.  The rig runs via USB power and the last found prime is always saved.  A handy 'shutdown' button is incorporated to allow the Raspbery PI project to be powered down gracefully if it needs to be moved.
-----
You'll need a Raspberry PI, 5VDC relay, PN2222A transistor, 16x2 I2C LCD, two resistors, and two normally open button switches.  A project box holds it all together.  Connect it all up like this:
Once on the breadboard it will look a bit like this:
 -----
Set up the RasPI to run the python code below at reboot (use @reboot in the sudo crontab).
Note, two files are expected to be found in the working directory:
     - prime_list.txt (List of prime numbers in order. One per line. As many as you like)
     - high_prime.txt (Holds the highest prime found in case of a restart/reboot)

#!/usr/bin/python

#
#  WhiskeyTangoHotel.Com
#  OCT 2016
#  
#  Program reads a file and displays prime # on LCD
#  On button press find next prime #.  Click a relay for each non prime
#  Write new highest prime to file to save if restarting the program
#
#  Gives an audible (relay click) representation of the distance between primes
#
#  Expects program to be in: /home/pi/RasPI/Programs-RasPI/Prime_Relay/
#
#  Leverages LCD script using I2C backpack for 16x2 and 20x4 screens.
#  Thanks to Matt Hawkins  http://www.raspberrypi-spy.co.uk/
#
#--------------------------------------

import smbus  # for LCD I2C display
import time # fpr sleep and pause delays
import os # this is for the shutdown button press
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD) # to use Raspberry Pi board pin numbers
# set up GPIO output channel
Relay_Pin = 11 # pin to drive the relay clicks via 2222a transistor
GPIO.setup(Relay_Pin, GPIO.OUT)
Relay_delay = .1  # time delay between relay clicks

Switch = 8  # Button switch to move to next prime number
GPIO.setup(Switch, GPIO.IN)

reboot_pin = 26 #  Push this button and the RasPI shuts down gracefully
#Set pin to input and set pull-up resistor to hold the pin is high
GPIO.setup(reboot_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

#######################################
### LCD DRIVER FUNCTIONS START HERE ###
#######################################

# Define some device parameters
I2C_ADDR  = 0x27 # I2C device address
LCD_WIDTH = 16   # Maximum characters per line

# Define some device constants
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command

LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

LCD_BACKLIGHT  = 0x08  # On
#LCD_BACKLIGHT = 0x00  # Off

ENABLE = 0b00000100 # Enable bit

# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005

#Open I2C interface
#bus = smbus.SMBus(0)  # Rev 1 Pi uses 0
bus = smbus.SMBus(1) # Rev 2 Pi uses 1 RasPI(Bob)

def lcd_init():
 # Initialise display
 lcd_byte(0x33,LCD_CMD) # 110011 Initialise
 lcd_byte(0x32,LCD_CMD) # 110010 Initialise
 lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
 lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off 
 lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
 lcd_byte(0x01,LCD_CMD) # 000001 Clear display
 time.sleep(E_DELAY)

def lcd_byte(bits, mode):
 # Send byte to data pins
 # bits = the data
 # mode = 1 for data
 #        0 for command

 bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
 bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT

 # High bits
 bus.write_byte(I2C_ADDR, bits_high)
 lcd_toggle_enable(bits_high)

 # Low bits
 bus.write_byte(I2C_ADDR, bits_low)
 lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
 # Toggle enable
 time.sleep(E_DELAY)
 bus.write_byte(I2C_ADDR, (bits | ENABLE))
 time.sleep(E_PULSE)
 bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
 time.sleep(E_DELAY)

def lcd_string(message,line):
 # Send string to display
 message = message.ljust(LCD_WIDTH," ")
 lcd_byte(line, LCD_CMD)
 for i in range(LCD_WIDTH):
  lcd_byte(ord(message[i]),LCD_CHR)
  
#######################################
### LCD DRIVER FUNCTIONS END HERE  ###
#######################################

# Highest calulated prime is stored in a file.  Get that number
highprime = open('/home/pi/RasPI/Programs-RasPI/Prime_Relay/high_prime.txt','r')
storedprime = (highprime.readline())
storedprime_val = int(storedprime)
print ' '
print "Recalled highest prime from file is: " + storedprime
print ' '

#Put status on LCD
lcd_init()
lcd_string("Init to last",LCD_LINE_1)
lcd_string("prime: " + str(storedprime_val),LCD_LINE_2)
highprime.close

def main():
 # Main program block 
 
 # Relay test clicks
 for i in range(1, 3):  
  GPIO.output(Relay_Pin,GPIO.HIGH) # Close relay
  time.sleep(Relay_delay) 
  GPIO.output(Relay_Pin,GPIO.LOW) # Open relay
  time.sleep(Relay_delay)  
 
 # Set the pointer to the correct place in the file that holds the prime list
 # prime_list.txt contains primes to 1,299,709
 primefile = open('/home/pi/RasPI/Programs-RasPI/Prime_Relay/prime_list.txt','r')
 currentprime_val = 0

 while storedprime_val > currentprime_val:
  currentprime = (primefile.readline())
  currentprime_val = int(currentprime)
  
 # Update the LCD
 lcd_string("PRESS for next",LCD_LINE_1)
 lcd_string("prime: " + str(currentprime_val),LCD_LINE_2)
 time.sleep(0.5)
 
 # Relay test clicks
 for i in range(1, 3): 
  GPIO.output(Relay_Pin,GPIO.HIGH) # Close relay
  time.sleep(Relay_delay) 
  GPIO.output(Relay_Pin,GPIO.LOW) # Open relay
  time.sleep(Relay_delay) 
 
 while True:
  
  if (GPIO.input(reboot_pin) == 0):
   # Update the LCD
   lcd_string("Primes in a Box",LCD_LINE_1)
   lcd_string("shutting down!!!" + str(currentprime_val),LCD_LINE_2)
   time.sleep(5)
   #Send command to system to shutdown
   os.system("sudo shutdown -h now")
  
  if (GPIO.input(Switch) == 0):
   # Update the LCD
   lcd_string("Calculating next",LCD_LINE_1)
   lcd_string("prime: " + str(currentprime_val),LCD_LINE_2)
  
   oldprime = currentprime_val
   currentprime = (primefile.readline())
   currentprime_val = int(currentprime)
   deltaprime = currentprime_val - oldprime 
   
   for i in range(1, deltaprime+1):  # click relay for each non prime between primes
    #print i
    GPIO.output(Relay_Pin,GPIO.HIGH) # Close relay
    time.sleep(Relay_delay) 
    GPIO.output(Relay_Pin,GPIO.LOW) # Open relay
    time.sleep(Relay_delay)   

   #Write the prime to a file so it can be recalled at prog start
   hp = open("/home/pi/RasPI/Programs-RasPI/Prime_Relay/high_prime.txt","w")
   hp.write(str(currentprime_val))
   
   print "CURRENT " +  str(currentprime_val)
   print "    OLD " + str(oldprime)
   print '------- '
   print "  DELTA " +  str(deltaprime)
   #print i
   print ' '

   # Update the LCD
   lcd_string("PRESS for next",LCD_LINE_1)
   lcd_string("prime: " + str(currentprime_val),LCD_LINE_2)

   hp.close
   time.sleep(.250)

if __name__ == '__main__':

 try:
  main()
 except KeyboardInterrupt:
  pass
 finally:
  lcd_byte(0x01, LCD_CMD)
----
Thanks for the visit and happy prime numbering!