Merge remote-tracking branch 'origin/main' into issue-8-split-kiosk-osd
# Conflicts: # README.md
This commit is contained in:
@@ -1,44 +1,62 @@
|
||||
# Ground Station
|
||||
|
||||
Orange Pi Zero 3W application for the custom OpenIPC/WFB ground station.
|
||||
Orange Pi Zero 3W supervisor and kiosk display for the custom OpenIPC/WFB ground station.
|
||||
|
||||
## Current baseline
|
||||
## Components
|
||||
|
||||
- receives framed GX12 RC channel data over the dedicated AUX1 UART at 115200 8N1;
|
||||
The ARM64 artifact intentionally contains two executables:
|
||||
|
||||
- `groundstation-linux-arm64` — the long-running root supervisor. It owns the GX12 UART, logging, RC state, video lifecycle, and starts the display process.
|
||||
- `groundstation-display-linux-arm64` — the unprivileged X11 kiosk renderer. It runs as `orangepi` on `DISPLAY=:0`; the supervisor owns it during normal use.
|
||||
|
||||
Current capabilities:
|
||||
|
||||
- receives framed GX12 RC channel data at 115200 8N1;
|
||||
- validates protocol version, frame length and CRC-16;
|
||||
- logs final mixed channel values;
|
||||
- starts an independent kiosk display process that maps two independent layers on `DISPLAY=:0` for the Viture target before video: the fullscreen fallback/menu canvas below video, and a transparent, input-transparent OSD overlay above video reserved as the future flight OSD rendering surface; both tolerate X11 downtime without restarting the supervisor and stop deterministically on shutdown;
|
||||
- video keeps rendering priority: the kiosk canvas stays below the `fpv-video` window, shows the exact fallback text `Brak połączenia WFB` only while no video output is presented, and the OSD overlay stays above video without ever presenting pixels over it or intercepting its input; future display targets (MIPI/touch) get their own target-specific templates instead of one global layout.
|
||||
- logs channel changes instead of every frame;
|
||||
- starts/stops `fpv-video` from the configured CH6 power/lifecycle control;
|
||||
- starts an independent kiosk display before video, tolerates temporary X11 downtime without restarting the supervisor, and stops deterministically on shutdown;
|
||||
- keeps the fullscreen fallback/menu canvas below `fpv-video`, showing `Brak połączenia WFB` only while video is absent;
|
||||
- maps a transparent, input-transparent OSD overlay above `fpv-video` as the reserved flight OSD rendering surface: it never presents pixels over the video or intercepts its input.
|
||||
|
||||
The first baseline is intentionally small. It does **not** yet control an AUX, manipulate the flight-control link, or replace the primary video pipeline.
|
||||
Future MIPI/touch targets use their own templates rather than sharing one global layout. The flight OSD will render on the existing transparent layer above video, preserving the pilot stream's priority.
|
||||
|
||||
## Run
|
||||
## Installation on Orange Pi
|
||||
|
||||
Extract the artifact and install **both** files:
|
||||
|
||||
```bash
|
||||
# Supervisor (YAML configuration, default /etc/groundstation/config.yml;
|
||||
# hardware UART paths live in the config file)
|
||||
groundstation -config /etc/groundstation/config.yml
|
||||
sudo install -m 0755 groundstation-linux-arm64 /usr/local/bin/groundstation
|
||||
sudo install -m 0755 groundstation-display-linux-arm64 /usr/local/bin/groundstation-display
|
||||
sudo systemctl restart groundstation
|
||||
```
|
||||
|
||||
# Test input mode
|
||||
groundstation -stdin
|
||||
Check logs:
|
||||
|
||||
# Kiosk canvas alone (the supervisor starts it; standalone for debugging)
|
||||
```bash
|
||||
systemctl status groundstation --no-pager
|
||||
journalctl -u groundstation -b --no-pager -n 80
|
||||
```
|
||||
|
||||
Do not run `groundstation-display` by hand during normal operation. It needs the X11 user/session environment and is managed by the supervisor. For isolated renderer debugging it supports:
|
||||
|
||||
```bash
|
||||
groundstation-display -target x11
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The service reads `/etc/groundstation/config.yml`. For the verified permanent GX12 connection, use:
|
||||
|
||||
```yaml
|
||||
serial:
|
||||
path: /dev/ttyS2
|
||||
```
|
||||
|
||||
`/dev/ttyS0` is the Orange Pi boot/debug UART and must not be used for the permanently connected GX12.
|
||||
|
||||
## Runtime dependencies
|
||||
|
||||
- an X11 session on `:0` owned by the `orangepi` desktop user (the kiosk waits for X11 to appear and reconnects after display-manager restarts);
|
||||
- `wmctrl` for the fullscreen/below kiosk window state — without it the canvas still maps but stays a plain window (logged);
|
||||
- `runuser` from util-linux (base install) for the unprivileged kiosk launch.
|
||||
|
||||
## Protocol
|
||||
|
||||
The v1 protocol is described in `AGENTS.md` once the repository policy file is approved. It carries 16 final EdgeTX channel outputs as a binary framed message with CRC.
|
||||
|
||||
## Project direction
|
||||
|
||||
1. GX12 Ground Station UART mode sends final RC/switch state to Orange Pi.
|
||||
2. Orange Pi logs and renders state while supervising the FPV video launcher.
|
||||
3. Add WFB quality, recorder state, and MIPI touch display independently of pilot video.
|
||||
4. Add a watchdog-protected override of exactly one reserved `SAFE/NORMAL/HIGH` AUX.
|
||||
- X11 session on `:0` owned by `orangepi`; the kiosk waits for X11 and reconnects after display-manager restarts;
|
||||
- `wmctrl` for the fullscreen/stacking kiosk window states; without it the kiosk windows map as normal windows and the condition is logged;
|
||||
- `runuser` from util-linux for the unprivileged kiosk launch.
|
||||
|
||||
@@ -19,19 +19,45 @@ func Open115200(path string) (*os.File, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
termios.Iflag = 0
|
||||
termios.Oflag = 0
|
||||
termios.Lflag = 0
|
||||
termios.Cflag &^= unix.CSIZE | unix.PARENB | unix.CSTOPB | unix.CRTSCTS
|
||||
termios.Cflag |= unix.CS8 | unix.CREAD | unix.CLOCAL
|
||||
termios.Ispeed = unix.B115200
|
||||
termios.Ospeed = unix.B115200
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
configureRaw115200(termios)
|
||||
|
||||
if err := unix.IoctlSetTermios(int(file.Fd()), unix.TCSETS, termios); err != nil {
|
||||
file.Close()
|
||||
return nil, fmt.Errorf("configure %s: %w", path, err)
|
||||
}
|
||||
|
||||
effective, err := unix.IoctlGetTermios(int(file.Fd()), unix.TCGETS)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return nil, fmt.Errorf("read back %s: %w", path, err)
|
||||
}
|
||||
if err := verifyBaud(*effective, unix.B115200); err != nil {
|
||||
file.Close()
|
||||
return nil, fmt.Errorf("verify %s: %w", path, err)
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// configureRaw115200 keeps the raw 115200 8N1 blocking-read configuration.
|
||||
// On Linux TCSETS takes the line speed from the CBAUD bits in Cflag, so
|
||||
// CBAUD must be cleared and B115200 selected there; Ispeed/Ospeed alone
|
||||
// leave an inherited baud (for example 9600 from boot defaults) in effect.
|
||||
func configureRaw115200(termios *unix.Termios) {
|
||||
termios.Iflag = 0
|
||||
termios.Oflag = 0
|
||||
termios.Lflag = 0
|
||||
termios.Cflag &^= unix.CSIZE | unix.PARENB | unix.CSTOPB | unix.CRTSCTS | unix.CBAUD
|
||||
termios.Cflag |= unix.B115200
|
||||
termios.Cflag |= unix.CS8 | unix.CREAD | unix.CLOCAL
|
||||
termios.Ispeed = unix.B115200
|
||||
termios.Ospeed = unix.B115200
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
}
|
||||
|
||||
func verifyBaud(termios unix.Termios, baud uint32) error {
|
||||
if got := termios.Cflag & unix.CBAUD; got != baud {
|
||||
return fmt.Errorf("effective baud bits %#o, want %#o", got, baud)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package serialport
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestConfigureRaw115200OverridesInheritedBaud(t *testing.T) {
|
||||
var termios unix.Termios
|
||||
termios.Iflag = unix.IGNBRK | unix.ICRNL
|
||||
termios.Oflag = unix.OPOST
|
||||
termios.Lflag = unix.ICANON | unix.ECHO
|
||||
termios.Cflag = unix.B9600 | unix.CS8 | unix.CREAD | unix.CLOCAL | unix.HUPCL
|
||||
termios.Ispeed = unix.B9600
|
||||
termios.Ospeed = unix.B9600
|
||||
|
||||
configureRaw115200(&termios)
|
||||
|
||||
if got := termios.Cflag & unix.CBAUD; got != unix.B115200 {
|
||||
t.Fatalf("effective baud bits=%#o want %#o", got, unix.B115200)
|
||||
}
|
||||
if got := termios.Cflag & unix.CSIZE; got != unix.CS8 {
|
||||
t.Fatalf("character size bits=%#o want %#o", got, unix.CS8)
|
||||
}
|
||||
for _, flag := range []uint32{unix.CREAD, unix.CLOCAL} {
|
||||
if termios.Cflag&flag == 0 {
|
||||
t.Fatalf("flag %#o lost", flag)
|
||||
}
|
||||
}
|
||||
for _, flag := range []uint32{unix.PARENB, unix.CSTOPB, unix.CRTSCTS} {
|
||||
if termios.Cflag&flag != 0 {
|
||||
t.Fatalf("flag %#o must be cleared", flag)
|
||||
}
|
||||
}
|
||||
if termios.Iflag != 0 || termios.Oflag != 0 || termios.Lflag != 0 {
|
||||
t.Fatal("raw mode must clear Iflag, Oflag and Lflag")
|
||||
}
|
||||
if termios.Ispeed != unix.B115200 || termios.Ospeed != unix.B115200 {
|
||||
t.Fatalf("speed fields=%d/%d want 115200/115200", termios.Ispeed, termios.Ospeed)
|
||||
}
|
||||
if termios.Cc[unix.VMIN] != 1 || termios.Cc[unix.VTIME] != 0 {
|
||||
t.Fatalf("VMIN=%d VTIME=%d want 1/0", termios.Cc[unix.VMIN], termios.Cc[unix.VTIME])
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyBaudAcceptsOnlyConfiguredLineSpeed(t *testing.T) {
|
||||
var inherited unix.Termios
|
||||
inherited.Cflag = unix.B9600 | unix.CS8
|
||||
if err := verifyBaud(inherited, unix.B115200); err == nil {
|
||||
t.Fatal("inherited 9600 line speed must fail verification")
|
||||
}
|
||||
|
||||
var configured unix.Termios
|
||||
configured.Cflag = unix.B115200 | unix.CS8 | unix.CREAD
|
||||
if err := verifyBaud(configured, unix.B115200); err != nil {
|
||||
t.Fatalf("115200 line speed must verify: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user