Monthly Archives: February 2017

Node Red and HighCharts

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

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

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

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

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

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

Sample data into HiCharts

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

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

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

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

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

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


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

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

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

    series: xx
});

}

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


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

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

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

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

Facebooktwittergoogle_pluspinterestlinkedin

The LCD Object

lcdIt has been a very busy week for us here in Spain – what with partying and what-not, so I’ve not had as much time as I would like to develop my JavaScript object skills, however, I’ve managed to get much further with a project I’ve been working on in the background – a little LCD display.

I wanted to simulate a real LCD and you’ll have seen in an earlier blog entry that I managed some JavaScript code to do this.  Well, I’ve had all SORTS of unexpected issues, now resolved and I’ll detail here what I’ve done.

colour changeSo first off, I was having some interaction with other objects on the page – resolved – but an issue that came to light when I got here to Spain – with my little Raspberry Pi that is running this stuff left in England – was that of loading external items – for example, images and fonts.  Designing items for Node-Red Dashboard is slightly different to working on an empty web page as you’re basically working inside a DIV – you don’t have the luxury of ONLOAD and HEADERS etc. and I found myself getting annoyed with my web seven-segment font not working when first loaded  - i.e. initial text would show up in a default font because of the time it takes to load the web font from England. I could not understand why this would always happen – surely there’s some caching going on? But if you read the comments in previous blogs – that apparently is not the case as the templates in Node-Red Desktop add a timestamp to URL calls which kills the cache. Well, not ALL of it but certainly makes life difficult when trying to load up fonts and images. 

LCD in redAnyway, the first idea that came out of various comments was to URL encode the fonts – and of course, wanting to keep with good practice I put all of that into a separate style sheet. NO! The page has to load the style sheet, so the problem doesn’t go away. The answer lay in having the font inline – now that SOUNDS messy but actually it’s not that big a deal especially if you don’t have wrapping turned on – it’s just a one liner – albeit a very long line. The breakthrough came when I read a suggested link which took me to Fontsquirrel – having initially used this 14-segment font for my LCD only to find that the capital S was a bit naff and the spacing was NOT identical on all characters, I was then quite excited when I found an excellent WOFF file called segment14 here.  Mono spaced and with a decent S.

Lovely, but of course this still had the same issue with loading – not a big issue at home but as soon as myself and the source were separated by a plane trip – the loading issue became apparent. Anyway, Joe Lippa and Antonio (Mr Shark) have been feeding me links and it turns out that FontSquirrel can load up the font and return a CSS file with the code all inline (they don’t appear to have 7 or 14 segment fonts so it is as well you can upload fonts you have found -  and have them converted) – so no external loading.  In the data that came back was a WOFF data block and a WOFF2 data block – I ditched the former as the latter works just fine.  I’ve put links to fonts here – I’ve modified them for my own use but you should know I’m not claiming any ownership of the fonts – others have done the excellent work on designing the fonts.

