Friday, 20 June 2025

Raspberry Pi Desktop Digital Signage (RaspbianOS + Wayland)

 

In my last post I mentioned turning a Raspberry Pi + Official Touch Screen into a nifty desk clock.  Well, it turns out that I actually have two desktop Raspberry Pis  and as I also wanted something to do with the second one I decided to turn it into digital signage that could swap between multiple full screen web pages in a kiosk mode browser.

Because I am probably a masochist, I wanted to do this with a typical Raspbian 64-bit OS install running Wayland. Many of the 'traditional' solutions to this problem are X-Windows based and these don't work with a Wayland descended compositor.

I'll detail the steps in the next couple of posts, but the summary of the recipe is

  •    Use python playwright and create a python script and shells script to drive the browser
  •    Use the /etc/xdg/labwc/autostart script to autostart this python script 
  •    Use ydotool to hide the mouse cursor 
  •    Create a shell scripts and a systemd service script to enable ydotool 

If you are still with me, you will have noticed that it takes much more effort to hide the mouse cursor than it does to create the page flipping kiosk browser solution. Thanks Wayland!

I'll cover the steps to get the browser automation working in this post, and cover the mouse automation in a future post.  Here is a detailed description of the first couple of steps above


Assumptions

For this recipe I am assuming that you have a Raspberry Pi 4 or better running the current (2025) 64-bit version of Raspbian OS based on Debian Bookworm.  I am also going to assume that you have configured Raspbian to automatically login to the GUI.


Install and configure SSH

Make sure that you have SSH installed and working on the device, so that you can control the device once the kiosk browser is running, otherwise you will be locked out!


Create a project folder

Create a folder to hold all of the scripts etc for this recipe. Mine is called /home/pi/dev/pysign


Create (and activate) a virtual python environment

We are going to be using python for this recipe, and for Raspbian OS this requires us to create and activate a virtual environment.  I recommend that you create this in the project folder to keep everything together.

>cd /home/pi/dev/pysign

> python3 -m venv /home/pi/dev/pysign/pysign_env

> source /home/pi/dev/pysign/pysign_env/bin/activate

(important, from this point forwards make sure that you activate the virtual environment in each ssh/terminal window that you use to do python related tasks)


Install Playwright Python

