Category Archives: node-red-dashboard

A Quick Plotly

Some time ago I wrote about HighCharts – a wonderful set of Javascript tools for doing graphs – and I gave an example of getting the graphs into Node-Red.  I’ll not repeat that as you have the link above.  At the time I’d not added in databases, I’ve since made some VERY simple code to bring in the charts from SQLITE -  I have to say it isn’t the fastest code in the world so I won’t embarrass myself by replicating it here. But at the time, my only limit was Node-Red Dashboard – there was as far as I could tell no way to get full screen or anything like it on a PC  -  until I took the plunge today and ask the right people – turns out that you can set an individual page in Node-Red Dashboard to WAY past the normal 6 blocks width simply by altering the size of a GROUP used only on that page.

Continue reading A Quick Plotly

Facebooktwittergoogle_pluspinterestlinkedin

Garden Data Collection

MiFloraThis entry is all about InfluxDB, Grafana and a cute little Bluetooth garden sensor!

Some time ago a pal of mine and I did some swaps of spare kit and I ended up with a Mi Flora sensor.  A rather pleasing looking device,  the MI Flora has a 3v lithium button cell and can transmit by Bluetooth LE, the light level, temperature, battery level and moisture.

Battery is supposed to last for months. I’ve provided an AliExpress link above which puts the units at under £8 Inc. postage. No doubt that price will vary dramatically depending on where you chose to spend your money. Amazon charge more like £13.

So having pulled off the protective tab which meant the battery would work, I put the unit to one side as I had no idea how to read it.

A week or so later in Spain, with poor weather my pal Jay came on to ask me if I’d used it. I had not – and so we decided to give it a go. As it turned out I had some cheap Bluetooth 4 dongles I’d bought on a whim last year. I plugged one into my Raspberry Pi 2. The program “hcitool” lets you scan for Bluetooth devices and as it happens it was already installed on the Pi. From the command prompt:

sudo hcitool lescan

Instant success as the Mi Flora device with it’s MAC address was revealed. I made a note of the MAC address.

I would need Python 3 to experiment – but this was already installed on the Pi and some code to get the information from the unit. This code turned out to be ok with a little modification. I ended up creating a folder called /home/pi/myflora and dumping the lot in there.

Jay had modified the program to send MQTT. That of course needed the PAHO client. In order to do that I needed something called PIP.

sudo apt-get install python3-pip

sudo pip3 install paho-mqtt

Note NOT pip (which works for Python 2.7) but pip3.

I then added to the MI flora directory a test program which Jay sent me called bathroom.py – presumably because he’d put the sensor in his bathroom – I adjusted the output slightly as I was putting my unit in the garden

import sys
import paho.mqtt.client as mqtt

from miflora.miflora_poller import MiFloraPoller, \
MI_CONDUCTIVITY, MI_MOISTURE, MI_LIGHT, MI_TEMPERATURE, MI_BATTERY

poller = MiFloraPoller("C4:7C:8D:32:08:63")
print("Getting data from Mi Flora")
print("FW: {}".format(poller.firmware_version()))
print("Name: {}".format(poller.name()))
print("Temperature: {}".format(poller.parameter_value(MI_TEMPERATURE)))
print("Moisture: {}".format(poller.parameter_value(MI_MOISTURE)))
print("Light: {}".format(poller.parameter_value(MI_LIGHT)))
print("Conductivity: {}".format(poller.parameter_value(MI_CONDUCTIVITY)))
print("Battery: {}".format(poller.parameter_value(MI_BATTERY)))

# Publishing the results to MQTT
mqttc = mqtt.Client("miflorabathroom")
mqttc.username_pw_set("admin","xxxx")
mqttc.connect("192.168.1.19", 1883)
mqttc.publish("miflora/garden", "{ \"battery\" : " + str(poller.parameter_value(MI_BATTERY)) + ", \"version\" : \"" + str(poller.firmware_version()) + "\", \"sunlight\" : " + str(poller.parameter_value(MI_LIGHT)) + ", \"temperature\" : " + str(poller.parameter_value(MI_TEMPERATURE)) + ", \"moisture\" : " + str(poller.parameter_value(MI_MOISTURE)) + ", \"fertility\" : " + str(poller.parameter_value(MI_CONDUCTIVITY)) + " }")
mqttc.loop(2)
# End of MQTT section

So the idea was that running “python3 garden.py” would send some MQTT to my Mosquitto MQTT broker – and indeed it did (change MAC address for the MI Flora and MQTT details in the above).  Subscribing to “miflora/garden” did the trick – I could fire off the program and with in seconds I’d see the resulting JSON package coming out into my MQTT-Spy client.

Next job was to get Node-Red talking to the MI Flora – I used an INJECT node, set to retrigger every 15 minutes – firing off to an EXEC node which contained nothing more than “python3 /home/pi/miflora/bathroom.py”

That worked – so now I had a regular supply of data in the form of an easy to use JSON package.

I split the package up – and fed it into a Node-Red Dashboard graph – and that was my first disappointment. If you fire more than 4 items into those graphs – the popup fails to show all of them – and the graphs are not very good anyway. I think a lot more work needs to go into that.

I do use GROVESTREAMS on-line (which is REALLY flexible) to store data remotely but there’s a limit as to how far back you can read for free – that was not the case when I first started using them and wrote the Node-Red-Contrib-Grove node to make it all easy.

So now I was on a search for a decent graphics package that would run on the Raspberry Pi (or similar). NOT as easy as it sound,  unless you’re very easily pleased when it comes to graphics.

After a lot of experimenting I decided to give Grafana a shot. I think if I started writing this blog again, I would would have Grafana in the title because this really, as it turns out, is the star of the show.

Having seen a few screenshots, I had visions of populating  a SQLITE 3 database and firing that data into Grafana.

Erm, no. Despite (wait for it) using SQLITE internally to store users and dashboards, the package does not actually support SQLITE for data !!!  I've explained that now, to save you some confusion later. When I did the install, it kept referring to SQLITE and could I HELL figure out how to set up a database for my data – that’s why.

As a Pi user (that means SD use so I’m not keen on MySQL as SD has limited WRITE capability and I worry about databases shortening the life of the SD - see the later article on SD life - very informative) the options for data sources did not look appealing to me at all, the least offensive being INFLUX and that is the one I settled on – partly as there is a node for it available for Node-Red.  Despite the pretty website this is still very preliminary at version 1.02. Influx doesn’t use terms like tables and fields which kept me going for a couple of hours – but it is good at storing time-stamped data – indeed you don’t even do the time-stamping. You create an empty database and start firing data at the DB in pairs – stream name – and data value – it really is very simple once you get started. I guess you could think of it as a single table with pairs of data-name and data-value.  I went down a few dead ends and discovered if you got your stream names wrong – good luck renaming them as INFLUX does not support renaming!

Anyway, it ‘s all very easy once you get started. I did things backwards and installed Grafana first. Here’s what I did – no guarantee it will work for you.

You might want to consider reading this before using InfluxDB on the Pi for long-term use - there are very few build options for small SBCs and this article gives the impression it likes more RAM than we would normally have on the likes of the Pi. I do have to say though that it is working just fine on mine after several days of operation. Comments welcome.

I grabbed the Grafana file for my Pi from here. https://github.com/fg2it/grafana-on-raspberry

sudo dpkg -i grafana_4.1.2-1487023783_armhf.deb
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable grafana-server

That was Grafana up and running on port 3000 – user name admin, password admin to actually DO anything – and quickly change that password!!! I did that, went into the ini file and disabled user registration as the admin can do that and opened up port 3000 redirect in my router so I could access this from the outside world. That all worked well.

wget http://ftp.us.debian.org/debian/pool/main/i/influxdb/influxdb_1.0.2+dfsg1-1_armhf.deb

sudo dpkg -i influxdb_1.0.2+dfsg1-1_armhf.deb

That set up influx and that ran without further ado at port 8083. I did NOT open that up to the outside world. In there I created a new empty database called “logger”

logger

In Grafana – which supports influx without any changes, I followed Engineer John’s instructions to set up the data-source and a new Dashboard.

So the next thing – would be to get some data.  I played with this for some time – should I keep incoming data source names simple – as you can’t rename them? Then Jay pointed out that in Grafana you can ALIAS names. That did it. So in influxDB node for Node-Red, you fire in a payload – and depending where you want to put your data – you use msg.measurement.. Yes, exactly, what’s wrong with msg.topic you might ask. So here is my little MI Flora MQTT node firing values into InfluxDB

MiFlora[6]

And that yellow function block…

node.status({fill:"blue",shape:"dot",text: msg.payload});

tmp=JSON.parse(msg.payload);

msg.measurement="battery"; msg.payload=tmp.battery; node.send(msg);
msg.measurement="sunlight"; msg.payload=tmp.sunlight; node.send(msg);
msg.measurement="temperature"; msg.payload=tmp.temperature; node.send(msg);
msg.measurement="moisture"; msg.payload=tmp.moisture; node.send(msg);
msg.measurement="fertility"; msg.payload=tmp.fertility; node.send(msg);

Yes, I know – far better ways to do this – but it was my first stab at it and it worked. For general use however I added an incoming function that merely copies msg.topic into msg.measurement and that means any single data-source coming in via MQTT can be blasted straight into InFluxDB.

Yes, that’s write – you don’t create tables or fields – you just send in field names and data and that’s it!

I also fed in some data from my general sensors – with horrible topics like pergola/lighting but of course thanks to the Grafana alias ability – names like that would not show up in Dashboards.

Having stored up some data it was time to get it into graphs – I took the VERY simplest approach of showing lines as I have at this point no idea what some of the Grafana functions do – I DO know that smoothing the data might be fun as Grafana relies on the data-source to do that and Influx doesn’t do that.  What IS nice is that you don’t have to worry about things like “I want the last 5 minute’s worth” or “show me the last month’s worth” as Grafana does all of that for you – from here  it gets easy.

New dashboard and under metrics ADD ROW – takes an hour or so to get to grips with this..

Grafana setup

I’ve expanded the top one here – I have 3 lines in that one graph – see the blank ALIAS BY  - if you want to change the source name (in this case battery) put something in there.

And that  is how I got to this point…

Grafana imagery - my stats

So top left you see battery status from my deep discharge battery and  the two MI Flora units, then moisture levels from the two units – light levels – and on the right centre – a whole host of temperature readings, overall humidity and finally just to play with – the UK temperature split off and displayed differently.

