Category Archives: node-red

Big Timer

tmp17DBBig Timer is probably the best timing node for Node-Red, providing a general purpose timer as well as  handling summer/winter correctly as well as (importantly) lighting up time (for which it needs longitude and latitude). After all you probably don’t turn the outside lights on at 6pm!! You turn them on when it gets DARK.

New: “toggle” mode. Now with new seconds timer mode AND updated for the latest Node-Red 0.17.5+ including new help formatting and tips on input and output.

Continue reading Big Timer

Facebooktwittergoogle_pluspinterestlinkedin

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

A Fine Sunny Weekend

Scargill's Man-CabinEvening all, it has been a busy weekend here at the Scargill’s man-cabin. No Friday, frequent blog-visitor JAY popped over for a visit and we spend some time discussing new stuff:

First on the list was a new toy Jay just picked up on offer at Maplin – a “SmartSensor Energy Egg” solo pack which comprises a PIR Egg and a mains socket. The two operate on 433Mhz and allow for turning a light on for a predetermined time when someone moves in the room. Simple enough – but At under £8 on offer I guess Jay could not refuse. I have one sitting in front of me now looking for a home in my office. There is a website – energy-egg.com but I advise against it – half of the links go no-where. Anyway – special offer – Maplin.

The gadgets are meant to run stand-alone but of course we had to try it out with the RFLINK 433Mhz setup and sure enough – the RFLINK registers activity in the PIR.

Turning the PIR on and off with the handy button on the top produced this output.

"20;31;Eurodomest;ID=0eec5a;SWITCH=05;CMD=ALLON;"

"20;30;Eurodomest;ID=0eec5a;SWITCH=01;CMD=OFF;"

ChatbotEnergy EggStrangely the effect was the opposite of what you might expect – the ALLON command turned the light off!! Anyway the upshot being we can easily monitor this device along with others using the RFLINK.  Sadly when we tried to turn this into a COMMAND to turn the light on and off from the RFLINK unit, while we could turn the device ON, the OFF command stubbornly refused to work – but we started sending emails off to Frank Zirrone of RFLink software fame – and he was very helpful – we’ve now send some diagnostics off (this all comes in the software folder for RFLink) and he’s taking a look.

Meanwhile we discovered a potential issue with the playground covered earlier in this blog – the mobile phone as server. I’ve been using TASKER on the phone to pick up MQTT messages from the Debian installation (to in turn play a really nice doorbell sound) – using the MQTTCLIENT Tasker Plug-in App which you might recall replaced it’s somewhat ill-fated predecessor. Well, it seems it is still not out of the woods.

ChatbotWhile it works GREAT, if you reboot the phone, while everything else comes up perfectly including Debian itself, Tasker and, well, yes, everything – the MQTT plugin – would you believe does NOT re-connect with the MQTT broker until you open the app.  Now this may be because it is starting before Debian is ready – but you would think that such a program designed to run in the background, would try, try, try again until it got through. We even tried using Tasker to initialise it – no joy.  So until the author comes back, we’re on the hunt for alternatives – and it is possible that TELEGRAM may be it  - Chatbotexcept having installed it on my phone (easy) and on my PC (easy) and installed node-red-contrib-chatbot on Node-Red - with a wopping 37 nodes  - I'm now utterly confused. Jay helped me set up a bot and send a message to it - but getting it to arbitrarily send a message... I'm not quite there yet - and what you're supposed to do with all those OTHER nodes....

ChatbotThere is a TELEGRAM app for Android, a plug in for Node-Red and in fact it comes on just about all platforms – this is similar to other messaging apps in that you can send text message back and forth – so the hope is that we can send messages between Node-Red on Debian – and the Android Tasker reliably – more on that later.

Anyone have any experience of using TELEGRAM? I swear this could be the death of me.  A video showing the various nodes in action would be nice!

 

I’ve fitted some door sensors 433Mhz model DC55 from Friedland – comprising a magnet and a reed-switch-triggered 4533Mhz transmitter – all very nice and small – they work a treat with RFLink but right now I’m looking for the meaning of the codes – clearly I figured out the ones for open and close – and there’s another when an internal spring is released – this is for security – but there is a 4th code which comes out very occasionally – and I have no idea what it is for. Maybe “battery ok” – maybe “battery shot” – no way to tell and their manuals don’t seem to help.  Any ideas anyone?

Finally – the script – just updated it to handle that Node-Red initialisation we discussed in a previous blog entry.

Hope you had a nice weekend.

Facebooktwittergoogle_pluspinterestlinkedin

Node Red Initialisation

There have been various conversations about Node-Red initialisation of variables recently. Here’s a solution.

So firstly – what’s the problem? Well, because of the asynchronous nature of Node-Red, it seems to be that sometimes you find yourself using a global variable before it has been defined. No lectures on using global variables please. I can see in my own systems, in the log, incidences of uninitialized variables.

My own solution to the issue of non-volatile storage has been to save global variables as a single object, into a file – and restore said on power up (thermostat settings etc.) – you could store them in MQTT, in a database, anywhere – doesn’t matter. The issue is restoring them.