Playwright (https://playwright.dev/) is a modern web testing suite that supports multiple browsers and has multiple SDKs.  Unlike older suites like Puppeteer, it works well Wayland and the version of Chromium installed by Raspbian OS.

To install playwright, use a terminal with the python virtual environment activated and run the the following commands:

> pip install pytest-playwright asyncio

This will install playwright and the python playwright SDK. It will also install asyncio, which we will use for making asynchronous python methods.


Create the browser automation script

Next we will create the python script that uses playwright to drive the browser.  Here is a basic script, which swaps between a couple of hard coded URLs.  I called mine pysign.py


from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        app_data_path="/tmp"
        browser = await p.chromium.launch_persistent_context(
                app_data_path,
                executable_path="/usr/lib/chromium/chromium",
                args=[ "--kiosk",
                        "--start-maximized",
#                        "--disable-infobars",
                        "--ozone-platform=wayland",
                        "--no-restore-on-startup",
                        "--noerrdialogs"],
                ignore_default_args=["--enable-automation"],
                headless=False,
                no_viewport=True
)
        page = await browser.new_page()
        while True:
                await page.goto("https://playwright.dev") #Display Web Page 1
                time.sleep(30)				  #Wait 30 seconds
                await page.goto("https://old.reddit.com") #Display Web Page 2
                time.sleep(30)
        await browser.close()

asyncio.run(main())

This script uses playwright to configure chromium to launch in full screen kiosk mode, then flips between two different URLs. The script pauses for 30 seconds after showing each page.
You can easily edit this to modify the web page URLs, change the timing or add additional pages.  A better version (for a future post) would also accept command line arguments and an array of URLs to flip between, but this version is good enough to prove the concept. 


Create a python launch script

Next we will create a shell script that will launch the python program above. Creating this script will simplify the automation process later.
create a new shell script in the project folder. I called mine pysign_launch.sh 

#!/bin/bash

#run chromium using playwright
cd /home/pi/dev/pysign
source ./pysign_env/bin/activate
python3 ./pysign.py


Use chmod to make this script executable

> chmod 777 ./pysign_launch.sh


Test the python launch script

At this point it is probably worth testing the python launch script. If all goes well then chromium should launch and start flipping between a couple of windows.

(open a GUI terminal)

> source /home/dev/pysign/pysign_launch.sh

( To terminate the script, you will need to use ps and kill from an SSH session.)


Autostart the python launch script

The next step is to make sure that our launch script is automatically launched when the user logs into the desktop.  This uses the same approach as in the previous post. We just need to add our script to /etc/xdg/labwc/autostart
> sudo nano /etc/xdg/labwc/autostart
 append the following line (modify as appropriate for the folders/filenames you have chosen for your implementation)
 /home/pi/dev/pysign/pysign_launch.sh &
For the record, my complete autostart file looks like this:
/usr/bin/lwrespawn /usr/bin/pcmanfm --desktop --profile LXDE-pi &
/usr/bin/lwrespawn /usr/bin/wf-panel-pi &
/usr/bin/kanshi &
/usr/bin/lxsession-xdg-autostart
/home/pi/dev/pysign/pysign_launch.sh &


Reboot and test

The final step is to reboot the device. If everything has gone well then a few seconds after the desktop appears, the browser should launch into kiosk mode and start flipping between the URLs in the python script.


Recommended site

The default sites in the script are fairly lame. One site I recommend trying is this great implementation of the 'digital rain' effect from The Matrix.

https://rezmason.github.io/matrix/



 It looks great on the Pi screen!


Conclusion

You'll probably notice that although the solution works, the mouse cursor is often annoyingly visible on the screen. In the next post I'll cover how to use ydotool to hide this.


Saturday, 7 June 2025

Raspberry Pi Desk Clock

I have a raspberry pi 5 in one of the original touch screen v1 official 7 inch display case.  It is always on my desk but is normally powered down.    

As part of a bigger project I am working on, I wanted to turn into a web-based digital signage device - something that would show a single web page in kiosk mode and re-display the page after a reboot. To test the idea, I wanted to turn the device into a simple desk clock.  Simple enough? Well actually....

The first issue I had was getting the device to reliably and automatically show a single web page.  When I started the device was running the Raspberry Pi port of Ubuntu 24, but with Gnome running the display and XFCE and XRDP also installed to enable a different remote desktop.  Getting a kiosk mode browser running with this setup proved to be challenging enough that I decided on a do over and reinstalled the device. 

Initially I tried to get Ubuntu Core running. Ubuntu Core is specifically designed for IOT devices like digital signs and has a Raspberry Pi port.  It should be the right choice for this sort of setup, but unfortunately I could not get the display screen to work.  For some reason the DSI screen device wasn't being activated, no matter how much I fiddled with the boot config.

In the end I abandoned that approach and fell back to reinstalling the current 64-bit version of the Raspberry Pi Desktop os (which is based on Debian Bookworm).  Getting the kiosk mode to work with the display screen with this setup was much easier. 

Raspbian OS (Bookworm) Kiosk Mode Browser

The key details how to set this up are in this forum post: https://forums.raspberrypi.com/viewtopic.php?t=379321

The summary is:  

  • Enable ssh and login
  • edit this file: 

/etc/xdg/labwc/autostart

  • add something like this line:

/usr/bin/lwrespawn chromium-browser --app=https://<YOUR URL> --kiosk --noerrdialogs ---disable-infobars --no-first-run --ozone-platform=wayland --start-maximized --no-restore-on-startup 

  •  save and reboot

This configures the desktop environment to autostart chromium in kiosk mode and display the configured web page

Online Clock Digital Signage 

With the kiosk mode working, the next step was to find a decent clock web page and call it a day, but this proved to be challenging.  Despite searching pages of results, I wasn't able to find a clock app that met these criteria:

  • minimalist, 
  • ad-free
  • customizable fonts and colors
  • persistent settings

In the end I got so frustrated with the lack of suitable options  that I decided to build and deploy my own.  

Vibe Coding With Claude Console

I didn't want to spend days building a clock app, so I decided to use Claude Console and vibe code it.  In my opinion this is a perfect use case for AI assistant coding - it is a single layer, simple app that is UX centric and has no likelihood of scalability, security or data integrity issues.  I like Claude Console for a couple of reasons

1) It is a pre-pay model.  You pay as you go instead of having to maintain a subscription