Purple LCDWith the font issue resolved I set about making a basic object for a display (which will no doubt mutate – but right now can be dropped into a template in Node-Red to show lines of simulated 7-segment display in a variety of colours. Methods can be invoked to updated the two lines and to change the colours – both background and foreground. I managed my colours by trial and error but you can of course change them and add more.

Simple testing of the LCD objectThis, then, is merely the first stage – everything works – you can change colours and text… so much more can be done – choice of how many lines, how many characters, what font size etc. but all of that is easy to add once the basic idea is in place. This is going to be added to my armoury of gauges and shapes available in Node-Red Dashboard – I’m playing with the GITHUB beta right now and it is coming along VERY nicely – now that the actual template width and height can be tweaked.

Anyway, there it is… if you want to play with this – grab the code and paste into a template via the “import clipboard” option (top right – 3 bars)– yes, it’s big because of the font – but very do-able. You can replicate this and make two or more displays but more likely the object and font should be split off into another template if you plan to do that. 

JSON

Note that in my test inject nodes I am now injecting JSON, not text (see image above and note the {} which means JSON is selected – then I’ve filled in some standard JSON text to adjust two variables) – just as easy but more powerful as you’ll see in the text injectors.

When this information comes into the node it is processed as such…

(function(scope){
        scope.$watch('msg', function(msg) {
            if ((msg.payload.lcdm1!==undefined)&&(msg.payload.lcdm1!==undefined)) lcd2.update(msg.payload.lcdm1,msg.payload.lcdm2);
            if (msg.payload.colour!==undefined) lcd2.updateCol(msg.payload.colour);
        });
})(scope);

Right now I don’t allow on or the other line to be processed but only both at once – but that would be easy to change.

If you look into the code there are two methods (functions) of relevance once the object is defined – this.update and this.updateCol – which are merely setting internal variables then calling the update function.  The text update function could easily be split into two.

At the very end, the LCD is created, needing only a DIV to work in..

var lcd2 = new myLcd("newDiv1");

I called it lcd2 as I also tried putting several on a page to ensure there was no interaction. They need unique names and unique div names and several will co-exist no problem (though it would make sense to have only one copy of the object definition if you want to do the job properly).

See previous blog entries for related info on LCD simulation (alpha blending of the all-segments on character under the actual text).

Specific questions are invited in the comments area (not 2 years later please).

Facebooktwittergoogle_pluspinterestlinkedin

Odds and Ends

Just a few odds and ends… we’ve arrived safely in Spain and I’m not getting quite as much blogging time as I’d like as there are repair jobs to do – but I managed a little item on Node-Red earlier – I’ve had people in here asking for simple-use examples – so that’s the first.

Gauge Progress: I’ve not forgotten my HTML5 Canvas gauge – it’s coming along nicely but along the way I’m hitting minor bottlenecks on image loading – and image pre-loaders are not helping. Most but not all of this came to light when I moved thousands of miles away from home with the attendant delays! I’ll return to this one soon.

Node-Red Menus: Regular readers may recall from earlier blogs that I’ve been griping about the menu in Node-Red being there even if you have only one page. Some may have noticed that a blog reader pointed out a simple CSS solution – well, now there is better – Node-Red master as of now has an option to turn the menu off. I’d give it a day before updating to make sure the latest version had filtered through to updates.

Mint Linux: I wrote a while ago about this – so often these things fall by the wayside – well, I’m still using it – can’t find anything wrong with it on my little black laptop.  It isn’t Windows but for development work – it runs a lot faster on that old hardware than Windows 7 did and everything works right down to my Logitech Bluetooth headphones. Anyone else using this on an Intel-powered laptop?

Raspberry Pi Power backups: You’ll probably know I’ve covered this in the past – various solutions for backing up the little SBCs in the event of power failure. My best solution up to now has been RavPower battery charging units – but not all of them seem to work in the same way. I did find a little LIPO unit that works on one round 3v6 battery but it’s power output would not work for a RPI3 for example (which takes more power than the RPI2. Well, I’ve just discovered and sent off for one of these. It’ll take a few weeks but I’ll let you know how that goes. Anyone bought something similar? How did that go for you?

Facebooktwittergoogle_pluspinterestlinkedin

NR Templates and the Inject Node

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

Inject and Debug Nodes

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

Node-Red setup

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

Debug window

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

Full debug message

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

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

Hello world

Output Hello World

Expanding the INJECT output

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

For some purposes however, this is not enough.

Messages

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

On way to differentiate them would be to use topic.

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

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

msg.needle=msg.payload;

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

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

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

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

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

Sending an object

Sending several messages in a JSON string

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

image

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

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

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

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

Testing the gauge

But what about existing objects

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

Injecting objects

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

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

thermostat object

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

expanded object

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

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

Can I create an object? Sure:

Test object creation

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

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

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

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

node.warn

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

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

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

Debugging

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

Facebooktwittergoogle_pluspinterestlinkedin

A Deep Learning Weekend

Gauge by Peter ScargillWell, this has been an interesting journey (a deep learning WEEK more like it) as I’ve been working on making my own widgets for Node-Red. I thought I’d share some of the things I’ve picked up. Experienced JavaScript object users please look away now. Those not interested in JavaScript look away now.

Note: The original video for this is out of date as I have learned more – the new video has some extra code, too – here it is - https://youtu.be/mTAPrvB1EeI

C Programmer: As a life-long C programmer, I came into JavaScript with some trepidation. Like alternative universes, JavaScript may look similar to C – but it is not the same – and that led to all manner of silly mistakes over time. However as I see a tiny light at the end of the tunnel, I have to say, it was worth the effort. Despite being a client-side interpreted language, it really is great fun to use.

The gauge: Well, as long as you keep it simple that is. When designing my first attempt at a gauge for Node-Red, I realised that a typical gauge operation goes like this:

Some features such as the background are typically set in an init function once only (and as such it would be better if the user is not involved in this. Other features such as needle positions are set dynamically and will change during operation.

This is generally handled one of two ways – either by setting a value and then calling an update function, or by running something in the background which checks for value changes so all you have to do is change a variable. On many of the gauges you’ll see flashing lights and smooth movement of gauge needles – that implies something running constantly or at least checking for changes and running when change is needed.

Concerns: This worries me as there can be quite some overhead in updating an entire gauge, umpteen times a second. So I decided that what was needed was a layering system as you might find in Photoshop – such that I could lay down the basic design and background of a gauge once only then dynamically clear and write to a top layer whenever changes are required. Such changes might include the state of a flashing light – or a needle not being where you want it, requiring successive incremental updates until the needle or needles do get to be where you want.

I then started thinking about variables and functions (methods). Did I want to update variables such as the needle pointer directly (direct variable manipulation) – or did I want to manipulate the input value before passing it through (that needs  a method/function).

That led me to create, as you will have seen earlier, a large blob of JavaScript for my gauge, including initial setup – then a timer-driven function to update the gauge regularly. That was stage one.

HTML5 Canvas: In the process I discovered (by looking at what others had done) that HTML5 Canvas (which really is easy once you get past the initial learning curve) Is a great choice. What did not come so easily was the realisation that the canvasses themselves are indeed transparent and you can layer one directly on top of another – just like Gimp or Photoshop layers. That led me to split the job into two – the overall backdrop which remains fixed throughout operation – and the top layer with my LEDs, needles and set points on it.

Adding a bunch of variables to this for setup and defaults brought me out into a sweat as I struggled to imagine how two or more gauges would interact. And THAT led me onto objects – something I’d never really touched in JavaScript before  – specifically to wrap up my entire code into a definition – which meant I could then simply create instances for EACH gauge without worrying about all those variables interacting. That was the point I last few days in deep learning.

Having wrapped everything up in a bulletproof package I was then faced with the question of how to access required functions and data externally. The obvious way (and the one I’ve adopted) is a series of callable functions for example:  mygauge1.needle1(50);

In the process I had to learn how to hide functions within the definition and make functions accessible externally. That was easy enough.

Here is a simple example – the assumption is that you have a jQuery link (to update a div) and a DIV called “fred”

1.  function myobj(){
2.  this.v1=5;
3.  init=function(passThis) { setInterval(every25ms, 25,passThis); }
4.  this.doubleit=function(r) { this.v1=r*2; }
5.  every25ms=function (passThis) { $("#fred").html(passThis.v1); }
6.  init(this);
7.  }

8.  brian=new myobj;
9.  //brian.doubleit(12);
10. //brian.v1=7;

There is a lot to this – and I’ll be using the word “this” a lot – so for clarify I’ll use italics for the special meaning of this.  It may look strange at first but if you follow me it soon won’t.

Definition: So the point of this was to create a test object definition – an object that would simply put a value in a DIV (I know, slightly overkill but I was trying to prove a point).  We start off with the definition in line 1.

Take a look at the code above – there is a lot in here despite their being only 9 lines. I figured any more and people might run away.

Firstly on line 1,  we define a type of object. A “myobj” type.  I’m not making anything here – just defining it (ends in line 7).

On line 2 – we define an externally available variable – v1. “this” makes it accessible externally (try removing “this”).

In line 3, we are defining  a function called init.  When the instance is created on line 8, init will be called internally (by line 6) and will start off a timer which will run every 25ms and which will call an internal function called “every25ms” (line 5), passing this to it. Do not get hung up on this yet.

Line 4 defines a function (again made available externally by “this” and called “doubleit”,  will double whatever is passed to it.

The  “every25ms”  function on line 5 which is to be called every 25ms by init… simply prints out the local variable.

Line 6 calls init (which is NOT accessible externally) when the instance is created in line 8 - to initialise the instance and start off the timer – this saves running an init externally.  From line 8 onwards,  the function “every25ms” is called every 25ms to put the value of this.v1 onto the DIV.

You’ll see “5” appear in your DIV if you test this somewhere – that’s the initial value. Note "passThis" – (arbitrary name). Because the function init is private - I don't prefix it with "this" – when it sets the interval timer it has pass to the timer function the outer “this”… - so I pass "this" in the init call.  This is new as a result of feedback in here suggesting a private “init” function that was no accessible externally. In the original version I used this.init() - and passing this worked. Removing that resulted in the timer callback function using the wrong "this".  Trace it through and it makes sense. The callback function needs to know about the outer "this".

Line 8 is where things start to happen. At that point, the instance (brian) is created and the timer starts all on it’s own -  thanks to the internal init function.

So far so good. How do I now interact with that variable v1? I wanted the choice of updating a variable or calling a function. The two commented lines are two ways to do this – their operation should be self-explanatory.

Line 9 simply won’t interact with local functions inside the definition and we don’t want them to – we want interaction with specific functions in each instance of the object – like “brian” for example. And that’s where “this” comes in. Add “this” to a variable name or a function name – and it becomes accessible to the outside world. For example you CANNOT do brian.init() or worse, brian.every25ms() and that’s a good thing!

Scope: So using this technique allowed me to keep all variables and functions private and only “expose” those I needed. LOVELY.  If I only wanted to use function and not expose any variables – I would not use for example “this” as a prefix to v1.

These ideas then form the basis of my current gauge. I can built a massive machine with lots of variables and functions inside and have no worries about interactions with other gauges because I’m only “exposing” stuff I want…. and gauge1.v1 has nothing to do with gauge2.v1

Of course I’m assuming that was as clear as mud -  but if you are interested, go have a play in this CodePen where I’ve left the code. See the number that comes up in the working area – try un-commenting those functions (methods) in lines 9 and 10. See what happens if you replace passThis.v1 with this.v1 or just v1.  See what happens if you remove references to this altogether. CodePen is great as changes run immediately and safely.

Merely experimenting with the code in that CodePen should do what it did for me – clear a massive amount of mental fog.

Simply creating v1 – and removing this and passThis in referring to v1 – leads to disaster – v1 is accessible externally on it’s own –so kiss goodbye to multiple instances.  But the same code using “var v1” leads to a save internal v1 NOT accessible as “brian.v1”. Your choice.

My new gauge object is coming along nicely – and a minimal installation involves little more than a link and a couple of lines of code – stunning how these simple ideas change the whole game.

Facebooktwittergoogle_pluspinterestlinkedin

A week of Learning

Working on the gauge in CodePenSome may be wondering if I’ve gone off on holiday, as I normally post here on an almost daily basis. Well, if you regularly read the comments sections you’ll see I’ve been heavily involved in answering questions, but more importantly I’ve taken one step back and two steps forward on this gauge thing. Read on.

But firstly – WIN SOMETHING! You’ve probably heard me talk about my pal Peter Oakes in Canada – he has an excellent YouTube Channel in which he talks about IOT – well, he’s just launched a competition and the prizes look pretty damned impressive indeed. Take a quick looks over here – you might want to participate…. https://www.hackster.io/contests/IOT2020

Thanks:  Before going any further, I’d like to take this opportunity to thank some people in there without naming names – for several donations received this week – you have no idea how this helps motivation, knowing that your work is appreciated. Thank you, guys!

The Gauge: Now - regular visitors will know I’ve been doing a lot of work on Node-Red-Dashboard-compatible widgets recently as I really do think this is the way forward for mobile access to DIY home control systems and also for desktop and wall access via touch displays -  but one thing that has bothered me up to now was the clumsiness of some of my coding – global variables all over the place, a lot of opportunity for interference from one node to another etc. Everything just works but the perfectionist in me wants to “do the job properly”.

So earlier this week I took a step back, seeing what “proper programmers” had done with turning these widgets into “objects”, with clean interfacing to the outside world. Well, trust me, it isn’t that easy. I asked around as to how I might encapsulate my Javascript into an object, exposing only those bits that needed to be exposed. No-one I know well enough to ask for help, could help in this particular instance! So, I took a step back, grabbed some coffee and thanks to my friend Google, I’m pleased to say I’m getting somewhere.

Shortly you will see my gauge once again featured in these pages but the way it is done and bits you’ll need to look at have been dramatically changed as has the chance of unwanted interaction – and the overhead has been substantially reduced due to a series of insights which have followed each other – and some of them I’m sure some others have missed. Read on.

So first off, I made a gauge with pretty colours – you’ll see it elsewhere in this blog. It works – why take it any further?

Well, the gauge features nice, smoothly moving needles and other bits. What’s wrong with that?  Every time the needle is updated in the existing design, the entire gauge has to be re-drawn. This is not uncommon but just felt wrong to me. To tackle this I had to understand LAYERS. It turns out that if you make the position of CANVASSES absolute, you can put one on top of another (you also need to understand zIndex but that’s easy). If you can do that, then you can draw the gauge backdrop including the text and pretty-coloured segments once only on the bottom layer and only then clear and update the top layer, say, 50 times a second with needle updates etc. That saves some calculating.

But now I needed two CANVAS items which looks a little odd when setting up so I worked out how to create both, totally in Javascript. That led to another problem, in Node-Red you can’t just put your CANVAS elements in the top left corner of the page BODY – it won’t work. I then reasoned that both CANVASSES could sit inside a DIV on top of each others – and indeed they can. So now all that is needed in HTML is to define a DIV and the Javascript will do the rest.

That still left me with a mass of messy Javascript - and the thought of explaining what was and what was not relevant to end users in the blog here was keeping me awake. That’s when I started looking into this whole object encapsulation thing. 2 days ago that was a distant world, something for the future. Today, I wonder why I waited so long to take the effort to learn. I had to learn some new tricks in the process – however, when you eventually see the new gauge, setting it up will be as easy as this.

var gauge1= new petesGauge;

gauge1.init({
  container: "myDiv",
  });


gauge1.value1(90);

Define a gauge, point it to a DIV at which point it appears magically – and then dynamically set a value which will slowly change in front of your eyes. No other information exposed to the outside world, no need to do anything other than set up an empty DIV and add a link to the Javascript compressed library.

Part of the magic here is in the encapsulation I’ve learned to handle – but also of course there are many defaults. In the top section you’ll be able to override defaults and turns things on and off if you want – and in the bottom section there will be several settable items – the value of two gauge needles, the value of set points, visibility of LEDs etc. All easy to use options.

I’m on the right path – I understand what I’m doing but I’ve only so much time to code this up.

If anyone fancies themselves a whiz at JS Objects, I’ve fathomed out how to get and set global vars in the object with simple methods – no problem but can i hell interact externally with internal vars – happy to have a chat with anyone interested in enlightening me.

jsFiddle helped me get this far but the best tool for the job IMHO apart from lack of formatting – is CodePen – you can make code changes dynamically and see changes in real time – what a difference this makes. Oh and I realised the one thing missing from my LCD display (not shown above) is the overspill of side lighting typically seen on real LCD displays. That’s coming soon.

New Toys: I’ve also discovered (thanks to MrShark) Atom this week which promises to be a great editor both in Windows and on my little SBCs.  My MINT installation, apart from falling to bits visually after a “sleep” operation, continues to function well on the laptop and I’ve received some nice new DIN rail brackets from China which can screw onto the back of a perspex sheet for mounting boards and supplies. More on all of this later. Incidentally for those wondering, my testbed wireless Orange Pi Zero and twin NanoPi M3 units are still sitting on the bench, still working perfectly despite disconnecting the WIFI several times deliberately.

Desktop Touch Controller: And on another subject, how is my Desktop Pi Touch Controller doing? For some time Desktop Controllernow I’ve had a 5” touch screen attached to a Raspberry Pi 2, using Node-Red Dashboard as a simple controller for my office – to turn lights on and off and keep an eye on temperatures etc.  Works a treat using Chromium in “kiosk mode” – but two things have annoyed me – one being that menu from Node-Red Dashboard – the second being the ability of the browser to fail occasionally due to connection issues and put up a stupid message instead of trying to reconnect.  Well, I may have cracked both of these as for the last few days the little unit has been sitting there rock-solidly doing it’s job.

The annoying Node-Red-Dashboard unwanted menu problem (well I only have one page on this unit – why do I want a menu) was solved simply by a reader who wrote in to suggest this added to the style.

#toolbar {display:none;}

Yes, that’s it – simple as that. Gone – space retrieved.

The second came from here. http://www.labs.bristolmuseums.org.uk/running-google-chrome-in-kiosk-mode-tips-tricks-and-workarounds/

So I have a couple of templates on the page – one at the top with my CSS and some useful Javascript – the second at the bottom.

I’ve added this to the top

<body onClick=”location.reload()”>

and this to the bottom.

<script type=”text/javascript”> $(function() { setTimeout(function () { if($(“#VisitorStoriesHelpText”).length>0){ $(‘body’).attr(“onClick”,””) } }, 1 * 1 * 1000);}); </script>

And yes, I know that body code is really not appropriate in a Node-Red template – but guess what… it seems to work. I’ve not had an issue with the display for days now. May be a fluke of course – but fingers crossed.

Facebooktwittergoogle_pluspinterestlinkedin

What, ANOTHER Gauge?

Now, before you say anything – this gauge is different to the rest.. It is all mine and it EVOLVING (last update Feb 03) if you’ve seen this blog entry before today…

Gauges: Peter Scargill's GaugeI’ve spent a lot of time ploughing through code, some of it years old (SteelSeries) and though many of these designs are beautiful, most are not that well supported if at all and some comprise libraries that are kitchen sink jobs that take some learning, so much so that recently, in a fit of peak, after failing to figure out how to add a simple second dial or eliminate the wasted space of that metal outer ring in SteelSeries (it is easy to make it disappear but not the space it occupies) that I decided to “give it a go” myself.

What you see here is not finished but it works (and is a lot prettier when you see it running).  It scales without internal adjustments other than the gauge size as I need something that will easily adapt to differing Node-Red template blocks.

Images: There are several images on my site – don’t use them permanently please – grab them for yourself – they likely will change anyway.

As the dials move below the (circular) set-points the LEDS will go on and off – one triggers BELOW the set-point (heat), the other ABOVE (dehumidifier).

In the process of making this I’ve made a number of dials and centres – all in PowerPoint – very simple.

Centre 1 by Peter ScargillCentre 2 by Peter ScargillCentre 2 by Peter Scargill

I call these three metalCarvedCentre.png, greenCarveCentre.png and greyCentre.png respectively. I don’t plan to make a lot as there must be millions of needles and centres out there already.

tmpFF04Glowing rings are easy to add… and I’m working on an LCD display background image that’s hopefully a little more convincing than the canvas-manufactured version I’m using right now.

Glowing rings by Peter Scargill - in PowerPoint

If you poke the test values, you should see the gauge needles move smoothly to their destination. So I’m really happy about (and this happens a LOT it seems) having to update the whole thing every 20ms for animation when all I want is to update the dials (and an LCD panel eventually) – but I can’t find LAYERS anywhere.

What I HAVE done elsewhere is use Z-laying with multiple canvases to achieve layering as you will see in the link below. Originally I had 3 layers – the background, the bits that change and the knob over the needle – all of which SHOULD have worked perfectly – but for reasons beyond me the knob didn’t always show. I moved it in with the dial and all is well – so only two identical canvas units are needed. This seems like a good general way forward. I will change my main dial code to follow this pattern eventually when I've pondered what effect this will have on interfacing.  Anyway, I put this together from scratch and it seems to work ok, Take a look at this.. https://jsfiddle.net/scargill/r4qstas2/6/

tmp42D9

A timer runs constantly but does nothing unless a change is made  and the values are then incremented or decremented until they  match the required values.

I think this will make a fine addition to the visual tools we have but still needs much tidying up so no comments about code quality please.

Fonts: Fonts are always a thing – and the normal web ones are today considered to be utterly pants. So – TTF fonts are good but also large – so I found this online convertor. You simply drag a TTF file of your choosing (clearly if for commercial purposes, you need to make sure it is free) and you get the option to download from a link – it works. So I wanted a nice dot matrix font for the little display here – and I found “Dots all for Now JL” which is pretty but also 44K. I ran it through the convertor and in less than a minute I was up and running with a font that is only 7k.

To use the font, I did this..

<style type="text/css">
@font-face {
font-family: "DOTMAT";
src: url("/myfonts/dotsalfn.woff") format('woff');
}
</style>

and then just referred to DOTMAT as you would any font. Now there are all sorts of warnings on the web about ensuring fonts are loaded first and working with CANVAS etc. – but somehow, in Node-Red, it all just works… which is nice and keeps the code simple.

And that’s it for now. I’m working on improving the code.

[{"id":"22bad52a.82347a","type":"ui_template","z":"c552e8d2.712b48","group":"40cf30b4.d9549","name":"MyGauge","order":0,"width":"6","height":"6","format":"<style>\n  @font-face {\n    font-family: \"DOTMAT\";\n    src: url(\"/myfonts/dotsalfn.woff\") format('woff');\n  } \n</style>\n\n<script>\n  var showNeedle2 = true;\nvar showLED1 = true;\nvar showLED2 = true;\nvar needleWidth1 = 1;\nvar needleWidth2 = 1;\n\nvar set1 = 0;\nvar set2 = 0;\nvar value1 = 0;\nvar value2 = 0;\n\nvar setpoint1 = -1;\nvar setpoint2 = -1;\nvar degrees = -1;\nvar degrees2 = -1;\n\nvar title = \"Pete's Aircon\";\nvar subTitle = \"Hmm1\";\nvar ledTitle1 = \"DEHUM\";\nvar ledTitle2 = \"HEATING\";\n\nvar needle1 = new Image();\nvar needle2 = new Image();\nvar centre = new Image();\n\n\n\nvar direction1 = 1;\nvar direction2 = 1;\n\n(function(scope) {\n  scope.$watch('msg', function(msg) {\n    if (typeof(msg.value1) != \"undefined\") value1 = msg.value1;\n    if (typeof(msg.value2) != \"undefined\") value2 = msg.value2;\n    if (typeof(msg.set1) != \"undefined\") set1 = msg.set1;\n    if (typeof(msg.set2) != \"undefined\") set2 = msg.set2;\n  });\n})(scope);\n\n\n\n\nfunction n(n) {\n  return n > 9 ? \"\" + n : \"0\" + n;\n}\n\n\nfunction init() {\n  needle1.src = \"http://www.scargill.net/things/needles/redCurvedNeedle.png\";\n  needle2.src = \"http://www.scargill.net/things/needles/greenCurvedNeedle.png\";\n  centre.src = \"http://www.scargill.net/things/needles/greyCarvedCentre.png\";\n  canvas = document.getElementById(\"fred\");\n  ctx = canvas.getContext(\"2d\");\n  cX = Math.floor(canvas.width / 2);\n  cY = Math.floor(canvas.height / 2);\n  dX = cX / 175; // divisor for centrepiece sizing\n  dY = cY / 175;\n\n  setInterval(draw, 50);\n\n}\n\n// draw a wedge\nfunction drawWedge(percent, color, count) {\n  var arcRadians = ((percent / 100) * 360) * (Math.PI / 180),\n    startAngle = totalArc,\n    endAngle = totalArc - arcRadians;\n  ctx.save();\n  ctx.beginPath();\n  ctx.moveTo(cX, cY);\n  ctx.arc(cX, cY, radius_outer, startAngle, endAngle, true);\n  /** cut out the inner section by going in the opposite direction **/\n  ctx.fillStyle = color;\n  ctx.arc(cX, cY, radius_inner, endAngle, startAngle, false);\n  ctx.closePath();\n  ctx.fill()\n  ctx.restore();\n  totalArc -= arcRadians;\n}\n\nfunction drawbit(i, colr) {\n  if (i & 1) drawWedge(1.1, colr, i);\n  else drawWedge(0.2, \"#cccccc\", i);\n}\n\n// draw the donut one wedge at a time\nfunction drawDonut() {\n  var r, g, b;\n  b = 0;\n  g = 0;\n  r = 255;\n  for (var i = 0; i < 100; i++) {\n    var r, g, b;\n    if (i < 40) {\n      g += 8;\n    }\n    if ((i > 40) && (i < 70)) {\n      g -= 8;\n      r -= 12;\n    }\n    if (i > 70) {\n      g -= 8;\n      b += 12;\n    }\n    drawbit(i, \"rgba(\" + r + \",\" + g + \",\" + b + \",1)\");\n  }\n}\n\nfunction roundRect(ctx, x, y, width, height, radius, fill, stroke) {\n  if (typeof stroke == 'undefined') {\n    stroke = true;\n  }\n  if (typeof radius === 'undefined') {\n    radius = 5;\n  }\n  if (typeof radius === 'number') {\n    radius = {\n      tl: radius,\n      tr: radius,\n      br: radius,\n      bl: radius\n    };\n  } else {\n    var defaultRadius = {\n      tl: 0,\n      tr: 0,\n      br: 0,\n      bl: 0\n    };\n    for (var side in defaultRadius) {\n      radius[side] = radius[side] || defaultRadius[side];\n    }\n  }\n  ctx.beginPath();\n  ctx.moveTo(x + radius.tl, y);\n  ctx.lineTo(x + width - radius.tr, y);\n  ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);\n  ctx.lineTo(x + width, y + height - radius.br);\n  ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);\n  ctx.lineTo(x + radius.bl, y + height);\n  ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);\n  ctx.lineTo(x, y + radius.tl);\n  ctx.quadraticCurveTo(x, y, x + radius.tl, y);\n  ctx.closePath();\n  if (fill) {\n    ctx.fill();\n  }\n  if (stroke) {\n    ctx.stroke();\n  }\n}\n\nfunction drawCircle() {\n  ctx.save();\n  /** outer ring **/\n  ctx.beginPath();\n  ctx.moveTo(cX, cY);\n  ctx.shadowBlur = 5 * dX;\n  ctx.shadowColor = \"rgba(40,40,40,1)\";\n  ctx.arc(cX, cY, radius + (8 * dX), 0, 2 * Math.PI, false);\n  ctx.arc(cX, cY, radius + (6 * dX), 0, 2 * Math.PI, true);\n  ctx.closePath();\n  ctx.fillStyle = \"rgba(40,40,40,1)\";\n  ctx.fill();\n  ctx.restore();\n  \n  \n  // do an arc of numbers...\n   ctx.save();\n    ctx.translate(canvas.width / 2, canvas.height / 2);\n    ctx.rotate(-140 * (Math.PI / 180));\n    ctx.font = \"bold \" + String(Math.floor(cX / 13)) + \"px Helvetica\";\n    ctx.textAlign = 'center';\n    ctx.fillStyle = '#000000';\n    for (var a=0;a<=100; a+=10)\n    {\n    ctx.rotate(23 * (Math.PI / 180));\n    ctx.fillText(n(a), 0, -(cY*0.65)); \n    }\n    ctx.restore();\n  \n\n  ctx.save();\n\n  if (showLED1 == true) {\n    /** Sub label 1 **/\n    ctx.font = \"bold \" + String(Math.floor(cX / 14)) + \"px Helvetica\";\n\n    // ctx.font = \"bold \" + String(Math.floor(cX / 14)) + \"px Helvetica\";\n    ctx.textAlign = 'center';\n    ctx.fillStyle = '#8A8A8A';\n    ctx.fillText(ledTitle2, cX + (cX / 2.5), cY - (cY / 9), (cX / 1));\n    if (degrees < setpoint2) {\n      ctx.beginPath(); // red led - size and scale need to be related to canvas - currently fixed\n      ctx.arc(cX + (cX / 2.5), cY, (cX / 15), 0, 2 * Math.PI, false);\n      ctx.closePath();\n      ctx.shadowBlur = 20;\n      ctx.shadowColor = \"rgba(255,0,0,1)\";\n      ctx.fillStyle = \"rgba(255,0,0,1)\";\n      ctx.fill();\n      ctx.restore();\n    } else {\n      ctx.beginPath(); // red led - size and scale need to be related to canvas - currently fixed\n      ctx.arc(cX + (cX / 2.5), cY, (cX / 15), 0, 2 * Math.PI, false);\n      ctx.closePath();\n      ctx.fillStyle = \"rgba(140,40,40,1)\";\n      ctx.fill();\n      ctx.lineWidth = 2 * dX;\n      ctx.strokeStyle = 'rgba(40,0,0,0.6)';\n      ctx.stroke();\n      ctx.restore();\n    }\n  }\n  if (showLED2 == true) {\n    /** Sub label 1 **/\n    ctx.font = \"bold \" + String(Math.floor(cX / 14)) + \"px Helvetica\";\n    ctx.textAlign = 'center';\n    ctx.fillStyle = '#8A8A8A';\n    ctx.fillText(ledTitle1, cX - (cX / 2.5), cY - (cY / 9), (cX / 1));\n\n    if (degrees2 > setpoint1) {\n      ctx.save(); // green led - size and scale need to be related to canvas - currently fixed\n      ctx.beginPath();\n      ctx.arc(cX - (cX / 2.5), cY, (cX / 15), 0, 2 * Math.PI, false);\n      ctx.closePath();\n      ctx.shadowBlur = 20;\n      ctx.shadowColor = \"rgba(0,200,0,1)\";\n      ctx.fillStyle = \"rgba(0,200,0,1)\";\n      ctx.fill();\n      ctx.restore();\n    } else {\n      ctx.save(); // green led - size and scale need to be related to canvas - currently fixed\n      ctx.beginPath();\n      ctx.arc(cX - (cX / 2.5), cY, (cX / 15), 0, 2 * Math.PI, false);\n      ctx.closePath();\n      ctx.fillStyle = \"rgba(40,110,40,1)\";\n      ctx.fill();\n      ctx.lineWidth = 2 * dX;\n      ctx.strokeStyle = 'rgba(0,40,0,0.6)';\n      ctx.stroke();\n      ctx.restore();\n    }\n  }\n\n  /** Main label **/\n  ctx.save();\n\n  ctx.beginPath;\n  roundRect(ctx, cX - (cX / 1.7), cY + (cY / 3), cX + (cX / 5.1), cY - (cY / 1.5), dX * 10, true);\n  ctx.clip()\n\n  ctx.beginPath;\n  ctx.strokeStyle = 'black';\n  ctx.lineWidth = 5;\n  ctx.shadowBlur = 15;\n  ctx.shadowColor = 'black';\n  ctx.shadowOffsetX = 0;\n  ctx.shadowOffsetY = 0;\n  ctx.fillStyle = \"rgba(255, 255, 180, .8)\";\n  roundRect(ctx, cX - (cX / 1.7) - 4, cY + (cY / 3) - 4, cX + (cX / 5.1) + 8, cY - (cY / 1.5) + 8, dX * 10, true);\n\n  ctx.restore();\n  ctx.save();\n\n  ctx.font = \"bold \" + String(Math.floor(cX / 8)) + \"px DOTMAT\";\n  ctx.textAlign = 'center';\n  ctx.fillStyle = '#8A8A8A';\n  ctx.fillText(title, cX, cY + (cY / 2.05));\n\n  /** Sub label **/\n  ctx.font = \"bold \" + String(Math.floor(cX / 12)) + \"px Helvetica\";\n  ctx.textAlign = 'center';\n  ctx.fillStyle = '#8A8A8A';\n  ctx.fillText(subTitle, cX, cY + (cY / 1.65));\n  ctx.restore();\n}\n\nfunction draw() {\n\n  if ((set1 == setpoint1) && (set2 == setpoint2) && (value1 == degrees) && (value2 == degrees2)) return;\n\n  if (set1 > setpoint1) setpoint1++;\n  else if (set1 < setpoint1) setpoint1--;\n  if (set2 > setpoint2) setpoint2++;\n  else if (set2 < setpoint2) setpoint2--;\n\n  if (value1 > degrees) degrees++;\n  else if (value1 < degrees) degrees--;\n  if (value2 > degrees2) degrees2++;\n  else if (value2 < degrees2) degrees2--;\n\n\n  width = 18 * dX,\n    radius = cX * .9,\n    radius_outer = cX * .9,\n    radius_inner = (radius - width) - (11 * dX),\n    kerning = 0.04,\n    color_alpha = 0.3;\n  totalArc = .47; // starting point for the arc\n\n  ctx.save();\n  // Radii of the white glow.\n  innerRadius = 20 * dX;\n  outerRadius = canvas.height / 2;\n  gradient = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, innerRadius, canvas.width / 2, canvas.height / 2, outerRadius);\n  gradient.addColorStop(0, 'white');\n  gradient.addColorStop(1, '#bbbbbb');\n  ctx.arc(canvas.width / 2, canvas.height / 2, canvas.height / 2 - (10 * dX), 0, 2 * Math.PI);\n  ctx.fillStyle = gradient;\n  ctx.fill();\n  ctx.restore();\n\n  drawCircle();\n  drawDonut();\n  \n  // Humid Circle\n  ctx.save();\n  ctx.beginPath();\n  ctx.translate(canvas.width / 2, canvas.height / 2);\n  tdegrees = -204 + (degrees2 * 227 / 100)\n    ctx.rotate((233 / 100 * setpoint1 - 118) * (Math.PI / 180));\n    ctx.beginPath();\n    ctx.arc(0, -(cY*0.82),cY*0.05, 0, 2 * Math.PI, false);\n    ctx.fillStyle = 'green';\n    ctx.fill();\n    ctx.lineWidth = 1.5;\n    ctx.strokeStyle = '#ffffff';\n    ctx.stroke();\n  ctx.restore();\n\n  // Temperature Circle\n  ctx.save(); \n  ctx.beginPath();\n  ctx.translate(canvas.width / 2, canvas.height / 2);\n  tdegrees = -204 + (degrees2 * 227 / 100)\n  ctx.rotate((233 / 100 * setpoint2 - 118) * (Math.PI / 180));\n  //ctx.beginPath();\n    ctx.arc(0, -(cY*0.82),cY*0.05, 0, 2 * Math.PI, false);\n    ctx.fillStyle = 'red';\n    ctx.fill();\n    ctx.lineWidth = 1.5;\n    ctx.strokeStyle = '#ffffff';\n    ctx.stroke();\n  ctx.restore();\n\n  // Save the current drawing state\n  ctx.save();\n  ctx.beginPath();\n  ctx.translate(canvas.width / 2, canvas.height / 2);\n  tdegrees = -204 + (degrees * 227 / 100)\n  ctx.rotate(tdegrees * (Math.PI / 180));\n\n  // shadow on lines??\n  ctx.shadowBlur = 4;\n  ctx.shadowColor = \"rgba(0,0,0,0.2)\";\n  ctx.shadowOffsetX = 5 * dX;\n  ctx.shadowOffsetY = 5 * dX;\n\n\n  ctx.drawImage(needle1, (canvas.width / 7) - (canvas.width / 4), -(canvas.height * needleWidth1 / 80), (canvas.height / 2), (canvas.width * needleWidth1 / 40));\n  // Restore the previous drawing state\n  ctx.restore();\n\n  if (showNeedle2 == true) {\n    // Save the current drawing state\n    ctx.save();\n    ctx.beginPath();\n    ctx.translate(canvas.width / 2, canvas.height / 2);\n    tdegrees = -204 + (degrees2 * 227 / 100)\n    ctx.rotate(tdegrees * (Math.PI / 180));\n    // shadow on lines??\n    ctx.shadowBlur = 4;\n    ctx.shadowColor = \"rgba(0,0,0,0.2)\";\n    ctx.shadowOffsetX = 5 * dX;\n    ctx.shadowOffsetY = 5 * dX;\n    ctx.drawImage(needle2, (canvas.width / 7) - (canvas.width / 4), -(canvas.height * needleWidth2 / 80), (canvas.height / 2), (canvas.width * needleWidth2 / 40));\n    ctx.restore();\n  }\n  ctx.save();\n  ctx.beginPath();\n  ctx.translate(canvas.width / 2, canvas.height / 2);\n  // draw the centre bit then restore the previous drawing state\n  ctx.drawImage(centre, (0 - (centre.height / 4)) * dX, (0 - (centre.width / 4)) * dY, (centre.height / 2) * dX, (centre.width / 2) * dY);\n  ctx.restore();\n  subTitle = \"Temp=\" + n(Math.floor(degrees)) + \"c \" + \"Hum=\" + n(Math.floor(degrees2)) + \"% \";\n}\n\ninit();\n\n</script>\n\n<canvas id = \"fred\"\nwidth = 310 height = 310 > </canvas>","storeOutMessages":true,"fwdInMessages":true,"x":980,"y":2480,"wires":[[]]},{"id":"fbecd5a1.989ac8","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"22","payloadType":"str","repeat":"","crontab":"","once":false,"x":630,"y":2340,"wires":[["9f9e89fe.db3518"]]},{"id":"6e98eeac.504cf","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"35","payloadType":"str","repeat":"","crontab":"","once":false,"x":630,"y":2380,"wires":[["9f9e89fe.db3518"]]},{"id":"9f9e89fe.db3518","type":"function","z":"c552e8d2.712b48","name":"msg.value1","func":"msg.value1=msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":790,"y":2360,"wires":[["22bad52a.82347a"]]},{"id":"10d0f056.a552e","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"11","payloadType":"str","repeat":"","crontab":"","once":false,"x":630,"y":2420,"wires":[["9eb8ebf8.be3108"]]},{"id":"ae37427b.2f8b3","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"60","payloadType":"str","repeat":"","crontab":"","once":false,"x":630,"y":2460,"wires":[["9eb8ebf8.be3108"]]},{"id":"9eb8ebf8.be3108","type":"function","z":"c552e8d2.712b48","name":"msg.value2","func":"msg.value2=msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":790,"y":2440,"wires":[["22bad52a.82347a"]]},{"id":"2251f312.3a575c","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"10","payloadType":"str","repeat":"","crontab":"","once":false,"x":630,"y":2500,"wires":[["7c5ad2de.3c767c"]]},{"id":"ae3f6f46.2aed8","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"20","payloadType":"str","repeat":"","crontab":"","once":false,"x":630,"y":2540,"wires":[["7c5ad2de.3c767c"]]},{"id":"7c5ad2de.3c767c","type":"function","z":"c552e8d2.712b48","name":"msg.set1","func":"msg.set1=msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":780,"y":2520,"wires":[["22bad52a.82347a"]]},{"id":"4a2ac4f5.5906dc","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"30","payloadType":"str","repeat":"","crontab":"","once":false,"x":630,"y":2580,"wires":[["f6ffef5f.562ca"]]},{"id":"8f5b3775.03c018","type":"inject","z":"c552e8d2.712b48","name":"","topic":"","payload":"40","payloadType":"str","repeat":"","crontab":"","once":false,"x":630,"y":2620,"wires":[["f6ffef5f.562ca"]]},{"id":"f6ffef5f.562ca","type":"function","z":"c552e8d2.712b48","name":"msg.set2","func":"msg.set2=msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":780,"y":2600,"wires":[["22bad52a.82347a"]]},{"id":"40cf30b4.d9549","type":"ui_group","z":"","name":"testa","tab":"66a97521.af8dac","disp":true,"width":"6"},{"id":"66a97521.af8dac","type":"ui_tab","z":"","name":"testa","icon":"dashboard"}]
Facebooktwittergoogle_pluspinterestlinkedin