Nodemcu and MQTT: How to start

The latest nodemcu version has MQTT protocol support already built in. MQTT is one of the supporting protocols for building IoT, Internet of Things devices,

So I’ve done some tests using the new MQTT functions, with code based from this thread on ESP8266 forum http://www.esp8266.com/viewtopic.php?f=19&t=1278&start=40 ( My code is based on the helix user code. Thanks!). I’ve made some changes to make things work in a more robust way, that I’m documenting the on this post.

My initial tests where done with nodemcu version 20150123 but I’ll write this post based on version NodeMCU 0.9.5 build 20150127  so if your are using an older or newer version of the firmware, the code on this post can behave a bit different of what is expected. Also I’m using a MQTT broker named RSMB ( Real small message broker) running in my Synology NAS, but we can run any MQTT broker. Also for testing I’m using the great MQTT-SPY program.

The init.lua code that I use is as documented in this post: https://primalcortex.wordpress.com/2014/12/30/esp8266-nodemcu-and-lua-language-and-some-arduino-issues/ . That code makes sure that an WiFi connection is made before continuing.  The only difference is that on the init.lua code, namely the launch function, if a successful connection is made, instead of calling a ping.lua file repeatedly, a mqtt.lua file is called once. To do this just change the CMDFILE to be CMDFILE = “mqtt.lua”. Note that the timer is one shot now. It calls the mqtt.lua file and that’s it. Then the mqtt.lua file takes control.

CMDFILE = "mqtt.lua"   -- File that is executed after connection
-- Change the code of this function that it calls your code.
function launch()
  print("Connected to WIFI!")
  print("IP Address: " .. wifi.sta.getip())
  tmr.alarm(0, 1000, 0, function() dofile(CMDFILE) end )  -- Zero as third parameter. Call once the file.
end  -- !!! Increase the delay to like 10s if developing mqtt.lua file otherwise firmware reboot loops can happen

The MQTT API is used by basically creating an MQTT Client with mqtt.Client(), then connecting, and finally we can subscribe or post to topics. Subscriptions will call a callback function when data is received, and Publishing will also call a callback function when data is sent.

It also seems that only at one message at the same time can be publish, otherwise we get an error like this unprotected error in call to Lua API (pir.lua:34: sending in process) and the module reboots. I’ve solved the issue on a first approach by using a semaphore that blocks a new call before the older one is finished.

The following code uses two different functions to publish to a topic, do to the use of the publish semaphore. Please read  the code comments to follow the way the code works:

-- Configuration to connect to the MQTT broker.
BROKER = "192.168.1.16"   -- Ip/hostname of MQTT broker
BRPORT = 1883             -- MQTT broker port
BRUSER = "user"           -- If MQTT authenitcation is used then define the user
BRPWD  = "pwd"            -- The above user password
CLIENTID = "ESP8266-" ..  node.chipid() -- The MQTT ID. Change to something you like

-- MQTT topics to subscribe
topics = {"topic1","topic2","topic3","topic4"} -- Add/remove topics to the array

-- Control variables.
pub_sem = 0         -- MQTT Publish semaphore. Stops the publishing whne the previous hasn't ended
current_topic  = 1  -- variable for one currently being subscribed to
topicsub_delay = 50 -- microseconds between subscription attempts, worked for me (local network) down to 5...YMMV
id1 = 0
id2 = 0

-- connect to the broker
print "Connecting to MQTT broker. Please wait..."
m = mqtt.Client( CLIENTID, 120, BRUSER, BRPWD)
m:connect( BROKER , BRPORT, 0, function(conn)
     print("Connected to MQTT:" .. BROKER .. ":" .. BRPORT .." as " .. CLIENTID )
     mqtt_sub() --run the subscription function
end)

function mqtt_sub()
     if table.getn(topics) < current_topic then
          -- if we have subscribed to all topics in the array, run the main prog
          run_main_prog()
     else
          --subscribe to the topic
          m:subscribe(topics[current_topic] , 0, function(conn)
               print("Subscribing topic: " .. topics[current_topic - 1] )
          end)
          current_topic = current_topic + 1  -- Goto next topic
          --set the timer to rerun the loop as long there is topics to subscribe
          tmr.alarm(5, topicsub_delay, 0, mqtt_sub )
     end
end

-- Sample publish functions:
function publish_data1()
   if pub_sem == 0 then  -- Is the semaphore set=
     pub_sem = 1  -- Nop. Let's block it
     m:publish("temperature","hello",0,0, function(conn) 
        -- Callback function. We've sent the data
        print("Sending data1: " .. id1)
        pub_sem = 0  -- Unblock the semaphore
        id1 = id1 +1 -- Let's increase our counter
     end)
   end  
end

function publish_data2()
   if pub_sem == 0 then
     pub_sem = 1
     m:publish("ts","hello",0,0, function(conn) 
        print("Sending data2: " .. id2)
        pub_sem = 0
        id2 = id2 + 1
     end)
   end  
end

--main program to run after the subscriptions are done
function run_main_prog()
     print("Main program")
     
     tmr.alarm(2, 5000, 1, publish_data1 )
     tmr.alarm(3, 6000, 1, publish_data2 )
     -- Callback to receive the subscribed topic messages. 
     m:on("message", function(conn, topic, data)
        print(topic .. ":" )
        if (data ~= nil ) then
          print ( data )
        end
      end )
end

This seems to work fine for some time, but then, even with some free heap space, it stops working.  No idea why. Even the subscriptions fail to work, and at the broker I get a ending connection event due to time-out.

20150206 213132.389 CWNAN0033I Connection attempt to listener 1883 received from client ESP8266-10350678 on address 192.168.1.82:32456 
20150206 214010.033 CWNAN0024I 120 second keepalive timeout for client ESP8266-10350678, ending connection

When at this stage that seems locked the semaphore is “red”: pub_sem = 1…

Another interesting notes: if at the main function the timer 2 and 3 have the same interval,  publish_data2 is never called. For example:

     tmr.alarm(2, 5000, 1, publish_data1 )
     tmr.alarm(3, 5000, 1, publish_data2 )

Interesting… probably both timers are triggered at the same time and only the first one is honoured.
Note: It is called but the resource is blocked… See the comments below.

Second the way this is done, it means that for every topic a publish_data function must be built, and this will increase the semaphore demand.

I’ll keep this post short and end it here, but I’ll write a follow up post where the publish_data function is unified for any topic, and, I hope, we will be using queues for queueing the messages to be published.

Advertisements

7 thoughts on “Nodemcu and MQTT: How to start

  1. Pingback: ESP8266, NodeMcu and MQTT: Event subscription load testing | Primal Cortex's Weblog

  2. Pingback: ESP8266, NodeMcu and MQTT: Event publishing queueing | Primal Cortex's Weblog

  3. >publish_data2 is never called
    It *IS* called but skipped since the resource is blocked. It is unlocked only in callback which is called later. If you remove the first “if” in both functions (no lock check) then both topics will be posted, but only one post callback function will be called (rather one will be called for both topics). It should be reworked somehow to do it right.

  4. Pingback: ESP8266 Sming framework – MQTT | Primal Cortex's Weblog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s