2) The agent is bound to the console and leaves your IDE alone.  You can take it or leave it any stage.

After an hour or so of iterating with Claude (and around 5 bucks in tokens) I had a an app that met all of my requirements.  I was even able to add a bit of sci-fi flair by including glow effects and my favourite sci-fi fonts (Orbitron and Michroma) 

Deployment 

I typically use CloudFlare Pages for simple static web hosting, because it gives you a one stop shop for registering a host name and deploying a CI build from GitHub.  This part of the project also went smoothly, and I was able to quickly get the site registered and deployed.

Due partially to my frustration with finding an alternative, and partially to Guinness Draught, the finished product is deployed at this host name:

https://suckmyclock.xyz/

After a little configuration, I am pretty happy with the look on the raspberry pi desktop:


Summary

I am happy with the above outcome. This is the retro-future look that I wanted when I started the project., and it adds nicely to my cleaned-up desk.  

In the end it took me far longer to work out how to host a kiosk mode web page then it did to build and deploy my own custom clock application.  I guess that shows the benefit of AI assistant coding tools, but as mentioned above this is almost the perfect use case for them as there is minimal possibility of hidden technical debt creeping into the solution. 

Using AI agents as smart scaffolding tools seems like a reasonable use for them -especially for simple projects, and in this case I think Claude console did the job much faster then I could have coded something myself.


Saturday, 31 May 2025

Big desk and a tiny win

I looked at my desk today and didn't like what I saw.  My desk is two-tier IKEA behemoth that I have had for nearly 20 years - it is super solid and probably has more surface area than any two desks in an actual office. The design of the desk wasn't the problem - the problem was the absolute state of everything on it.

Over the years the desk has turned into an total rats nest of cables and random IT gear. As of this morning it had (at least) the following items:

3 monitors, a mac mini, a raspberry pi, a laser printer,  a latte panda, a work laptop. a work iphone, a personal laptop, a KVM switch, an amplifier and speakers, a (very neglected) HOTAS joystick,  a VDSL modem and a charging station and batteries for my power tools. Oh, and there is also a half-built spider bot. My actual PC is on the floor under the desk . 

 All of this stuff has cables, and even more cables, and power bricks and supporting power boards. (I have a total of six power boards running the whole thing), and years of an 'I'll fix it up later' approach to cable management had created an ugly tangle of nastiness.

So I decided to do something about the mess.  More accurately, I decided to start doing something because by the end of day one I am still only half-way through, but I have enough working again to be able to write this blog post.

I also have an 80 litre container full of hardware that I need to either need to reinstall or ditch at some stage, but for now I revelling in the partially complete neatness.

About half-way through the tear down process today I realised that I could solve a lot of the desk issues by adding a riser to the back half of the desk (behind the monitors). This riser would a) hide a lot of cables and b) give me more surface area for IT gear.

I used to dabble in cabinet making woodwork. At one stage I did a lot of it but over the last year or two I let things slide, and the shed slowly turned from a mini woodworking shop into a junk pile (there is a pattern emerging here).  Last weekend I cleared some of it out.  It is still a work in progress but I can at least get to my workbench again (the shed is 3mx3m - it is a very small woodwork shop).

So, having cleared the shed last weekend, and with the driver of needing a shelf for the desk, I picked up my tools for the first time in what must be about two years and had a crack. And it felt goood. I had let things slide so much that I forget the simple satisfaction that you can achieve with hand tools and wood.  

My tools were still sharp. The brass-backed tenon saw that I bought as a rusty wreck in a job lot from e-bay and carefully restored still cut a clean line. Similarly for the old jack plane I had also salvaged and restored, and the shooting board I knocked together from scrap plywood. Making some saw cuts and plane shavings was fun. It felt like rediscovering a small piece of myself that I had lost at some stage in the previous years.

