Reassessing Lua on the NodeMCU

Ok, so in the last few articles, we focused on what the NodeMCU + Lua ecosystem could do for us; and actually, it does a great deal of stuff in a very straightforward way.

The Lua API, albeit a bit php-esque sometimes, is pretty simple to understand and to code. The Esplorer / esptool combo is not complex and can get you started on the NodeMCU in a few minutes, which is great.

But.

But after developing quite a few production-grade devices with the Lua firmware, I began to experience some frustration over the limitations of the language and the debug capabilities:

  • unwanted reboots

I experienced quite a few reboots for no reason (although I think this is heap related, but well) and there is no log to tell you why it rebooted.

  • memory leaks

Sometimes the heap fills up, sometimes it empties, but that's not reproducible, non deterministic, and totally random. The same code can run for two weeks with a steady heap, and then the behavior starts to be erratic. This is weird.

  • ADC weird behaviour

At one point I wanted to test the basic functionality of the ADC0 pin. Turns out the result for adc.read() for a pin at GND was 23 while being a normal 1023 for a pin at 3.3V ... which is quite odd.

  • memory, memory, memory

Having to compile every file each time I make a change and limit the number of function calls in my code is a hassle that is difficult to live with when writing production code

The alternative

Ok, so we have an alternative (not only one though), and it sounds promising.. the Arduino IDE now supports the ESP8266 board directly !

Thanks to the guys at the ESP8266 Community forum, it works pretty well out of the box and the installation on the Arduino IDE could not be simpler.

Installation

You just have to go in the preferences (Arduino > 1.6.5) and add a new board manager url :

It is : http://arduino.esp8266.com/stable/package_esp8266com_index.json.

Click OK and you have your new boards ready to use :

Ooooooh yeah. NodeMCU 1.0 is actually the Amica revision.

NB : you don't have to flash a specific firmware to use it with the Arduino IDE. In fact, it's the firmware that you code directly

A simple MQTT example

I will redo a very simple MQTT example here to match the functionnality of the previous tutorial I did with Lua (see http://www.foobarflies.io/a-simple-connected-object-with-nodemcu-and-mqtt/).

The NodeMCU will connect to the Wifi, then connect to a broker and send regular pings, while accepting and logging messages on a specific endpoint.

The bare minimum

Well, it's exactly like for an Arduino sketch, so it's quite straightforward if you already know the Arduino ecosystem :

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
}
The libraries

Of course you'll need some libraries to make the following example. I use only one for this app : the PubSubClient from lmroy.

You can add it as easily as you would add a library for an Arduino project, I won't detail the process.

The config file

Well, we're talking C here now, so the config file is a straightforward header file config.h (for instance, you could use anything else you like) :

// Wifi credentials
const char* ssid = "MyWifiSSID";
const char* password = "MyPassword";

// MQTT Broker credentials
int mqtt_port = 1883;
const char* mqtt_device_name = "ESP8266";
const char* mqtt_server = "broker.example.com";
const char* mqtt_user = "myUser";
const char* mqtt_password = "myMQTTpassword";

const uint32_t ID = ESP.getChipId();

const char* endpoint = "nodemcu/";
char endpoint_register[20];
char endpoint_ping[20];

Here, you notice that we have a specific API for getting the ESP basic stuff. You can have a look in the source for some details on the functions available.

ESP.getChipId() is the equivalent to node.chipid() in the Lua API.

The Wifi setup

The WiFi setup is quite similar to a standard Arduino Wifi Shield code; we're setting the basics, and connecting, and looping until we have an IP:

void connect_wifi()
{
  delay(10);
  
  // We start by connecting to a WiFi network
  Serial.print("\nConnecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

There are a lot of variables that are not defined here for clarity (but it's in the source file, of course).

Our application code

As per the previous article, the process will be :

  • Creating a callback function for when we receive a message
  • Connecting to the broker
  • Subscribing to the nodemcu/mynodeid endpoint to receive targeted messages
  • Setting a recurring ping function that will send the node ID to nodemcu/ping so that the broker (and the application consuming the messages) will know that we're alive and well

So here we go (I included the full code):

/*
 Basic ESP8266 MQTT + Auth

 It will reconnect to the server if the connection is lost using a blocking reconnect function.
*/

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

#include "config.h"

WiFiClient espClient;
PubSubClient client(espClient); // This is the MQTT object

long lastMsg = 0;
char ping_msg[20];

void connect_wifi()
{
  delay(10);
  
  // We start by connecting to a WiFi network
  Serial.print("\nConnecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length)
{
  Serial.print(topic);
  Serial.print(": ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

void send_ping()
{
  sprintf(ping_msg, "id=%08X", ID);
  client.publish(endpoint_ping, ping_msg);
}

void register_myself()
{
  client.subscribe(endpoint_register);
}

void reconnect()
{
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect(mqtt_device_name, mqtt_user, mqtt_password)) {
      Serial.println("connected");
      // Once connected, register ...
      register_myself();
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

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

  // Setup Wifi
  connect_wifi();

  // Setup MQTT
  client.setServer(mqtt_server, mqtt_port);
  sprintf(endpoint_register, "nodemcu/%08X", ID);
  sprintf(endpoint_ping, "nodemcu/%s", "ping");
  // Sets the callback for MQTT messages
  client.setCallback(callback);
}

void loop()
{

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  // Send ping
  long now = millis();
  if (now - lastMsg > 1000) {
    lastMsg = now;
    send_ping();
  }
}

NB : We don't use any timer here like we did in the Lua code, just a millis() check in the loop function;

Uploading and compiling

The compiling process is very easy : it's the same as if you had an Arduino at hand, just click "Upload" !

The last lines should look like this, as usual :

Using library ESP8266WiFi at version 1.0 in folder: ...
Using library PubSubClient at version 2.6 in folder: ...

Sketch uses 216,904 bytes (20%) of program storage space. Maximum is 1,044,464 bytes.
Global variables use 34,330 bytes (41%) of dynamic memory, leaving 47,590 bytes for local variables. Maximum is 81,920 bytes.

Any compilation error should pop up in orange before that.

Testing with a desktop MQTT client

Using MQTT-Spy like we did previously, well, it works !

And sending coucou several times to the correct endpoint, we can open the Serial Monitor to check that everything is fine :

So now

We have the exact same functionalities as my previous example, but with a few differences :

  • We code in C. That doesn't seem like a big thing but the language is consistent and its API well-known. AND the problem with Lua is that it's excessively high level for this small chip. 4MB of C is plenty for a complex and big program, not in Lua.

  • My code is compiled, optimized and not interpreted, so it doesn't consume my heap if I put too many function calls ... Moreover, I know the amount of SRAM I'm using right when compiling, and I can tell if I got enough leeway or not.

  • There is far less erratic behaviors to report. No need to plug/unplug the device or reset it between node.compile() or, even better, no need to reflash the firmware when the chip goes berzerk

  • It just works

Final thoughts (so far)

So, I'm going to try this new workflow of development for ESP8266. I know the Espressif SDK is also there somewhere with a stable reputation, but I have a feeling the Arduino IDE and the C language is the perfect combo for this chip.

It took me literally 10 minutes to rewrite the Lua example in C, so that's not a big deal, and I plan on rewriting previous Lua bits in C quite soon to test the behavior of other libs and of the pins (ADC, 1-wire, etc ...).

In the meantime, if you have experienced the same, do not hesitate to comment this post — I know Dave from http://internetofhomethings.com/ have had approximately the same problems (see here) for example.