On power up one assumes the left most tab in Node-Red will run first and that is generally true – but where anything asynchronous is happening – like getting a file for example (hint) there could be delays and other code could run using one of your variables before you’ve actually set it up. This usually does NOT result in Node-Red crashing but you can get rubbish results. Of course you can check every time you use a variable if it exists but that can get messy REAL quick.

A conversation started in the Node-Red discussion groups recently – one of many - and I just latched onto an idea and ran with it.  Of course my first few attempts resulted in suicidal feelings as they failed time after time – but just mid- morning it all started to come together.

So, first things first – the Node-Red settings.js (created when Node-Red is installed and could be for example at /home/pi/.node-red/settings.js)  file runs BEFORE Node-Red comes online – so you know that if you initialise stuff in there, it will be set up in time for you to use – but messing with that file from within Node-Red could be dangerous and you could kill the whole installation. It also  defeats the object of having everything in one nice visual interface.

Here’s another way

Within settings.js there is “functionGlobalContext” which sets up any globals you might need for example – fs object for using files. Here is that section (typically) with one line added into settings.js

functionGlobalContext: {

os:require('os'),

moment:require('moment'),

fs:require('fs'),

mySettings:require("/home/pi/.node-red/redvars.js")

    },

See that last line with “mySettings” -  you can call that what you like.

In (for example) your /home/pi directory, create a simple file called redvars.js (or anything you like really). In there you could put this..

and thanks to Nick O’Leary for the specifics…

module.exports = {

    "fred": 1,

    "brian": 2

}

However, what happens if the redvars.js file is corrupt for any reason – answer? Node-Red crashes..

Here then is a better way:  

At the VERY START of setting.js add this..

var mySettings;

try {

mySettings = require("/home/pi/.node-red/redvars.js");

    } catch(err) {

mySettings = {};

    }

So mySettings is either populated or made empty. No crashing. Now you only need to add ONE line to the functionGlobalContext – here it is in my case… yours might be different…

functionGlobalContext: {

os:require('os'),

moment:require('moment'),

fs:require('fs'),

mySettings:mySettings

    },

As you can see, with an additional comma then one line, a foolproof version of mySettings has been added.

Run Node-Red and you will now find that (for example)  global.get(“mySettings.fred”)  returns 1.  SO – now all you have to do is to put what you need in that file redvars.js – and if you need to update them you can do that within Node-Red. 

In my case I have a variable called counter in there.   Every time I update something, I update that counter to 10;   I have a little timer job running that checks that var and if not zero, decrements it – if it is now zero, it updates the file… see how I did that below.

If you look at my original article on this subject – all seemed well, simply stringify the object and save it to disk – and that’s really how I’d like it to be - but my mechanism for RETRIEVING the object on power-up failed miserably because of the async nature of the file read – often resulting in use of the variables before they existed. This way, that won’t happen and all it needs is to stringify the object and add “module exports-“ at the start.

In the unlikely event that the redvars.file is corrupt, on power-up, mySettings will be undefined – easy to check and if necessary overwrite.

So one simple change on initial setup of Node-Red – and from there on you can create or amend non-volatile global variables from within Node-Red.  You could expand on this of course – that is up to you.

Update 26/03/2017 - This modification is now in the script!

Facebooktwittergoogle_pluspinterestlinkedin

RFLink and Node-Red

RFLink Board and MegaA couple of weeks ago I went to stay with my pal Jonathan and he had some Byron doorbell pushes to play with.

This article updated 22/03/2017

He showed me a universal RF 433Mhz receiver board he purchased which could look at the signals from the various kids of 433Mhz standard transmitting units.

This was interesting as I have an Acurite weather station with the most appalling interface which requires a PC to be on constantly in order to remotely access the information.

I don’t know what planet the designers were on – but it was a gift from my wife, solar powered and including rain level, wind speed and direction etc.. a nice job other than the software. It has been sitting outside the wall on my office for months doing nothing.  I thought it might be interesting to get that going.

RFLink

The unit my friend was using was rather expensive but he suggested I try the RFLink software along with a DIY unit comprising an Arduino Mega2560, a little board called an RFLink V1.1.4 (now updated) and an antenna – you see the lot here.

tmpC75DThe kit arrived days ago and I put it together with a soldering iron easily enough but had to wait for a Mega board to arrive which it did this week. I downloaded the software – very simply install program for the PC – couple of button presses really – and that was that. I plugged the little RF board into the MEGA, plugged the Mega into my PC and… out of the blue, information from my Acurite board appeared out of no-where, as did  more one-liners from my Byron button presses and even our doorbell. It took no time at all with help from a fellow enthusiast who’s used this stuff before to figure out how to send a signal back to the doorbell to make it work.

All very nice but I needed this into Node-Red.

I took my latest Raspberry Pi using DietPi and after adjusting comms permissions, simply plugged the USB device into the Pi and set up a serial node for both transmit and receive.