It took about an hour to dimension and glue the 3 parts for the riser, but that was an hour well spent.  The riser itself isn't pretty - it is literally a 1200mm board with two vertical ends glued to it - no sanding, shaping or staining (yet) - but it is the right size, it plumb and it gets the job done.  

I am looking at it as I type this.  More importantly I am not looking at a nasty rats nest of cables, A combination of the new riser and a lot of velcro has made a huge difference.

Clearing out the shed last week wasn't hard, but it was really hard to get motivated to do it. Clearing up my desk also wasn't hard but I've been putting it off for years - and the problem has been growing each time I put it off.  Forcing myself to get started with both jobs not only improved things but let me rediscover a lost passion/skill along the way.

And yes, I am giving myself massive props for a really small win because somewhere over the years I turned into someone who is much more likely to let things slide the other way, and I really don't like that. I am trying to rehab my drive to build things and to take care of the small stuff and I'll gladly take the wins along the way.

Also if I am going to be able to transition from office work to being self-employed, taking care of the small stuff is going to become really, really important. A slack, manana attitude is not going to cut it.   

(The floor under the desk is still covered with power boards, power bricks and a cable jungle, but I've ordered a cable tray to help me take care of that section of the clean-up project.  That will be a story for another day).

Friday, 23 May 2025

Leaping into the unknown

I haven't posted in a while because the work situation went non-linear. To cut a long (and very boring) story short, I am being made redundant due to my employer deciding to offshore all development.

This outcome is not necessarily a bad thing, but the the mechanics of the redundancy process as enacted by my workplace have been confusing and unnecessarily drawn out. Things career wise have been up in the air for the last couple of months, but now I have some clarity and some breathing space. 

As it turns out, I am gong to lose a job that stopped being interesting a couple of years ago, and get compensated enough in the process that I can afford to do my own thing for a few months.  I'll probably eventually end up looking for more work, but for at least six months I will have the luxury of being my own boss.

That  means that I will have time to start serious work on setting up my own online business, so that is of course what I am going to do. If it takes off then I might not have to look for more work.  If it doesn't then at least I'll be spending the next few months on refreshing and honing my full stack development skills.

I'll try and keep this blog updated with my progress, but I don't know how much detail I am going to give away yet - I want to at least get a working prototype together before I start making things public. All I am prepared to say at this point is that the business will be a SAAS targeting corporate customers, it will revolve around Augmented Reality and there is an awful lot of work to do.

One of the things I can share is the current tech stack.  The solution is going to be a multi-tier solution with an Angular front end and a dotnet core back end.  Although I have enjoyed playing with Vue and Supabase, I pivoted from them for a number of reasons:

1) Angular skills are much more marketable, and choosing this front-end framework will improve my future employability

2)  Angular is much improved from when I first started looking at it.  The more I play with Angular 19 (and Angular Material) the more I like it for building complex but maintainable front-end solutions.

3) I want full control of the whole stack, and DotNet Core Identity seems to be a great choice for building low-cost multi-tenant solutions. 

This stack should enable me to build something that starts small. but can be scaled out over time.  I hope that becomes necessary!

Anyway, I am exciting for what the next few months will bring. I have a rare opportunity to be my own boss for a while. I really want to make the most of it.

Oh, by the way. In the interim since the last post I also spent some time on getting some industry certifications in preparation for potential future job hunting.  I was able to certify as an Azure Developer Associate and an AWS Cloud Practitioner.  I also hope to study and pass the AWS Developer Associate exam before the end of the year.


Sunday, 16 February 2025

Hiatus - pressing the pause button

 

Some career things have come up which mean that I will need to put the main themes of this blog on pause for a while.  I need to focus on studying for industry certs, and there won't be a lot of time to keep the regular blog cadence and project themed post going.  

Given that I am largely talking to myself here, this is unlikely to cause a global panic, but a future data archaeologist might care.

