Category Archives: node-red

A Fine Sunny Day

Scargill's Man-CabinEvening all, it has been a busy day here at the Scargill’s man-cabin. Yesterday, 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!

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/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’s a better way. At the VERY START of setting.js add this..

var mySettings;

try {

mySettings = require("/home/pi/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.

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

// 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");
}

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

Big Timer

tmp17DBBig Timer is probably the best timer 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.

Updated to v 1.5.6 –March 06, 2017 - Recommend updating. Here’s a new video!

BigTimers

What: Node-Red-Contrib-Bigtimer is both a simple timer – and a full-function timer – depending on your needs. At it’s simplest, you set on and off times, the topic and payload you need to go out to, say, MQTT and your longitude and latitude (most browsers will fill this in automatically), you then set your schedule and you are done.

Why: Existing timers didn’t do what I wanted – also – I have a watering system – I want it to run at dusk and dawn every day – but not in December through February (ice)!  I had to have control over both days of the week and months. Existing timers didn’t do that either. Many seemed to be based on the old mechanical timers but without moving parts. That won’t do.

Firstly: the name. It’s a timer – but it’s a BIG one – so I’ve called it BigTimer. (note: the original scheduler is no longer developed – BigTimer does everything Scheduler does and much, much more).

Secondly: it has an input. You can inject various words into BigTimer – and get instant over-ride action. "1" or “on”, "0" or “off”. "auto" or  “default” etc.

Does that need explaining? “on” for manual override to on, “off” for manual override to  off (both of which reset on the next auto state change), “default” or “auto” to return the timer to normal operation. More details later.

Manual Override: If you set the timer to turn on, say at dusk and off at midnight, you can override the setting during the day by sending “on” to the input. This override will reset at the next auto change of state.

If you inject "manual" in conjunction with  “on” and “off”, the state will change until the set timeout (in minutes) times out - so if you turn the lights on in manual and forget you did it? The unit will return to default operation in X minutes (which you can set - defaulting to 24 hours - 1440 minutes) after any manual change you make. You can also send a "sync" command which simply outputs the current state with a side effect of decrementing the timeout counter - handy for testing.

Here is a test setup for BigTimer, just using simply injection nodes:

BigTimer test setup

And here’s what happens if you press ON (a simple inject – you can send that message in the payload by any means you like)…

Bigtimer test setup[6]

But what are those other two outputs?

Output 1: is a message – you control the topic and payload – so I use it, for example, as I do, to send a message to MQTT to control something. You can have this message go out once – or every minute. There are other components to the msg object on the first output, there for information only.

Object

In this example (version 1.5.5 of Bigtimer onwards) you see various items in the output -also note that this view is in Node-Red 0.16.2 which is different to older versions – I strongly recommend you upgrade if using an earlier version.

You can see msg.payload which is whatever you enter in order to fire a message off to another node or function to make something happen. In addition (and for information only) we have msg.state, msg.value, msg.AutoState, msg.manualState and others. For simple use, you can of course ignore the additional outputs, but they are handy for checking. msg.state shows “on”, “off” or “none” and in the case of the first two (on/off) msg.time shows the time for the next change of state.

Output 2: outputs a 1 or 0 every minute when checking (in msg.payload) – you can use that to light up something.

Output 3: puts out another text message in msg.payload which you can set – I point it to a speech synth so it might say “external lighting coming on”. There is one message for the ON state, another for the OFF state. Ignore it if you don’t have a use for it.

And to get this node?

Make sure you are in your Node-Red installation folder (for example /home/pi/.node-red) and type:

npm install node-red-contrib-bigtimer

So in my case, I keep all my nodes under /home/pi/.node-red/node-modules.  So I do the install from /home/pi/.node-red

Restart Node-red and Bob’s your uncle – it should appear on the left with the other Nodes. If you happen to have the ADMIN node in Node-red you don’t even need to do any of that – you can install node-red-contrib-bigtimer straight from the admin tab.

In addition, I have recently,  as a response to a sensible request from one of our blog readers, changed the code so that if either the ON payload or OFF payload is blank – then no message will be sent at that time – this means you can now easily OR timers together. So if you need several time intervals in a day or other complicated setup you can simply use a number of timers and feel the first output to one common destination!

Here’s an example of TWO timers simply sending stuff to a debug node for testing…

two BIGTIMERS

Backtrack for Beginners: Node-Red is a simple-to-use but very powerful visual tool for wiring the Internet of Things and a tool for connecting together hardware devices, APIs and online services in new and interesting ways. It is also a lot of fun.

I control a range of Arduino and ESP-8266 devices using Raspberry PI2 and smiilar. You can of course run Node-Red and the other tools on a wide range of machines – Orange Pi, Roseapple Pi, Odroid C2 etc using Debian, DietPi, Xenial etc. I even have my normal installation script running on a laptop using Mint Linux.

tmp871On this particular Pi I run MQTT (Mosquito with Websockets), SQLITE, APACHE and Node-Red. I started using Blynk as my mobile remote controller then went on to using ImperiHome but today I concentrate on using Node-Red Dashboard which has (IMHO) so very much potential.  I also use Nextion touch-LCD display devices for local displays. All of this can be managed within Node-Red. If you find this interesting you might like my  home control 2016 post here or the Nextion Wi-Fi Touch Display article.

Here (as requested): to the right is a screenshot of a typical setup… click on the image to enlarge.

So in total you have a timer you can set to go on and off at specific times of the day, or dusk and dawn, on certain days, certain months, you can even set it to run on specific days of the month (Christmas day?) or even certain weekdays of every month (second Tuesday).

You can tick a box to have it output on power-up or not, you can tick a box to have it auto-repeat the message every minute or not. You can add positive and negative offsets to the times (including dusk and dawn etc.), you can optionally add your OWN offset to the UTC time of the host computer and you even select a random value within the limits of the offsets (security lighting etc.) You can temporarily suspend the schedule via a tick box or a message.

And here are the words the node will accept at the input to override automatic operations. The input is not case-sensitive.

sync simply forced output
on or 1 turn the output on
off or 0 turn the output off
default or auto return to auto state
stop stop the scheduler
on_override manually override the on time (in minutes or hours and minutes - space separated
off_override manually override the off time (in minutes or hours and minutes - space separated)

In the drop-down boxes for on and off times you can select times or words like dusk,dawn, sunrise, sunset, night etc. All are dependent on the longitude and latitude you put into the relevant boxes and will adjust every day as necessary.

A good way to learn about BigTimer is to put a debug node (set to show the complete message) on the top output – and inject text payloads (using the INJECT node) to the inputs as demonstrated above. 

Output from error and warnOut of the first output, as well as the normal msg.payload, you can extract msg.state which might be “on”, “off”, or “auto” – and msg.value which might be 1 or 0.  This is so that if you are using say, ImperiHome and store states in global variables, if the time reverts back to auto, you can let ImperiHome know what is happening.

Another way to test BigTimer a good way to do it is like this..

Big Timer Node

In the FUNCTION nodes to the right of BigTimer, I’ve simply put node.error(msg); and node.error(msg); respectively. The only difference is one has a red bar (error) the other a yellow bar (warn). These make a handy alternative to using the debug node for testing.

Recent additions to the inputs include on_override and off_override. These are in response to requests to be able to change the on and off times from the Dashboard. Please note that override information you inject here (in hours and minute or just minutes, space-delimited) is lost when Node-Red is restarted or the board rebooted.

If you make good use of this node – please put a link to this blog entry somewhere in your writings so people will come back here. Or if proves REALLY useful you could feed my Ebay habit – there’s a link on the right of the blog.

See the Node-Red info panel in Node-Red for up to date information.

Enjoy and please do report any issues back here.


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:

<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

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

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

The Ongoing Knob

jQuery KnobI refer you to this article if you’re new in here - http://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