What I am looking at is a single board computer - no specific one so only using standard features.
Minimize the OS stuff to speed up boot and keep the user out of the system.
send and receive serial port messages
run some sort of application to display data, menu's etc
save data to files.
There are three major choices:
The Debian/Arch/Distro approach is to install a minimal system that provides the services your application needs. You'll probably want to rip out systemd, and use either one of the existing init systems, or write your own, to speed up boot and the system robust enough as an appliance. See Devuan for non-systemd alternatives like non-systemd udev.
Yocto and OpenEmbedded are designed to help with constructing your own distribution, whereas Linux from Scratch and Beyond shows all the bits and pieces needed to get a working system together.
The next choice is whether you'll run X11, Wayland, or on pure framebuffer (actually DRI with OpenGL ES acceleration, and not a traditional framebuffer). X will let you run more than one application, and write the UI or entire application in whatever programming language you prefer. I don't know how well Wayland is supported on armhf or arm64, especially Mali hardware. Qt supports bare framebuffer (EGLFS plugin for
Qt5 or
Qt6), and you can use it in a commercial proprietary appliance relying on the GPL or LGPL license; in that case, using Yocto and Python3 for at least the user interface is probably a good idea (as that makes the licensing boundary extremely clear, although dynamic linking is considered a derivation boundary nowadays). You can always put any performance-sensitive sekret sauce in a dynamically linked native library, and access via Python built-in
ctypes module.
Hardware-wise, optimize for I/O ops on small files, and not maximum streaming read/write. PCIe or SATA SSDs will be your friends here, even for low-power SBCs. MicroSD cards are dead slow in comparison.
Python seems to be the universal thing these days on single board computers. Is it the whole solution, or part of it?
If you have any performance-sensitive stuff, you'll want to do it in native machine code, and using C or C++ for these is quite common.
Using Python for the user interface makes development cycle easier and faster, because you won't need to build the software before running it. The Qt project has a somewhat straightforward Yocto + Qt Embedded toolchain, where your development machine uses a standard X+Qt environment, but the target on plain OpenGL ES with no X. If you do run an X server, you can also use GTK+ via gobject introspection bindings, i.e. gir, with Python. Basically, both GTK+ and Qt are very 'native-like' in Python. I even prefer to use their UI builder interfaces at runtime, constructing the user interface widgets from an XML file instead of generating the code to do so, but this can slow the UI coming up initially by a second or so, mostly because of the large number of I/O ops. See Glade for GTK+ for a GUI editor that can generate such XML .ui files, and Designer for Qt. (In case someone claims you need a commercial Qt license, skip their mistaken beliefs, and go read e.g.
this instead.)
Also, I personally love the idea of the UI being something knowledgeable end users could tinker with, using only an SSH client and a text editor, without risking the entire device, as long as they keep a backup copy of the original Python code and UI files, of course. While Tux is my mascot, it is not my idol, and I've done a lot of proprietary work too; the dynamic linking when using open Python UI code and any proprietary processing in native-only dynamic libraries is also a valid copyright license/derivation boundary, so it is an interesting option even for proprietary appliances.
The appliance is always run using a dedicated user account, not as root, unless one is an idiot. Compared to a standard desktop Linux system, the GUI application replaces the 'greeter', the application that displays the standard login form. You do not necessarily need a window or session manager at all. Isolating the UI under a single user account also means that if you set up your init system right, you can simply kill all processes running as that user, and restart X and the GUI application, without having to reboot the system if something hangs up –– for example, if your main GUI process crashes.
Using X and multiple windows means that you can extend your UI with external programs, which can help keep individual parts simple, and ensure you utilize multi-core SoCs to their fullest. Even though these SBCs don't have desktop-like amounts of CPU power, they can still shift a lot of data around, so do not be too wary about using pipes and Unix domain socket pairs to move data between processes.
As to serial stuff, you'll want to use termios instead of any serial library: the libraries are all crappy, and the termios interface already exposes everything you need. If you have dedicated devices, like USB-Serial dongles, use udev and udev rules to generate symlinks in /dev, and use those symlinks names directly without any lookup/serial port search nonsense. With Python, you'll want to use a separate thread for each serial port, or even two threads if using asynchronous messaging instead of a conversational request-response interface.
I seriously believe the same regarding USB bulk transfers and usbfs /dev/usb/nnn/mmm interface –– avoiding libusb altogether ––, but saying that out aloud will probably start a flamewar.