So, in the interests of setting the record wrong:  I recently bought some terrible Cat 6 cables from a guy called Ean Asir on ebay. They were so bad that I cut one open to have a look at the wiring. The copper quality was atrocious! The wires were corroded and the insulation was just falling off.  They were very rude when I tried to return them, so I left a 1 star review. Don't buy anything made of copper from this guy! 

Thursday, 23 January 2025

Article SAAS - back-end integration

The front-end functionality of my world simplest SAAS project is now complete enough that I want to start adding some real back-end services. This is so that I can firstly add functional authentication and persistence, and eventually add web hook integration to a payment provider.

I am going to use Supabase for the back-end for this project.  It is a great, well-documented set of services that has a fully functional free usage tier.  It is a one-stop shop for auth, persistence, web hooks and many other features. It is based on open source products, it supports both client-only and multi-tier architectures and it supports a local development model where you can build your solution on a local instance of the Supabase stack. What is not to like?

Initial Setup

To get started, I have created a free tier account and a Supabase project for the article site.

I then followed this guide  to setting up a local version of the Supabase stack and linking it to my project.

With the Getting Started steps complete, I have a local installed version of the Supabase environment that I can start using:

Database Schema


Another really great feature of Supabase is that the locally stack still includes the full Studio UI for managing the project.  This means that I can quickly set up my initial database schema:




Supabase is designed primarily for client-only applications where the browser hosted application is interacting directly with the persistence layer. To enable this to occur securely, Supabase supports row-level access policies for its database tables.  These policies define which operations and which rows uses can perform on each table.   These policies often require tuning as you develop and test the solution, but it is a good idea to set up some initial policies when you define your tables.  



With the initial database schema defined, we can create a migration that exports the postgres-sql that can be used to recreate or publish this schema later

>npx supabase migration new create_initial_schema

will create a new  migration file: 


>npm supabase db diff --schema public

will give a dump of the SQL required to migrate our database is its current status.
using a text editor to copy this sql to a migration file will preserve this state change and enable us to scrip the rebuild of the database in the future.



User Accounts

Creating user accounts is a little trickier. The Supabase authentication service uses its own database schema to manage user authentication. This table stores the 'master record' for each user identity, but is purely concerned with authentication.

We also want to store own own details for our user in the user_accounts table, and we want to make sure that  a user_accounts  record is created whenever a new user signs up.  

Also, by default Supabase doesn't give you a user interface for setting or resetting a user's password. This is probably to reduce security risks. 

Eventually we will manage the account creation through the user sign-up process on our site, but in the mean time we can inject some test users via SQL.

This Gist (not mine - I found it with a google search ) shows how to inject users into the Supabase auth schema.

Once we have done that, we can run a simple SQL statement to populate our user_accounts table based on the currently defined users


Once those scripts are executed, we have some test account to play with


To make this a repeatable process, we can add this SQL to the database seeding script supabase/seed.sql

This will ensure that these accounts are created whenever we reset the database, and gives us a repeatable way of setting our test database to a known state.


Conclusion

With the above steps complete, I now have a scaffolded back end that I can start integrating into the site. The next step will be to wire up our services layer to Supabase, then create the user interface for signing up and logging in  users.


Saturday, 11 January 2025

2025 New Year Cont - Project State Of Play

Continuing the 'new year reflection'  theme,  I thought that I would use this post to list all of the projects that I currently have in-flight.  This is intended to hi-light my shameful habit of serially starting projects, and hopefully prompt me to do better this year. 

Here are the things I am currently working on

Software

  • Article Site simple SAAS
  • Text Adventure Engine (and text adventure)
  • C# port of FreeRDP
  • 'Big Idea' SAAS

Hardware

  • Hexbot Robot (and control software)
  • Cable driven robot arm

(These are the projects that I have at least 'touched' in the last 12 months. There is a litany of other abandoned ones that have been left by the wayside over the years.)

Of these, the Article Site simple SAAS project is the one that I have discussed most on this blog.  The other projects probably need a little more introduction. I'll expand on them below.

Text Adventure Engine


