ESP8266 OpenWeatherMap simple client/display using I2C 16×2 display

Since I’m doing some experiments using I2C with the ESP8266 ESP-01 board, and also having available, from some arduino projects, a 16×2 LCD with an IIC I2C adapter, I thought to do a quick program to show the current weather conditions on my location on the 16×02 display.

The hardware

The 16×2 LCD that I have, has an I2C adapter bought on eBay based on the PCF8574 port expander. This allows to control the display only using the I2C bus, and nothing more except power. At a first stage I’ve tried to use the display at 3.3V, since the PCF8574 and the display supports it, but for the display at this voltage level the contrast is too low, which meant that I could not see nothing on the display at 3.3V.

So this means that I need to use the LCD at 5V, which the ESP01 doesn’t support. The solution for this problem is to use an I2C voltage level shifter from 5V to 3.3V and vice versa. Anyway I own a few, also bought on eBay, and connect the SCL and SDA lines through the level shifter to the SDA and SDL lines, GPI02 and GPIO0 of the ESP01 board. The LCD is powered up at 5v, while the ESP-01 is kept running at 3.3V.

Using  the LCD

For using the LCD, and specifically for the I2C PCF8574 based port expander, I’ve found a simple and effective Lua class for using the display: Nodemcu 16×2 LCD Lua driver

The only thing needed to use this code is to initialize the I2C bus and reference the class. For example:

local busid = 0  -- I2C Bus ID. Always zero
local sda= 4     -- GPIO2 pin mapping is 4
local scl= 3     -- GPIO0 pin mapping is 3

i2c.setup(busid,sda,scl,i2c.SLOW)

lcd = dofile("lcd1602.lua")()
lcd.light(0)
lcd.locate(0,0)
lcd.put("Hello World!")

And with this code snippet, with the IIC I2C port expander based on the PCF8574, the LCD works. I’m emphasizing that the I2C adapter is based on the PCF8574 chip, because there are other I2C 16×02 adapters with other chips, that might work or not with this library. YMMV.

Getting OpenWeather data
Basically I’m using this site Lisbon, PT whether that offers a free API for requesting the current weather data on my location. The request is done to their API site, and the result is return in JSON format. On the API site we can request several types of data, and see some examples for those requests. On my code I’m requesting the current weather conditions, and for that we need to use the following link:

http://api.openweathermap.org/data/2.5/find?q=Lisbon&units=metric

And the answer is something like:

{"message":"accurate","cod":"200","count":1,"list":[{"id":2267057,"name":"Lisbon","coord":{"lon":-9.13333,"lat":38.716671},"main":{"temp":18.63,"pressure":1016,"humidity":82,"temp_min":17.78,"temp_max":20},"dt":1441183037,"wind":{"speed":2.1,"deg":300,"var_beg":260,"var_end":340},"sys":{"country":"PT"},"clouds":{"all":20},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}]}]}

So what we have to do is to request the data, decode the JSON, fill up the Lua variables with the decoded JSON data, and show them on the LCD, simple, right?

Well, the code is quite simple, but after a while the NodeMcu heap is gone and the ESP8266 reboots, but ohh well, it shows that its is possible to be done.

The code

The code is as following:

Initialization:

--- Get Weather from openweathemap.org.
local json = require "cjson"  -- Json object for decoding JSON data. Is available on the firmware
local Location = "Lisbon"  -- My location.
local Metrics  = "metric"  -- I'm receiving data with the Metric system Celsius and m/s

local busid = 0  -- I2C Bus ID. Always zero
local sda= 4     -- GPIO2 pin mapping is 4
local scl= 3     -- GPIO0 pin mapping is 3

local MaxTemp = 0  -- Those are self explanatory...
local MinTemp = 0
local CurTemp = 0
local Humidity = 0
local WindSpeed = 0
local Wdesc = "No data yet!..."
local WSCalls = 0  -- I'm counting the number of calls to the API...
-- Initialize the I2C Bus for displaying data on the I2C 16x02 connected LCD.
i2c.setup(busid,sda,scl,i2c.SLOW)

After the initializations, we have the function that does the request, and on the callback function we decode the data. On the receiving data, humidity can never be zero, and if after the answer for the request still has that value (the initial value) I assume that the call failed. While I have no valid answer, I’m calling the API each 10s, and then only each 10 Minutes. Let’s not over call the API. It’s free after all. This control is done by timer 5.

function getWeather()
  conn=net.createConnection(net.TCP, 0)
  conn:on("receive", function(conn, payload) 
      record = json.decode(payload)
      MaxTemp = record.list[1].main.temp_max
      MinTemp = record.list[1].main.temp_min
      CurTemp = record.list[1].main.temp
      Humidity= record.list[1].main.humidity
      Wdesc = record.list[1].weather[1].main .." , "..record.list[1].weather[1].description
      WindSpeed = record.list[1].wind.speed
      --print("Data received!")
      --print(payload)
    end )
  conn:connect(80,"188.226.175.223")  -- One of the IP's of api.openweathermap.org
  conn:send("GET /data/2.5/find?q="..Location.."&units="..Metrics.."\r\nHTTP/1.1\r\nHost: api.openweathermap.org\r\n"
        .."Connection: keep-alive\r\nAccept: */*\r\n\r\n")
  WSCalls = WSCalls + 1  
  if Humidity == 0 then
    tmr.alarm(5,10000, 0 , getWeather) -- The call might have failed so we keep the timer at 10s interval
  else
    tmr.alarm(5,600000, 0 , getWeather)-- Sucess. Call the API each 10 minute interval
  end
end

The following two functions will scroll the information horizontally on the LCD display. On the line zero the weather data, and on the line 1 the heap status and number of API calls. We this info we can see the heap slowly deteriorating until the module reboots after around 30/35 API calls.

function notice() 
--  print(node.heap()); 
  lcd.run(1, "Heap: "..node.heap().."  WSCalls: "..WSCalls.."  ", 250, 1, notice) 
end

function noticeWeather()
  lcd.run(0, "TCurr: "..CurTemp.."C Max: "..MaxTemp.."C Min: "..MinTemp.."C Desc: "..Wdesc.." Humidity: "..Humidity.."% Wind: "..WindSpeed.."m/s   " ,550, 2, noticeWeather )
end

Of course if we don’t have an LCD we can just print the data on the serial port.

Finally the code that starts it all:

getWeather()   -- Get the weather now.
tmr.alarm(5,10000, 0 , getWeather) -- Set's the timer for the initial API call each 10s

lcd = dofile("lcd1602.lua")()  -- Instantiates the LCD functions. 
lcd.light(0)
lcd.locate(1,0)

notice()
noticeWeather()

The above code, while simple in nature heavily taxes heap…. With the NodeMcu firmware of July 2015, the initial heap after running the init.lua and connecting to WiFi is around 30K, and with the first API call drops to around 10K. From that point with each call the heap drops steadily until the module reboots due to an out of memory error.

The complete code is available here: GitHub Code .

EDIT: It is now necessary to add an API Key to the OpenWeatherMap requests. Register, it is free, and get the API key. My code in GitHub shows how to use the API key.

Advertisements

3 thoughts on “ESP8266 OpenWeatherMap simple client/display using I2C 16×2 display

  1. This is great, but to be able to request the server you should connect to the Internet in the first place, shouldn’t you? There should be some AP involved. Or this portion is omitted?

  2. Pingback: NodeMCU Lua ESP8266 – Můj nový WordPress

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