I have a raspberry pi zero 2w running a 24″ monitor that is displaying relevant information in an area of the farm. The Raspi isn’t strong enough to run Chromium or Firefox (only 512MB RAM) so I am VNCing into a server that is displaying a Firefox window with the info I want. Takes much less RAM to VNC, and I have enough left over to run Node Red.
The problem is that the 24″ screen isn’t big enough to display everything, so I need to open a second tab that shows other stuff. That’s easy enough to do upon startup with a service file in /etc/systemd/system
[Unit]
Description=Launches Firefox fullscreen in the :3 X session for the etable screen
After=network-online.target
[Service]
User=etable
Type=simple
RemainAfterExit=yes
ExecStartPre=/bin/sleep 10
Environment=DISPLAY=:3
ExecStart=/usr/bin/firefox-esr
[Install]
WantedBy=graphical.target
Firefox has the two tabs I want already defined in defaults so it loads them up when it runs.
To get the two tabs switching back and forth, I use node red with the following flow:

[
{
"id": "1a3d9ae75c833589",
"type": "group",
"z": "64b7c4985fcb7349",
"style": {
"stroke": "#999999",
"stroke-opacity": "1",
"fill": "none",
"fill-opacity": "1",
"label": true,
"label-position": "nw",
"color": "#a4a4a4"
},
"nodes": [
"060aaf4b78b14dd5",
"2b7e0ee6d87c7de2",
"c8ba8b78e8999b10",
"6f4a2accaba5a034",
"6d98b5b2fd59f86a",
"1e13380653b2a62d",
"d6778ddedf9f8d53",
"2e73f15f19dc86a4",
"fda8631ae092c027",
"885a08861676cac8"
],
"x": 74,
"y": 359,
"w": 1192,
"h": 302
},
{
"id": "060aaf4b78b14dd5",
"type": "rbe",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"name": "if button state changes",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": true,
"property": "payload",
"topi": "topic",
"x": 460,
"y": 500,
"wires": [
[
"c8ba8b78e8999b10"
]
]
},
{
"id": "2b7e0ee6d87c7de2",
"type": "mqtt in",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"name": "",
"topic": "su900-modbus/stat/POWER",
"qos": "0",
"datatype": "auto-detect",
"broker": "9d32dc412883433f",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 220,
"y": 500,
"wires": [
[
"060aaf4b78b14dd5"
]
]
},
{
"id": "c8ba8b78e8999b10",
"type": "function",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"name": "2 minutes if button, otherwise 30 seconds",
"func": "var temp = msg.payload;\nvar button = global.get(\"button\");\nif ((temp != button) && ((temp == \"ON\") || (temp == \"OFF\"))) {\n global.set(\"button\",temp);\n global.set(\"test\",temp);\n msg.payload = {};\n msg.payload = 120;\n}\nelse if (temp === false) {\n msg.payload = {};\n msg.payload = 30;\n}\nelse return;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 680,
"y": 400,
"wires": [
[
"6d98b5b2fd59f86a",
"1e13380653b2a62d"
]
]
},
{
"id": "6f4a2accaba5a034",
"type": "rbe",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"name": "",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": true,
"property": "payload",
"topi": "topic",
"x": 410,
"y": 400,
"wires": [
[
"c8ba8b78e8999b10"
]
]
},
{
"id": "6d98b5b2fd59f86a",
"type": "timer-node",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"name": "",
"topic": "",
"timer": "",
"payloadOn": "true",
"payloadOnType": "bool",
"payloadOff": "false",
"payloadOffType": "bool",
"x": 710,
"y": 520,
"wires": [
[
"6f4a2accaba5a034",
"2e73f15f19dc86a4",
"fda8631ae092c027"
],
[]
]
},
{
"id": "1e13380653b2a62d",
"type": "change",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"name": "change timer set to timer start",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "true",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 570,
"y": 600,
"wires": [
[
"d6778ddedf9f8d53"
]
]
},
{
"id": "d6778ddedf9f8d53",
"type": "delay",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"name": "",
"pauseType": "delay",
"timeout": "100",
"timeoutUnits": "milliseconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 790,
"y": 620,
"wires": [
[
"6d98b5b2fd59f86a"
]
]
},
{
"id": "2e73f15f19dc86a4",
"type": "debug",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"name": "debug 1",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1100,
"y": 440,
"wires": []
},
{
"id": "fda8631ae092c027",
"type": "function",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"name": "only send tab switch command once",
"func": "var temp = msg.payload;\nif (temp === false) {\n return msg;\n}",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1050,
"y": 520,
"wires": [
[
"885a08861676cac8"
]
]
},
{
"id": "885a08861676cac8",
"type": "exec",
"z": "64b7c4985fcb7349",
"g": "1a3d9ae75c833589",
"command": "/bin/bash /home/pi/Desktop/switchtabs.sh",
"addpay": "",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "Swap tabs",
"x": 1170,
"y": 600,
"wires": [
[],
[],
[]
]
},
{
"id": "9d32dc412883433f",
"type": "mqtt-broker",
"name": "DebianVM",
"broker": "192.168.3.98",
"port": 1883,
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": 4,
"keepalive": 60,
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
MQTT connection to the ESP8266 device running tasmota with the button attached and a dummy relay defined. It sends out a stat message when the button is pressed with ON or OFF as the payload. Homebridge is polling stat every 60 seconds so I put in a filter node to only send on the data when it changes (i.e. when the button is pressed)
Then a function node that checks for a button press vs a loop (the payload is different) and sets the timer depending. The user wants the screen static for 2 minutes if he pushes the button, but otherwise the screen should rotate on a 30 second loop.
var temp = msg.payload;
var button = global.get("button");
if ((temp != button) && ((temp == "ON") || (temp == "OFF"))) {
global.set("button",temp);
global.set("test",temp);
msg.payload = {};
msg.payload = 120;
}
else if (temp === false) {
msg.payload = {};
msg.payload = 30;
}
else return;
return msg;
The timer node pushes false if the timer runs out (which is when I want to call the exec node) and a true when the timer starts. In the exec node, I call a script that calls xdotool to press Ctrl-Tab.
In the node: /bin/bash /home/pi/Desktop/switchtabs.sh
In switchtabs.sh:
#!/bin/sh
export DISPLAY=:0
xdotool key ctrl+Tab
exit 0
Make sure switchtabs.sh is executable with sudo chmod +x switchtabs.sh