I have briefly mentioned this project in a previous post. This project is my attempt to make a text adventure engine in pure typescript that can be used to create a sophisticated text adventure with a simple JSON payload (e.g configuration only - no scripting required).   The goal for this project is to support the creation of multiple Infocom style text adventure games in the near future.
My vague idea is to use the medium of text adventures to have a fun project to work on as a relief from the more serious projects, while also improving my narrative skills.

C# port of FreeRDP


This project is an attempt to create a pure c# RDP Client, based on the FreeRDP open source C code base. The motivation here is that there doesn't seem to be a good RDP client that can be used with Unity (which uses C# as its base development language).
However, what I found while attempting is that the RDP protocol is very complicated, with multiple protocol layers, endian specific buffer programming and lots of crypto/security programming. 
I made progress despite that, but the project quickly became a drain - this is the sort of project that is large enough to require full time attention for while, and I have a lot of other things I want to look at instead.  The major downside here is that the end results of the project won't have enough return on investment compared with other things I wont to work on - the actual payoff for me will be quite low compared with the effort required.  This is more of a 'bragging rights' project than something I really need.
Even so, I haven't completely given up on this one and I do intend to pick it up again at some stage. It might get a post or two in the future.

Big Idea SAAS


This project will likely be the next SAAS project I tackle after completing the Article Site one.  I have a 'concept of a plan' for a SAAS site that has some potential to make actual money for paying customers, but I am still sketching things out and I don't want to make the idea public before making more progress. In particular, I  need to do some market testing to see if the idea has any traction.
For now, I am just going to say that this project is related to enabling code free implementation of Augmented Reality heads up displays for Industrial use.

Hexbot Robot


A while ago I bought one of these Hi-Wonder robot kits. The kit was pretty good, but I was disappointed by the ancient version of JetPack and ROS it shipped with.  I also found that the back current protection on the custom Hi-Wonder adaptor board for the Jetson wasn't great, as I managed to completely fry the Jetson nano while trying to charge the battery.

So, when life gives you lemons... make your own robot. I decided to modify the base hexbot to my own Frankenstein design and I've been plugging away at this project (on and off) for about a year now.

I'll make a more detailed post about the robot soon, but for now the main changes I have made are

  1. Replace the tiny supplied LiPO battery pack with an adaptor than can use power tool 18v pluggable battery packs (mission life of 45Wh)
  2. Replace the Jetson Nano with a Raspberry Pi 3 running Ubuntu (cheaper + less power drain)
  3. Rewrite the robot control software from scratch in python/ROS2 
This is a nice little project for boosting my robotics skills (which are more or less 'intermediate')
The hardware side has required a lot of 3D printing, customization and learning about serial servo control.
The software side has required a lot of python, base robotics knowledge and an improved understanding of legged robot control.

At the moment the robot can be powered on,  supports SSH control, can adopt various leg poses and has relative accurate inverse kinematic control (you can move each leg tip by a specific vector)

I am currently working on re-integrating the lidar module and depth camera, and creating a gait controller that will enable the robot to walk specific distances. The steps after than will be accurate turning and dead-reckoning navigation.

Cable-Driven Small Robot Arm


This project is an attempt to design and 3D print a 6DOF cable actuated robot arm that will be light weight enough to add onto a future upscaled hexbot.  The design uses rolling joints + a pulley system to ensure linear motion of all joints while minimizing the weight of the arm structure. So far I have a  prototype builds for the 3DOF wrist and for the elbow and shoulder joints, but they all still need to be integrated into a common structure.  (I don't have any mechanical engineering training, so most of this work is iterative try-it-and-see stuff ).  I'll try and make some more progress with this project in the near future.

Summary


So that's the current project list.  I am going to try hard to get to a decent milestone with at least one of these before starting anything else!

One thing I've noticed with the above project list is that it doesn't include any VR or 3D development (which is my day job), but that is probably for the best. I want to keep my home projects very much separated from my work.  If I work on anything in Unity or VR it will probably be commercially sensitive and I won't be able to talk about it here.

Anyway, that is probably enough for this post. Time to do some actual work.


Raspberry Pi Desktop Digital Signage (RaspbianOS + Wayland)

  In my last post I mentioned turning a Raspberry Pi + Official Touch Screen into a nifty desk clock.  Well, it turns out that I actually ha...