From there on it was easy. Far from complete - but thought you might be interested – the combination of the two boards and that software seems to work really well up to now for receiving from 433Mhz sensors and for sending out commands to 433Mhz boards.

I had a slight concern about how long you have to leave the Byron SX35 pushbuttons before pressing again (3 seconds). But I  mentioned this to the author and within an hour he came back with an update which made the delay much more practical.

Valid input instructions from the various sensors is in this link along with the Arduino software download….

http://www.nemcon.nl/blog2/protref

I bought a bog-standard Arduino 2560 (cheap Chinese version)

I bought this board… the RFLink 433 kit – requires a little soldering…

https://www.nodo-shop.nl/en/21-rflink-gateway

10 minute soldering (take note of version numbers – important), 10 minutes max to blow the software. Test the board (56k baud) to ensure when nearby sensors are sending results – they are coming in and then I wrote this test… it isn’t very elegant yet.

My SERIAL node is set to split input on character “\n” so the code has to get rid of return characters as you’ll see in the “replace” line below. Note also that when you send out serial (to actually control something) it should be followed by both “\r\n”.

RF433

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

// So firstly a generic means of getting incoming items into an object

var the433 = {};
msg.payload = msg.payload.replace(/(\r\n|\n|\r)/gm,"");
node.warn(msg.payload);
var parts433 = msg.payload.split(";");

the433.p1 = parts433[0];
the433.p2 = parts433[1];
the433.name = parts433[2];

var a = 3;
while (a < parts433.length) {
    var bits433 = parts433[a].split("=");
    switch (bits433[0]) {
        case "ID": the433.id = bits433[1]; break;
        case "SWITCH": the433.switch = bits433[1]; break;
        case "CMD": the433.cmd = bits433[1]; break;
        case "SET_LEVEL": the433.set_level = parseInt(bits433[1], 10); break;
        case "TEMP": the433.temp = parseInt(bits433[1], 16) / 10; break;
        case "HUM": the433.hum = parseInt(bits433[1], 10); break;
        case "BARO": the433.baro = parseInt(bits433[1], 16); break;
        case "HSTATUS": the433.hstatus = parseInt(bits433[1], 10); break;
        case "BFORECAST": the433.bforecast = parseInt(bits433[1], 10); break;
        case "UV": the433.uv = parseInt(bits433[1], 16); break;
        case "LUX": the433.lux = parseInt(bits433[1], 16); break;
        case "BAT": the433.bat = bits433[1]; break;
        case "RAIN": the433.rain = parseInt(bits433[1], 16) / 10; break;
        case "RAIN": the433.rainrate = parseInt(bits433[1], 16) / 10; break;
        case "WINSP": the433.winsp = parseInt(bits433[1], 16) / 10; break;
        case "AWINSP": the433.awinsp = parseInt(bits433[1], 16) / 10; break;
        case "WINGS": the433.wings = parseInt(bits433[1], 16); break;
        case "WINDIR": the433.windir = parseInt(bits433[1], 10); break;
        case "WINCHL": the433.winchl = parseInt(bits433[1], 16); break;
        case "WINTMP": the433.wintmp = parseInt(bits433[1], 16); break;
        case "CHIME": the433.chime = parseInt(bits433[1], 10); break;
        case "SMOKEALERT": the433.smokealert = bits433[1]; break;
        case "PIR": the433.pir = bits433[1]; break;
        case "CO2": the433.co2 = parseInt(bits433[1], 10); break;
        case "SOUND": the433.sound = parseInt(bits433[1], 10); break;
        case "KWATT": the433.kwatt = parseInt(bits433[1], 16); break;
        case "WATT": the433.watt = parseInt(bits433[1], 16); break;
        case "CURRENT": the433.current = parseInt(bits433[1], 10); break;
        case "CURRENT2": the433.current2 = parseInt(bits433[1], 10); break;
        case "CURRENT3": the433.current3 = parseInt(bits433[1], 10); break;
        case "DIST": the433.dist = parseInt(bits433[1], 10); break;
        case "METER": the433.meter = parseInt(bits433[1], 10); break;
        case "VOLT": the433.volt = parseInt(bits433[1], 10); break;
        case "RGBW": the433.rgbc = parseInt(bits433[1].substring(0, 2), 16);
            the433.rgbw = parseInt(bits433[1].substring(2, 4), 16); break;
    }
    a++;
}

// SO - the above is general... here is my specific setup for temporarily displaying
// the Acurite info
if ((the433.p1 == "20") && (the433.name == "Acurite") && (the433.id == "c826")) {
    if (typeof the433.temp !== 'undefined') temp = the433.temp;
    if (typeof the433.hum !== 'undefined') hum = the433.hum;
    if (typeof the433.bat !== 'undefined') bat = the433.bat;
    if (typeof the433.rain !== 'undefined') rain = the433.rain;
    if (typeof the433.winsp !== 'undefined') winsp = the433.winsp;
    if (typeof the433.windir !== 'undefined') windir = the433.windir;

    node.warn("Temperature: " + temp + "c");
    node.warn("Humidity: " + hum + "%");
    node.warn("Battery: " + bat);
    node.warn("Rain: " + rain + "mm");
    node.warn("Wind Speed: " + winsp + "km/h");
    node.warn("Wind Dir: " + (windir * 22.5) + " degrees");
}

