Keeping the dashboard alive

Hi folks,

I have been bragging too much lately about Node-RED's capabilities. As a result, I'm getting more and more questions from people in my environment :woozy_face: Today I received a very interesting question, but seems my brain has shrinked by the cold weather ...

Does anybody know if it is possible to keep a dashboard alive, i.e. without 'connection lost' issues. Have seen already some proposals in the past, but I would like to have a pure Node-RED solution. So no third-party tools, or tweaking on operating system level or ... An automatic reconnect (after a connection list) is also not what I'm aiming at.

  1. Found e.g. here a way to keep an AngularJs application alive. But I'm not sure whether that is a good solution?
  2. I'm also not sure whether it is enough to keep the AngularJs application alive (like in the previous point): I assume that the websocket channel also should be kept alive somehow? Or is it enough to just keep pushing data into the dashboard nodes, to keep the websocket alive and kicking?
  3. And suppose I could succeed in the previous two points (i.e. web app and websocket alive), perhaps keeping a websocket channel + AngularJs application open could be a bad practice at long term? I could image that e.g. some system resource might need a cleanup/free from time to time ...

All ideas, tips, keywords, ... are welcome!

Bart

1 Like

Why isn't it alive ? As long as the connection to the server is there it should stay alive... The message only appears if the connection is really lost in which case you won't get any data so it can't be "alive"...

Or is that not the case - in which case how can I replicate it ?

I've noticed sometimes when the dashboard client is on a mobile device that is waken after sleep that the dashboard lost its connection to the server without the message "connection lost" being displayed. Refreshing the page reestablishes the connection but it would be nice if it would reestablish itself automatically in that case.

I've noticed this on Android too. But it seems to "come back" if you wait awhile after the phone "wakes up" of course refreshing the page gets you there faster.

I've multiple dashboard tabs open in Chromium on my desktop computer and they stay active unless the connection really is lost, and once the sourcing system reboots (ping based watchdog timer on the wifi connection -- PiZero-W wifi chip flakeyness) everything functions fine again.

Hi guys,

Like @hugobox has described, I get that frequently when my Android phone awakens. So I thought this was normal behaviour of the dashboard on various systems. Think I need to create a test setup with a screen that is ON all the time.

It think it does that already on my Android phone.
Moreover the guy who asked me this, is going to hang 3 monitors in his restaurant. So would be a bit amateurish if there appears a 'reconnecting' periodically on the screens ... Therefore I was looking for a solution that the AngularJS application don't goes into sleep mode.

Ok, is that is the current behaviour then I'm worrying about nothing.

Will need to do some tests and report my results here ...
Thanks all !!

Hi Bart,

I think you are referring to the case where an android device after some time (while there is no user activity) automatically decides to drop its wifi connection (so save battery) and consequently the dashboard will report lost connection the next time the user is checking the node-red dashboard.

A working solution for this problem, would interest me as well.

Hi Jan, indeed it was Android. Didn't realize that was the cause, but sounds logical...

Not sure what we can do about that - Surely that is down to the browser on the phone reacting properly to the wake-up signal from the OS to give itself a nudge. The websocket doesn't know it's been told to go to sleep and will just see that the last message was ages ago and so must be "disconnected" and will then reconnect correctly. Short of not actually telling the user then not sure what I can do.

I've replaced my iPhone 6 by an iPhone XR recently, there is still a Connection lost message but the refreshing time is now much shorter 1-2 sec from 6-7 sec.
The new A12 chip makes the difference.

It is not only to do with the wifi connection, Android will also put the application to sleep after a while.

When I put 2 graphs with 10 data sources on the active dashboard, I could see "connection lost" even when tablet didn't go to suspend mode. It looks like every source (data with unique topic) is refreshing the page (partially, can see "loading" popup for each source) so with 10+ my (a bit old) tablet is barely handling so much traffic(?) and can cause connection lost. Then, unfortunately, the whole page is being reloaded.
Sometimes I have to wait ~20..60s to see actual dashboard, which is far from perfect for tablet that is supposed to hang on the wall as a wall panel. I've moved graphs to some other tab so the main tab is now more smooth. I wonder if graph could be optimized.

Anyway, I'm using WallPanel so it can keep screen alive but that's not the working solution for "connection lost" issue.

1 Like

At what rate are you adding points, and what is the time range of the graph? For example you might be adding the points 1 per minute for each line and the graph range is 4 hours.

One graph has range of 1h, second has 24h. Points are added every few minutes if I remember correctly (temperature around the house).

That is not too excessive, ten lines every two minutes for 24 hours would be 7200 points which all have to be shuffled across when you get an update. Perhaps the tablet is just not up to the job though. Is it ok if you view it on a PC or newer mobile device?
Another possibility is that you have a poor wifi connection to the tablet.

