{"id":335,"date":"2025-06-30T13:54:59","date_gmt":"2025-06-30T13:54:59","guid":{"rendered":"http:\/\/blog.ltzs.us\/?p=335"},"modified":"2026-04-15T11:41:54","modified_gmt":"2026-04-15T11:41:54","slug":"status-display-with-monitor-tv-and-raspi-zero-2w","status":"publish","type":"post","link":"http:\/\/blog.ltzs.us\/?p=335","title":{"rendered":"Status display with monitor\/TV and Raspi Zero 2W"},"content":{"rendered":"\n<p>I have a number of places around the farm where I need information displayed so workers can keep track of machine status, temperatures, and power consumption. I have a central server that has a influxdb database and grafana to display the info on various dashboards. The cheapest way I have found to have a live dashboard is using an old computer monitor and a Raspberry Pi Zero 2W. The monitor has USB ports that can power the Raspi, but settings need to be adjusted so they are powered all the time, otherwise if the screen goes into powersave mode, it will cut the Raspi also. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8902-1024x768.jpg\" alt=\"\" class=\"wp-image-338\" srcset=\"http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8902-1024x768.jpg 1024w, http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8902-300x225.jpg 300w, http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8902-768x576.jpg 768w, http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8902-1536x1152.jpg 1536w, http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8902-850x638.jpg 850w, http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8902.jpg 2016w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The Raspi is connected via a mini-HDMI adapter -&gt; HDMI cable -&gt; HDMI\/DVI-D adapter. Aliexpress sells those adapters for cheap.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"824\" src=\"http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8901-1024x824.jpg\" alt=\"\" class=\"wp-image-336\" srcset=\"http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8901-1024x824.jpg 1024w, http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8901-300x241.jpg 300w, http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8901-768x618.jpg 768w, http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8901-850x684.jpg 850w, http:\/\/blog.ltzs.us\/wp-content\/uploads\/2025\/06\/IMG_8901.jpg 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The Zero 2W only has 512MB RAM, so does not run a desktop environment very well. It can handle a basic desktop, but trying to load a browser (Chromium) with a heavy website like Grafana would be glacially slow. So I let the server do the heavy lifting and just VNC from the raspberry pi. <\/p>\n\n\n\n<p>The steps to accomplish this are as follows:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Establish a new user on the server (Debian LXQT) using <a href=\"http:\/\/blog.ltzs.us\/?p=235\" data-type=\"link\" data-id=\"http:\/\/blog.ltzs.us\/?p=235\">this guide<\/a>. Be sure to adjust the VNC session screen resolution to the monitor you are using (in my case 1920&#215;1080).<\/li>\n\n\n\n<li>VNC into the session you just created.<\/li>\n\n\n\n<li>Disable xscreensaver in preferences.<\/li>\n\n\n\n<li>Make firefox start on boot by going to Preferences-&gt;LXQt Settings-&gt;Session Settings, click on AutoStart, click Add, name it firefix and put <mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>\/usr\/bin\/firefox-esr -kiosk<\/code><\/mark> in the command spot.<\/li>\n\n\n\n<li>Launch Firefox, arrange the tabs how you want, then go to Settings-&gt;Home-&gt;Use Current Pages to lock the pages on launch.<\/li>\n\n\n\n<li>In URL, type about:config, accept the warning and search for <mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>browser.sessionstore.resume_from_crash<\/code><\/mark>, double click on true and it will change to false. Do the same for <mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>browser.sessionstore.max_resumed_crashes<\/code><\/mark>, double click the value and change it to 0 (zero).<\/li>\n<\/ol>\n\n\n\n<p>Now the VNC session is ready for the raspi to connect to it.<\/p>\n\n\n\n<p>On the raspberry pi, load a 32-bit OS from the website or Raspberry Pi Imager. The things that need to happen on the Raspi are:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Use Display Power Management System commands to turn the screen on and off on a schedule (i.e. off at night) and to prevent the screen from going to sleep (defaults to 10 minutes) because of no mouse movement.<\/li>\n\n\n\n<li>A script to launch VNC Viewer on boot, using <a href=\"https:\/\/github.com\/RMPR\/atbswp\" data-type=\"link\" data-id=\"https:\/\/github.com\/RMPR\/atbswp\">ATBSWP<\/a>.<\/li>\n\n\n\n<li>Change windowing system to X11, enable VNC<\/li>\n<\/ol>\n\n\n\n<p>Create two scripts, screenon.sh and screenoff.sh<\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo nano ~\/Desktop\/screenon.sh<\/code><\/mark><\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">#!\/bin\/sh\n\n# Determine execution context\nHOUR=$(date +%H)\nif [ &quot;$HOUR&quot; = &quot;07&quot; ]; then\n    CONTEXT=&quot;7am timer&quot;\n    TIMEOUT=30  # Short timeout since display should be ready\n    SLEEP_INTERVAL=1\nelse\n    CONTEXT=&quot;boot&quot;\n    TIMEOUT=120  # Longer timeout for boot\n    SLEEP_INTERVAL=2\nfi\n\necho &quot;Starting screenon script ($CONTEXT execution)&quot;\n\n# Set DISPLAY environment variable\nexport DISPLAY=:0\necho &quot;Set DISPLAY to :0&quot;\n\n# Wait for X11 to be available with context-appropriate timeout\nELAPSED=0\necho &quot;Waiting for X11 display to become available...&quot;\n\nwhile ! xset -display :0 q &gt;\/dev\/null 2&gt;&amp;1; do\n    if [ $ELAPSED -ge $TIMEOUT ]; then\n        echo &quot;ERROR: Timeout waiting for X11 display after $TIMEOUT seconds ($CONTEXT)&quot; &gt;&amp;2\n        exit 1\n    fi\n    \n    echo &quot;X11 not ready yet, waiting... (elapsed: ${ELAPSED}s) [$CONTEXT]&quot;\n    sleep $SLEEP_INTERVAL\n    ELAPSED=$((ELAPSED + SLEEP_INTERVAL))\ndone\n\necho &quot;X11 display is now available (waited ${ELAPSED}s) [$CONTEXT]&quot;\n\n# Set DPMS timeouts to maximum (effectively disable screen blanking)\necho &quot;Setting DPMS timeouts to maximum...&quot;\nif \/usr\/bin\/xset -display :0 dpms 32767 32767 32767; then\n    echo &quot;Successfully set DPMS timeouts&quot;\nelse\n    echo &quot;ERROR: Failed to set DPMS timeouts&quot; &gt;&amp;2\n    exit 1\nfi\n\n# Force display on\necho &quot;Forcing display on...&quot;\nif \/usr\/bin\/xset -display :0 dpms force on; then\n    echo &quot;Successfully forced display on&quot;\nelse\n    echo &quot;ERROR: Failed to force display on&quot; &gt;&amp;2\n    exit 1\nfi\n\n# Verify current DPMS settings\necho &quot;Current DPMS settings:&quot;\n\/usr\/bin\/xset -display :0 q | grep -A 5 &quot;DPMS&quot;\n\necho &quot;Screenon script completed successfully ($CONTEXT)&quot;\nexit 0<\/code><\/pre>\n\n\n\n<p>Change permissions so script can execute <mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo chmod 755 ~\/Desktop\/screenon.sh<\/code><\/mark><\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo nano ~\/Desktop\/screenoff.sh<\/code><\/mark><\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">#!\/bin\/sh\n\necho &quot;Starting screenoff script (5pm execution)&quot;\n\n# Set DISPLAY environment variable\nexport DISPLAY=:0\necho &quot;Set DISPLAY to :0&quot;\n\n# Quick check if X11 is available (should be at 5pm)\nif ! xset -display :0 q &gt;\/dev\/null 2&gt;&amp;1; then\n    echo &quot;ERROR: X11 display not available at 5pm - this is unexpected&quot; &gt;&amp;2\n    exit 1\nfi\n\necho &quot;X11 display confirmed available&quot;\n\n# Force display off\necho &quot;Forcing display off...&quot;\nif \/usr\/bin\/xset -display :0 dpms force off; then\n    echo &quot;Successfully forced display off&quot;\nelse\n    echo &quot;ERROR: Failed to force display off&quot; &gt;&amp;2\n    exit 1\nfi\n\necho &quot;Screenoff script completed successfully (5pm)&quot;\nexit 0<\/code><\/pre>\n\n\n\n<p>Change permissions so script can execute <mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo chmod 755 ~\/Desktop\/screenoff.sh<\/code><\/mark><\/p>\n\n\n\n<p>Create service files for screenon.sh and screenoff.sh, and timer files for the timed parts. Note that screenoff.timer has Persistent=true removed so if it fails for some reason it won&#8217;t try immediately on startup. For screenon.service WantedBy=graphical.target will make it run on boot to set the DPMS timeouts:<\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo nano \/etc\/systemd\/system\/screenon.service<\/code><\/mark><\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">[Unit]\nDescription=Keep screen on - disable DPMS\nAfter=graphical.target\nWants=graphical.target\n\n[Service]\nType=oneshot\nUser=pi\nGroup=pi\nEnvironment=DISPLAY=:0\nExecStart=\/home\/pi\/Desktop\/screenon.sh\nRemainAfterExit=yes\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=graphical.target<\/code><\/pre>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo nano \/etc\/systemd\/system\/screenon.timer<\/code><\/mark><\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">[Unit]\nDescription=Run screenon service at 7am daily\nRequires=screenon.service\n\n[Timer]\nOnCalendar=*-*-* 07:00:00\nPersistent=true\n\n[Install]\nWantedBy=timers.target<\/code><\/pre>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo nano \/etc\/systemd\/system\/screenoff.service<\/code><\/mark><\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">[Unit]\nDescription=Turn screen off - force DPMS off\nAfter=graphical.target\nWants=graphical.target\n\n[Service]\nType=oneshot\nUser=pi\nGroup=pi\nEnvironment=DISPLAY=:0\nExecStart=\/home\/pi\/Desktop\/screenoff.sh\nStandardOutput=journal\nStandardError=journal<\/code><\/pre>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo nano \/etc\/systemd\/system\/screenoff.timer<\/code><\/mark><\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-\">[Unit]\nDescription=Run screenoff service at 5pm daily\nRequires=screenoff.service\n\n[Timer]\nOnCalendar=*-*-* 17:00:00\n\n[Install]\nWantedBy=timers.target<\/code><\/pre>\n\n\n\n<p>Activate all the services and timers<\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo systemctl daemon-reload<\/code><\/mark><\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo systemctl enable screenon.service<\/code><\/mark><\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo systemctl enable screenon.timer<\/code><\/mark><\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo systemctl enable screenoff.service<\/code><\/mark><\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo systemctl enable screenoff.timer<\/code><\/mark><\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo systemctl start screenon.timer<\/code><\/mark><\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo systemctl start screenoff.timer<\/code><\/mark><\/p>\n\n\n\n<p>For Automate The Boring Stuff With Python (ATBSWP), VNC into the Raspi, follow the instructions to install it, clear the desktop like it will look at bootup, then launch it with <mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>python3 atbswp\/atbswp.py<\/code><\/mark> and then push record. Click the terminal icon up top, type in <mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>vncviewer 192.168.3.98:5904 -Fullscreen -ColorLevel Full -Scaling 1920x1080<\/code><\/mark> (adjusting values as appropriate), press enter, type the password, press enter, then push the stop button.<\/p>\n\n\n\n<p>This will create the script that you can adjust to remove mouse movements, reduce delays and increase delays as necessary (since the Raspi is often slow starting up). Here is my script:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">#!\/bin\/env python3\n# Created by atbswp v0.3.1 (https:\/\/git.sr.ht\/~rmpr\/atbswp)\n# on 30 Nov 2024 \nimport pyautogui\nimport time\npyautogui.FAILSAFE = False\n\ntime.sleep(10.0)\npyautogui.mouseDown(152, 15, &#039;left&#039;)\npyautogui.mouseUp(152, 15, &#039;left&#039;)\ntime.sleep(3.0)\npyautogui.write(&#039;vncviewer 192.168.3.98:5904 -Fullscreen -ColorLevel Full -Scaling 1920x1080&#039;)\ntime.sleep(0.2)\npyautogui.press(&#039;enter&#039;)\ntime.sleep(25)\npyautogui.write(&#039;password&#039;)\npyautogui.press(&#039;enter&#039;)<\/code><\/pre>\n\n\n\n<p>To make that script launch at startup, you can use a few different methods, like systemd, crontab or the obscure method I found, editing a file named autostart in the ~\/.config\/lxsession\/LXDE-pi folder with the following line added.<\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo nano ~\/.config\/lxsession\/LXDE-pi\/autostart<\/code><\/mark><\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-\">@lxterminal -e \/home\/pi\/Desktop\/boot.sh<\/code><\/pre>\n\n\n\n<p>The file should look like<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-\">@lxpanel --profile LXDE-pi\n@pcmanfm --desktop --profile LXDE-pi\n@xscreensaver -no-splash\n@point-rpi\n@lxterminal -e \/home\/pi\/Desktop\/boot.sh<\/code><\/pre>\n\n\n\n<p>boot.sh is a bash script that just calls the python ATBSWP script.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-\">#!\/bin\/sh\n\/usr\/bin\/python3 \/home\/pi\/Desktop\/launchvncsession\nexit 0<\/code><\/pre>\n\n\n\n<p>Next step is to make boot.sh executable<\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo chmod 755 \/home\/pi\/Desktop\/boot.sh<\/code><\/mark><\/p>\n\n\n\n<p>Next step, change to X11 windowing environment<\/p>\n\n\n\n<p><mark style=\"background-color:#fcb900\" class=\"has-inline-color\"><code>sudo raspi-config<\/code><\/mark><\/p>\n\n\n\n<p>choose Advanced Options, then A6 Wayland, then W1 X11<\/p>\n\n\n\n<p>If you want to enable VNC access so you can troubleshoot, chooseInterface Options, then I3 VNC <\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have a number of places around the farm where I need information displayed so workers can keep track of machine status, temperatures, and power consumption. I have a central server that has a influxdb database and grafana to display the info on various dashboards. The cheapest way I have found to have a live&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-335","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=\/wp\/v2\/posts\/335","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=335"}],"version-history":[{"count":9,"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=\/wp\/v2\/posts\/335\/revisions"}],"predecessor-version":[{"id":457,"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=\/wp\/v2\/posts\/335\/revisions\/457"}],"wp:attachment":[{"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=335"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=335"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.ltzs.us\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=335"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}