[/pcsh]

Put that in a Node-Red template – attach a Node-Red serial Node set to take serial input from USB0 at 56k – character /r as a separator and deliver ascii strings…and that – is just the beginning…  note also that the designer of this free software has also added GPIO control both input and output – on several pins (recently expanded so check his docs).

Tests: Right now for my tests  - I have the K10000 phone acting as a server running Debian and running Tasker and the MQTT client Tasker plugin with the same phone running as a resource…. and I can now fire an MQTT message at the latter to get a doorbell message out! Meanwhile a Raspberry Pi is running that RFLink unit and when one of the Byron doorbell pushes is pressed – a message is sent out to the phone to play the doorbell – yes, I know, somewhat over the top – but I’m just experimenting for now… and sure enough – press the button and pretty much in real time the doorbell sound appears.

433Mhz to MQTT GatewayIn the comments below you’ll see reference to an ESP8266 to MQTT Gateway – and this would be ideal as it would be all in one little box – whereas I need to stick something like a Pi on the end to generate a wireless MQTT signal…. so – I grabbed the software and (disregarding several wasted hours due to a duff FTDI) put together one of these – as you’ll see in the photo on the right-  the antenna is due to kind feedback below – and as you can see, it is a precision job (it is accurate however).

Well, I have a 4-way remote control for a cheap Chinese remote and indeed this little system does pick it up and sends a unique number for each key off as MQTT – lovely – however – even with a decent little aerial the unit does not pick up (or recognise) my weather station of any of my BYRON pushbuttons – and the data coming back is crude compared to the RFLink software so at first glance, not impressed.

Costs:  Ok of course the hardware for the little ESP board is DIRT CHEAP compared to what I’ve put together – which in turn is cheap compared to one of these all in one boxes – but you pay your money – I’m sticking with https://www.nodo-shop.nl/en/21-rflink-gateway – at under £20 plus £9 for the aerial plus a Mega2560 (cheap from China) I think it is worth it (no I don’t know the company and no I didn’t get samples etc).

The transceiver they supplied costs  £16.51 on Ebay so the board with connectors and the transceiver really is a good deal.  You can of course use cheaper receivers – but the software writer suggests these might be naff. I’d like to hear back from someone who had had GOOD results just using the Mega board (they’re only a fiver from China) and other boards. There is information here on that subject. RXB6 board seems cheap but don’t buy from the UK as they seem to be a rip here – on guy wants nearly £8 inc postage – China for same board – under £2 all in. I’m kind of stuck for testing one of these as I’m off to Spain in a few weeks and chances are it won’t get to the UK in time for me leaving!! Shame as I’d like to have given one a shot on my spare MEGA board.

Summary: Already the RFLINK setup has made my day by turning my otherwise useless weather station into another working part of my home control – and I’ll soon have buttons all over the place. It also has good range though I think aerial design and positioning could be improved.  I can’t do a range comparison with the ESP project as it only recognises one my my 433Mhz devices and even then gives out a number, no proper ID and name… still – worth keeping an eye on for future developments.


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

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:

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

<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>

[/pcsh]

The sample flow above

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

[{"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"}]

[/pcsh]

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:

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

<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>

[/pcsh]

The actual flow for copying into Node-Red:

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

[{"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"}]

[/pcsh]

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

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

[{"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"}]

[/pcsh]

Facebooktwittergoogle_pluspinterestlinkedin

SteelSeries and Node-Red

tmp768EYou should read the previous blog entry before this one – as the principles are the same as are some of the details such as the location of your /myjs folder for Node-Red – so read the other article first. This guage IMHO is the best yet!

To run the example on the right here you should get the SteelSeries library. 2 minified JS files is all you need.

See flow below… you will need the following incoming variables – all done for you in the example but of course you’ll want to customise…

msg.value   // the value of the pointer – in this case 0-100

msg.threshold // the value of the red threshold indicator

msg.userLed  // if true the green user LED will be flashing. You can turn this off if you don’t need it. I’ll use it to indicate a dehumidifier is running.

If the value is less than the threshold, the red LED will flash. This can be inverted in the code. If msg.userLed is true the green LED will be flashing.

I’ve ALSO added as you can see separate ODOMETER (msg.odo - you could have LCD instead) and a trending UP/DOWN/STEADY/OFF  indicator and demo controls – discovering what is available on this gauge is half the battle!!

Everything is configurable, I’ve brought out what I think is useful – the rest is buried in steelseries.js which comes with the package – there is no documentation as such. All you need to store in your myjs library is steelseries-min.js and tween-min.js – but is it worth it? SURE IS  - this is a wonderful device… I can’t wait to get one on a nice large display on the wall.