For sure tablet has its good days behind which was the reason I gave it a second life as a simple wall panel. Anyway, a bit of offtopic here with the graph (perhaps that widget requires stronger machine to work smoothly). I mentioned it as it could give some hint on how to get connection lost without long wait or playing with sleeping the device. I assume it can freeze the browser for enough time for the connection to be dropped (that's my guess). It's not persistent, just "from time to time" - the more points/data sources, the easier to observe the behaviour.

For stronger devices I would suggest create temporary high loaded graph to try to invoke connection lost and then play on how to avoid that to resolve the main issue from this thread.

I have a number of android tablets around in the house (always on power, wifi connected) and I think if you add code so that the browser sends "something" over the websocket, the connection will be kept alive. At least I think so...
In addition you can add a function that regularly checks the status of the websocket connection and re-initiates it if needed.

All this I have implemented in a dashboard ui_template and it seems working fine (at least I do not notice any disconnects)

I will see if I can give some code examples from my implementation

2 Likes

Looking forward to your examples. I've an old Nook tablet that I've installed LinageOS (Cyanogenmod successor) that I'd like to use for a 24/7 monitor, but the screen going to sleep issue makes it kind of pointless -- the idea is to get valid info when I glance at the screen and waiting or having to touch something defeats the whole purpose.

You should be able to tell Android to keep the screen active. Google should tell you how.

To keep the screen on (dimmed) I use an app named Screen On

This allows you to keep the screen on at a customized level (I just let it dim, it works fine for a quick view when passing by)

The example here is for a ui_template node (Widget in group). It will send a ping every 15 second. You can put it in an existing group. Right now I am actually getting uncertain if the code runs in the browser or in the server, well, how to check that?
image


<!DOCTYPE html>
<html>
<script type="text/javascript">

var theScope = scope;
var keepAlive = null;

scope.$watch('msg', function(msg) {
    init(msg.payload);
});

function init(dta) {
    if(keepAlive != null){
        clearInterval(keepAlive);
    }
    theScope.send({payload:'just the init'});
    keepAlive = setInterval(doPing, 15000);
}

function doPing() {
    theScope.send({payload:'just a ping: '+keepAlive});
}

</script> 

</html>

I have another implementation for a standard web page (not using the dashboard) where I initiate and control the websocket connection myself (I do not know if it is possible or how to check the status of the websocket connection from code inside a ui_template node)

Anyway, will make an example of how to do that in a standard template node as well

3 Likes

This second example is a ”traditional” web page using the websocket nodes.

To check the status of a websocket connection, there is a property available (wSocket.readyState in the code sample below) we can use

When the readyState is 1, the connection is OK, when it is 3 or 0, it is lost/disconnected. So we can use this to re-initiate the connection when & if needed

The sample works as follows:

When you load the page in your browser, the websocket connection gets initiated and if successful, the background goes green (if it fails, it goes grey)

Two timers are started, one for a Ping function, the other for checking the websocket connection state

First you see the websocket checking the state every 1,5 second, writing just 1 on the screen if connection is fine

After 15 seconds, you will see the first incoming websocket message from the Ping timer, screen background now goes black. When in this state, everything is just fine.

If you run the browser on a mobile device, you can now try to disconnect the wifi. The screen should go gray within a second or so. When you turn on the wifi again, the connection is quickly re-established and operational again

image

The flow:

[{"id":"fd1492e8.21c1f","type":"http response","z":"d01d2553.fd9838","name":"","x":580,"y":650,"wires":[]},{"id":"56d7b424.222dac","type":"http in","z":"d01d2553.fd9838","name":"","url":"/test","method":"get","upload":false,"swaggerDoc":"","x":240,"y":650,"wires":[["1f42f57e.0f2f0b"]]},{"id":"1f42f57e.0f2f0b","type":"template","z":"d01d2553.fd9838","name":"test","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<html lang=\"en\">\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/> \n<META HTTP-EQUIV=\"CACHE-CONTROL\" CONTENT=\"NO-CACHE\">\n<META HTTP-EQUIV=\"PRAGMA\" CONTENT=\"NO-CACHE\">\n<title>KeepAlive example</title>\n<!-- comment -->\n\n\n<script type=\"text/javascript\">\nvar pingOK = true;\nvar initial = setInterval(refresh, 1500);\nvar keepAlive = setInterval(kAlive, 15000);\nvar wSocket = null;\nvar inProgress = false;\nvar prot = \"\";\nvar ip = \"\";\nvar url = \"\";\n\nfunction init() {\n// event handlers for WebSocket:\n    inProgress = true;\n    wSocket = null;\n    prot = \"\";\n    ip = location.host;\n\n    if (location.protocol == \"https:\") {\n        prot = \"wss://\";\n    }\n    else {\n        prot = \"ws://\";\n    }\n\n    url = prot + ip + \"/ws/location\"; \n    writeToScreen(url);\n    wSocket = new WebSocket(url);\n    wSocket.onopen = function() {\n        document.getElementById(\"theBody\").style.backgroundColor =\"green\"; //Green\n    };\n    wSocket.onmessage = wsMessage;\n    wSocket.onclose = function() {\n        document.getElementById(\"theBody\").style.backgroundColor =\"#C0C0C0\"; //Grey\n    };\n    inProgress = false;\n}\n\n\nfunction kAlive() {\n    if (wSocket.readyState == 1 && !inProgress) {\n        doPing();\n    }\n}\n\n\nfunction doPing() {\n    pingOK = false;\n    doSend({'method':'Ping','id':keepAlive});\n}\n\n\nfunction refresh() {\n    writeToScreen(wSocket.readyState);\n\n    if (wSocket.readyState == 3 || wSocket.readyState == 0){\n        if (!inProgress) {\n            pingOK = true;\n            //writeToScreen('refresh:inProgress :'+inProgress+wSocket.readyState);\n            init();\n        }\n    }\n}\n\n\nfunction doSend(strng) {\n    if (wSocket.readyState == 1) {\n        wSocket.send(JSON.stringify(strng));\n        //wSocket.send(strng);\n    }\n}\n\n\nfunction writeToScreen(message) { \n    var pre = document.createElement(\"p\"); \n    pre.style.wordWrap = \"break-word\"; \n    pre.innerHTML = message; \n    theBody.appendChild(pre); \n} \n\n\nfunction wsMessage(event) {\n    //alert(event.data);\n    writeToScreen(event.data);\n    inProgress = true;\n    document.getElementById(\"theBody\").style.backgroundColor =\"black\"; //Black\n    pingOK = true;\n    inProgress = false;\n}\n\n</script>\n\n<body id=\"theBody\" onLoad=\"init()\" bgcolor=black text=white link=white vlink=white alink=white/>\n<br />\n</body>\n\n\n\n\n","x":430,"y":650,"wires":[["fd1492e8.21c1f"]]},{"id":"26a9c7a9.0e8208","type":"websocket in","z":"d01d2553.fd9838","name":"","server":"d43b3f82.93abc","client":"","x":260,"y":730,"wires":[["a397318d.2f10b"]]},{"id":"4489e27d.f932ec","type":"websocket out","z":"d01d2553.fd9838","name":"","server":"d43b3f82.93abc","client":"","x":620,"y":730,"wires":[]},{"id":"a397318d.2f10b","type":"function","z":"d01d2553.fd9838","name":"","func":"delete msg._session;\nif (msg.payload !== undefined){\n    return msg;\n}\n","outputs":1,"noerr":0,"x":430,"y":730,"wires":[["4489e27d.f932ec"]]},{"id":"d43b3f82.93abc","type":"websocket-listener","z":"","path":"/ws/location","wholemsg":"false"}]

The code for the html page (part of the flow):`

<html lang="en">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 
<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
<title>KeepAlive example</title>
<!-- comment -->


<script type="text/javascript">
var pingOK = true;
var initial = setInterval(refresh, 1500);
var keepAlive = setInterval(kAlive, 15000);
var wSocket = null;
var inProgress = false;
var prot = "";
var ip = "";
var url = "";

function init() {
// event handlers for WebSocket:
    inProgress = true;
    wSocket = null;
    prot = "";
    ip = location.host;

    if (location.protocol == "https:") {
        prot = "wss://";
    }
    else {
        prot = "ws://";
    }

    url = prot + ip + "/ws/location"; 
    writeToScreen(url);
    wSocket = new WebSocket(url);
    wSocket.onopen = function() {
        document.getElementById("theBody").style.backgroundColor ="green"; //Green
    };
    wSocket.onmessage = wsMessage;
    wSocket.onclose = function() {
        document.getElementById("theBody").style.backgroundColor ="#C0C0C0"; //Grey
    };
    inProgress = false;
}


function kAlive() {
    if (wSocket.readyState == 1 && !inProgress) {
        doPing();
    }
}


function doPing() {
    pingOK = false;
    doSend({'method':'Ping','id':keepAlive});
}


function refresh() {
    writeToScreen(wSocket.readyState);

    if (wSocket.readyState == 3 || wSocket.readyState == 0){
        if (!inProgress) {
            pingOK = true;
            //writeToScreen('refresh:inProgress :'+inProgress+wSocket.readyState);
            init();
        }
    }
}


function doSend(strng) {
    if (wSocket.readyState == 1) {
        wSocket.send(JSON.stringify(strng));
        //wSocket.send(strng);
    }
}


function writeToScreen(message) { 
    var pre = document.createElement("p"); 
    pre.style.wordWrap = "break-word"; 
    pre.innerHTML = message; 
    theBody.appendChild(pre); 
} 


function wsMessage(event) {
    //alert(event.data);
    writeToScreen(event.data);
    inProgress = true;
    document.getElementById("theBody").style.backgroundColor ="black"; //Black
    pingOK = true;
    inProgress = false;
}

</script>

<body id="theBody" onLoad="init()" bgcolor=black text=white link=white vlink=white alink=white/>
<br />
</body>

3 Likes