There’s a lot to take in – and I am as you can see NOT an expert at this yet so before firing questions back – you might want to spend some time looking at the links on this blog entry and using Google. If you think another database is better by all means write in to say why – and if you think there’s a better easy-to-fit-in-Pi graphing system, preferable one that works with SQLITE – again do let us know. I started all of this with zero information late afternoon yesterday and had it all working by the end of last night – so it’s not THAT big a deal even if my clumsy writing makes it look that way.

Not entirely sure what’s going on the this battery level on my first day – but it did occur to me that the unit is intended for putting into plant-pots – not sitting outside in the sopping rain so I’ve covered it in cling-film!! As we’re off back to the UK for a few weeks,  I don’t want to come back to a rusted heap.  Note the Mi Flora battery at the top – seems to be self-recovering!!

Update: the little Bluetooth sensors have now been running for months and the battery levels remain fine – the Bluetooth range however is crap – best to have a Bluetooth dongle nearby to talk to them – don’t even think about two thicknesses of wall!

Grafana output

Update 27/03/2017: I've been hitting issues with out of bounds temperature data – i.e. a couple of duff readings crept in - with values like 2000 and -64  which of course flattened the data completely. I realised quickly that InfluxDB has no DELETE function and as far as I could tell - things like comparing data with a value made the graph line disappear.

I put something up on the web and someone came back and asked if I was using InfluxDB 1.2 which apparently has a DELETE function. Well, I wasn't - I was way back at 1.01 - worse - the same folder where that came from only went up to 1.1

However, thanks to THIS helpful chap - I easily updated to Influx 1.2 without the issue he described. I STILL can't add something like "where value < 100" but maybe I can delete ??? Anyone with more info on this please by all means chip in...

Update 18/04/2017: I've just fitted the second of these units and also fixed a minor error in the code above.  All that is needed for a second unit is a copy of the program with different MAC address and different MQTT publish address. Sure you could use the same one twice with command line parameters but I took the easy option.

Bluetooth RANGE is becoming an issue. The Pi is in my office - breezeblock wall then the garden - the second unit I tried maybe 15ft or so from the office wall - not a chance.  Clearly BT4 is good for power, not so good for range.

So - there's a nice job for an ESP32... BT in - WIFI out.  A relay!

Update 30/04/2017: At the time of writing, InfluxDB is up to version 1.2.2 and if you follow the link I gave above but instead of 1.2.0  use 1.2.1 in the two relevant links - you will get the latest version of InfluxDB. Similarly I found Grafana 4.20 - same place as before but called https://bintray.com/fg2it/deb/download_file?file_path=main%2Fg%2Fgrafana_4.2.0_armhf.deb

I just repeated the Grafana install routine to update it using the new simpler name grafana_4.2.0_armhf.deb - all worked perfectly.

I'm pretty happy with Grafana and I now know how to delete data points (by time) but if you really want to use SQLITE and do your own thing - take a look at the excellent work that Csongor Varga has done - https://www.youtube.com/watch?v=nkKf26oKzhQ


If you like this post – please share a link to it by social media, by email with friends or on your website.
More readers means more feedback means more answers for all of us. Thank you!

Facebooktwittergoogle_pluspinterestlinkedin

Node-RED Global Flow and Context

There’s a mouthful. This short article is about the various kinds of variables used in Node-Red and more importantly how to initialise them and how to view them.

So in Node-Red when you want to use variables – there are three basic types as well as local variables.

So for example – you’ve opened up a FUNCTION block and you want to use some variables. VAR is always a good start.

var fred=1;

A way to get a variable to PERSIST behind that simply function invocation to the next time around, use this;

context.set(“fred”,1);

If you’d like that var to be available to any other function in that flow (or page if you like) - use:

flow.set(“fred”,1);

and if you need a global variable that can be accessed anywhere within Node-Red including other flows  – I use them all the time for things like thermostat objects etc… use global.

global.set(“fred”,1);

Of course you can recall said variables as global.get(“fred”); etc. Don't use the old method (example global.context.fred) - there's a reason the Node-Red guys are steering us in this direction.

This is not meant as a course on programming – you can read all about this on the Node-Red site – just look up variables.

So three useful types of variable.

But there’s a problem. Node-Red runs asynchronously and in my first efforts I would have an INIT tab and initialise my global variables there. Once in a blue moon I would see a non-destructive failure with the logs showing that I’d tried to use a variable before it had been initialised (created, even).  So in a platform where everything is running asynchronously how do you ensure (without checking every single time you use it) that a variable has been initialised…. especially important for globals.

One way which I don’t really like is to ensure that the variable is initialised in the left most tab. as that runs first… ok… kind of….

Well it turns out “there’s a node for that”.  Node-Red-Contrib-Config does that – you can set up a variable – which could be a whole object – an no matter where you put this node – it will run BEFORE the main flows start up – I tested it, it works. My only niggle might be that the VALUE field needs to be expandable – a single line is not much use for large objects.

npm install node-red-contrib-config

So that’s one side of it.

The other side is – would it not be nice if you could have a tab to show you the current status, names and values of all of your global variables – and more. Well, now you can.

This needs a SLIGHT mod to Node-Red so if you’re new you might want to back stuff up.

In the directory you normally (hopefully locally) npm install stuff –

https://www.npmjs.com/package/node-red-contrib-contextbrowser

So the install is:

npm install node-red-contrib-contextbrowser

Stop Node-Red and in (in my case) and make a slight edit

nano /usr/lib/node_modules/node-red/red/runtime/nodes/context.js

This is the function you need to alter - very carefully add one line as I have in bold. That’s it – don’t make any other changes.….and this IS case-sensitive.

function createContext(id,seed) {
var data = seed || {};
var obj = seed || {};
obj.get = function get(key) {
return util.getMessageProperty(data,key);
};
obj.set = function set(key, value) {
util.setMessageProperty(data,key,value);
}
  obj.getKeys = function() { return Object.getOwnPropertyNames(data); }
return obj;
}

If you're something that does not show bold, the line starting "obj.getKeys" is the addition.

(NODE RED GUYS – any chance of making the above standard???)

Start up Node-Red (this works even if you use HTTPS and have password on Node-Red)

And lo – you should have a new tab on the right… click in the editor on a node or function you know has variables initialised…

and then use the refresh in that new tab..

My test..

tmp6E51

The contents of that orange function

tmpD029

 

And..

tmp65A3

As you can see, you must select the particular node or function with the mouse first – then hit the little refresh in the contextbrowser tab - you then see above freddy and the value in the tab – along with any others you’ve defined in that node or function. Ok it’s not quite debugging but better than nothing.

Now moving to the Global tab is another matter as I have several globals including some big objects – if they are very big – you might have to hit the little … indicator to expand them.

tmp3C87

So for me this is marvellous – I can now set up global vars with the confidence that they will be initialised properly at the start – and I can also get a nice overall view of the globals I’ve used – something I could not easily do before.


If you like this post – please share a link to it by social media, by email with friends or on your website.
More readers means more feedback means more answers for all of us. Thank you!

Facebooktwittergoogle_pluspinterestlinkedin

Node Red and HighCharts

HiChart Charts in Node-RedFirst things first – HighCharts are not free for commercial use. However, for your own website, a school site or a non-profit organisation they are free to use. You are also OK to make modifications. So – that’s the “cover my back” bit out of the way – now, how to use them in Node-Red.

I’ve looked at this before, because their charts are very, very nice.  As some of you know, I’m now sold on Grafana but I also really like the idea of using charts with my favourite database – SQLITE and this is not supported as far as I can see with Grafana. Indeed I’m glad there is a Node-Red Node for InfluxDB as I had trouble getting my head around inputting data into InfluxDB otherwise (the node makes it REALLY easy).

And so it was that I came back to HighCharts. Now one of the demos of theirs that I really like, involves plotting variable numbers of time-based lines in a graph (temperature, humidity etc)– with potentially missing values. I found when using other chart systems that if you had the odd missing value due to power failure etc, you had to fix that – with the HighCharts demo it just draws a smooth curve between the two.

Another of their demos features zooming and it is only in the last couple of days when taking another look – that I realised you could combine the two – zoomable multi-line graphs.

What you see on the right is little more than a tweeked version of their basic demo – with a few bits taken out to make the whole thing work in the limited space we typically have in Node-Red Desktop – and zooming built in.

I tested everything in JSFiddle and then tried dumping it into Node-Red – nothing – zilch. I found that using the libraries remotely just would not work. Out of desperation I grabbed the libraries, one of which is big and put them into one of my usual “myjs” directories on the Raspberry Pi and lo and behold, it all worked.

Sample data into HiCharts

Here you see above my test – now bear in mind this is very preliminary. In the brown FUNCTION nodes I have 2 different sets of data just to test things… and these feed an object in msg.payload through to the Dashboard UI. I’ve made two of these – using two different DIVs. One is called “mine1” and the other is called “mine2” – they fill DIV ids “container1” and “container2" respectively. Everything else is the same.

So – starting off with their demo here http://www.highcharts.com/demo/spline-irregular-time

I took that demo and wrapped the lot in a function (object) – so as to hide everything from the outside world in case I wanted two or more of these on a page. I then wrapped the code inside a local, externally accessible function. This could be way more efficient – but I wanted something up and running and as you will see I have two completely independent graphs here.

Here is the code inside the blue template. The only thing that changes is the reference to the DIV (container1/container2) and the references to the new object (mine1/mine2) – and of course near the end I’ve fed unique titles and subtitles when creating the object but the latter do not have to be unique.

<script src="/myjs/highcharts.js"></script>
<script src="/myjs/exporting.js"></script>
<div id="container1" style="min-width: 300px; height: 300px; margin: 0 auto"></div>

<script>
   (function(scope){
        scope.$watch('msg', function(msg) {
          mine1.graph(msg.payload);
        });
    })(scope);


