Thimble Learning Platform
Back to full view

Wi-Fi Bot - Part 2

Introduction

Demonstration and instructions on how to program the Thimble WiFi Bot Kit. This part of the tutorial is only partially complete.

Objectives

  • Use an ultrasonic sensor to make a self-driving robot
  • Learn how WiFi works
  • Connect a WiFi module (ESP8266) to an Arduino
  • Control your robot via WiFi
  • Schematic

    The Electrical Circuit

    We hope you enjoyed getting your robot to move and programming it via dead reckoning! For part two, we'll look at the ultrasonic sensor and the WiFi module. First, let's look at the entire system and see how everything is connected. Below is a schematic that shows all of the functional blocks that make up the WiFi Bot printed circuit board (PCB).

    The WiFi Bot PCB SchematicThe WiFi Bot PCB Schematic

    The PCB was designed in KiCAD, an open source PCB development tool. First, the design was sketched out on paper and prototyped on a bread board. After testing of the each functional block, the schematic was entered into KiCAD's Eeschema schematic entry tool

    The schematic is arranged by functional block. Click through the images below to see a description of each block, as well as the components for that functional block highlighted in the board design file and the circuit board itself.

    The WiFi Bot PCB Schematic
    The WiFi Bot PCB in KiCAD PCBNEW
    The WiFi Bot PCB Design
    The WiFi Bot PCB

    Voltage Regulator 

    The voltage regulator used on this board is the LD1117S33TR. The LD1117 is a fixed low dropout voltage regulator. It takes 5V as an input and outputs a fixed 3.3V with an output current up to 800mA. This circuit comes almost directly from the reference design in the datasheet, with the addition of the D2 LED to indicate power. R2 is a current limiting resistor for this LED. This specific regulator was chosen to ensure there is ample power available for the WiFi module (3.3V Vcc with a peak current consumption of around 320mA).

    Voltage Regulator Functional Block
    Voltage Regulator in Schematic

    The images below show the voltage regulator as it appears in the KiCAD PCB design and on the physical board.

    Voltage regulator components in PCB design
    Voltage regulator components on PCB

    Motor Controller 

    This motor driver is the TB6612FNG, manufactured by Toshiba. This device was previously discussed in the motor controller section in part 1 of this tutorial. To reiterate, the TB6612FNG is a dual DC motor driver that can take a power supply voltage of up to 15V to drive 2 DC motors with at a continuous 1.2A output current and a peak current of 3.2A for a single 10ms pulse. Internally, this IC is composed of two H-Bridges and the control logic to control them. The schematic for this module comes directly from the datasheet reference design.

    Motor Control
    Motor Control

    The images below show the motor control functional block as it appears in the KiCAD PCB design and on the physical board.

    Motor Control components in PCB design
    Motor Control components on PCB

    WiFi Module 

    The WiFi Module used is the ESP-01, a very simple development board featuring the ESP8266 IC. This low cost IC made a huge impact on the "maker" scene when it first debuted, spawning many open source projects, a dedicated forum, and even an extremely detailed 400+ page e-book. It was developed and is sold by Espressif as a Internet of Things (IoT) self-contained WiFi solution used as either a bridge from an existing microcontroller to WiFi or as a self-contained device. Volume production of the device started in 2014 and Espressif have already shipped tens of millions of units, primarily due to its low cost and the vibrant open source libraries the community has developer for it.

    The ESP-01 module and pinout diagram are pictured below.

    ESP-01 WiFi Module
    ESP-01 WiFi Module

    VCC for the ESP block = 3.3V. Since the ESP is not 5V tolerant, the schematic functional block for the ESP module includes a level shifting block to allow 5V Arduinos to communicate with the 3.3V I/O pins of the ESP module. There is also a connection to the RESET line of the Arduino so that the ESP is also reset when the Arduino RESET button is pressed. The diode ensures a reset of the ESP only does not also reset the Arduino. The 10K ohm R1 resistor is used to pullup the CH_PD pin of the module (a multi-function chip enable pin). For hacking convenience, all of the ESP I/O headers are broken out and jumpers JP3 and JP4 are provided for convenience if you want to change which Arduino pins the ESP is electrically connected to.

    WiFi Module
    WiFi Module

    The images below show the WiFi functional block as it appears in the KiCAD PCB design and on the physical board.

    Wifi Module on PCB
    Wifi Module on PCB

    Ultrasonic Range Finding Module 

    The Ultrasonic Range Finding Module is an HCSR04. It can be used to measure distances.

    HCSR04 Ultrasonic Range Finding Module
    40KHz Piezoelectric Transducers

    How it works:

    As shown in the timing diagram below, once triggered, the module emits a short 8 cycle sonic burst of ultrasound at 40khz on one of the piezo transducers (shown in the image above). It then raises the echo line high, and lowers the echo line as soon as it detects an echo on the other transducer. Accordingly, the pulse width of the echo line is proportional to the distance to the object. By timing this pulse, it is possible to calculate the range to the object (Distance that sound travels = Speed of sound in the medium * Time that sound travles).

    Ultrasonic Module Timing DiagramUltrasonic Module Timing Diagram

    Because we are measuring an echo, we are interested in knowing half of the distance the wave traveled (Distance between sensor and object = 0.5 * Distance that sound travels). A visualization of this is shown in the diagram below.

    How the sensor functions. Red waves are transmitted, yellow are reflected.How the sensor functions. Red waves are transmitted, yellow are reflected.

    The HCSR04 is based off of the SRF04 (very well documented on this page) design. It uses an 8 bit microcontroller to generate the pulses and control the circuit. A MAX232 IC is used to produce +/-10V to drive the 40khz piezo transducer at close to 20V. A full reverse engineered schematic and detailed writeup on the module is available here.

    Because the ultrasonic module is self contained, all that is necessary is to connect it to power, ground, and data pins on the Arduino, as shown in the schematic below.

    Ultrasonic Module
    Ultrasonic Module

    The images below show the ultrasonic module functional block as it appears in the KiCAD PCB design and on the physical board.

    Ultrasonic Module on PCB
    Ultrasonic Module on PCB

    Photointerrupter 

    The photointerrupters included in the kit (shown below) are used in conjunction with the wheel encoders to function as wheel (speed) encoders for the robot. Implementing this yourself is optional and left as a challenge. The photointerruptor part number is HY301-21. They consist of an LED and a phototransistor. The LED shines light into the phototransistor. By measuring the resulting voltage, it is possible to determine if an object is in the path of the beam.

    PhotointerrupterPhotointerrupter

    The circuit for the photointerrupter consists of a current limiting resistor for the LED and the biasing circuitry for the phototransistor.

    Photointerrupter
    Photointerrupter

    The images below show the photointerrupter functional block as it appears in the KiCAD PCB design and on the physical board.

    Photointerrupter on PCB
    Photointerrupter on PCB
    Using the Ultrasonic Module

    Adding the Ultrasonic Module to the Robot 

    Start by inserting the ultrasonic module into the robot as shown in the photo below.

    Ultrasonic Module where to place
    Ultrasonic Module in place

    Your robot should look similar to the one in the photo below.

    Ultrasonic Module in placeUltrasonic Module in place

    The ultrasonic module (as discussed in the previous section) can be used to measure distances. We'll start with a very basic Arduino sketch from the NewPing library that prints the distance detected to the serial monitor.

    Programming the Ultrasonic Module 

    Make sure you install the NewPing library using Arduino's library manager. If you have not done this before, visit Arduino's library manager guide to see how. NOTE: NewPing may not yet be listed in the Arduino library manager. You can download the library zip file here.

    Once you have installed the library (and restarted the Arduino IDE, if necessary), you can access this example code from the Arduino IDE by clicking on the File menu. File > Examples > NewPing > NewPingExample. Note, however, that you must adjust the pin definitions to map to the layout of the WiFi Bot PCB. These definitions are shown in the code below.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // ---------------------------------------------------------------------------
    // Example NewPing library sketch that does a ping about 20 times per second.
    // ---------------------------------------------------------------------------
    
    #include <NewPing.h>
    
    #define TRIGGER_PIN  10  // Arduino pin tied to trigger pin on the ultrasonic sensor.
    #define ECHO_PIN     7   // Arduino pin tied to echo pin on the ultrasonic sensor.
    #define MAX_DISTANCE 200 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
    
    NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.
    
    void setup() {
      Serial.begin(115200); // Open serial monitor at 115200 baud to see ping results.
    }
    
    void loop() {
      delay(50);                     // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
      Serial.print("Ping: ");
      Serial.print(sonar.ping_cm()); // Send ping, get distance in cm and print result (0 = outside set distance range)
      Serial.println("cm");
    }
    

    Open the Serial monitor (as shown in the images below) and check to see a similar output.

    Open Serial Monitor
    Monitor the output of the NewPingExample.ino sketch. Place your hand in front of the sensor to see the distance output reflected on your screen.

    The code below will provide you a hint if you need it. This code will make the robot stop moving if it detects an obstacle within 10cm. This works by intrdoucing a Boolean obstacleDetected . Booleans are variables that can have one of two values: true or false. We initialize the variable obstacleDetected to false and then check it each time we set the motors to move. In the loop() function, we update this variable based on the distance value calculated from the ultrasonic module.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    #include <NewPing.h>
    
    #define TRIGGER_PIN  10
    #define ECHO_PIN     7
    #define MAX_DISTANCE 200
    
    // Boolean (True or False) variable to control whether or not
    // the motors move
    bool obstacleDetected = false;
    
    // Sonar object (ultrasonic module)
    NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
    
    // Pin assignments
    #define AIN1 3
    #define AIN2 4
    #define APWM 5
    #define BIN1 12
    #define BIN2 13
    #define BPWM 11
    #define STBY 6
    
    
    // Constants for motor control functions
    #define STEPTIME 600 
    #define STRAIGHTSPEED 200
    #define TURNSPEED 120
    #define TURNTIME 300
    
    // Array to track current PWM values for each channel (A and B)
    int pwms[] = {APWM, BPWM};
    
    // Offsets to be used to compensate for one motor being more powerful
    byte leftOffset = 0;
    byte rightOffset = 0;
    
    // Variable to track remaining time
    unsigned long pwmTimeout = 0;
    
    // Function to write out pwm values
    void writePwms(int left, int right){
        analogWrite(pwms[0], left);
        analogWrite(pwms[1], right);
    }
    
    // Move the robot forward for STEPTIME
    void goForward(){
        // If an obstacle was detected, stop
        if(obstacleDetected){
            stop();
        } else {
            // Otherwise, advance
            digitalWrite(STBY, HIGH);
            digitalWrite(AIN1, LOW);
            digitalWrite(AIN2, HIGH);
            digitalWrite(BIN1, LOW);
            digitalWrite(BIN2, HIGH);
            writePwms (STRAIGHTSPEED-leftOffset,STRAIGHTSPEED-rightOffset);
            pwmTimeout = millis() + STEPTIME;
        }
    }
    
    // Move the robot backward for STEPTIME
    void goBack(){
        // If an obstacle was detected, stop
        if(obstacleDetected){
            stop();
        } else {
            // Otherwise, advance
            digitalWrite(STBY, HIGH);
            digitalWrite(AIN1, HIGH);
            digitalWrite(AIN2, LOW);
            digitalWrite(BIN1, HIGH);
            digitalWrite(BIN2, LOW);
            writePwms (STRAIGHTSPEED-leftOffset,STRAIGHTSPEED-rightOffset);
            pwmTimeout = millis() + STEPTIME;
        }
    }
    
    // Turn the robot left for TURNTIME
    void goLeft(){
        // If an obstacle was detected, stop
        if(obstacleDetected){
            stop();
        } else {
            // Otherwise, advance
            digitalWrite(STBY, HIGH);
            digitalWrite(AIN1, HIGH);
            digitalWrite(AIN2, LOW);
            digitalWrite(BIN1, LOW);
            digitalWrite(BIN2, HIGH);
    
            writePwms (TURNSPEED,TURNSPEED);
            pwmTimeout = millis() + TURNTIME;
        }
    }
    
    // Turn the robot right for TURNTIME
    void goRight(){
        // If an obstacle was detected, stop
        if(obstacleDetected){
            stop();
        } else {
            digitalWrite(STBY, HIGH);
            digitalWrite(AIN1, LOW);
            digitalWrite(AIN2, HIGH);
            digitalWrite(BIN1, HIGH);
            digitalWrite(BIN2, LOW);
    
            writePwms (TURNSPEED,TURNSPEED);
            pwmTimeout = millis() + TURNTIME;
        }
    }
    
    // Stop the robot (using standby)
    void stop(){
        digitalWrite(STBY, LOW); 
    }
    
    // Arduino setup function
    void setup(){
        // Initialize pins as outputs
        pinMode (STBY, OUTPUT);
        pinMode (AIN1, OUTPUT);
        pinMode (AIN2, OUTPUT);
        pinMode (APWM, OUTPUT);
        pinMode (BIN1, OUTPUT);
        pinMode (BIN2, OUTPUT);
        pinMode (BPWM, OUTPUT);
        pinMode (TRIGGER_PIN, OUTPUT);
        pinMode (ECHO_PIN, INPUT);
        Serial.begin(115200);
    }
    
    // Loop (code betwen {}'s repeats over and over again)
    void loop(){
    
        // Trigger ultrasonic ping
        int uS = sonar.ping();
        // Calculate distance in cm
        int distance = uS / US_ROUNDTRIP_CM;
        Serial.print("Ping: ");
        Serial.print(distance);
        Serial.println("cm");
    
        // If there is an object within 10cm of the sensor
        // NOTE: The NewPing library returns 0 if no object is detected.
        if(distance <= 10 && distance != 0){
            // Set the obstacleDetected flag to disable the motors
            obstacleDetected = true;
        }
    
        // Make the robot go Forward.
        goForward();
        // Wait for one second
        delay(1000);
        // Make the robot stop
        stop();
        // Wait for one second
        delay(1000);
        // Make the robot go backward
        goBack();
        // Wait for one second
        delay(1000);
        // Make the robot stop
        stop();
        // Wait for one second
        delay(1000);
    }
    
    The WiFi Module

    Adding the WiFi Module to the Robot 

    The WiFi Module

    First, add the WiFi Module to the robot. Please note the orientation in the photos. Do not plug the module in backwards.

    WiFi ESP8266-01 Module where to place
    WiFi ESP8266-01 Module in place

    Your robot should look similar to the one in the photo below.

    WiFi Module in placeWiFi Module in place

    Testing the WiFi Module 

    The following code tests the WiFi Module. Make sure to install the WiFiEsp library through the Arduino Library Manager. The steps to do this are shown in the images below.

    Open Arduino Library Manager
    Type the name of the library into the "Filter your search.." text field and click install.
    You have installed the library!

    The following code tests that you have installed the ESP8266 properly and that it is able to connect to your network. Open the Serial monitor (as shown in the images below) and check to see a similar output. In this example, E100 was the ssid of the network it connected to. You should see your ssid in place of E100.

    Open Serial Monitor
    Monitor the results of the ESP8266-BasicTest.ino

    ESP8266-BasicTest.ino

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    /*
       WiFiEsp test: BasicTest
    
       Performs basic connectivity test and checks.
    */
    
    #include "WiFiEsp.h"
    
    // Emulate Serial1 on pins 7/6 if not present
    #ifndef HAVE_HWSERIAL1
    #include "SoftwareSerial.h"
    SoftwareSerial Serial1(8, 9); // RX, TX
    #endif
    
    
    char ssid[] = "ssid";     // your network SSID (name)
    char pwd[] = "password";  // your network password
    char pwdErr[] = "xxxx";   // wrong password
    
    
    void setup()
    {
        Serial.begin(115200);
        Serial1.begin(9600);
        WiFi.init(&Serial1);
    }
    
    void loop()
    {
        assertEquals("Firmware version", WiFi.firmwareVersion(), "1.5.4");
        assertEquals("Status is (WL_DISCONNECTED)", WiFi.status(), WL_DISCONNECTED);
        assertEquals("Connect", WiFi.begin(ssid, pwd), WL_CONNECTED);
        assertEquals("Check status (WL_CONNECTED)", WiFi.status(), WL_CONNECTED);
        assertEquals("Check SSID", WiFi.SSID(), ssid);
    
        IPAddress ip = WiFi.localIP();
        assertNotEquals("Check IP Address", ip[0], 0);
        Serial.print("IP Address: ");
        Serial.println(ip);
    
        byte mac[6]={0,0,0,0,0,0};
        WiFi.macAddress(mac);
    
        Serial.print("MAC: ");
        Serial.print(mac[5], HEX);
        Serial.print(":");
        Serial.print(mac[4], HEX);
        Serial.print(":");
        Serial.print(mac[3], HEX);
        Serial.print(":");
        Serial.print(mac[2], HEX);
        Serial.print(":");
        Serial.print(mac[1], HEX);
        Serial.print(":");
        Serial.println(mac[0], HEX);
        Serial.println();
    
        assertEquals("Disconnect", WiFi.disconnect(), WL_DISCONNECTED);
        assertEquals("Check status (WL_DISCONNECTED)", WiFi.status(), WL_DISCONNECTED);
        assertEquals("IP Address", WiFi.localIP(), 0);
        assertEquals("Check SSID", WiFi.SSID(), "");
        assertEquals("Wrong pwd", WiFi.begin(ssid, pwdErr), WL_CONNECT_FAILED);
    
        IPAddress localIp(192, 168, 168, 111);
        WiFi.config(localIp);
    
        assertEquals("Connect", WiFi.begin(ssid, pwd), WL_CONNECTED);
        assertEquals("Check status (WL_CONNECTED)", WiFi.status(), WL_CONNECTED);
    
        ip = WiFi.localIP();
        assertNotEquals("Check IP Address", ip[0], 0);
    
    
        Serial.println("END OF TESTS");
        delay(60000);
    }
    
    
    ////////////////////////////////////////////////////////////////////////////////////
    
    
    void assertNotEquals(const char* test, int actual, int expected)
    {
        if(actual!=expected)
            pass(test);
        else
            fail(test, actual, expected);
    }
    
    void assertEquals(const char* test, int actual, int expected)
    {
        if(actual==expected)
            pass(test);
        else
            fail(test, actual, expected);
    }
    
    void assertEquals(const char* test, char* actual, char* expected)
    {
        if(strcmp(actual, expected) == 0)
            pass(test);
        else
            fail(test, actual, expected);
    }
    
    
    void pass(const char* test)
    {
        Serial.print(F("********************************************** "));
        Serial.print(test);
        Serial.println(" > PASSED");
        Serial.println();
    }
    
    void fail(const char* test, char* actual, char* expected)
    {
        Serial.print(F("********************************************** "));
        Serial.print(test);
        Serial.print(" > FAILED");
        Serial.print(" (actual=\"");
        Serial.print(actual);
        Serial.print("\", expected=\"");
        Serial.print(expected);
        Serial.println("\")");
        Serial.println();
        delay(10000);
    }
    
    void fail(const char* test, int actual, int expected)
    {
        Serial.print(F("********************************************** "));
        Serial.print(test);
        Serial.print(" > FAILED");
        Serial.print(" (actual=");
        Serial.print(actual);
        Serial.print(", expected=");
        Serial.print(expected);
        Serial.println(")");
        Serial.println();
        delay(10000);
    }
    

    Controlling the Robot via UDP 

    UDP packets are a core transport layer internet protocol technology. It is a connectionless model, meaning there is no prior arrangement to a packet being sent. UDP is useful because it is a very simple protocol and a very fast protocol. However, because it is connectionless, there is no guarantee that a packet arrives at its destination.

    The following example receives UDP packets from the local network and responds with an ACK. If you combine this with the motor control code, you can use UDP packets to drive the robot.

    Under this sketch configuration, packets are sent from a remote device on the local network to the ESP WiFi module. The WiFi module then sends the data to the Arduino over a software serial interface. The sketch running on the Arduino recognizes this data and prints it to the serial monitor and sends a response.

    Load this sketch onto your Arduino and then open the Serial monitor. You should see the WiFiEsp library connect to your SSID, print an IP address, and then print a message indicating it is "Lisenting on port 3333"

    Monitor the results of the WifiBotUDP.ino SketchMonitor the results of the WifiBotUDP.ino Sketch

    Now you are ready to send a packet to the robot. On OSX or Linux, you can send a UDP packet via the terminal using the command below, replacing 10.0.7.106 with the IP address of your robot. Also, ensure your computer is on the same wifi network as your robot:

    Download file Copy to clipboard
    1
    echo -n "test packet" > /dev/udp/10.0.7.106/3333
    

    OSX TerminalOSX Terminal

    On Windows, you will need to install an additional program in order to send UDP packets. Netcat can be installed via cygwin, or there are apps in the Microsoft store that may serve the purpose. Packet Sender is a cross platform utility that you may consider as well.

    If you are on an iOS or Android device, there are a number of apps in the iOS app store or play store that can send and receive UDP packets.

    Whatever you use to send the packets, set the port to 3333 and the "to" IP address to the one reported by your robot on the serial terminal.

    Below is an example command for sending a packet via netcat:

    Download file Copy to clipboard
    1
    echo -n "w" | nc -u -w1 10.0.7.106 3333
    

    WiFiBotUDP.ino

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    /*
     WiFiEsp example: WiFi UDP Send and Receive String
    
     This sketch wait an UDP packet on localPort using a WiFi shield.
     When a packet is received an 'ACK' packet is sent to the client on port remotePort.
    
     For more details see: http://yaab-arduino.blogspot.com/p/wifiesp-example-client.html
    */
    
    
    #include <WiFiEsp.h>
    #include <WiFiEspUdp.h>
    
    #ifndef HAVE_HWSERIAL1
    #include "SoftwareSerial.h"
    SoftwareSerial Serial1(8, 9); // RX, TX
    #endif
    
    char ssid[] = "ssid";            // your network SSID (name)
    char pass[] = "password";        // your network password
    int status = WL_IDLE_STATUS;     // the Wifi radio's status
    
    unsigned int localPort = 3333;  // local port to listen on
    
    char packetBuffer[255];          // buffer to hold incoming packet
    char ReplyBuffer[] = "ACK";      // a string to send back
    
    WiFiEspUDP Udp;
    
    void setup() {
      // initialize serial for debugging
      Serial.begin(115200);
      // initialize serial for ESP module
      Serial1.begin(9600);
      // initialize ESP module
      WiFi.init(&Serial1);
    
      // check for the presence of the shield:
      if (WiFi.status() == WL_NO_SHIELD) {
        Serial.println("WiFi shield not present");
        // don't continue:
        while (true);
      }
    
      // attempt to connect to WiFi network
      while ( status != WL_CONNECTED) {
        Serial.print("Attempting to connect to WPA SSID: ");
        Serial.println(ssid);
        // Connect to WPA/WPA2 network
        status = WiFi.begin(ssid, pass);
      }
      
      Serial.println("Connected to wifi");
      printWifiStatus();
    
      Serial.println("\nStarting connection to server...");
      // if you get a connection, report back via serial:
      Udp.begin(localPort);
      
      Serial.print("Listening on port ");
      Serial.println(localPort);
    }
    
    void loop() {
    
      // if there's data available, read a packet
      int packetSize = Udp.parsePacket();
      if (packetSize) {
        Serial.print("Received packet of size ");
        Serial.println(packetSize);
        Serial.print("From ");
        IPAddress remoteIp = Udp.remoteIP();
        Serial.print(remoteIp);
        Serial.print(", port ");
        Serial.println(Udp.remotePort());
    
        // read the packet into packetBufffer
        int len = Udp.read(packetBuffer, 255);
        if (len > 0) {
          packetBuffer[len] = 0;
        }
        Serial.println("Contents:");
        Serial.println(packetBuffer);
    
        // send a reply, to the IP address and port that sent us the packet we received
        Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
        Udp.write(ReplyBuffer);
        Udp.endPacket();
      }
    }
    
    
    void printWifiStatus() {
      // print the SSID of the network you're attached to:
      Serial.print("SSID: ");
      Serial.println(WiFi.SSID());
    
      // print your WiFi shield's IP address:
      IPAddress ip = WiFi.localIP();
      Serial.print("IP Address: ");
      Serial.println(ip);
    
      // print the received signal strength:
      long rssi = WiFi.RSSI();
      Serial.print("signal strength (RSSI):");
      Serial.print(rssi);
      Serial.println(" dBm");
    }
    
    

    Once you have loaded the sketch above, and opened the serial monitor, you should see the IP address printed to the serial monitor and you should see

    The solution to this challenge is shown below. Try it for yourself first, though!!

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    #include <WiFiEsp.h>
    #include <WiFiEspUdp.h>
    
    // Emulate Serial1 on pins 6/7 if not present
    #ifndef HAVE_HWSERIAL1
    #include "SoftwareSerial.h"
    SoftwareSerial Serial1(8, 9); // RX, TX
    #endif
    
    char ssid[] = "E100";            // your network SSID (name)
    char pass[] = "QAwR75?Wru?w";        // your network password
    int status = WL_IDLE_STATUS;     // the Wifi radio's status
    
    unsigned int localPort = 3333;  // local port to listen on
    
    char packetBuffer[255];          // buffer to hold incoming packet
    char ReplyBuffer[] = "ACK";      // a string to send back
    
    byte leftOffset = 0;
    byte rightOffset = 0;
    /* This could be a pwm class */
    unsigned long pwmTimeout = 0;
    
    #define AIN1 3
    #define AIN2 4
    #define APWM 5
    #define BIN1 12
    #define BIN2 13
    #define BPWM 11
    #define STBY 6
    #define STEPTIME 600 
    #define STRAIGHTSPEED 200
    #define TURNSPEED 120
    #define TURNTIME 300
    
    #include <SoftwareSerial.h>
    SoftwareSerial esp(8, 9);
    
    int pwms[] = {APWM, BPWM};
    
    void writePwms ( int left, int right) {
        analogWrite (pwms[0], left);
        analogWrite (pwms[1], right);
    }
    
    void goForward ( ) {
        digitalWrite(STBY, HIGH);
        digitalWrite(AIN1, LOW);
        digitalWrite(AIN2, HIGH);
        digitalWrite(BIN1, LOW);
        digitalWrite(BIN2, HIGH);
        writePwms (STRAIGHTSPEED-leftOffset,STRAIGHTSPEED-rightOffset);
        pwmTimeout = millis() + STEPTIME;
    }
    
    void goBack() {
        digitalWrite(STBY, HIGH);
        digitalWrite(AIN1, HIGH);
        digitalWrite(AIN2, LOW);
        digitalWrite(BIN1, HIGH);
        digitalWrite(BIN2, LOW);
        writePwms (STRAIGHTSPEED-leftOffset,STRAIGHTSPEED-rightOffset);
        pwmTimeout = millis() + STEPTIME;
    }
    
    void goLeft () {
        digitalWrite(STBY, HIGH);
        digitalWrite(AIN1, HIGH);
        digitalWrite(AIN2, LOW);
        digitalWrite(BIN1, LOW);
        digitalWrite(BIN2, HIGH);
    
        writePwms (TURNSPEED,TURNSPEED);
        pwmTimeout = millis() + TURNTIME;
    }
    
    void goRight () {
        digitalWrite(STBY, HIGH);
        digitalWrite(AIN1, LOW);
        digitalWrite(AIN2, HIGH);
        digitalWrite(BIN1, HIGH);
        digitalWrite(BIN2, LOW);
    
        writePwms (TURNSPEED,TURNSPEED);
        pwmTimeout = millis() + TURNTIME;
    }
    
    void stop(){
        //enable standby  
        digitalWrite(STBY, LOW); 
    }
    
    WiFiEspUDP Udp;
    
    void setup() {
        pinMode (STBY, OUTPUT);
        pinMode (AIN1, OUTPUT);
        pinMode (AIN2, OUTPUT);
        pinMode (APWM, OUTPUT);
        pinMode (BIN1, OUTPUT);
        pinMode (BIN2, OUTPUT);
        pinMode (BPWM, OUTPUT);
    
        // initialize serial for debugging
        Serial.begin(115200);
        // initialize serial for ESP module
        Serial1.begin(9600);
        // initialize ESP module
        WiFi.init(&Serial1);
    
        // check for the presence of the shield:
        if (WiFi.status() == WL_NO_SHIELD) {
            Serial.println("WiFi shield not present");
            // don't continue:
            while (true);
        }
    
        // attempt to connect to WiFi network
        while ( status != WL_CONNECTED) {
            Serial.print("Attempting to connect to WPA SSID: ");
            Serial.println(ssid);
            // Connect to WPA/WPA2 network
            status = WiFi.begin(ssid, pass);
        }
    
        Serial.println("Connected to wifi");
        printWifiStatus();
    
        Serial.println("\nStarting connection to server...");
        // if you get a connection, report back via serial:
        Udp.begin(localPort);
    
        Serial.print("Listening on port ");
        Serial.println(localPort);
    }
    
    void loop() {
        // If pwmTimeout has been set
        if (pwmTimeout > 0) {
            // Compare the current time to pwmTimeout
            if (millis() > pwmTimeout) {
                // If pwmTimeout has elapsed, zero pwmTimeout and stop the motors by zeroing the PWM signals
                pwmTimeout = 0;
                writePwms (0,0);       
            }
        }
    
        // if there's data available, read a packet
        int packetSize = Udp.parsePacket();
        if (packetSize) {
            Serial.print("Received packet of size ");
            Serial.println(packetSize);
            Serial.print("From ");
            IPAddress remoteIp = Udp.remoteIP();
            Serial.print(remoteIp);
            Serial.print(", port ");
            Serial.println(Udp.remotePort());
    
            // read the packet into packetBufffer
            int len = Udp.read(packetBuffer, 255);
            if (len > 0) {
                packetBuffer[len] = 0;
            }
            Serial.println("Contents:");
            Serial.println(packetBuffer);
    
            // Use a switch statement to examine the first character of the packet buffer
            switch(packetBuffer[0]){
                case 'w':
                    // If the value is 'w', move the robot forward
                    goForward();
                    break;
                case 'a':
                    // If the value is 'a', move the robot left
                    goLeft();
                    break;
                case 's':
                    // If the value is 's', move the robot backward
                    goBack();
                    break;
                case 'd':
                    // If the value is 's', move the robot right
                    goRight();
                    break;
            }
    
            // send a reply, to the IP address and port that sent us the packet we received
            Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
            Udp.write(ReplyBuffer);
            Udp.endPacket();
    
            // Zero the packetBuffer first character until we receive a new packet
            packetBuffer[0] = 0;
        }
    }
    
    void printWifiStatus() {
        // print the SSID of the network you're attached to:
        Serial.print("SSID: ");
        Serial.println(WiFi.SSID());
    
        // print your WiFi shield's IP address:
        IPAddress ip = WiFi.localIP();
        Serial.print("IP Address: ");
        Serial.println(ip);
    
        // print the received signal strength:
        long rssi = WiFi.RSSI();
        Serial.print("signal strength (RSSI):");
        Serial.print(rssi);
        Serial.println(" dBm");
    }
    

    Controlling remotely 

    There are a number of ways to further control the robot remotely. We will explore one of these in the next section. Feel free to play around, however. The WiFiEsp Library includes a number of examples, nearly all of which can be modified to work with the WiFi Robot.

    Controlling From a Phone

    Using Blynk.cc

    The Blynk app provides a user-friendly way to build user interfaces for IoT projects. The following sketch can be used with the Blynk app (avaialble from Blynk.cc).

    Firmware setup (Arduino) 

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    #define BLYNK_PRINT Serial    // Comment this out to disable prints and save space
    #include <ESP8266_Lib.h>
    #include <BlynkSimpleShieldEsp8266.h>
    
    #include <NewPing.h>
    
    #define TRIGGER_PIN  10
    #define ECHO_PIN     7
    #define MAX_DISTANCE 200
    
    NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
    
    // Pin assignments
    #define AIN1 3
    #define AIN2 4
    #define APWM 5
    #define BIN1 12
    #define BIN2 13
    #define BPWM 11
    #define STBY 6
    
    int x_speed = 0;
    int y_speed = 0;
    int distance = 0;
    
    enum motor_state_t {
        STOP,
        FORWARD,
        BACKWARD
    };
    
    void setLeftMotor(motor_state_t direction, uint8_t speed){
        if(direction == STOP){
            digitalWrite(AIN1, LOW);
            digitalWrite(AIN2, LOW);
        }else if(direction == BACKWARD){
            digitalWrite(AIN1, HIGH);
            digitalWrite(AIN2, LOW);
        }else if(direction == FORWARD){
            digitalWrite(AIN1, LOW);
            digitalWrite(AIN2, HIGH);
        }
        analogWrite (APWM, speed);
    }
    
    void setRightMotor(int8_t direction, uint8_t speed){
        if(direction == STOP){
            digitalWrite(BIN1, LOW);
            digitalWrite(BIN2, LOW);
        }else if(direction == BACKWARD){
            digitalWrite(BIN1, HIGH);
            digitalWrite(BIN2, LOW);
        }else if(direction == FORWARD){
            digitalWrite(BIN1, LOW);
            digitalWrite(BIN2, HIGH);
        }
        analogWrite (BPWM, speed);
    }
    
    void stopRobot(){
        digitalWrite(STBY, LOW);
    }
    
    void goRobot(){
        digitalWrite(STBY, HIGH);
    }
    
    
    // You should get Auth Token in the Blynk App.
    // Go to the Project Settings (nut icon).
    char auth[] = "authtoken";
    
    // Your WiFi credentials.
    // Set password to "" for open networks.
    char ssid[] = "ssid";
    char pass[] = "password";
    
    // Hardware Serial on Mega, Leonardo, Micro...
    //#define EspSerial Serial1
    
    // or Software Serial on Uno, Nano...
    #include <SoftwareSerial.h>
    SoftwareSerial EspSerial(8, 9); // RX, TX
    
    // Your ESP8266 baud rate:
    #define ESP8266_BAUD 9600
    
    ESP8266 wifi(&EspSerial);
    
    void setup()
    {
        // Initialize pins as outputs
        pinMode (STBY, OUTPUT);
        pinMode (AIN1, OUTPUT);
        pinMode (AIN2, OUTPUT);
        pinMode (APWM, OUTPUT);
        pinMode (BIN1, OUTPUT);
        pinMode (BIN2, OUTPUT);
        pinMode (BPWM, OUTPUT);
        pinMode (TRIGGER_PIN, OUTPUT);
        pinMode (ECHO_PIN, INPUT);
        // Set console baud rate
        Serial.begin(9600);
        delay(10);
        // Set ESP8266 baud rate
        EspSerial.begin(ESP8266_BAUD);
        delay(10);
    
        Blynk.begin(auth, wifi, ssid, pass);
    }
    
    BLYNK_READ(V5) {
        int uS = sonar.ping();
        distance = uS / US_ROUNDTRIP_CM;
        Serial.print("Ping: ");
        Serial.print(distance);
        Serial.println("cm");
        // Write to Blynk Virtual Pin 5
        Blynk.virtualWrite(V5, distance);
    }
    
    BLYNK_READ(V0){
        Blynk.virtualWrite(V0, 100);
    }
    
    BLYNK_WRITE(V2) {
        x_speed = param.asInt();
        Serial.print("x_speed: ");
        Serial.println(x_speed);
    }
    
    BLYNK_WRITE(V3) {
        y_speed = param.asInt();
        Serial.print("y_speed: ");
        Serial.println(y_speed);
    }
    
    BLYNK_WRITE(V1) {
        // Joystick range is 0 to 1024. Resting zero position of the joystick is 512
        const int ZERO_POSITION = 512;
    
        // Use a threshold to account for near zero position
        const int THRESHOLD_OFFSET = 90;
    
        // speed for a hard clouckwise/counterclockwise turn
        const int TURN_SPEED = 120;
    
        // Parameters received from Blynk app
        int x = param[0].asInt();
        int y = param[1].asInt();
    
        // Direction variables indicating direction of joystick movement
        // Possible values: -1, 0, 1
        int x_direction;
        int y_direction;
    
        // Print x and y values for debugging
        Serial.print("X = ");
        Serial.print(x);
        Serial.print("; Y = ");
        Serial.println(y);
    
        // Determine direction of the joystick
        x_direction = 0;
        y_direction = 0;
    
        if(x > (ZERO_POSITION + THRESHOLD_OFFSET)){
            x_direction = 1;
        }else if(x < (ZERO_POSITION - THRESHOLD_OFFSET)){
            x_direction = -1;
        }
    
        if(y > (ZERO_POSITION + THRESHOLD_OFFSET)){
            y_direction = 1;
        }else if(y < (ZERO_POSITION - THRESHOLD_OFFSET)){
            y_direction = -1;
        }
    
        // Move the WiFiBot according to the x and y direction:
        // STOP:       x= 0, y= 0
        // FORWARD:    x= 0, y= 1
        // BACKWARD:   x= 0, y=-1
        // RIGHT UP:   x= 1, y= 1
        // RIGHT:      x= 1, y= 0
        // RIGHT DOWN: x= 1, y=-1
        // LEFT UP:    x=-1, y=-1
        // LEFT:       x=-1, y= 0
        // LEFT DOWN:  x=-1, y=-1
    
        if (x_direction == 0 && y_direction ==0){
            // STOP: x= 0, y= 0
            stopRobot();
            setLeftMotor(STOP, 0);
            setRightMotor(STOP, 0);
            Serial.println("STOP");
        } else {
            // Robot is moving, set STBY pin accordingly using the goRobot function
            goRobot();
            Serial.print("Go ");
    
            // Set the direction and speed of the motors accordingly
            if(x_direction == 0){
    
                if(y_direction == 1){
                    Serial.println("Forward");
                    // FORWARD: x= 0, y= 1
                    setLeftMotor(FORWARD, x_speed);
                    setRightMotor(FORWARD, y_speed);
                }else if(y_direction == -1){
                    // BACKWARD:   x= 0, y=-1
                    Serial.println("Backward");
                    setLeftMotor(BACKWARD, x_speed);
                    setRightMotor(BACKWARD, y_speed);
                }
                //
            }else if(x_direction == 1){
                if(y_direction == 1){
                    // RIGHT UP:   x= 1, y= 1
                    Serial.println("Right up");
                    setLeftMotor(FORWARD, x_speed);
                    setRightMotor(STOP, y_speed);
                }else if(y_direction == 0){
                    // RIGHT:      x= 1, y= 0
                    Serial.println("Right");
                    setLeftMotor(FORWARD, TURN_SPEED);
                    setRightMotor(BACKWARD, TURN_SPEED);
                }else if(y_direction == -1){
                    // RIGHT DOWN: x= 1, y=-1
                    Serial.println("Right down");
                    setLeftMotor(BACKWARD, x_speed);
                    setRightMotor(STOP, y_speed);
                }
            }else if(x_direction == -1){
                if(y_direction == 1){
                    // LEFT UP:    x=-1, y=-1
                    Serial.println("Left up");
                    setLeftMotor(STOP, x_speed);
                    setRightMotor(FORWARD, y_speed);
                }else if(y_direction == 0){
                    // LEFT:       x=-1, y= 0
                    Serial.println("Left");
                    setLeftMotor(BACKWARD, TURN_SPEED);
                    setRightMotor(FORWARD, TURN_SPEED);
                }else if(y_direction == -1){
                    // LEFT DOWN:  x=-1, y=-1
                    Serial.println("Left down");
                    setLeftMotor(STOP, x_speed);
                    setRightMotor(BACKWARD, y_speed);
                }
            }
        }
    }
    
    void loop()
    {
        Blynk.run();
    }
    

    Configuring Blynk app 

    Download the Blynk app from the app store and follow the steps below to create a new project to control your robot. Each step matches the corresponding screenshot.

    Click create a new project in the Blynk app

    Give your project a name, select ESP8266, choose a theme, and click Create

    Blynk will send you an email with your auth token. Copy and paste this into the Arduino code

    A new blank canvas for your project. Hit the + icon in the upper right corner to add a new widget.

    Add 2 Slider widgets, a Joystick widget, and a Value Display widget.

    Your Blynk canvas should look similar to this.

    Edit the slider widget by tapping on it. Then set the name to "right motor speed."

    Set the output to Virtual pin V2

    Change the upper limit from 1024 to 255

    Edit the second slider widget similarly. Set its name to "left motor speed," the pin to Virtual pin V3, and the upper limit to 255

    Edit the Joystick widget. Switch output from "split" to "merge."

    Edit the Value Display widget. Set the name as "ping cm" and set the input to Virtual Pin V5.

    Tap on the "Pin" box and set it to Virtual pin V1

    Upload your code to the UNO board. Then, hit the play button on the blink app highlighted in the upper right corner of this screenshot.

    Your Blynk sketch is now live. Tap the chip-shaped button highlighted to ensure your device is connected.

    If everything is working, you should see your device is online. Make sure there are batteries in your robot, and you're almost ready to start driving it around.

    Set the right and left motor speed to 255 and you can use the joystick to drive it around. If the robot does not drive straight, you can tweak the motor speeds.

    Click create a new project in the Blynk app
    Give your project a name, select ESP8266, choose a theme, and click Create
    Blynk will send you an email with your auth token. Copy and paste this into the Arduino code
    A new blank canvas for your project. Hit the + icon in the upper right corner to add a new widget.
    Add 2 Slider widgets, a Joystick widget, and a Value Display widget.
    Your Blynk canvas should look similar to this.
    Edit the slider widget by tapping on it. Then set the name to "right motor speed."
    Set the output to Virtual pin V2
    Change the upper limit from 1024 to 255
    Edit the second slider widget similarly. Set its name to "left motor speed," the pin to Virtual pin V3, and the upper limit to 255
    Edit the Joystick widget. Switch output from "split" to "merge."
    Edit the Value Display widget. Set the name as "ping cm" and set the input to Virtual Pin V5.
    Tap on the "Pin" box and set it to Virtual pin V1
    Upload your code to the UNO board. Then, hit the play button on the blink app highlighted in the upper right corner of this screenshot.
    Your Blynk sketch is now live. Tap the chip-shaped button highlighted to ensure your device is connected.
    If everything is working, you should see your device is online. Make sure there are batteries in your robot, and you're almost ready to start driving it around.
    Set the right and left motor speed to 255 and you can use the joystick to drive it around. If the robot does not drive straight, you can tweak the motor speeds.
    What's Next

    Done Now Have Fun 

    You've built the WiFi robot!

    Now come up with some cool ways to modify and hack it to do more!

    Done Now Have Fun Video
    Have fun.
    Troubleshooting

    General Help 

    Note: We will continue to update this section as issues arise. If you don't find something helpful here, please look through or search the forums.