I’m thinking general use to show temperature, odometer could show humidity, green user LED for dehumidifier on, RED light for heating on and the trending indicator to show maybe whether the heat is on the way up or down – though that much should be obvious from whether the heating is on or not… maybe a use I’ve not thought of yet. Or not – you can turn it off if you want. Wonderful.

flow

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.

Looking for help: If anyone knows how to get rid of that chrome frame - DO let me know. frameVisible=false makes it hide but does not expand the gauge to fill in the space -in other words you are still left with an invisible frame - the only article I can find suggests this should not be the case - but it is. Given a small 3*3 frame size I'm after all the resolution I can get).

Here is my code for the example above (also available here):

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

[{"id":"8d862a4b.c1d588","type":"ui_template","z":"c552e8d2.712b48","group":"1e03a2b2.83a61d","name":"rgraph","order":1,"width":"6","height":"6","format":"<script src=\"/myjs/tween-min.js\"></script>\n<script src=\"/myjs/steelseries-min.js\"></script>\n<script>\nvar radial4;\n    (function(scope){ \n        scope.$watch('msg', function(msg) {\n           if (typeof(msg.value) != \"undefined\") radial4.setValueAnimated(msg.value);\n           if (typeof(msg.threshold) != \"undefined\") radial4.setThreshold(msg.threshold);\n           if (typeof(msg.odo) != \"undefined\")radial4.setOdoValue(msg.odo);  \n           if (typeof(msg.userLed) != \"undefined\") radial4.setUserLedOnOff(msg.userLed);  \n           if (typeof(msg.trend) != \"undefined\")\n            {\n                if (msg.trend==1)     radial4.setTrend(steelseries.TrendState.UP);\n                if (msg.trend==0)     radial4.setTrend(steelseries.TrendState.STEADY);\n                if (msg.trend==-1)    radial4.setTrend(steelseries.TrendState.DOWN);\n                if (msg.trend==-2)    radial4.setTrend(steelseries.TrendState.OFF);\n            }\n        });\n    })(scope);\n\n    var sections = [steelseries.Section(0, 25, 'rgba(0, 0, 220, 0.3)'),\n                        steelseries.Section(25, 50, 'rgba(0, 220, 0, 0.3)'),\n                        steelseries.Section(50, 75, 'rgba(220, 220, 0, 0.3)') ],\n\n            // Define one area\n    areas = [steelseries.Section(75, 100, 'rgba(220, 0, 0, 0.3)')],\n\n    radial4 = new steelseries.Radial('canvasRadial4', {\n            gaugeType: steelseries.GaugeType.TYPE4,\n            size: 292,\n            section: sections,\n            area: areas,\n            titleString: \"Heating\",\n            unitString: \"Degrees C\",\n            threshold: 50,\n            thresholdRising: false,\n            userLedVisible: true,\n            useOdometer: true,\n            lcdVisible: true,\n            trendVisible: true\n        });\n                        \n    radial4.setFrameDesign(steelseries.FrameDesign.BLACK_METAL);\n    radial4.setValueAnimated(0);\n    radial4.setThreshold(50);\n    radial4.blinkUserLed(0);\n    radial4.setOdoValue(0);\n\n\n</script>\n\n<canvas id=\"canvasRadial4\" width=\"401\" height=\"401\"></canvas>\n","storeOutMessages":true,"fwdInMessages":false,"x":490,"y":920,"wires":[[]]},{"id":"94d08897.6b6f78","type":"inject","z":"c552e8d2.712b48","name":"Set needle to 88","topic":"","payload":"88","payloadType":"num","repeat":"","crontab":"","once":false,"x":140,"y":700,"wires":[["a72af19d.e17e2"]]},{"id":"f5301eaa.f021d","type":"inject","z":"c552e8d2.712b48","name":"Set needle to 33","topic":"","payload":"33","payloadType":"num","repeat":"","crontab":"","once":false,"x":140,"y":740,"wires":[["a72af19d.e17e2"]]},{"id":"a72af19d.e17e2","type":"function","z":"c552e8d2.712b48","name":"","func":"msg.value=msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":720,"wires":[["8d862a4b.c1d588"]]},{"id":"cb9d2598.de4918","type":"inject","z":"c552e8d2.712b48","name":"Set threshhold to 60","topic":"","payload":"60","payloadType":"num","repeat":"","crontab":"","once":false,"x":150,"y":780,"wires":[["d1f505b0.62c9f8"]]},{"id":"b50f3ae1.7cbdd8","type":"inject","z":"c552e8d2.712b48","name":"Set threshold to 30","topic":"","payload":"30","payloadType":"num","repeat":"","crontab":"","once":false,"x":150,"y":820,"wires":[["d1f505b0.62c9f8"]]},{"id":"d1f505b0.62c9f8","type":"function","z":"c552e8d2.712b48","name":"","func":"msg.threshold=msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":800,"wires":[["8d862a4b.c1d588"]]},{"id":"cd9b1fe2.9c989","type":"inject","z":"c552e8d2.712b48","name":"Set user LED ON","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"x":140,"y":861,"wires":[["199bc0e7.607eff"]]},{"id":"59205954.a730c8","type":"inject","z":"c552e8d2.712b48","name":"Setuser LED OFF","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"x":150,"y":901,"wires":[["199bc0e7.607eff"]]},{"id":"199bc0e7.607eff","type":"function","z":"c552e8d2.712b48","name":"","func":"msg.userLed=msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":881,"wires":[["8d862a4b.c1d588"]]},{"id":"f13cc85.7df0038","type":"comment","z":"c552e8d2.712b48","name":"Example use of Steelseries Gauge","info":"","x":180,"y":660,"wires":[]},{"id":"ffa9ff04.4fc7f","type":"inject","z":"c552e8d2.712b48","name":"Trend UP","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"x":120,"y":940,"wires":[["2320de53.8b2e42"]]},{"id":"3c6f1b6f.e42924","type":"inject","z":"c552e8d2.712b48","name":"Steady","topic":"","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"x":110,"y":980,"wires":[["2320de53.8b2e42"]]},{"id":"2320de53.8b2e42","type":"function","z":"c552e8d2.712b48","name":"","func":"msg.trend=msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":960,"wires":[["8d862a4b.c1d588"]]},{"id":"e0daa005.96fd5","type":"inject","z":"c552e8d2.712b48","name":"Down","topic":"","payload":"-1","payloadType":"num","repeat":"","crontab":"","once":false,"x":110,"y":1020,"wires":[["2320de53.8b2e42"]]},{"id":"893a4668.be5a78","type":"inject","z":"c552e8d2.712b48","name":"Off","topic":"","payload":"-2","payloadType":"num","repeat":"","crontab":"","once":false,"x":110,"y":1060,"wires":[["2320de53.8b2e42"]]},{"id":"a222fdf0.4934d","type":"inject","z":"c552e8d2.712b48","name":"ODO 50","topic":"","payload":"50","payloadType":"num","repeat":"","crontab":"","once":false,"x":120,"y":1100,"wires":[["f7bee9bf.d05478"]]},{"id":"7a3e24c1.17e7dc","type":"inject","z":"c552e8d2.712b48","name":"ODO 78.6","topic":"","payload":"78.6","payloadType":"num","repeat":"","crontab":"","once":false,"x":120,"y":1140,"wires":[["f7bee9bf.d05478"]]},{"id":"f7bee9bf.d05478","type":"function","z":"c552e8d2.712b48","name":"","func":"msg.odo=msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":1120,"wires":[["8d862a4b.c1d588"]]},{"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"}]

