Mktwo Badge

From All Hands Active Wiki
Revision as of 21:40, 4 April 2016 by LogiK (talk | contribs)
Jump to navigation Jump to search

OTA (Over The Air, A.K.A. via WiFi) firmware loading!

Reference: https://github.com/esp8266/Arduino/blob/master/doc/ota_updates/readme.md#application-example

First, we'll use this example code that rolls in a modified strandtest with the OTA code:

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
  #include <avr/power.h>
#endif

#define PIN 12

// Parameter 1 = number of pixels in strip
// Parameter 2 = Arduino pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(6, PIN, NEO_GRB + NEO_KHZ800);

// IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across
// pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input
// and minimize distance between Arduino and first pixel.  Avoid connecting
// on a live circuit...if you must, connect GND first.


const char* ssid = "TEST";
const char* password = "TEST";

//Keep track of the initial bootup period.  We might want to enter the OTA upload mode.
bool bootup = true;

void setup() {
  Serial.begin(115200);
  Serial.println("Booting");

  //Set up the pin for LED use.
  strip.begin();
  //Should clear out any colors that might be stuck on at startup. 
  strip.show();
  
  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("myesp8266");

  // No authentication by default
  // ArduinoOTA.setPassword((const char *)"123");

  ArduinoOTA.onStart([]() {
    Serial.println("Start");
    //Make sure all LEDs are off when we start uploading.
    solidColor(strip.Color(0, 0, 0), 0);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
    solidColor(strip.Color(0, 0, 0), 0);
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    //Make green LED brightness a factor of upload progress.
    int brightness = (progress / (total / 255));
    solidColor(strip.Color((int)brightness, 0, 0), 0);
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });

  //Allows us to use the "program" (top) button in our program.
  pinMode(0, INPUT_PULLUP);
}

void loop() {
  //When the program first starts up, we must check if the user wishes to enter OTA upload mode.
  if(bootup == true) {
    //Give the user a few moments to hold down the program button.
    delay(2000);

    //User is holding down the program button, so go into OTA upload mode.
    if(digitalRead(0) == LOW) {
      otaUpload();
    }
    
    //If we fall through to here, the program button isn't being held down, and we will just run the normal code.
    //Never return to this code again, until the user resets the device.
    bootup = false;
    Serial.println("Skipping OTA upload...");
  }
  
  // Some example procedures showing how to display to the pixels:
  colorWipe(strip.Color(127, 0, 0), 50); // Red
  colorWipe(strip.Color(0, 127, 0), 50); // Green
  colorWipe(strip.Color(0, 0, 127), 50); // Blue
  colorWipe(strip.Color(255, 255, 255), 50); // White RGBW
  // Send a theater pixel chase in...
  theaterChase(strip.Color(127, 127, 127), 50); // White
  theaterChase(strip.Color(127, 0, 0), 50); // Red
  theaterChase(strip.Color(0, 0, 127), 50); // Blue

  rainbow(20);
  rainbowCycle(20);
  theaterChaseRainbow(50);
}

//This function handles the process of waiting for an OTA upload to happen at bootup.
void otaUpload(void) {
  Serial.println("Entering OTA upload mode!");
  //Start up WiFi
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  
  //Check for a good connection.
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    //TODO: Make a better failure mode here... LED error status?
    ESP.restart();
  }

  //Begin upload handling.
  ArduinoOTA.begin();
  Serial.println("OTA upload ready!");
  Serial.print("ESP IP address: ");
  Serial.println(WiFi.localIP());

  Serial.println("Waiting for upload...");
  solidColor(strip.Color(0, 0, 50), 0);
  //We just wait in here very patiently until the user sends us something.  The only way out is to reset.
  while(true) {
    //Set up an LED/Serial "heartbeat" status to let user know we are waiting for data in OTA upload mode.
    setBrightness(50, 1000);
    Serial.print(" ?");
    ArduinoOTA.handle();
    setBrightness(255, 1000);
  }
}

void solidColor(uint32_t c, int wait) {
  for(uint16_t i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

void setBrightness(uint16_t b, int wait) {
  strip.setBrightness(b);
  strip.show();
  delay(wait);
}

// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

//Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
  for (int j=0; j<10; j++) {  //do 10 cycles of chasing
    for (int q=0; q < 3; q++) {
      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, c);    //turn every third pixel on
      }
      strip.show();

      delay(wait);

      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}

//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
  for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
    for (int q=0; q < 3; q++) {
      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
      }
      strip.show();

      delay(wait);

      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

We will flash this code as we would normally using the FTDI cable -- no sweat! This will allow us to update OTA going forward (unless something really goes wrong).

OK, let's try an OTA flash.

Save yourself some grief: ensure you don't have some kind of firewall running that might block traffic. This limitation makes it difficult for firewalled systems, since the espota.py program used to do OTA binds to a random port on the server's end (i.e. your computer), and the Arduino IDE listens for an Avahi/Bonjour/multicast DNS message from the ESP to put it in the "Ports" menu. You need to allow port 5353/UDP from 224.0.0.251 (or IPv6 FF02::FB) to receive the mDNS stuff. You would normally need to allow incoming TCP traffic on ports 10000-60000 from the ESP to get the server-side working.

Under menu item Tools | Upload Using, select "OTA".

Now wait a few moments. Eventually under the menu item Tools | Port, you should see something like "esp8266-<something> at <ip_address> (Generic ESP8266 Module)". If you don't see this after waiting a while, restart the Arduino IDE and look in that menu again.

Once you see it, select it, and now you can flash the firmware without a cable using the Upload button. It should compile things, and then you should see the ESP's LED eventually start blinking as it receives the binary and reboots.

Caveats:

  • If your code "blocks" for any reason, it will cause the ArduinoOTA.handle() function to never be called in the main loop. Make sure you can periodically call this function in the loop(), or the OTA flash will time out.
  • Serial monitor doesn't work with OTA.
  • If things ever go sideways, you can always re-flash with the FTDI cable again. Just change the Upload Using and Port settings back to "Serial", "/dev/ttyUSB0", respectively.

Alternative command-line OTA flash method:

Verify/Compile your program in the Arduino IDE with CTRL+R. It will place the file somewhere in a place like /tmp/build<long-hex-number>.tmp/strandtestOTA.ino.ino.bin.

Now you can just use the espota.py program from the command-line to keep it from using a random server-side port:

python ~/.arduino15/packages/esp8266/hardware/esp8266/2.1.0/tools/espota.py -i <ESP's IP address> -p <default ESP listen port, defaults to 8266> -P <server's port, which you have un-firewalled> -f /tmp/build<long-hex-number>.tmp/strandtestOTA.ino.ino.bin