var sample_data1=[{
        name: 'Winter 2012-2013',
        // Define the data points. All series have a dummy year
        // of 1970/71 in order to be compared on the same x axis. Note
        // that in JavaScript, months start at 0 for January, 1 for February etc.
        data: [
            [Date.UTC(1970, 9, 21), 0],
            [Date.UTC(1970, 10, 4), 0.28],
            [Date.UTC(1970, 10, 9), 0.25],
            [Date.UTC(1970, 10, 27), 0.2],
            [Date.UTC(1970, 11, 2), 0.28],
            [Date.UTC(1970, 11, 26), 0.28],
            [Date.UTC(1970, 11, 29), 0.47],
            [Date.UTC(1971, 0, 11), 0.79],
            [Date.UTC(1971, 0, 26), 0.72],
            [Date.UTC(1971, 1, 3), 1.02],
            [Date.UTC(1971, 1, 11), 1.12],
            [Date.UTC(1971, 1, 25), 1.2],
            [Date.UTC(1971, 2, 11), 1.18],
            [Date.UTC(1971, 3, 11), 1.19],
            [Date.UTC(1971, 4, 1), 1.85],
            [Date.UTC(1971, 4, 5), 2.22],
            [Date.UTC(1971, 4, 19), 1.15],
            [Date.UTC(1971, 5, 3), 0]
        ]
    }, {
        name: 'Winter 2013-2014',
        data: [
            [Date.UTC(1970, 9, 29), 0],
            [Date.UTC(1970, 10, 9), 0.4],
            [Date.UTC(1970, 11, 1), 0.25],
            [Date.UTC(1971, 0, 1), 1.66],
            [Date.UTC(1971, 0, 10), 1.8],
            [Date.UTC(1971, 1, 19), 1.76],
            [Date.UTC(1971, 2, 25), 2.62],
            [Date.UTC(1971, 3, 19), 2.41],
            [Date.UTC(1971, 3, 30), 2.05],
            [Date.UTC(1971, 4, 14), 1.7],
            [Date.UTC(1971, 4, 24), 1.1],
            [Date.UTC(1971, 5, 10), 0]
        ]
    }, {
        name: 'Winter 2014-2015',
        data: [
            [Date.UTC(1970, 10, 25), 0],
            [Date.UTC(1970, 11, 6), 0.25],
            [Date.UTC(1970, 11, 20), 1.41],
            [Date.UTC(1970, 11, 25), 1.64],
            [Date.UTC(1971, 0, 4), 1.6],
            [Date.UTC(1971, 0, 17), 2.55],
            [Date.UTC(1971, 0, 24), 2.62],
            [Date.UTC(1971, 1, 4), 2.5],
            [Date.UTC(1971, 1, 14), 2.42],
            [Date.UTC(1971, 2, 6), 2.74],
            [Date.UTC(1971, 2, 14), 2.62],
            [Date.UTC(1971, 2, 24), 2.6],
            [Date.UTC(1971, 3, 2), 2.81],
            [Date.UTC(1971, 3, 12), 2.63],
            [Date.UTC(1971, 3, 28), 2.77],
            [Date.UTC(1971, 4, 5), 2.68],
            [Date.UTC(1971, 4, 10), 2.56],
            [Date.UTC(1971, 4, 15), 2.39],
            [Date.UTC(1971, 4, 20), 2.3],
            [Date.UTC(1971, 5, 5), 2],
            [Date.UTC(1971, 5, 10), 1.85],
            [Date.UTC(1971, 5, 15), 1.49],
            [Date.UTC(1971, 5, 23), 1.08]
        ]
    }];
    
function doit(theContainer,title,subtitle)
{
this.graph=function(xx)
{
Highcharts.chart(theContainer, {
    chart: {
        zoomType: 'x',
        type: 'spline'
    },
    title: {
        text: title
    },
    subtitle: {
        text: subtitle
    },
    credits: { enabled: false },
    xAxis: {
        type: 'datetime',
        dateTimeLabelFormats: { // don't display the dummy year
            month: '%e. %b',
            year: '%b'
        },
        title: {
            text: 'Date'
        }
    },
    
    exporting: {
            buttons: {
                contextButton: {
                    enabled: false
                } } },

    yAxis: {
        title: {
            text: 'Snow depth (m)'
        },
        min: 0
    },
    tooltip: {
       headerFormat: '<b>{series.name}</b><br>',
       pointFormat: '{point.x:%e. %b}: {point.y:.2f} m'
    },

    plotOptions: {
        spline: {
            marker: {
                enabled: false
            }
        }
    },

    series: xx
});

}

}
mine1=new doit("container1","title1","Subtitle1");
mine1.graph(sample_data1);
</script>


and here is the code inside one of those orange functions – the only thing that changes is the data.

msg.payload=[{
        name: 'Win 2012-2013',
        // Define the data points. All series have a dummy year
        // of 1970/71 in order to be compared on the same x axis. Note
        // that in JavaScript, months start at 0 for January, 1 for February etc.
        data: [
            [Date.UTC(1970, 9, 21), 0],
            [Date.UTC(1970, 10, 4), 0.28],
            [Date.UTC(1970, 10, 9), 0.25],
            [Date.UTC(1970, 10, 27), 0.2],
            [Date.UTC(1970, 11, 2), 0.28],
            [Date.UTC(1970, 11, 26), 0.28],
            [Date.UTC(1970, 11, 29), 0.47],
            [Date.UTC(1971, 0, 11), 0.79],
            [Date.UTC(1971, 0, 26), 0.72],
            [Date.UTC(1971, 1, 3), 1.02],
            [Date.UTC(1971, 1, 11), 1.12],
            [Date.UTC(1971, 1, 25), 1.2],
            [Date.UTC(1971, 2, 11), 1.18],
            [Date.UTC(1971, 3, 11), 1.19],
            [Date.UTC(1971, 4, 1), 1.85],
            [Date.UTC(1971, 4, 5), 2.22],
            [Date.UTC(1971, 4, 19), 1.15],
            [Date.UTC(1971, 5, 3), 0]
        ]
    }, {
        name: 'Win 2013-2014',
        data: [
            [Date.UTC(1970, 9, 29), 0],
            [Date.UTC(1970, 10, 9), 0.4],
            [Date.UTC(1970, 11, 1), 0.25],
            [Date.UTC(1971, 0, 1), 1.66],
            [Date.UTC(1971, 0, 10), 1.8],
            [Date.UTC(1971, 1, 19), 1.76],
            [Date.UTC(1971, 2, 25), 2.62],
            [Date.UTC(1971, 3, 19), 2.41],
            [Date.UTC(1971, 3, 30), 2.05],
            [Date.UTC(1971, 4, 14), 1.7],
            [Date.UTC(1971, 4, 24), 1.1],
            [Date.UTC(1971, 5, 10), 0]
        ]
    }, {
        name: 'Win 2014-2015',
        data: [
            [Date.UTC(1970, 10, 25), 0],
            [Date.UTC(1970, 11, 6), 0.25],
            [Date.UTC(1970, 11, 20), 1.41],
            [Date.UTC(1970, 11, 25), 1.64],
            [Date.UTC(1971, 0, 4), 1.6],
            [Date.UTC(1971, 0, 17), 2.55],
            [Date.UTC(1971, 0, 24), 2.62],
            [Date.UTC(1971, 1, 4), 2.5],
            [Date.UTC(1971, 1, 14), 2.42],
            [Date.UTC(1971, 2, 6), 2.74],
            [Date.UTC(1971, 2, 14), 2.62],
            [Date.UTC(1971, 2, 24), 2.6],
            [Date.UTC(1971, 3, 2), 2.81],
            [Date.UTC(1971, 3, 12), 2.63],
            [Date.UTC(1971, 3, 28), 2.77],
            [Date.UTC(1971, 4, 5), 2.68],
            [Date.UTC(1971, 4, 10), 2.56],
            [Date.UTC(1971, 4, 15), 2.39],
            [Date.UTC(1971, 4, 20), 2.3],
            [Date.UTC(1971, 5, 5), 2],
            [Date.UTC(1971, 5, 10), 1.85],
            [Date.UTC(1971, 5, 15), 1.49],
            [Date.UTC(1971, 5, 23), 1.08]
        ]
    }];
    
return msg;

With more work, this could be really useful – and don’t forget to check the BIG range of graphs and other widgets in the HiCharts demos – there is some wonderful stuff in there.

Back to databases - as you can see it would not be hard at all to pull data like this out of just about any database! That's my next job after I tidy this up a bit.

Facebooktwittergoogle_pluspinterestlinkedin

NR Templates and the Inject Node

When testing Node-Red, the common INJECT node is invaluable. Indeed it is just about the only way to instantly inject information into another node. Here is a typical example.

Inject and Debug Nodes

It doesn’t get any simpler than this. Here I have merely dropped an “Inject” node onto the Node-Red working surface along with a “Debug” node. I’ve made no changes to either of them.  Note that “inject” and “debug” nodes come as standard with Node-Red.

Node-Red setup

Correctly set up, on the right of the Node-Red screen, you can see (or make available via the top-right three horizontal bars – view – “debug messages”) a debug window and simply by pressing that button-like item to the left of the inject node, you can put a timestamp into the debug window as such:

Debug window

Simple enough. Note that this says (in red) “msg.payload”.  In homage I guess to MQTT, the inject node outputs a simple object which deals primarily with msg.topic and msg.payload. You can use these for whatever messages you want.  The debug node by default shows only msg.payload but you can of course change this to show the entire message object.

Full debug message

