Mktwo Badge

From All Hands Active Wiki
Revision as of 23:02, 10 April 2016 by LogiK (talk | contribs)
Jump to navigation Jump to search

OTA (Over The Air, A.K.A. via HTTP) 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 webserver code:

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

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

#define PIN 12

//The number of times we try to connect to the specified network before we give up and reboot.
#define WL_RETRY_COUNT 5

// 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* host = "esp8266-%06x-webupdater";
//The longest string size we can use for the hostname.
#define MAX_HOST_NAME_LEN 26

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;

ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

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();
  
  //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...");
  }

  setBrightness(50, 0);
  
  // 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_AP_STA);
  
  //Check for a good connection.
  int retryCount = 0;

  solidColor(strip.Color(0, 50, 0), 0);
  setBrightness(50, 0);
  Serial.println("Connecting to network...");
  WiFi.begin(ssid, password);
  
  while(WiFi.waitForConnectResult() != WL_CONNECTED) {
    WiFi.begin(ssid, password);
    //Show LED/Serial heartbeat if we are having problems connecting.
    setBrightness(100, 2000);
    Serial.println("Connection Failed! Retrying...");
    setBrightness(50, 2000);
    
    if(retryCount == WL_RETRY_COUNT) {
      //Reboot if we can't get a connection.  Might be something funky going on with the state of the hardware?
      //TODO: Have more grace with this in the future?
      ESP.restart();
    }

    retryCount++;
  }

  //Begin upload handling.
  //Might cut off characters if the constant string is too long.
  char myHost[MAX_HOST_NAME_LEN];
  snprintf(myHost, sizeof(myHost), host, ESP.getChipId());
  
  MDNS.begin(myHost);
  httpUpdater.setup(&httpServer);
  httpServer.begin();
  MDNS.addService("http", "tcp", 80);
  
  Serial.print("ESP IP address: ");
  Serial.println(WiFi.localIP());
  Serial.printf("HTTPUpdateServer ready! Open http://%s.local/update in your browser\n", myHost);
  
  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(" ?");
    httpServer.handleClient();
    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 with HTTP going forward (unless something really goes wrong).

OK, now that we've done it the old way, let's try an OTA flash.

You might wish to save yourself some grief: if you have a running firewall, make sure you can receive Avahi/mDNS/Bonjour requests by allowing port 5353/UDP from 224.0.0.251 (or IPv6 FF02::FB).

Reboot the board, and hold down the programming button (the top button). If you do this in time, all the LEDs will flash blue, letting you know it's ready to receive a binary. If you have the serial monitor running, you will catch it telling you about it going into programming mode, and if everything is configured correctly, should eventually give you a web address like "http://esp8266-<ESP ID>-webupdater.local/update". You can also use "avahi-browse -art" in the commandline to get a listing that should include this board's hostname. It is possible to get these confused very easily if there are multiple boards running on OTA programming mode, since there's no easy way to determine ESP ID by looking at the chip (that I'm aware of anyhow). The LEDs will flash red if it is stuck trying to associate with the network, and after a few tries will simply reboot. Check your SSID/Password strings.

Anyhow, if you can see the URL, go to it with your computer's browser. You will see a very simple form with "Browse..." and "Upload" buttons. Click the Browse button and choose the binary image you wish to flash. If you "verify" the project in the Arduino IDE, it should be compiled to /tmp/build<hex string>.tmp/<sketchname>.ino.bin. Go ahead and select that file, then hit upload. If the OTA flashing succeeds, the website will reply simply "OK", and the board will reboot.

Caveats:

  • If things ever go sideways, you can always re-flash with the FTDI cable again.