[/pcsh]

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.

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

<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>
 

[/pcsh]

Facebooktwittergoogle_pluspinterestlinkedin

The Ongoing Knob

jQuery KnobI refer you to this article if you’re new in here - https://tech.scargill.net/more-dashboard-goodness/ – we’ve been having a lively discussion and lots of exciting new possibilities coming out of a chance discovery of the JQUERY KNOB and what is now our ability to integrate that with Node-Red. The purpose of this is to increase the usability of the web-based Node-Red Dashboard by introducing new and useful controls.

I won’t go through the history here – it’s all in that blog entry which you should read first before getting into discussion here.

So now we can create a touch/mouse-driven knob for Node-Red Dashboard, in a template – and we can replicate as many of these as we like in a tab. Many thanks to those who helped get this off the ground.

But once the novelty wears off, we find that we need more work on this for some scenarios. The knob can be coloured and it’s shape and size can be changed but what about that value in the middle. Early attempts at adding a “%” sign or similar failed miserably – until I actually started looking at the docs for the knob – at which point some things became clear.

I can think of two immediate modifications to the basic knob which come in as useful – the first is the addition of a percentage or degrees character – the second is slightly more complicated. I want to use the knob to show a time in hours and minutes and to let me alter that – worse I want the time to increment in 15 minute intervals – giving me back a value in minutes while SHOWING HH:MM -  so - how to do that:

It turns out to be surprisingly easy if you’ll bear with me.

So firstly the easy one – setting limits and steps can all be done in the input definition.

<input  id="jqueryknob{{$id}}"
        class="cDial1"
        data-width="98%"
        data-height="98%" 
        data-cursor=true
        data-thickness=.25
        data-linecap=round
        data-fgColor="#66CC66"
        data-bgColor="#CCAAAA"
        data-angleOffset=-125
        data-angleArc=250
        data-min="0"
        data-max="1440"
        data-step="15"

        value="{{msg.payload ?  msg.payload : 0}}"
>

If you refer back to the original article – the input definition is in there and I’ve simply lifted that and added 3 more lines – so now we can have any start and any end values and a step – in my case the number of minutes in the day. So how do we get from THAT to the display above right.

It turns out there is an extra bit we can shove into the code… a snippet from the original blog follows with something new – two examples here.