Note that here (image above) we also see a blank topic (because the unmodified inject node has that as empty – you could put “Merry Christmas” in there if you like. Also included is _msgid which is of no interest to us for now unless you have a specific need for a unit number.

In general then,  you can fire out msg.topic and msg.payload from the inject node.  Instead of putting out a timestamp,  you can put out a string (for example “Hello world”) and this is output in msg.payload.

Hello world

Output Hello World

Expanding the INJECT output

For many purposes, we are done – you know know how to output a value into msg.payload and you can easily add msg.topic to that.  An inject node then can directly output data suitable for, say an MQTT node which needs both msg.topic and msg.payload.

For some purposes however, this is not enough.

Messages

In the above image, I need to inject one of two messages into a template. Easy enough, “message 1” as string into one injector, “message 2” into another. But what if we have a string of messages for different purposes going into that template?

On way to differentiate them would be to use topic.

if (msg.topic==”needle1”)  { /*use msg.payload to influence  a gauge needle */ }
if (msg.topic==”lcd”) { /* use msg.payload to influence a display */ }

Another way to do this is by putting a function node after the inject node…

msg.needle=msg.payload;

The output of this then, is your message sitting inside msg.needle instead of msg.payload.

Do-able but two things about that. Firstly you still can only output one message at a time and now you have the extra work and additional screen real estate used up for the additional function node. Not very elegant. If that works for you – stop here.

So what if we want a simpler way that may include sending multiple messages out at once.

In the inject.node you can’t change the name of “msg.payload” – but you can change what it outputs – a number, a string etc.. and in this case of particular interest, JSON.

Let’s take an example. Let’s say that by pressing the test button we want to set a needle value to 10 AND we want to send the message “hello” to an LCD display in our gauge.

Sending an object

Sending several messages in a JSON string

This LOOKS like you are sending a string of text but look what comes out – when you view the debug payload:

image

Not a string, but an object  - but how do you make use of this? Simple. In your template – or function or whatever you are firing this message into:

if (msg.payload.needle!==undefined) { /*make use of msg.payload.needle */ }
if (msg.payload.lcd!==undefined) { /* make use of msg.payload.lcd  */ }

This way, directly from a single inject node you can send either one, or several messages at once. The check for undefined means you don’t always have to send all of them.

Hence, for example, in testing my gauge, I can have a number of buttons testing  different parts of the gauge and one of them sending messages to both lines of the internal LCD at once without making a big mess of the screen. In the example below, the bottom two injects each send 2 different commands to the gauge when pressed.

Testing the gauge

But what about existing objects

Another way to send multiple messages through the injector allows you to make use of existing global objects…

Injecting objects

In this example, I happen to have an existing object called “thermostat” – which can be referred to as “context.global.thermostat” though the more modern way to do that is by use of global.get and global.put.

Armed with the above, which contains in this case lots of information about the state of my house… I can send that – either manually or of course in a timed fashion, to the debug node.

thermostat object

A little confusing at first until you notice the three dots at the bottom… which mean “I’m only showing you some of the result. Click on them and..

expanded object

Far too much information to display here – but I think you get the point – though msg.payload looks restrictive – in fact you can send a whole load of information thanks to the ability of the INJECT node to carry an object in msg.payload.

In this case, if you set DEBUG to look for msg.payload.desired you will indeed see a result of 23.

Can I create an object? Sure:

Test object creation

In this case – the content of the injector is irrelevant. Just drag it onto the screen.  Here we have (for the sake of it) two debug nodes – one is looking for msg.payload.actual and the other is looking for msg.payload.desired.

And here are the contents of that simple function node in the middle.

msg.payload={
desired:25,
actual:23
}
return msg;

Debugging:  Keith Howell wrote in to remind me about node.warn and node.error – two very handy debugging functions. Here’s an example of use…

node.warn

Again out of sheer laziness I’ve used the inject node on the left straight out of the box as it were. On the right the orange “test” function – again – drag from the left… double-click to open and enter the following:

node.warn("just info, really!");
node.error("ooh errr!");

and the result in the debug area when you press the timestamp button…

Debugging

Isn’t that handy – the only difference here being the colour – yellow for warning (I’d have used blue but hey ho) and red for error.  Using this instead of a debug node could save a little real estate on the screen in some cases.

Facebooktwittergoogle_pluspinterestlinkedin

Thermometers

Thermometers for Node-Red

Three thermometers in a row for Node-Red (or as many or few as you like really) for Node-Red Dashboard. Another fine example of simple gauges and unlike some it is easy to make multiple gauges on one page – I will demonstrate three.

Here is the library source, I grabbed the larger one and saved as gauges-min.js in my /myjs folder – see previous blogs for setting this up with Node-Red.

Here is the code for one – and a flow example with three independent units.

You can of course alter the colours to whatever you want.

thermometer flow for Node-Red

The code for one template:

<script src="/myjs/gauge.min.js"></script>

<script>
  (function(scope){ 
            scope.$watch('msg', function(msg) {
               if (typeof(msg.payload) != "undefined") { gauge2.value=msg.payload; gauge2.draw(); }
            });
    })(scope);

var gauge2 = new LinearGauge({
    renderTo: 'mycanvas',
    valueBox: false,
    highlights: [ 
            {"from": 0, "to": 10, "color": "rgba(50, 50, 200, .75)"},
            {"from": 10, "to": 20, "color": "rgba(50, 200, 200, .75)"},
            {"from": 20, "to": 30, "color": "rgba(50, 200, 50, .75)"},
            {"from": 30, "to": 50, "color": "rgba(200, 200, 50, .75)"},
            {"from": 50, "to": 100, "color": "rgba(200, 50, 50, .75)"}
            ],
    barWidth: 10,
    units: "°C",
    borderShadowWidth: 0,
    borders: false,
    value: 35
}).draw();

</script>

<canvas id="mycanvas" 
    data-type="linear-gauge"
    data-width="106"
    data-height="270"
    data-units="°C"
    data-min-value="0"
    data-start-angle="90"
    data-ticks-angle="180"
    data-value-box="false"
    data-max-value="220"
    data-major-ticks="0,20,40,60,80,100,120,140,160,180,200,220"
    data-minor-ticks="2"
    data-stroke-ticks="true"
    data-color-plate="#fff"
    data-border-shadow-width="0"
    data-borders="false"
    data-needle-type="arrow"
    data-needle-width="2"
    data-needle-circle-size="7"
    data-needle-circle-outer="true"
    data-needle-circle-inner="false"
    data-animation-duration="1500"
    data-animation-rule="linear"
    data-bar-width="10"
    data-value="35"
></canvas>

The sample flow above

[{"id":"ddc888ba.e45578","type":"ui_template","z":"c552e8d2.712b48","group":"33279d5b.72b122","name":"controller","order":0,"width":"2","height":"5","format":"<script src=\"/myjs/gauge.min.js\"></script>\n\n<script>\n  (function(scope){ \n            scope.$watch('msg', function(msg) {\n               if (typeof(msg.payload) != \"undefined\") { gauge3.value=msg.payload; gauge3.draw(); }\n            });\n    })(scope);\n\nvar gauge3 = new LinearGauge({\n    renderTo: 'mycanvas2',\n    valueBox: false,\n    highlights: [ \n            {\"from\": 0, \"to\": 10, \"color\": \"rgba(50, 50, 200, .75)\"},\n            {\"from\": 10, \"to\": 20, \"color\": \"rgba(50, 200, 200, .75)\"},\n            {\"from\": 20, \"to\": 30, \"color\": \"rgba(50, 200, 50, .75)\"},\n            {\"from\": 30, \"to\": 50, \"color\": \"rgba(200, 200, 50, .75)\"},\n            {\"from\": 50, \"to\": 100, \"color\": \"rgba(200, 50, 50, .75)\"}\n            ],\n    barWidth: 10,\n    units: \"°C\",\n    borderShadowWidth: 0,\n    borders: false,\n    value: 35\n}).draw();\n\n</script>\n\n<canvas id=\"mycanvas2\" \n    data-type=\"linear-gauge\"\n    data-width=\"106\"\n    data-height=\"270\"\n    data-units=\"°C\"\n    data-min-value=\"0\"\n    data-start-angle=\"90\"\n    data-ticks-angle=\"180\"\n    data-value-box=\"false\"\n    data-max-value=\"220\"\n    data-major-ticks=\"0,20,40,60,80,100,120,140,160,180,200,220\"\n    data-minor-ticks=\"2\"\n    data-stroke-ticks=\"true\"\n    data-color-plate=\"#fff\"\n    data-border-shadow-width=\"0\"\n    data-borders=\"false\"\n    data-needle-type=\"arrow\"\n    data-needle-width=\"2\"\n    data-needle-circle-size=\"7\"\n    data-needle-circle-outer=\"true\"\n    data-needle-circle-inner=\"false\"\n    data-animation-duration=\"1500\"\n    data-animation-rule=\"linear\"\n    data-bar-width=\"10\"\n    data-value=\"35\"\n></canvas>\n","storeOutMessages":false,"fwdInMessages":false,"x":960,"y":2120,"wires":[[]]},{"id":"27cb8298.0e94de","type":"ui_template","z":"c552e8d2.712b48","group":"33279d5b.72b122","name":"controller","order":0,"width":"2","height":"5","format":"<script src=\"/myjs/gauge.min.js\"></script>\n\n<script>\n  (function(scope){ \n            scope.$watch('msg', function(msg) {\n               if (typeof(msg.payload) != \"undefined\") { gauge4.value=msg.payload; gauge4.draw(); }\n            });\n    })(scope);\n\nvar gauge4 = new LinearGauge({\n    renderTo: 'mycanvas3',\n    valueBox: false,\n    highlights: [ \n            {\"from\": 0, \"to\": 10, \"color\": \"rgba(50, 50, 200, .75)\"},\n            {\"from\": 10, \"to\": 20, \"color\": \"rgba(50, 200, 200, .75)\"},\n            {\"from\": 20, \"to\": 30, \"color\": \"rgba(50, 200, 50, .75)\"},\n            {\"from\": 30, \"to\": 50, \"color\": \"rgba(200, 200, 50, .75)\"},\n            {\"from\": 50, \"to\": 100, \"color\": \"rgba(200, 50, 50, .75)\"}\n            ],\n    barWidth: 10,\n    units: \"°C\",\n    borderShadowWidth: 0,\n    borders: false,\n    value: 35\n}).draw();\n\n</script>\n\n<canvas id=\"mycanvas3\" \n    data-type=\"linear-gauge\"\n    data-width=\"106\"\n    data-height=\"270\"\n    data-units=\"°C\"\n    data-min-value=\"0\"\n    data-start-angle=\"90\"\n    data-ticks-angle=\"180\"\n    data-value-box=\"false\"\n    data-max-value=\"220\"\n    data-major-ticks=\"0,20,40,60,80,100,120,140,160,180,200,220\"\n    data-minor-ticks=\"2\"\n    data-stroke-ticks=\"true\"\n    data-color-plate=\"#fff\"\n    data-border-shadow-width=\"0\"\n    data-borders=\"false\"\n    data-needle-type=\"arrow\"\n    data-needle-width=\"2\"\n    data-needle-circle-size=\"7\"\n    data-needle-circle-outer=\"true\"\n    data-needle-circle-inner=\"false\"\n    data-animation-duration=\"1500\"\n    data-animation-rule=\"linear\"\n    data-bar-width=\"10\"\n    data-value=\"35\"\n></canvas>\n","storeOutMessages":false,"fwdInMessages":false,"x":960,"y":2200,"wires":[[]]},{"id":"7df32c36.100ef4","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"20","payloadType":"num","repeat":"","crontab":"","once":false,"x":770,"y":2100,"wires":[["ddc888ba.e45578"]]},{"id":"54305565.bdfd7c","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"60","payloadType":"num","repeat":"","crontab":"","once":false,"x":770,"y":2140,"wires":[["ddc888ba.e45578"]]},{"id":"7907996d.21af38","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"10","payloadType":"num","repeat":"","crontab":"","once":false,"x":770,"y":2180,"wires":[["27cb8298.0e94de"]]},{"id":"583f639c.99440c","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"90","payloadType":"num","repeat":"","crontab":"","once":false,"x":770,"y":2220,"wires":[["27cb8298.0e94de"]]},{"id":"c8fdd54c.823418","type":"ui_template","z":"c552e8d2.712b48","group":"33279d5b.72b122","name":"controller","order":0,"width":"2","height":"5","format":"<script src=\"/myjs/gauge.min.js\"></script>\n\n<script>\n  (function(scope){ \n            scope.$watch('msg', function(msg) {\n               if (typeof(msg.payload) != \"undefined\") { gauge2.value=msg.payload; gauge2.draw(); }\n            });\n    })(scope);\n\nvar gauge2 = new LinearGauge({\n    renderTo: 'mycanvas',\n    valueBox: false,\n    highlights: [ \n            {\"from\": 0, \"to\": 10, \"color\": \"rgba(50, 50, 200, .75)\"},\n            {\"from\": 10, \"to\": 20, \"color\": \"rgba(50, 200, 200, .75)\"},\n            {\"from\": 20, \"to\": 30, \"color\": \"rgba(50, 200, 50, .75)\"},\n            {\"from\": 30, \"to\": 50, \"color\": \"rgba(200, 200, 50, .75)\"},\n            {\"from\": 50, \"to\": 100, \"color\": \"rgba(200, 50, 50, .75)\"}\n            ],\n    barWidth: 10,\n    units: \"°C\",\n    borderShadowWidth: 0,\n    borders: false,\n    value: 35\n}).draw();\n\n</script>\n\n<canvas id=\"mycanvas\" \n    data-type=\"linear-gauge\"\n    data-width=\"106\"\n    data-height=\"270\"\n    data-units=\"°C\"\n    data-min-value=\"0\"\n    data-start-angle=\"90\"\n    data-ticks-angle=\"180\"\n    data-value-box=\"false\"\n    data-max-value=\"220\"\n    data-major-ticks=\"0,20,40,60,80,100,120,140,160,180,200,220\"\n    data-minor-ticks=\"2\"\n    data-stroke-ticks=\"true\"\n    data-color-plate=\"#fff\"\n    data-border-shadow-width=\"0\"\n    data-borders=\"false\"\n    data-needle-type=\"arrow\"\n    data-needle-width=\"2\"\n    data-needle-circle-size=\"7\"\n    data-needle-circle-outer=\"true\"\n    data-needle-circle-inner=\"false\"\n    data-animation-duration=\"1500\"\n    data-animation-rule=\"linear\"\n    data-bar-width=\"10\"\n    data-value=\"35\"\n></canvas>\n","storeOutMessages":false,"fwdInMessages":false,"x":960,"y":2040,"wires":[[]]},{"id":"8cc2ade9.5db9b","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"5","payloadType":"num","repeat":"","crontab":"","once":false,"x":770,"y":2020,"wires":[["c8fdd54c.823418"]]},{"id":"277f033f.43ecbc","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"48","payloadType":"num","repeat":"","crontab":"","once":false,"x":770,"y":2060,"wires":[["c8fdd54c.823418"]]},{"id":"33279d5b.72b122","type":"ui_group","z":"","name":"LCD Test","tab":"34cddaf3.8a9cd6","disp":true,"width":"6"},{"id":"34cddaf3.8a9cd6","type":"ui_tab","z":"","name":"testz","icon":"dashboard"}]
Facebooktwittergoogle_pluspinterestlinkedin

Yet Another Gauge

tmp6CBFThis one has been long in coming because despite my quickly evolving knowledge of Javascript and Canvas, that last leap – getting variable information out of a canvas and back into Node-Red – has kept me on edge for a couple of days now.  Dave (DCEEJAY) pointed me to various solutions all of which failed for me but then this morning he came up with a winner. I’ll go into detail in this article.

The gauge is simple enough – very much like the ones that Node-Red Dashboard already has – but it is adjustable (hmm, that’s a thought – I have suggested the Node-Red guys add a tick option to make the existing gauges adjustable. For now – this is it.

So – this started off as one of the examples from RGraph – you’ll need to include those libraries as previously. So note that the units used (degrees C) can be changed to anything you want – percentage or whatever. That is in the setup and is the entry marked “unitsPost”.

We have the by now familiar function(scope) function which allows us to inject values into the gauge – in this case msg.payload sets the gauge value.

We also have a special function which returns variable “value” to Node-Red on mouse up. This is the piece of the puzzle that has kept me going for days.  We also have a function which occurs on mouse up that grabs the current gauge value and stores it in “value”.  In the template itself, I’ve un-ticked “pass through messages” so that clicking or touching the gauge returns the new value of the gauge  - but injecting values does NOT product an output.

Now why is that last point important. Well, put such a gauge up on your computer screen and also on a secondary screen or phone – and watch what happens. Adjust one and…. nothing – the OTHER screen shows no change – oh dear. You need to inject the output back into the input for this to happen and unless you un-tick that box – the gauge will disappear up it’s own bum! For reasons beyond me you can’t connect a template’s output back to it’s input – so I just use an empty function to do that – well, empty other than I take the opportunity to put the value under the function just for effect.

I’ve made this to fit a 6*4 block. You can make it smaller by adjusting the size of the canvas, the gutter offsets and font size – but I could only manage to get one on a given tab without some unexplained interaction so I left the gauge this size which is handy for fingers on a typical phone.

tmp25CC

A view of the code:

<script src="/myjs/RGraph/RGraph.common.core.js"></script>
<script src="/myjs/RGraph/RGraph.common.dynamic.js"></script>
<script src="/myjs/RGraph/RGraph.semicircularprogress.js"></script>

<script>
    var value=0;

    (function(scope){
        scope.$watch('msg', function(msg) {
               if (typeof(msg.payload) != "undefined") { ssp.value=msg.payload; ssp.grow(); }
        });
  
})(scope);

this.scope.action = function() {
    return value;
}
    
    var ssp = new RGraph.SemiCircularProgress({
        id: 'newgauge1',
        min: 0,
        max: 100,
        value: 86,
        options: {
            gutterTop: 2,
            gutterLeft: 20,
            gutterRight: 5,
            gutterBottom: 70,
            unitsPost: '°C',
            labelsCenterSize: 40,
            labelsCenterValign: 'center',
            labelsMinSize: 10,
            labelsMinOffsetAngle:-0.1,
            labelsMinOffsetx:0,
            labelsMinOffsety:0,
            labelsMaxSize: 10,
            labelsMaxOffsetAngle:0.1,
            labelsMaxOffsetx:0,
            labelsMaxOffsety:0,
            adjustable: true,
            textAccessiblePointerevents: false,
            colors: ['Gradient(#224499:white)'],
            anglesStart: RGraph.PI - 0.5,
            anglesEnd: RGraph.TWOPI + 0.5
        }
    }).grow();
  
ssp.canvas.onmouseup = function (e)
{
    var obj   = e.target.__object__;
    value=obj.value;
}  
 </script>

<canvas ng-mouseup="send({payload:action()})" class="knob" id="newgauge1" width="300" height="200">
    [No canvas support]
</canvas>

The actual flow for copying into Node-Red:

[{"id":"3cbe837a.faf1dc","type":"ui_template","z":"c552e8d2.712b48","group":"33279d5b.72b122","name":"controller","order":0,"width":"6","height":"4","format":"<script src=\"/myjs/RGraph/RGraph.common.core.js\"></script>\n<script src=\"/myjs/RGraph/RGraph.common.dynamic.js\"></script>\n<script src=\"/myjs/RGraph/RGraph.semicircularprogress.js\"></script>\n\n<script>\n    var value=0;\n\n    (function(scope){\n        scope.$watch('msg', function(msg) {\n               if (typeof(msg.payload) != \"undefined\") { ssp.value=msg.payload; ssp.grow(); }\n        });\n  \n})(scope);\n\nthis.scope.action = function() {\n    return value;\n}\n    \n    var ssp = new RGraph.SemiCircularProgress({\n        id: 'newgauge1',\n        min: 0,\n        max: 100,\n        value: 86,\n        options: {\n            gutterTop: 2,\n            gutterLeft: 20,\n            gutterRight: 5,\n            gutterBottom: 70,\n            unitsPost: '°C',\n            labelsCenterSize: 40,\n            labelsCenterValign: 'center',\n            labelsMinSize: 10,\n            labelsMinOffsetAngle:-0.1,\n            labelsMinOffsetx:0,\n            labelsMinOffsety:0,\n            labelsMaxSize: 10,\n            labelsMaxOffsetAngle:0.1,\n            labelsMaxOffsetx:0,\n            labelsMaxOffsety:0,\n            adjustable: true,\n            textAccessiblePointerevents: false,\n            colors: ['Gradient(#224499:white)'],\n            anglesStart: RGraph.PI - 0.5,\n            anglesEnd: RGraph.TWOPI + 0.5\n        }\n    }).grow();\n  \nssp.canvas.onmouseup = function (e)\n{\n    var obj   = e.target.__object__;\n    value=obj.value;\n}  \n </script>\n\n<canvas ng-mouseup=\"send({payload:action()})\" class=\"knob\" id=\"newgauge1\" width=\"300\" height=\"200\">\n    [No canvas support]\n</canvas>","storeOutMessages":true,"fwdInMessages":false,"x":940,"y":1880,"wires":[["5ea77ad6.1b8064","8dc0930c.a5aaf"]]},{"id":"ecf3d231.ddba3","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"40","payloadType":"str","repeat":"","crontab":"","once":false,"x":770,"y":1880,"wires":[["8dc0930c.a5aaf"]]},{"id":"d9c0b1d6.35dbb","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"80","payloadType":"str","repeat":"","crontab":"","once":false,"x":770,"y":1920,"wires":[["8dc0930c.a5aaf"]]},{"id":"5ea77ad6.1b8064","type":"debug","z":"c552e8d2.712b48","name":"","active":true,"console":"false","complete":"false","x":1130,"y":1880,"wires":[]},{"id":"8dc0930c.a5aaf","type":"function","z":"c552e8d2.712b48","name":"pass thru","func":"node.status({fill:\"blue\",shape:\"dot\",text:msg.payload});\n\nreturn msg;","outputs":1,"noerr":0,"x":940,"y":1940,"wires":[["3cbe837a.faf1dc"]]},{"id":"33279d5b.72b122","type":"ui_group","z":"","name":"LCD Test","tab":"34cddaf3.8a9cd6","disp":true,"width":"6"},{"id":"34cddaf3.8a9cd6","type":"ui_tab","z":"","name":"testz","icon":"dashboard"}]
Facebooktwittergoogle_pluspinterestlinkedin

Another LCD

LCD colours by Peter ScargillWhen writing that last blog entry about an LCD with time and date display and programmable temperature for Node-Red Dashboard, it occurred to me that it would be worthwhile making a simple one-liner (6*1) LCD with a 20-character display. At the same time I wanted to simplify it right down and here’s the result. Should make a good starting point for anyone wanting to develop their own.

Flow

In this case you simply fire a message at the template node. You only need one font for this from the ones used in the previous entry (really you should read the previous article to understand how to load the fonts in – trivial).

I’ve done a check on msg.payload and if the first character is a period I then use the remainder of the message to select colour – hence…

.blackOnOrange
.blackOnGreen
.blackOnBlue
.blackOnYellow
.blackOnWhite
.blackOnPink
.yellowOnRed
.whiteOnCyan
.orangeOnBlack
.limeOnBlack

And that really is it – really simple to use – make the template 6*1,insert the code, make sure the font is in place and when you run it – you can select the sample text or play with the colours – enjoy.

LCD colours by Peter Scargill

LCD colours by Peter Scargill

LCD colours by Peter Scargill

[{"id":"7744e0e4.38986","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"Hello there","payloadType":"str","repeat":"","crontab":"","once":false,"x":800,"y":1180,"wires":[["864d97a5.30d348"]]},{"id":"543d4cba.38a7a4","type":"inject","z":"c552e8d2.712b48","name":"green","topic":"","payload":".blackOnGreen","payloadType":"str","repeat":"","crontab":"","once":false,"x":790,"y":1220,"wires":[["864d97a5.30d348"]]},{"id":"658a2f15.d95bc","type":"inject","z":"c552e8d2.712b48","name":"orange","topic":"","payload":".blackOnOrange","payloadType":"str","repeat":"","crontab":"","once":false,"x":790,"y":1260,"wires":[["864d97a5.30d348"]]},{"id":"b60e3a3f.931078","type":"inject","z":"c552e8d2.712b48","name":"blue","topic":"","payload":".blackOnBlue","payloadType":"str","repeat":"","crontab":"","once":false,"x":790,"y":1300,"wires":[["864d97a5.30d348"]]},{"id":"62767df.a99d984","type":"inject","z":"c552e8d2.712b48","name":"yellow","topic":"","payload":".blackOnYellow","payloadType":"str","repeat":"","crontab":"","once":false,"x":790,"y":1340,"wires":[["864d97a5.30d348"]]},{"id":"ad983ec0.bd2ed","type":"inject","z":"c552e8d2.712b48","name":"white","topic":"","payload":".blackOnWhite","payloadType":"str","repeat":"","crontab":"","once":false,"x":790,"y":1380,"wires":[["864d97a5.30d348"]]},{"id":"a046dc0e.953ea","type":"inject","z":"c552e8d2.712b48","name":"pink","topic":"","payload":".blackOnPink","payloadType":"str","repeat":"","crontab":"","once":false,"x":790,"y":1420,"wires":[["864d97a5.30d348"]]},{"id":"8372ff69.e0a4c","type":"inject","z":"c552e8d2.712b48","name":"red and light text","topic":"","payload":".yellowOnRed","payloadType":"str","repeat":"","crontab":"","once":false,"x":820,"y":1460,"wires":[["864d97a5.30d348"]]},{"id":"5fcd3b1d.a64954","type":"inject","z":"c552e8d2.712b48","name":"cyan and white","topic":"","payload":".whiteOnCyan","payloadType":"str","repeat":"","crontab":"","once":false,"x":820,"y":1500,"wires":[["864d97a5.30d348"]]},{"id":"a81aab9f.8f78a8","type":"inject","z":"c552e8d2.712b48","name":"lime on black","topic":"","payload":".limeOnBlack","payloadType":"str","repeat":"","crontab":"","once":false,"x":810,"y":1540,"wires":[["864d97a5.30d348"]]},{"id":"864d97a5.30d348","type":"ui_template","z":"c552e8d2.712b48","group":"33279d5b.72b122","name":"LCD","order":0,"width":"6","height":"1","format":"<script>\n    var LCDColours={\n    \"blackOnOrange\": {items:[\"#222\",\"#fb7c00\"]},    \n    \"blackOnGreen\" : {items:[\"#222\",\"#66ac66\"]},\n    \"blackOnBlue\" : {items:[\"#222\",\"#8888ff\"]},\n    \"blackOnYellow\" : {items:[\"#222\",\"#bbbb44\"]},\n    \"blackOnWhite\" : {items:[\"#222\",\"#aaaaaa\"]},\n    \"blackOnPink\" : {items:[\"#222\",\"#ff8888\"]},\n    \"yellowOnRed\" : {items:[\"#ccaa22\",\"#aa2222\"]},\n    \"whiteOnCyan\" : {items:[\"#dddddd\",\"#227777\"]},\n    \"orangeOnBlack\" : {items:[\"#ff8800\",\"#000000\"]},  \n    \"limeOnBlack\" : {items:[\"#00cc55\",\"#000000\"]} \n    } ;\n\n   (function(scope){ \n            scope.$watch('msg', function(msg) {\n                if (typeof(msg.payload) != \"undefined\") \n                        {\n                            if (msg.payload.substring(0,1)!=\".\") $(\"#LCDTextBody\").text(msg.payload);  \n                            else\n                            {\n                              msg.payload=msg.payload.substring(1);\n                              $(\".LCDWrapper\").css('background-color', LCDColours[msg.payload].items[1]);  $(\".LCDTextFront\").css('color', LCDColours[msg.payload].items[0]); \n                              if (LCDColours[msg.payload].items[1]==\"#000000\") $(\".LCDTextBack\").css('color',\"rgba(255,255,255,0.15)\"); else  $(\".LCDTextBack\").css('color',\"rgba(0,0,0,0.1)\");                                \n                                \n                            }\n                        }   \n            });\n    })(scope);\n\n</script>\n\n<style type=\"text/css\">\n@font-face {\n  font-family: \"D14MI\";\n  src: url(\"/myfonts/DSEG14Modern-Italic.woff\") format('woff');\n}\n\n\n.LCDWrapper{\n\tposition:relative;\n\tborder:3px solid #000;\n\tborder-radius:8px;\n\theight:66px;\n\twidth:304px;\n\tcolor: 0;\n    font-family: \"D14MI\";\n\tbackground-color:#66ac66;\n\tbox-shadow: 3px 3px 10px 0px rgba(0,0,0,0.3) inset; \n}\n\n.LCDTextBack{\n\tz-index:50; color:rgba(0,0,0,0.1); \n}\n\n.LCDTextFront{\n\tz-index:51; color:rgba(0,0,0,1);\n}\n\n.LCDTextBack,.LCDTextFront {\n   \tposition:absolute;\n\ttop:3px;\n\tleft:6px; \n\tfont-size:18px;\n}\n\n</style>\n\n<div class=\"LCDWrapper\">\n\t<span class=\"LCDTextBack\">~~~~~~~~~~~~~~~~~~~~</span>\n\t<span class=\"LCDTextFront\" id=\"LCDTextBody\" ></span>\n\n</div>\n","storeOutMessages":false,"fwdInMessages":false,"x":990,"y":1320,"wires":[[]]},{"id":"8e623ee.81f9bc","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"Goodbye","payloadType":"str","repeat":"","crontab":"","once":false,"x":800,"y":1140,"wires":[["864d97a5.30d348"]]},{"id":"33279d5b.72b122","type":"ui_group","z":"","name":"LCD Test","tab":"34cddaf3.8a9cd6","disp":true,"width":"6"},{"id":"34cddaf3.8a9cd6","type":"ui_tab","z":"","name":"testz","icon":"dashboard"}]
Facebooktwittergoogle_pluspinterestlinkedin

LCD Display for Node-Red

Pete's LCDAs must be obvious by now, I’m on a roll here. Having decided that Node-Red Dashboard is the way forward ( would in the past have used BLYNK or Imperihome) I’ve been getting to grips with the dashboard and as you’ll see if you check recent blogs I’ve had a fair bit of success with various CANVAS based libraries, getting them into the Dashboard.

Pete's LCDAnd so it was that over the weekend, MrShark contacted me with this link – a very nice, simple stand-along clock display. Well, that fired me up and so I grabbed the code and started ripping it apart.  One thing led to another and here we have a nice LCD display programmable in a wide range of (I have to say stunning) colours and able to display temperature or weather icons on the right  – and in the process I learned a few things such as what position-absolute is all about and a great deal about transparency.

Pete's LCDIn the display here you see the use of border shadows, 7-segment display fonts and transparency to give the impression of an old-fashioned LCD display – I think it does a nice job.  The code here displays the time, date and day automatically and you can inject temperature into it – but a brief look and you’ll soon realise that you could put just about anything in there – so I look forward to feedback from readers.  To make this work, from the link above I had to grab 3 fonts (DSEG7Modern-Italic.woff,  DSEG14Modern-Italic.woff and DSEG7Modern-BoldItalic.woff – also required is  DSEGWeather.woff) from the link above (into myfonts – see previous blogs for the reason I made up directory names like myfonts and mycss etc).

Pete's LCDSo – here is the code to put into a Dashboard template – you just need to inject msg.payload to set the temperature OR weather icons depending on mode – see flow – most of this is just for you to experiment with.

tmp484EThere are several spans in here – all position absolute – which means they overlap and are all relative to the top left of the main DIV – the trick with the LCD is simply the 8 figure semi-transparent – with you number or letter sitting on top of it. Works a treat I should say.  You’ll want a 6*2 size for the template… but of course you may choose to resize this completely.

flow

tmpF88BAn important item include the z-index, I’ve used 50 and 51 arbitrarily – the original code this all came from used 100 and that ended up over-writing the Node-Red Dashboard menu! Of course if you don’t want to show temperature you could make that humidity or anything else really.

tmpA42CInjecting weather – see flow – includes: characters “0” to “9” for; background all segments, sun, cloud, rain, hard rain, snow, thunder rain, thunder hard rain, thunder, sun and cloud and finally “:” for all off.

Below is the code for the flow - make sure you have those fonts and inject this lot into a page in the dashboard - you'll need to change the tab details to suit yourself.

Oh, this page I found while looking for colour ideas…

[{"id":"5439cd84.a88ae4","type":"ui_template","z":"c552e8d2.712b48","group":"1e03a2b2.83a61d","name":"Time and Temp","order":0,"width":"6","height":"2","format":"<script>\n    var icon=\"T\";\n\n        \n    var colours={\n    \"blackOnOrange\": {items:[\"#222\",\"#fb7c00\"]},    \n    \"blackOnGreen\" : {items:[\"#222\",\"#66ac66\"]},\n    \"blackOnBlue\" : {items:[\"#222\",\"#8888ff\"]},\n    \"blackOnYellow\" : {items:[\"#222\",\"#bbbb44\"]},\n    \"blackOnWhite\" : {items:[\"#222\",\"#aaaaaa\"]},\n    \"blackOnPink\" : {items:[\"#222\",\"#ff8888\"]},\n    \"yellowOnRed\" : {items:[\"#ccaa22\",\"#aa2222\"]},\n    \"whiteOnCyan\" : {items:[\"#dddddd\",\"#227777\"]},\n    \"orangeOnBlack\" : {items:[\"#ff8800\",\"#000000\"]},  \n    \"limeOnBlack\" : {items:[\"#00cc55\",\"#000000\"]},  \n    }  \n    \n    var daylist = [\"sun\", \"mon\", \"tue\", \"wed\", \"thu\", \"fri\", \"sat\"];\n    (function(scope){ \n            scope.$watch('msg', function(msg) {\n               if (typeof(msg.type) != \"undefined\") icon=msg.type;\n    \n               if (icon==\"t\")\n                    {\n                        $(\"#DSEGWEATHER-BACK\").text(\" \");\n                        $(\"#DSEGWEATHER-ICON\").text(\" \");\n                        $(\"#DSEGTempcF\").text(\"C\");\n                        $(\"#DSEGTempcB\").text(\"8\");                        \n                        if (typeof(msg.payload) != \"undefined\") { $(\"#DSEGTempF\").text(msg.payload);  $(\"#DSEGTempB\").text(\"88\"); }\n                    }\n                else\n                    {\n                        $(\"#DSEGTempF\").text(\"\");\n                        $(\"#DSEGTempcF\").text(\"\");\n                        $(\"#DSEGTempB\").text(\"\");\n                        $(\"#DSEGTempcB\").text(\"\");\n                        $(\"#DSEGWEATHER-BACK\").text(\"0\");\n                        if (typeof(msg.payload) != \"undefined\") $(\"#DSEGWEATHER-ICON\").text(msg.payload);\n                        \n                    }\n               if (typeof(msg.colour) != \"undefined\") {\n                              $(\".Clock-Wrapper\").css('background-color', colours[msg.colour].items[1]);  $(\".lcdClock\").css('color', colours[msg.colour].items[0]); \n                              if (colours[msg.colour].items[1]==\"#000000\") $(\".background\").css('color',\"rgba(255,255,255,0.15)\"); else  $(\".background\").css('color',\"rgba(0,0,0,0.1)\");\n                        }\n            });\n    })(scope);\n\n    function genTimerStrings(tm, num){\n    \n    \tvar i;\n    \tvar ret = tm.toString(10);\n    \tvar left = ret.length;\n    \n    \tif( left < num){\n    \t\tfor(i=0; i<( num - left ); i++ ){\n    \t\t\tret = String(0) + ret;\n    \t\t}\n    \t}\n    \treturn ret;\n    }\n\n    function updateTimer(){\n    \tvar ret;\n    \tvar date = new Date();\n    \tvar tm_year, tm_mon, tm_date, tm_hour, tm_min, tm_sec, tm_msec,tm_day;\n    \tvar colon;\n    \ttm_year = date.getFullYear();\n    \ttm_mon = date.getMonth()+1;\n    \ttm_date = date.getDate();\n    \ttm_day = date.getDay();\n    \ttm_hour = date.getHours();\n    \ttm_min = date.getMinutes();\n    \ttm_sec = date.getSeconds();\n    \ttm_msec = date.getMilliseconds();\n    \n    \ttm_mon = genTimerStrings(tm_mon, 2);\n    \ttm_date = genTimerStrings(tm_date, 2);\n    \ttm_hour = genTimerStrings(tm_hour, 2);\n    \ttm_min = genTimerStrings(tm_min, 2);\n    \ttm_sec = genTimerStrings(tm_sec, 2);\n    \ttm_day = daylist[tm_day];\n    \n    \tif( tm_msec > 499 ){\n    \t\tcolon = ' ';\n    \t}else{\n    \t\tcolon = ':';\n    \t}\n    \n    \tdocument.getElementById(\"DSEGClock\").innerHTML = tm_hour + colon + tm_min + \"<span style=\\\"font-size:30px;\\\">\"  + tm_sec + \"</span>\";\n    \tdocument.getElementById(\"DSEGClock-Year\").innerHTML = \"<span class=\\\"D7MI\\\">\" + tm_year + \"-\" + tm_mon + \"-\" + tm_date + ' ' + \"</span><span class=\\\"D14MI\\\">\" + tm_day  +  \".\" + \"</span>\";\n    \n    \tsetTimeout(\"updateTimer()\", 500 - date.getMilliseconds()%500 );\n    }\n\n    updateTimer();\n    \n</script>\n\n<style type=\"text/css\">\n.lcdClock {\n\tbackground-color:#fbfbfb;\n\tfont-size:100%;\n\tpadding-left:10px;\n\tpadding-right:10px;\n\tpadding-bottom:10px;\n\tmax-width:300px;\n\tline-height:160%;\n\tcolor:#222;\n\tfont-family:Meiryo, 'Lucida Grande','Hiragino Kaku Gothic ProN', sans-serif;\n}\n\n@font-face {\n  font-family: \"D7MI\";\n  src: url(\"/myfonts/DSEG7Modern-Italic.woff\") format('woff');\n}\n\n@font-face {\n  font-family: \"D14MI\";\n  src: url(\"/myfonts/DSEG14Modern-Italic.woff\") format('woff');\n}\n\n@font-face {\n  font-family: \"D7MBI\";\n  src: url(\"/myfonts/DSEG7Modern-BoldItalic.woff\") format('woff');\n}\n\n@font-face {\n  font-family: \"DWEATHER\";\n  src: url(\"/myfonts/DSEGWeather.woff\") format('woff');\n}\n\n.Weather-Background{\n\tz-index:50;\n\tposition:absolute;\n\ttop:24px;\n\tleft:226px;\n\tfont-size:68px;\n}\n\n.Weather-Front{\n\tz-index:51;\n\tposition:absolute;\n\ttop:24px;\n\tleft:226px;\n\tfont-size:68px;\n}\n\n.D7MI {\nfont-family: \"D7MI\";\n}\n\n.D7MBI {\nfont-family: \"D7MBI\";\n}\n\n.D14MI {\nfont-family: \"D14MI\";\n}\n\n.DWEATHER {\nfont-family: \"DWEATHER\";\nfont-size:68px;\nheight:68px;\n}\n\n.Clock-Wrapper{\n\tposition:relative;\n\tborder:6px solid #000;\n\tborder-radius:9px;\n\theight:68px;\n\twidth:280px;\n\tbackground-color:#fb7c00;\n\tbackground-color:#66ac66;\n\tbox-shadow: 4px 4px 28px 0px rgba(0,0,0,0.3) inset; \n}\n\n.Clock-Time-Background{\n\tz-index:50;\n}\n\n.Clock-Time-Front{\n\tz-index:51;\n}\n\n.Clock-Time-Background,.Clock-Time-Front {\n   \tposition:absolute;\n\ttop:38px;\n\tleft:5px; \n\tfont-size:42px;\n}\n\n.background { color:rgba(0,0,0,0.1); }\n\n.Clock-Year-Background{\n\tz-index:50;\n\tfont-size:18px;\n}\n\n.Clock-Year-Front{\n\tz-index:51;\n}\n\n.Clock-Year-Background,.Clock-Year-Front {\n   \tposition:absolute;\n\ttop:2px;\n\tleft:5px; \n\tfont-size:18px;\n}\n\n.temp { z-index:51; }\n.tempBack { z-index:50; color:rgba(0,0,0,0.1); }\n.temp,.tempBack {\n   \tposition:absolute;\n\ttop:28px;\n\tleft:210px; \n\tfont-size:42px;\n}\n\n.tempc { z-index:51; }\n.tempcBack { z-index:50;  }\n.tempc,.tempcBack {\n   \tposition:absolute;\n\ttop:36px;\n\tleft:278px; \n\tfont-size:24px;\n}\n\n#DSEG7_OUTPUT{\n\tfont-family: \"D7MI\";\n}\n\n#DSEG14_OUTPUT{\n\tfont-family: \"D14MI\";\n}\n\n#DSEG14_OUTPUT, #DSEG7_OUTPUT{\n\tfont-size:18px;\n\tmargin-top:2px;\n\tmargin-bottom:10px;\n}\n\n</style>\n\n\n<div class=\"Clock-Wrapper center lcdClock\">\n\t<span class=\"Clock-Time-Background background  D7MBI\">88:88<span style=\"font-size:30px;\">88</span></span>\n\t<span id=\"DSEGClock\" class=\"Clock-Time-Front D7MBI\"></span>\n\t<span class=\"Clock-Year-Background background\"><span class=\"D7MI\">2088-88-88</span><span class=\"D14MI\"> ~~~</span></span>\n\t<span id=\"DSEGClock-Year\" class=\"Clock-Year-Front\"></span>\n\t\n\t<span id=\"DSEGTempF\" class=\"temp D7MBI\">00</span>\n\t<span id=\"DSEGTempB\" class=\"tempBack background D7MBI\">88</span>\t\n\t<span id=\"DSEGTempcF\" class=\"tempc D7MI\">C</span>\n\t<span id=\"DSEGTempcB\" class=\"tempcBack background D7MI\">8</span>\n\t\n\t\n\t<span id=\"DSEGWEATHER-BACK\" class=\"Weather-Background background DWEATHER\"></span>\n\t<span id=\"DSEGWEATHER-ICON\" class=\"Weather-Front DWEATHER\"></span>\n\t\t\n</div>\n\n","storeOutMessages":true,"fwdInMessages":false,"x":480,"y":1480,"wires":[[]]},{"id":"e26a920b.fd916","type":"inject","z":"c552e8d2.712b48","name":"21c","topic":"","payload":"21","payloadType":"num","repeat":"","crontab":"","once":false,"x":90,"y":1220,"wires":[["5439cd84.a88ae4"]]},{"id":"a92b9b7a.795228","type":"inject","z":"c552e8d2.712b48","name":"32c","topic":"","payload":"32","payloadType":"num","repeat":"","crontab":"","once":false,"x":90,"y":1260,"wires":[["5439cd84.a88ae4"]]},{"id":"6d378317.dd743c","type":"inject","z":"c552e8d2.712b48","name":"green","topic":"","payload":"blackOnGreen","payloadType":"str","repeat":"","crontab":"","once":false,"x":90,"y":1400,"wires":[["a23865de.5f59b8"]]},{"id":"24a314bf.90927c","type":"inject","z":"c552e8d2.712b48","name":"orange","topic":"","payload":"blackOnOrange","payloadType":"str","repeat":"","crontab":"","once":false,"x":90,"y":1440,"wires":[["a23865de.5f59b8"]]},{"id":"a23865de.5f59b8","type":"function","z":"c552e8d2.712b48","name":"","func":"msg.colour=msg.payload;\nmsg.payload=undefined;\nreturn msg;","outputs":1,"noerr":0,"x":270,"y":1480,"wires":[["5439cd84.a88ae4"]]},{"id":"c8edfc9b.be6ce","type":"inject","z":"c552e8d2.712b48","name":"blue","topic":"","payload":"blackOnBlue","payloadType":"str","repeat":"","crontab":"","once":false,"x":90,"y":1480,"wires":[["a23865de.5f59b8"]]},{"id":"18a1c3a.b9c283c","type":"inject","z":"c552e8d2.712b48","name":"yellow","topic":"","payload":"blackOnYellow","payloadType":"str","repeat":"","crontab":"","once":false,"x":90,"y":1520,"wires":[["a23865de.5f59b8"]]},{"id":"a5c4dd24.4b6ff","type":"inject","z":"c552e8d2.712b48","name":"white","topic":"","payload":"blackOnWhite","payloadType":"str","repeat":"","crontab":"","once":false,"x":90,"y":1560,"wires":[["a23865de.5f59b8"]]},{"id":"83e6ddc0.ec7bd","type":"inject","z":"c552e8d2.712b48","name":"pink","topic":"","payload":"blackOnPink","payloadType":"str","repeat":"","crontab":"","once":false,"x":90,"y":1600,"wires":[["a23865de.5f59b8"]]},{"id":"ebfd33d8.c8b13","type":"inject","z":"c552e8d2.712b48","name":"red and light text","topic":"","payload":"yellowOnRed","payloadType":"str","repeat":"","crontab":"","once":false,"x":120,"y":1640,"wires":[["a23865de.5f59b8"]]},{"id":"26972f6.8af94d","type":"inject","z":"c552e8d2.712b48","name":"cyan and white","topic":"","payload":"whiteOnCyan","payloadType":"str","repeat":"","crontab":"","once":false,"x":120,"y":1680,"wires":[["a23865de.5f59b8"]]},{"id":"10449ff5.d9588","type":"inject","z":"c552e8d2.712b48","name":"Type t for TEXT","topic":"","payload":"t","payloadType":"str","repeat":"","crontab":"","once":false,"x":120,"y":1760,"wires":[["fe927255.d2e2a"]]},{"id":"3e44785a.6181b8","type":"inject","z":"c552e8d2.712b48","name":"Type w for weather","topic":"","payload":"w","payloadType":"str","repeat":"","crontab":"","once":false,"x":130,"y":1800,"wires":[["fe927255.d2e2a"]]},{"id":"fe927255.d2e2a","type":"function","z":"c552e8d2.712b48","name":"","func":"msg.type=msg.payload;\nmsg.payload=undefined;\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":1740,"wires":[["5439cd84.a88ae4"]]},{"id":"94706c5b.a9292","type":"inject","z":"c552e8d2.712b48","name":"weather 2","topic":"","payload":"2","payloadType":"str","repeat":"","crontab":"","once":false,"x":100,"y":1299,"wires":[["5439cd84.a88ae4"]]},{"id":"77b95bb8.1ce774","type":"inject","z":"c552e8d2.712b48","name":"weather 3","topic":"","payload":"3","payloadType":"str","repeat":"","crontab":"","once":false,"x":100,"y":1339,"wires":[["5439cd84.a88ae4"]]},{"id":"30c75186.ef732e","type":"inject","z":"c552e8d2.712b48","name":"lime on black","topic":"","payload":"limeOnBlack","payloadType":"str","repeat":"","crontab":"","once":false,"x":110,"y":1720,"wires":[["a23865de.5f59b8"]]},{"id":"1e03a2b2.83a61d","type":"ui_group","z":"","name":"testy","tab":"f9bab960.c839b8","disp":true,"width":"6"},{"id":"f9bab960.c839b8","type":"ui_tab","z":"","name":"testy","icon":"dashboard"}]
Facebooktwittergoogle_pluspinterestlinkedin

RGraph with Node-Red

RGraph GaugeHaving gone from spending countless hours staring at HTML5 CANVAS, I’m now at the “meh” stage as it starts to dawn on my how it works.

And so it was that I stumbled on RGraph – or put another way, Christmas for widget-lovers.

If you’ve been following these blog entries you’ll know that Node-Red has TEMPLATES in the UI – and that you can put your own stuff into the templates and that recently the fog has lifted on getting variables in and out of the templates.

In recent blogs I’ve been constantly improving a thermostat control page and that took me off looking for a gauge with two pointers – one to show temperature, the other to show humidity.

And that’s when I stumbled upon RGraph. If you read this – and understand it – you will open the doors to a boatload of gauges, thermometers, charts and graphs so tuck in:

First things first, if you’ve already played with Node-Red in here you will likely have made a /myjs folder (home/pi/.node-red/public/myjs or similar – defined in your Node-Red settings.js file) to put various Javascript files in. Well, add this lot in a sub-folder called RGraph – you can call it freddy if you like but I thought it reasonable to use the name the way they use it. I grabbed the latest stable version from here. Inside there is a folder called RGraph – and inside that is a folder called libraries – I grabbed the contents of that folder and put it inside my /public/RGraph folder. It may be there is a use for other stuff in there – but for now that’s all I’ve taken.

So – then I dropped in a template – made it 6*6 and inside that template I put this lot – code shown below.

Now, if you don’t like my colours – change them. You can change just about anything including the size but you may need to adjust the font size if you do that. Experiment!

To change the two pointers – which I’ve chosen to call temperature and humidity – you might use them for petrol and oil – or whatever….I simply pass MSG as is common in Node-Red – but not msg.template – instead msg.temperature and msg.humidity – you can call them whatever you like.

The point of this is not to demonstrate my crap taste in colours – once you follow what I’ve done here – that entire, massive library of CANVAS-related gauges and charts is yours for the taking!  You can make the gauges interactive – but as I had two, not one pointers in this example, I skipped that. Details are in the extensive RGraph documentation.  Copy me and drop them an encouraging lines to say MORE IOT PLEASE!!

Oh and if you don’t like animation – where I say “grow” say “draw”.

(As an aside, I got this working today as well - https://www.codeproject.com/Articles/304874/HTML-Canvas-Aqua-Gauge)  very pretty but doesn’t scale well.

Prerequisites: Far too often in blogs like this we “assume” that everyone is keeping up – if not – may I suggest a quick look at this page I put up specifically to give a little background – which might help explain this article.

<script src="/myjs/RGraph/RGraph.common.core.js" ></script>
<script src="/myjs/RGraph/RGraph.gauge.js" ></script>

<script language="javascript" type="text/javascript">

           (function(scope){ 
                scope.$watch('msg', function(msg) {
                   gauge3.value=[msg.temperature,msg.humidity];
                   gauge3.grow();
                });
            })(scope);
            
            var gauge3 = new RGraph.Gauge({
                id: 'cvs',
                min: 0,
                max: 100,
                value: [23,60],
                options: {
                    titleTop: 'Temperature',
                    titleTopSize: '16',
                    titleTopFont: 'Impact',
                    titleTopColor: '#ff8888',
                    titleTopPos: 0.25,
                    titleBottom: 'Humidity',
                    titleBottomSize: '14',
                    titleBottomFont: 'Impact',
                    titleBottomColor: '#8888ff',
                    titleBottomPos: 0.3,
                    backgroundColor: 'black',
                    backgroundGradient: true,
                    centerpinColor: '#666',
                    needleSize: [null, 50],
                    needleColors: ['Gradient(transparent:yellow:orange:#ff8888:#ff8888)', 
                                    'Gradient(transparent:cyan:green:blue:blue)'],
                    textColor: 'white',
                    tickmarksBigColor: 'white',
                    tickmarksMediumColor: 'white',
                    tickmarksSmallColor: 'white',
                    borderWidth: 1,
                    borderOuter: '#666',
                    borderInner: '#3333',
                    colorsRanges: [
                                    [0,10,'rgba(0,0,255,0.6)'], 
                                    [10,20,'rgba(0,128,255,0.6)'], 
                                    [20,30,'rgba(0,255,255,0.6)'], 
                                    [30,40,'rgba(0,255,128,0.6)'], 
                                    [40,50,'rgba(0,255,0,0.6)'], 
                                    [50,60,'rgba(128,255,0,0.6)'], 
                                    [60,70,'rgba(255,255,0,0.6)'], 
                                    [70,80,'rgba(255,128,0,0.6)'],                                                                                                                                                 
                                    [80,90,'rgba(255,64,0,0.6)'],   
                                    [90,100,'rgba(255,0,0,1']
                                  ],
                    textAccessible: true
                }
            });
            gauge3.grow();

    </script>

    <canvas id="cvs" width="300" height="300">[No canvas support]</canvas>
 
Facebooktwittergoogle_pluspinterestlinkedin