iot for dummies | part 2

[or] coding over-the-air

2019/07/29

Tags: code diy electronics

All lessons (and the project files):

  1. First steps
  2. Coding over-the-air
  3. Putting files into SPIFFS
  4. Untethered serial monitor

In this lesson we will learn how to connect to our Arduino through WiFi network, as well as program it over-the-air. Being tethered sucks, doesn’t it? This thing is crucial if you’ve already placed your Arduino inside a case, or into an evil robotic brain you’re making in your backyard and it’s no longer reachable through a USB adapter, but you still want to be able to tweak the code if needed.

Of course, you can find dozens of nice tutorials online on how to do this, but here I will teach you one bonus thing which I actually couldn’t find online myself (over-the-air serial monitor).

OTA setting _This is the final result of this lesson which you will see in your serial monitor (yes, my wifi `SSID` is `DIReX`, and, no, I couldn't have come up with anything better)._

Libraries

This time we will need to have a few non-default libraries installed in PlatformIO. I noticed that my version of PlatformIO already has most (if not all of them) pre-installed, but otherwise you’ll need to install them through PlatformIO interface yourself (PlatformIO > PlatformIO Home > Libraries > Search Libraries > ...). In this lesson we’ll only be using the following two:

#include <ESP8266WiFi.h>   /* to connect to WiFi */
#include <ArduinoOTA.h>    /* for the over-the-air updating */

But for the future we’d ideally like to also have these ones:

#include <ESP8266HTTPClient.h>   /* to handle http connection */
#include <ESP8266WebServer.h>    /* to run a web server */
#include <FS.h>                  /* to work with the built-in filesystem */

Connecting to WiFi

Now use the following code to connect to your WiFi network, just don’t forget to paste your WiFi name and password in the SSID and PASS fields saved as pointers to constant characters (i.e., as constant strings basically).

#include <Arduino.h>
#include <ESP8266WiFi.h>

const char * SSID = "<YOUR_WIFI_SSID>";
const char * PASS = "<YOUR_WIFI_PASS>";

void setup() {
  Serial.begin(74880);
  WiFi.begin(SSID, PASS);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(SSID);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
}

Once you load this to your Arduino and open the serial monitor (make sure to press the little reset button on NodeMCU after opening the monitor), it will try to connect to your WiFi network, and when successful will write the local IP address that was assigned to your ESP8266 module by your WiFi router. We will need this IP in the future, so write it down somewhere. For convenience, I’m going to use something like 192.168.0.1, but you should be using whatever you see in your serial monitor.

Connecting.......
Connected to <YOUR_WIFI_SSID>

IP address: <ESP_LOCAL_IP>

You can also put these commands to some void function and include it as a header (which you should put in the include/ folder) and call that function in the setup(). Or if you’re more of an OOP-person, you can always define a structure and put all these and other commands into an initializer. This is to say, that all default programming laws and conventions apply here.

Setting up OTA

Now before moving forward, we want to get rid of the USB cable asap, and put our Arduino away and still be able to update its instructions remotely. For that we’ll be using something called ArduinoOTA library (over-the-air). The following code will do the job, copy it to the main.cpp and reflect on its content.

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>

const char * SSID = "<YOUR_WIFI_SSID>";
const char * PASS = "<YOUR_WIFI_PASS>";

void setup() {
  Serial.begin(74880);

  /* connect to WiFi */
  WiFi.begin(SSID, PASS);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(SSID);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  /* set up OTA */
  ArduinoOTA.onStart([]() {
    Serial.println("Start");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    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");
  });
  ArduinoOTA.begin();
  Serial.println("OTA ready");
}

void loop() {
  /* at each cycle Arduino will be checking ...
  ... if any incomming connection is trying ...
  ... to load some code */
  ArduinoOTA.handle();
}

After you upload this to your Arduino and see the OTA ready in your serial monitor it’s time to check if it really works. Unplug your Arduino from your pc/laptop and connect it to an external power supply. Notice, that you basically need to connect two pins (any of the 3v3 and GND) to the corresponding wires in your power supply. Now some NodeMCU-s can actually have a built-in voltage regulator that will drop an incoming 5V supply to a necessary 3.3V (in that case 5V should be connect to the VIN pin).

Once Arduino is connected to a power supply (you will see an LED blink at the beginning), it will run the code we just gave it: it will connect to our WiFi network, initialize OTA and look for any incoming connection to upload the new code (this is what ArduinoOTA.handle() does). We now can upload a new code to it remotely!

For that we’ll need to change our platformio.ini file so that it looks like this (instead of 192.168.0.1 you’ll paste the local IP address read from the serial monitor previously):

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
upload_protocol = espota      ; < add this
upload_port = 192.168.0.1     ; < add this

Ok, now make sure the new code you upload still contains the OTA initialization and handling, since otherwise after you flash the Arduino, it will lose the ability to upload new instructions. Now let’s try to upload our favorite LED blink example over the air.

This time I will put all the initializations to a header file include/Initialize.h:

#ifndef INITIALIZE_H
#define INITIALIZE_H

void initializeWifi() {
  WiFi.begin(SSID, PASS);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(SSID);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void initializeOTA() {
  ArduinoOTA.onStart([]() {
    Serial.println("Start");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    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");
  });
  ArduinoOTA.begin();
  Serial.println("OTA ready");
}

#endif

While my main.cpp file will look like this (and don’t forget to #include "Initialize.h"):

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>

const char * SSID = "<YOUR_WIFI_SSID>";
const char * PASS = "<YOUR_WIFI_PASS>";

#include "Initialize.h"

void setup() {
  Serial.begin(74880);
  initializeWifi();
  initializeOTA();
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  ArduinoOTA.handle();
  digitalWrite(LED_BUILTIN, LOW);
  delay(2000);
  digitalWrite(LED_BUILTIN, HIGH);
  delay(2000);
}

Now click Upload, and it should try to connect to your Arduino through WiFi and upload the code. If it fails, make sure to check the platformio.ini as instructed above and try again. The loading screen should look something like this.

OTA upload

So now your Arduino should connect to WiFi, initialize OTA, handle it on every loop iteration and then turn the LED ON and OFF once every 2 seconds. And the best part is, we just uploaded these instructions through WiFi!


There’s a slight flaw in this code actually. delay built-in function while being pretty neat for small projects is a very dangerous tool for any serious large project. Effectively all the operations are stalled while the delay function is called, so in reality Arduino will not be handle-ing OTA during 4 second interval in the loop, while the delay is on. That’s pretty bad, but there are ways to avoid that. For now, try to think of some neat solution how the same thing could be achieved avoiding these kinds of global pauses. You might want to use the built-in millis() function which returns (as unsigned long) the number of milliseconds passed since the Arduino was turned on.

cd ~