Victor Meunier

Software engineer

Make your own crypto currencies display with NodeMCU (ESP8266)

Unless you've been living under a rock, you've surely seen the rise of crypto-currencies in the past years. Because of the nature of current cryptos, their prices are always fluctuating. I wanted a way to keep track of that. That's where the ESP8266 and the NodeMCU µController come to the rescue. It's a cheap and easy way to access the internet, with all the capabilities of a µController.

This project will make use of the following principles :

  • Connecting to WiFi with the ESP8266 (NodeMCU 0.9)
  • Grabing data from a public API
  • Parsing data in JSON format
  • Controlling an LED dot matrix display (MAX7219)
  • Using UDP to access a NTP server

If you want to follow along I'll detail every step, or you can grab the finished code here on my Github.

NodeMCU 0.9 - Setup

!! CAREFUL !! At the time of writing (22/05/18), the Arduino Board Manager has the v2.4.1 of ESP8266. This version is known to have a memory leak problem and I was struggling alot with this version ! The solution is to manually add the board with the lastest Git version. Follow the link to do it : https://github.com/esp8266/Arduino/blob/master/README.md#using-git-version

If you don't mind the memory leak, or that the Board Manager version of the NodeMCU is > 2.4.1, follow the next step :

Once your NodeMCU is plugged into your computer, you'll want to open the Arduino IDE and navigate to Preferences > Additionnal Boards Manager URLs you'll add the following :

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

Then you can nagivate to the Boards Manager and search for ESP8266.

There you can select esp8266 and click Install.
From here, you should be able to find the famouse Blink example in the NodeMCU examples.
If you see the LED blinking, you can move on to the next part.

Understanding the API

chasing-coins is a well know website where you can find lots of informations about all sorts of cryptos.
The great thing is that the provide a free public API . If you follow the link you can see there are different methods you can use. I'll be using the first one, to get informations about the top 10 currencies in one request.

The request will be :
https://api.coinmarketcap.com/v2/ticker/?convert=EUR&limit=10
If you click on it you'll see the result is JSON formatted. If you've never used JSON you may feel a bit overwhelmed, but don't worry, we'll get to the parsing later on.

NodeMCU - Connecting to WiFi

First, you'll want to import the right libraries :

                      #include <ESP8266WiFi.h>
                      #include <WiFiClientSecure.h>
                  

Define the SSID of you network and add your password

                    // You network info
                    const char* ssid = "network_name";
                    const char* password = "your_passwd";
                  

The last thing to create is a client to connect to the WiFi.

                    // Use WiFiClientSecure class to create TLS connection
                    WiFiClientSecure client;
                  

Here, we define a WiFiClientSecure because we want to access an HTTPS website. So, if your API is using HTTP instead, you could simply define a WiFiClient

Inside the setup, we define the WiFi mode as STA for ... and we initiate the connexion by calling the beging method with the SSID and the password we defined earlier.
We print a dot until we are connected, and then print the local IP that was given to us.

                    void setup() {
                      WiFi.mode(WIFI_STA);
                      WiFi.begin(ssid, password);
                    
                      // USE TLS connexion
                      Serial.print("Connecting..");
                      while (WiFi.status() != WL_CONNECTED) {
                        delay(1000);
                        Serial.print(".");
                      }
                      Serial.println("WiFi connected");
                      Serial.println("IP address: ");
                      Serial.println(WiFi.localIP());
                    }
                  

At this stage, if you see an IP address, you're able to connect to WiFi and thus go on to the next part : HTTP request

The HTTP request

To get the response from the API we saw earlier, we have to ask for it ! That's where the HTTP request comes in.

We define the host and the port of the API. Generally, API are on port 443, but you can check directly on the website you're using. They generally have good documentation about their APIs.

Note that the host doesn't include any "http", or "www". It's just the root name of the website you'll be accessing.
                    // API host and port
                    const char* host = "chasing-coins.com";
                    const int httpsPort = 443;
                  
Note : If you're accessing an HTTPS website, you should check for the certificate and extract the SHA1 fingerprint. You can see it when clicking the info button on your browser. More info here.
Note 2 : The fingerprint will change every so often. However, it still works if the certifcate doesn't match.
                      const char* fingerprint = "008713570d4ab603e3435ba107ecc7b69b";
                  

Next step is to connect to the server.

                      Serial.print("Connecting to ");
                      Serial.println(host);
                      if (!client.connect(host, httpsPort)) {
                        Serial.println("connection failed");
                      }
                    
                      // If using HTTPS 
                      if (client.verify(fingerprint, host)) {
                        Serial.println("certificate matches");
                      } else {
                        Serial.println("certificate doesn't match");
                      }
                    
                  

Because we are using a TCP connexion, we first need to have a little handshake with the server. We use the connect method with host and port as parameters.
The verifiy method takes the fingerprint and host as parameters to check if certificate matches with the host you're trying to access.

Finally, we can build our request.

                      // API request
                      String url = "/api/v1/top-coins/"+String(MAX_CURRENCIES);
                      Serial.print("requesting URL: ");
                      Serial.println(url);
                    
                      client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                                    "Host: " + host + "\r\n" +
                                    "User-Agent: CryptoDisplayESP8266\r\n" +
                                    "Connection: close\r\n\r\n");
                      Serial.println("request sent");
                  

We use a GET request, because we want to indicate the server that we want something. The url, indicates the endpoint, and the protocole. In the API's doc, you can find all the url you can access and with what parameters. The host, is self explanatory. User-Agent is the name you want the server to see, in addition to your IP address. Lastly, we add "close" to Connection, to tell the server that we want to close the connexion after we received the data

Note the '\r\n' at the end of each line. It's called the carriage return . At the end, we have two of these to indicate the end of our request.