$("#jqueryknob" + scope.$id).knob({
    'format': function (value) {
        return value + ‘%’;
    },
               
    change : function (value) {
    },
    release : function (value) {
        if(scope.msgSend)scope.sendData(value);
    },

The above adds a percentage sign to the display WITHOUT affecting the actual output from our template – you could of course add any other symbol. The example below though looking more complex is just formatting to give me my HH:MM format – again without affecting the output of the template which in this case is in minutes.

 

$("#jqueryknob" + scope.$id).knob({
   'format': function (value) {
        return ("0" + (Math.floor( value / 60))).slice(-2) + ":" + ("0" + (value % 60)).slice(-2);
    },
               
    change : function (value) {
    },
    release : function (value) {
        if(scope.msgSend)scope.sendData(value);
    },

So that one addition above allows you to control what you see in the text of the knob without affecting the actual value.

I suspect there is far more to be gained from further examination of this widget. ANGULAR also has a knob - https://radmie.github.io/ng-knob/ and that has some nice features including subtitles. Perhaps we can look at this one sometime!

For now here is the latest update of the actual code used in my test knob – set the template to 6*6. I’ve removed a line that wasn’t doing anything and changed msg.payload to msg so in future we might add more stuff in there (thanks Dave).

Don’t forget to create a template with the CSS in it – and make sure your template tickbox settings are like this.

 

Template tick-box settings 

Enjoy.

Facebooktwittergoogle_pluspinterestlinkedin

Node Red Weather

One of the many things that Node-Red makes easy to capture – is the weather – and all credit is due to the people who put the DARKSKY API together.

Animated weather

What you are seeing above is a 6 day forecast in ANIMATED, COLOUR images (I appreciate it does not look animated here). It is LOVELY. This is running on my Raspberry Pi and of course the display, being a web page can be displayed on a tablet, a PC screen, a phone or an integrated touch-screen. Don’t go using old browsers though – this makes use of HTML5 and vector graphics so you should be reasonably up to date.

To get a nice pretty weather forecast for your home control display or whatever you plan to use it for, you need:

a. information

b. A display mechanism

In this case the information comes from DarkSky via a simple API

DarkSky

If you wish to “call” the DarkSky API you may do this up to 1000 times a day for free. Reader Jay and I felt that grabbing the information every 5 minutes might be a good start. Once the novelty wears off you might consider every 15 minutes or so. A timestamp node is simply used as a trigger – to get the information when Node-Red powers up and every 5 minutes from there on.

An HTTP request gets you a massive object with just about everything you could ever want to know about the weather and finally a function attempts to read that object with minimal error checking. I say this because in the past I’ve had the very odd bad package from DarkSky using the NODE that is available (I suggest you avoid that at least for now) which does not harm but looks bad in the logs!

The “http request” node in the middle requires little information – just your unique code which you get when you sign up (for free) with DarkSky – and your longitude and latitude.

DarkSky details

Here is the full URL I used with a fictional key – and my own coordinates.

https://api.darksky.net/forecast/xxxxxxxxxxxx/55.130575,-2.2534091?units=uk2

Now you COULD read all the information from the new object and make a whole boatload of MQTT messages – some of which you might never use. If you were to do this – your flow might look like this.. and my thanks to reader Jay for saving me all that work.

massive flow

i think you’ll agree that is slightly over the top – but it does give you an idea of how much info you can get out of this and that is NOT a complete list.

On the other hand it was good fun for a bit, checking out all the info

info from DarkSky

So, instead, what we’re going to do is simply pull out of the object what we need… See below. When this is called, the function I’ve dropped in after that simply contains…

try {
var weathertest=msg.payload.currently;
global.set("DarkSky",msg.payload);
node.send (msg);
}
catch(e) {}

"weathertest" is used purely to provoke an error if there is something wrong with the data and can be ignored from now on. So now with luck we have an object called “DarkSky” out of which we can pull all manner of things as needed – and of course being global we can access this from any flow at any time.

So – here’s a EXAMPLE function to get the current weather summary

try {
msg.payload=global.get("DarkSky").currently.summary;
node.send(msg);
}
catch(e) {}

What’s with the “try—catch”? Well, this is new for me – that comment I made above about accessing anything at any time - I got sick of seeing harmless complains about undefined objects in my logs due to using something before it has been defined – or bad data coming through etc.,  so my new thing is to actually check that a variable exists before using it. If you call this function BEFORE you’ve grabbed the weather for the very first time – nothing will output – simple!

So – how far can we go with this? VERY far – it’s a BIG object but for what we need here – it is all very simple.

global.get(“DarkSky”).when.what

where “when” is one of the following:

  • minutely
  • hourly
  • currently
  • daily.data[x] where x starts at 1 for today and goes to 7 days,

msg.payload=global.get("DarkSky").daily.data[2].humidity;  // humidity for tomorrow !!

“what” can be one of the following:

  • summary
  • nearestStormDistance
  • precipIntensity
  • precipProbability
  • temperature
  • apparentTemperature
  • humidity
  • windSpeed
  • visibility
  • cloudCover
  • pressure
  • icon

I’m sure you’ll  agree that’s a pretty damned comprehensive selection, nicely hidden away in an object for you to pick out what you need. cloudCover and humidity you might want to multiply by 100 to make them useful – and temperature is in degrees C though you can no doubt change that to anything you want – read the API docs.

For now what we’re interested in is the icon – the name of an icon to represent the weather for seven days. The icon can be one of the following:

  • clear-day
  • clear-night
  • partly-cloudy-day
  • partly-cloudy-night
  • cloudy
  • rain
  • sleet
  • snow
  • wind
  • fog

It JUST so happens that there is a set of animated icons called SKYCONS which match up with this exactly – and they are in technicolour – you have control over the colour of various bits of them including:

  • main
  • moon
  • fog
  • fogbank
  • snow
  • leaf
  • rain
  • sun

So putting this all together – you can make a full colour set of icons which adjust for each of several days. As I am using Node-Red Dashboard, 6 icons fit nicely onto my phone so I simply picked the next 6 days.

You might want to satisfy yourself that you can grab this data before moving onto the next stage. Get yourself a DarkSky account, punch in the details and maybe go and grab today’s weather. Of course without these icons you still have lots of info you can use for wall displays or maybe calculating what to do with your heating system – for example in cold weather, if it is damp I like to ramp the heating up just a little more.

And now, onto the imagery.

You will need to be comfortable with creating a folder to put JavaScript libraries for use with Node-Red. In the Node-Red settings file you can define such a directory – just make sure you don’t call it “js” as Node-Red already has one of those and you’ll end up in trouble. I call mine “myjs”.  Go and grab the skycons.js file and put it on your system. You’ll find a lot of information in that link I’ve just given you.

For 6 days your flow will look like this..

weather flow

So – every 5 minutes or however often you choose, grab the relevant icon from the global object you are populating and inject it into one of these templates.

Here is the code for template 1.

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

<script src="/myjs/skycons.js"></script>

<canvas id="icon1"  width="50" height="50"></canvas>

<script>
var skycons = new Skycons({"monochrome": false, 
                "colors": { 
                      "main": "#779966", 
                      "moon":"#666666",
                      "fog": "#555522",
                      "fogbank": "#884488",
                      "cloud": "#999999",
                      "snow": "#aaaaaa",
                      "leaf":"#00ff00",
                      "rain": "#0000ff",
                      "sun": "#ff3300"
                    } });
skycons.add("icon1", Skycons.PARTLY_CLOUDY_DAY);
skycons.play();

    (function(scope) {
        scope.$watch('msg.payload', function(data) {
           if (data=="clear-day") skycons.set("icon1", Skycons.CLEAR_DAY);
           if (data=="clear-night") skycons.set("icon1", Skycons.CLEAR_NIGHT);
           if (data=="partly-cloudy-day") skycons.set("icon1", Skycons.PARTLY_CLOUDY_DAY);
           if (data=="partly-cloudy-night") skycons.set("icon1", Skycons.PARTLY_CLOUDY_NIGHT);
           if (data=="cloudy") skycons.set("icon1", Skycons.CLOUDY);
           if (data=="rain") skycons.set("icon1", Skycons.RAIN);
           if (data=="sleet") skycons.set("icon1", Skycons.SLEET);
           if (data=="snow") skycons.set("icon1", Skycons.SNOW);
           if (data=="wind") skycons.set("icon1", Skycons.WIND);
           if (data=="fog") skycons.set("icon1", Skycons.FOG);
        });
    })(scope);

</script>

[/pcsh]

The other templates are identical EXCEPT that: they don’t need the include – and they don’t need the definition of skycons. Also – wherever you see “icon1” – change that to “icon2” etc.. up to “icon6” assuming you want 6 days. “icon7” if you want to go all the way to 7 days.

If this does not work smoothly – check out that you ARE pointing to the javascript library and that you’ve made no mistakes – check that the data is actually going into the template node and make sure you have an up to date Node-Red and Node-Red Dashboard.  That will serve well later as I’m no-where NEAR finished making use of templates… just the thorny matter of getting data into and out of them to get to grips with.

If you have problems formatting these images - I noted when moving them mine came out stretched - this is some js and css  I use for other purposes but I think you need the padding css in a template (make this the first template - auto size - it will not show up) - to remove padding around the edges .

[pcsh lang="js" tab_size="4" message="" hl_lines="" provider="manual"]

<style>
  .filled { 
      height: 100% !important;

      padding: 0 !important;
      margin: 0 !important;
  }
  .nr-dashboard-template {
      padding: 0;
      margin: 0;
  }
  
  .rounded {
  border-radius: 12px 12px 12px 12px;
}
 
   .bigfont {
  font-size: 18px;
}

   .smallfont {
  font-size: 12px;
}
  
</style>

<script>
$('.vibrate').on('click', function() {
  navigator.vibrate(100);
});

function restore_bg(x) {
            $(this).css("background-color", x);
    };

$('.touched').on('mousedown', function() {
    var x= $(this).css("background-color");
    $(this).css("background-color", "yellow");
    
    setTimeout(restore_bg.bind(this,x),200);
    navigator.vibrate(80);
    });
    
</script>

[/pcsh]

Facebooktwittergoogle_pluspinterestlinkedin