Tutorial: Advanced dashboards for Node-RED (and cryptocurrency)

In this tutorial we’ll combine two interesting areas, how to build interactive dashboards in Node-RED with use of the Binance cryptocurrency node created by the folks at Sense Tecnic Systems Inc.

The dashboard we are aiming for is shown below and the full flow can be copied and pasted into your own Node-RED canvas from the code block at the end of this tutorial.


Overview of the flow

As you can see, the dashboard we’ve created allows a user to choose a specific cryptocurrency (the coloured tabs along the top) which, when clicked, fetches the realtime data from the binance cryptocurrency exchange node. The latest data, eg bid price, volume etc is shown in the left panel, and a graph of the latest data is also shown. A slider in the bottom left allows you to interactively select the amount of data you want to see and an input field allows you to limit the number of datapoints selected.

The flow for this dashboard is shown below, it’s a little more complex than other flows we’ve shown, so we wont explain all aspects in detail, however we’ll give you enough to understand how it works and of course, you can copy the flow (see bottom) and experiment with it yourself.

There are 3 main components to the flow:

  • Text elements of the demo UI: At top is the part of the flow that handles the user selecting the cryptocurrency they want to chart. These are they 9 UI button nodes top left. When selected these generate the correct ticker pair, eg BTCUSDT, which is displayed using a UI text node, then the ticker pair is passed through a rate limit node and into a function node that transfers the message payload into the message topic (msg.topic) which is then sent through to the Binance getBookTicker node for bid/ask prices or to the Binance getDatStats node for the rest of the data. All data is then sent through to UI text nodes for display. The ticker is also used in the load logo function node to pull an image of the cryptocurrency for display on the UI.
  • Graphing element of demo UI: the next piece of the flow creates the chart. On initial start the initialize input node triggers this part of the flow, otherwise it is triggered when the user selects a cryptocurrency. A message is generated in set values for api function node which the load chart options function node combines with an interval and number of data points (set by the slider and #datapoints input node) to ask the Binance candleStick node for the appropriate data. The build chart data function node uses the data from Binance to then create the appropriate data for the standard node-RED chart ui node.
  • Historical slider element: a simple slider is used to set how much data should be charted (from 5mins through to 1 month) which is used in the load chart options node to ask Binance for the correct amount of data.

A fourth element, a Generic error handler: There is a generic error handler flow at the bottom of the page which catches all errors and generates a UI message indicating there was a problem.

Some critical details

Although the flow may look complicated, it is actually quite simple and mostly uses standard nodes. The main function nodes are:

  • set values for API function node in the upper part of the flow
if (typeof msg.payload === 'string') {
 flow.set("selectedTickerPair", msg.payload);
}
msg.topic = flow.get("selectedTickerPair");
return msg;

Which simply copies over the ticker pair in the msg.payload to the topic which the Binance nodes need to generate the correct query.

  • load chart options function node
msg.topic = flow.get("selectedTickerPair");
msg.payload = {};
msg.payload.interval = flow.get("interval");
msg.payload.limit = flow.get("limit");

return msg;

This sets its own topic to the message topic from the previous node (now set to the cryptocurrency ticker pair symbol) and adds two fields to the message. The limit (max data points) which is set by the #datapoints user input field and simply passes along whatever value the user sets. The interval is set by a ui slider node. Since the slider just generates integer values, we need to convert it to a string, eg 15m, 12h, 3d etc that the Binance node will understand. This is generated in the set values for API function node in the lower part of the flow and is basically a large case switch that converts integers to the appropriate string. A subset is shown below.

var interval = "1d";

switch(msg.payload) {
 case 0:
      interval = "1m";
      break;
 case 1:
      interval = "3m";
      break;
 case 2:
      interval = "5m";
      break; 
 case 3:
      interval = "15m";
      break;
 case 4:
      interval = "1h";
      break;
  • build chart data function node
var series = [msg.topic]
var data = msg.payload.map(function (d) {
 return {
 x: d[0],
 y: d[4] // close price
 }
});

msg.payload = [{
 series: series,
 data: [data]
}]

return msg;

The Binance getCandlestick node returns an array of data (specified by tickerpair, interval and limit) which this small piece of code simply uses a map operation to extract the data it uses, i.e. the d[0] value (time) and the d[4] value (closing price) which are then passed as an array to the UI chart node.

Full flow

You can copy the full flow from the code box below and paste into your own Node-RED instance so you can experiement with the dashboard.

Have fun!

[{"id":"3e6934b2.79480c","type":"ui_text","z":"d7d920d1.1e628","group":"684f06d8.eb11a8","order":2,"width":"0","height":"0","name":"","label":"Bid Price","format":"{{msg.payload.bidPrice}}","layout":"row-spread","x":800,"y":60,"wires":[]},{"id":"283ee802.c99158","type":"ui_chart","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":3,"width":"18","height":"10","label":"","chartType":"line","legend":"true","xformat":"auto","interpolate":"linear","nodata":"Click on a crypto or provide a ticker pair","dot":true,"ymin":"","ymax":"","removeOlder":"30","removeOlderPoints":"","removeOlderUnit":"86400","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":890,"y":600,"wires":[[],[]]},{"id":"592d6f1e.79145","type":"binance-get-candlesticks","z":"d7d920d1.1e628","name":"","ticker":"","interval":"1d","limit":"30","startTime":"","endTime":"","x":860,"y":480,"wires":[["e918db8d.87e3d8"]]},{"id":"e918db8d.87e3d8","type":"function","z":"d7d920d1.1e628","name":"built chart data","func":"\nvar series = [msg.topic]\nvar data = msg.payload.map(function (d) {\n return {\n x: d[0],\n y: d[4] // close price\n }\n});\n\nmsg.payload = [{\n series: series,\n data: [data]\n}]\n\n\nreturn msg;","outputs":1,"noerr":0,"x":860,"y":540,"wires":[["283ee802.c99158"]]},{"id":"3a5bf45.9251f0c","type":"function","z":"d7d920d1.1e628","name":"set values for API","func":"if (typeof msg.payload === 'string') {\n flow.set(\"selectedTickerPair\", msg.payload);\n}\nmsg.topic = flow.get(\"selectedTickerPair\");\nreturn msg;","outputs":1,"noerr":0,"x":390,"y":200,"wires":[["90a8d0bc.4aeac","304459df.84fe86","c0332b3e.107478","58d47e8a.52b39"]]},{"id":"b1a9caa7.d0e068","type":"ui_button","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":2,"width":"2","height":"1","passthru":false,"label":"Refresh","color":"","bgcolor":"","icon":"refresh","payload":"selectedTickerPair","payloadType":"flow","topic":"","x":100,"y":60,"wires":[["71902fa5.40cf","686b8209.90607c"]]},{"id":"2d31df00.dddce","type":"ui_button","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":6,"width":"2","height":"1","passthru":false,"label":"Ethereum","color":"","bgcolor":"#555555","icon":"","payload":"ETHBTC","payloadType":"str","topic":"","x":100,"y":140,"wires":[["71902fa5.40cf"]]},{"id":"4a25114.13304f","type":"ui_button","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":7,"width":"2","height":"1","passthru":false,"label":"Neo","color":"","bgcolor":"#58be02","icon":"","payload":"NEOBTC","payloadType":"str","topic":"","x":90,"y":180,"wires":[["71902fa5.40cf"]]},{"id":"1a781586.60994a","type":"ui_button","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":4,"width":"2","height":"1","passthru":false,"label":"BTC","color":"","bgcolor":"#ff8e13","icon":"","payload":"BTCUSDT","payloadType":"str","topic":"","x":90,"y":100,"wires":[["71902fa5.40cf"]]},{"id":"7cc83e6.13d54c","type":"ui_button","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":5,"width":"2","height":"1","passthru":false,"label":"Litecoin","color":"","bgcolor":"#bebebe","icon":"","payload":"LTCBTC","payloadType":"str","topic":"","x":100,"y":220,"wires":[["71902fa5.40cf"]]},{"id":"90a8d0bc.4aeac","type":"binance-get-book-ticker","z":"d7d920d1.1e628","name":"","ticker":"","x":620,"y":80,"wires":[["3e6934b2.79480c","30816eb1.acf7b2"]]},{"id":"30816eb1.acf7b2","type":"ui_text","z":"d7d920d1.1e628","group":"684f06d8.eb11a8","order":3,"width":"0","height":"0","name":"","label":"Ask Price","format":"{{msg.payload.askPrice}}","layout":"row-spread","x":800,"y":100,"wires":[]},{"id":"71902fa5.40cf","type":"ui_text_input","z":"d7d920d1.1e628","name":"","label":"Ticker pair","group":"3fa75da0.6d85c2","order":1,"width":0,"height":0,"passthru":true,"mode":"text","delay":300,"topic":"","x":370,"y":120,"wires":[["673df297.a4404c"]]},{"id":"304459df.84fe86","type":"binance-get-day-stats","z":"d7d920d1.1e628","name":"","ticker":"","x":610,"y":180,"wires":[["38c0811d.2ab6fe","319d75a2.00504a","8e238a8f.ec6f48","bf8d0cbf.6f50f"]]},{"id":"2bb366e1.79afaa","type":"catch","z":"d7d920d1.1e628","name":"","scope":null,"x":100,"y":640,"wires":[["6206c86d.bf05b8","15052e06.11e1a2"]]},{"id":"4a8fb8a1.e63068","type":"ui_toast","z":"d7d920d1.1e628","position":"top right","displayTime":"3","highlight":"","outputs":0,"ok":"OK","cancel":"","topic":"","name":"","x":550,"y":640,"wires":[]},{"id":"6206c86d.bf05b8","type":"debug","z":"d7d920d1.1e628","name":"","active":true,"console":false,"complete":"error","x":240,"y":680,"wires":[]},{"id":"15052e06.11e1a2","type":"function","z":"d7d920d1.1e628","name":"","func":"msg.topic = \"API Error\";\nif (msg.error) {\n msg.payload = msg.error.message\n} else {\n msg.payload = \"\"\n}\nreturn msg;","outputs":1,"noerr":0,"x":230,"y":640,"wires":[["22694b27.9c4fd4"]]},{"id":"38c0811d.2ab6fe","type":"ui_text","z":"d7d920d1.1e628","group":"684f06d8.eb11a8","order":4,"width":"0","height":"0","name":"","label":"24 hr Low","format":"{{msg.payload.lowPrice}}","layout":"row-spread","x":800,"y":140,"wires":[]},{"id":"319d75a2.00504a","type":"ui_text","z":"d7d920d1.1e628","group":"684f06d8.eb11a8","order":5,"width":"0","height":"0","name":"","label":"24 hr high","format":"{{msg.payload.highPrice}}","layout":"row-spread","x":800,"y":180,"wires":[]},{"id":"8e238a8f.ec6f48","type":"ui_text","z":"d7d920d1.1e628","group":"684f06d8.eb11a8","order":6,"width":"0","height":"0","name":"","label":"24 hr Price % change","format":"{{msg.payload.priceChangePercent}}%","layout":"row-spread","x":840,"y":220,"wires":[]},{"id":"bf8d0cbf.6f50f","type":"ui_text","z":"d7d920d1.1e628","group":"684f06d8.eb11a8","order":7,"width":"0","height":"0","name":"","label":"24 hr volume","format":"{{msg.payload.volume}}","layout":"row-spread","x":810,"y":260,"wires":[]},{"id":"cf4f08bb.9ceda8","type":"ui_button","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":8,"width":"2","height":"1","passthru":false,"label":"Ripple","color":"","bgcolor":"#0086be","icon":"","payload":"XRPBTC","payloadType":"str","topic":"","x":90,"y":260,"wires":[["71902fa5.40cf"]]},{"id":"6a86f23d.54b9ec","type":"ui_slider","z":"d7d920d1.1e628","name":"","label":"","group":"684f06d8.eb11a8","order":9,"width":0,"height":0,"passthru":true,"topic":"","min":"2","max":"14","step":1,"x":90,"y":580,"wires":[["963cb0a3.0c805"]]},{"id":"11b7370d.f93ff9","type":"function","z":"d7d920d1.1e628","name":"set values for api","func":"\nvar interval = \"1d\";\n\nswitch(msg.payload) {\n case 0:\n interval = \"1m\";\n break;\n case 1:\n interval = \"3m\";\n break;\n case 2:\n interval = \"5m\";\n break; \n case 3:\n interval = \"15m\";\n break;\n case 4:\n interval = \"1h\";\n break;\n case 5:\n interval = \"2h\";\n break;\n case 6:\n interval = \"4h\";\n break;\n case 7:\n interval = \"6h\";\n break;\n case 8:\n interval = \"8h\";\n break;\n case 9:\n interval = \"12h\";\n break;\n case 10:\n interval = \"1d\";\n break;\n case 11:\n interval = \"3d\";\n break;\n case 12:\n interval = \"1w\";\n break;\n case 13:\n interval = \"1M\";\n break;\n}\n\nflow.set(\"interval\", interval);\nmsg.payload = interval;\n\nreturn msg;","outputs":1,"noerr":0,"x":410,"y":580,"wires":[["5b8217e7.65f4f8","c0332b3e.107478"]]},{"id":"963cb0a3.0c805","type":"delay","z":"d7d920d1.1e628","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"3","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":230,"y":580,"wires":[["11b7370d.f93ff9"]]},{"id":"90628d7.2b2047","type":"ui_button","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":9,"width":"2","height":"1","passthru":false,"label":"Monero","color":"","bgcolor":"#ff6600","icon":"","payload":"XMRBTC","payloadType":"str","topic":"","x":100,"y":300,"wires":[["71902fa5.40cf"]]},{"id":"5b8217e7.65f4f8","type":"ui_text","z":"d7d920d1.1e628","group":"684f06d8.eb11a8","order":8,"width":"0","height":"0","name":"","label":"Chart time interval","format":"{{msg.payload}}","layout":"row-spread","x":630,"y":580,"wires":[]},{"id":"8d98a98c.b79718","type":"inject","z":"d7d920d1.1e628","name":"initialize","topic":"10","payload":"10","payloadType":"num","repeat":"","crontab":"","once":true,"x":100,"y":540,"wires":[["6a86f23d.54b9ec"]]},{"id":"9bfab0f0.119d1","type":"ui_numeric","z":"d7d920d1.1e628","name":"","label":"# datapoints","group":"684f06d8.eb11a8","order":10,"width":0,"height":0,"passthru":true,"topic":"","format":"{{value}}","min":"1","max":"500","step":1,"x":110,"y":480,"wires":[["2eaadaeb.ab8fb6"]]},{"id":"79abf723.549248","type":"inject","z":"d7d920d1.1e628","name":"initialize","topic":"30","payload":"30","payloadType":"num","repeat":"","crontab":"","once":true,"x":100,"y":440,"wires":[["9bfab0f0.119d1"]]},{"id":"1895296a.741bc7","type":"function","z":"d7d920d1.1e628","name":"set values for api","func":"flow.set(\"limit\", parseInt(msg.payload));\n\nreturn msg;","outputs":1,"noerr":0,"x":470,"y":480,"wires":[["c0332b3e.107478"]]},{"id":"c0332b3e.107478","type":"function","z":"d7d920d1.1e628","name":"load chart options","func":"msg.topic = flow.get(\"selectedTickerPair\");\nmsg.payload = {};\nmsg.payload.interval = flow.get(\"interval\");\nmsg.payload.limit = flow.get(\"limit\");\n\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":480,"wires":[["592d6f1e.79145"]]},{"id":"2eaadaeb.ab8fb6","type":"delay","z":"d7d920d1.1e628","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"3","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":270,"y":480,"wires":[["1895296a.741bc7"]]},{"id":"686b8209.90607c","type":"ui_toast","z":"d7d920d1.1e628","position":"top right","displayTime":"3","highlight":"","outputs":0,"ok":"OK","cancel":"","topic":"Refreshing chart","name":"","x":390,"y":60,"wires":[]},{"id":"96138b39.2d61d8","type":"ui_template","z":"d7d920d1.1e628","group":"684f06d8.eb11a8","name":"Logo display","order":1,"width":"4","height":"3","format":"<div ng-bind-html=\"msg.payload\"></div>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":810,"y":300,"wires":[[]]},{"id":"58d47e8a.52b39","type":"function","z":"d7d920d1.1e628","name":"load logo","func":"var symbol = msg.topic.slice(0,-3);\nsymbol = symbol.toLowerCase();\n\nif (symbol === \"btcu\") { // USDT case\n symbol = \"btc\";\n}\n\nvar imgUrl = \"https://raw.githubusercontent.com/cjdowner/cryptocurrency-icons/master/128/color/\"+symbol+\".png\";\n\nmsg.payload = \"<img src='\"+imgUrl+\"'alt='Logo image not found' />\";\n\nreturn msg","outputs":1,"noerr":0,"x":660,"y":300,"wires":[["96138b39.2d61d8"]]},{"id":"673df297.a4404c","type":"delay","z":"d7d920d1.1e628","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"2","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":370,"y":160,"wires":[["3a5bf45.9251f0c"]]},{"id":"2bf673c4.ad97ac","type":"ui_button","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":9,"width":"2","height":"1","passthru":false,"label":"Stellar","color":"","bgcolor":"#99a2a6","icon":"","payload":"XLMBTC","payloadType":"str","topic":"","x":90,"y":340,"wires":[["71902fa5.40cf"]]},{"id":"e2a8bad4.68cb78","type":"ui_button","z":"d7d920d1.1e628","name":"","group":"3fa75da0.6d85c2","order":9,"width":"2","height":"1","passthru":false,"label":"Zcash","color":"","bgcolor":"#e49b2e","icon":"","payload":"ZECBTC","payloadType":"str","topic":"","x":90,"y":380,"wires":[["71902fa5.40cf"]]},{"id":"22694b27.9c4fd4","type":"delay","z":"d7d920d1.1e628","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":370,"y":640,"wires":[["4a8fb8a1.e63068"]]},{"id":"684f06d8.eb11a8","type":"ui_group","z":"","name":"Numbers","tab":"122ee689.480fd9","order":1,"disp":false,"width":"5"},{"id":"3fa75da0.6d85c2","type":"ui_group","z":"","name":"Chart","tab":"122ee689.480fd9","order":2,"disp":false,"width":"18"},{"id":"122ee689.480fd9","type":"ui_tab","z":"","name":"Binance Node Demo","icon":"fa-bitcoin"}]

Author: Rodger Lea

Currently CEO of Internet of Things startup, Sense Tecnic, Dr. Lea has over 25 years experience spanning academic, large corporations and startups. For the last 10 years, he has started or helped start 4 new companies while managing an active research program (University of British Columbia, Canada and Lancaster University, UK) into distributed and ubiquitous computing, the IoT and Smart Cities.