Now we read the response from the server.

                      while (client.connected()) {
                        String line = client.readStringUntil('\n');
                        if (line == "\r") {
                          Serial.println("headers received");
                          break;
                        }
                      }
                      String line = client.readStringUntil('\r\n');
                      client.readStringUntil('\r').toCharArray(currencyInfos, 3000);
                  

You might have to change that part a little depending on the API you've used and the response you're getting from the server.
The line String line = client.readStringUntil('\r\n') is just here to remove a part I was not interested in. After that, I read until '\r' and put it inside the global char array currencyInfos.

ArduinoJSON : Parsing the response

Now that we've got the response from the server as a JSON formatted string, it is time to parse it. For that, we'll use the ArduinoJSON library. This library is really well documented and there's a wonderful online tool to help you out with the parsing.

                      DynamicJsonBuffer jsonBuffer(bufferSize); // Don't put that outside the loop 
                      JsonObject& root = jsonBuffer.parseObject(currencyInfos);
                      
                      // Test if parsing succeeds.
                      if (!root.success()) {
                        Serial.println("parseObject() failed");
                        return;
                      }
                  

Parsing is a one liner, but it can get really tricky if you don't do everything correctly. Here, I first create a new DynamicJsonBuffer with a size of 3000. I've determined the size that was needed with the assistant I've linked earlier. I use a DynamicJsonBuffer because the size required is greater than a 1000. It's really important that you NEVER declare your buffer as global as it will fail to parse it the second time you use it. This link explains why.
After that, we can test if the parsing is a success. If it isn't, we return because we don't want to go on to the next part.

Here I simply grab the revelant data from the parsed response. I highly recommand using the assistant if you don't know how you should parse it.

                    for(int i = 0; i < MAX_CURRENCIES; i++){
                      JsonObject& root_i = root[String(i+1)];
                      st_currencies[i].symbol = root_i["symbol"];
                      st_currencies[i].price_eur = root_i["price"];
                      st_currencies[i].percentage_change_1_hour = root_i["change"]["hour"];
                    }
                  

Matrix dot display : MD7219

To drive the 4 dot matrix display, we'll use the MD_MAX72xx and the MD_Parola libraries. The MD_Parola is especially useful to have all the nice animations on the screen.

                    #include <MD_Parola.h>
                    #include <MD_MAX72xx.h>
                    
                    // HARDWARE SPI
                    MD_Parola P = MD_Parola(CS_PIN, MAX_DEVICES);
                  
                    // Scrolling parameters
                    uint8_t scrollSpeed = 40;    // default frame delay value
                    textEffect_t scrollEffect = PA_SCROLL_LEFT;
                    textPosition_t scrollAlign = PA_LEFT;
                    uint16_t scrollPause = 2000; // in milliseconds
                  

We create a new MD_Parola obect, calling the constructor with CS_PIN defined as pin 8 for my device and MAX_DEVICES as 4, the number of dot matrix displays. When using only two parameters the library will try to use hardware SPI. If you want to use software SPI, with your own pins, just use this :

                    // SOFTWARE SPI
                    MD_Parola P = MD_Parola(DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
                  

The parameters are just the one I found inside an example sketch of the library. Feel free to dig a bit deeper as there are a lot of different parameters !

                    P.begin(); // Start parola
                    P.displayText(curMessage, scrollAlign, scrollSpeed, 0, scrollEffect, scrollEffect);
                  

You start the display with the begin() method and dispaly the message that you want with displayText(). The message is located inside curMessage.

                    if (P.displayAnimate()) // if finished displaying
                    {
                      P.displayReset(); // reset to display again
                    }
                  

Those two lines check to see if the animation is done. If it is, we reset the display for it to start redrawing its animation.

Getting UTC time : NTP

Now that we've got the data and got it displayed, it is time to add the add the last piece : The NTP request ! NTP (Network Time Protocol) is used to know the exact time. There's alot of NTP server out there, feel free to select the one you want.
Here we'll actually use a pool of server. This ensure that we always have a server up to give us the UTC time. l

First, we declare few objects and variables to access the NTP server.

                    WiFiUDP udp;
                    IPAddress timeServerIP;
                    const char* ntpServerName = "time.nist.gov";   
                    unsigned int localPort = 2390; 
                  

We declare a new WiFiUDP object as NTP server use this protocole. The IPAdress object will be used to retrieve the server's IP. ntpServerName is the pool root name. And finally, the localPort is the port on wich we'll be listenning for the response.

                                    sendNTPpacket(timeServerIP); 
                                    //wait a bit or do something else  
                                    checkNTPresponse(); 
                                

I've used two functions to send and check for NTP response. I'm not going to explain every line of it as it is pretty long and the code is well commented. However, if you have any question please let know. Few things to keep in mind :

  • Since we are using UDP, you don't know when the response will arrive. Thus, I recommend you don't passively wait for the response, as it would slow down everything.
  • We ask for UTC time, so I had to add 2 hours to get the right time for me. You should be able to ask for UTC+ whatever, but I didn't take the time to dig in.
  • The server will give you an epoch time. Few conversions need to be done to have the time as hours:minutes:seconds.

Finished !

Now, you should have everything to make your wonderful CryptoDisplay ! Of course, you can find all the source code in the GitHub repo.
This project is fairly simple but gives you a look at multiple technologies, wich I find very intersting. You could go further and add some cool functionnalities, like :

  • Small webpage hosted on the NodeMCU to choose the crypto you want.
  • Alerts when a crypto reach a certain price. Either visual/auditive alerts or webbased (email, push notification)..
  • Use a dedicating µController to drive the display (ATTiny for example). That way you have no stutter when the NodeMCU is busy making the HTTP request.

Share it!

Comments

Subscribe to my weekly newsletter