Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .github/img/dockge_01.jpg
Binary file not shown.
55 changes: 31 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![OpenMower header](.github/img/open_mower_header.jpg)](https://github.com/ClemensElflein/OpenMower)

This repository contains the official OpenMowerOS (OMOSv2) image for running the [OpenMower](https://github.com/ClemensElflein/OpenMower) project on your OM's Pi4/CM4.
This repository contains the official OpenMowerOS (OMOSv2) image for running the [OpenMower](https://github.com/ClemensElflein/OpenMower) project on your OM's Pi/CM.

➡️ What’s new in the latest release? See [WHATSNEW.md](./WHATSNEW.md).

Expand All @@ -25,14 +25,14 @@ This repository contains the official OpenMowerOS (OMOSv2) image for running the
Tip: Click a section title to expand/collapse.

<details>
<summary><b>Install OpenMowerOS on your Pi4/CM4</b></summary>
<summary><b>Install OpenMowerOS on your Pi/CM</b></summary>


1. Flash the latest image (link TODO) to an SD card, preferably using [**Raspberry Pi** Imager](https://www.raspberrypi.com/software/).
1. Flash the latest OpenMowerOS v2.x [OpenMowerOS_YYYYMMDD.zip](https://github.com/ClemensElflein/OpenMowerOS/releases) to an SD card or your CM, preferably using [**Raspberry Pi** Imager](https://www.raspberrypi.com/software/).

2. ***Optional: Raspberry Pi Imager configuration***<br>
When prompted by Raspberry Pi Imager, you can change some custom settings:
1. As shown here, but never use a username other than `openmower`!<br>
1. As shown here, but the username must be `openmower`.
![General Settings](.github/img/rpimager_general.png)
2. You may also add your SSH public key for quicker SSH login;
password login remains active (even if it's an either/or selection).<br>
Expand All @@ -43,10 +43,10 @@ Tip: Click a section title to expand/collapse.
<details>
<summary><b>First boot and network setup</b></summary>

3. After writing the image, eject the card, insert it into your mowers Pi4 or xCore, and turn it on.
3. After writing the image, eject the card, insert it into your mower’s Pi or xCore, and turn it on.

4. Your Pi will boot multiple times.<br>
***Sometimes, after the first boot, it may fail to reboot*** (red LED near the HDMI plug remains on, whereas the green one doesn't flicker anymore). If that happens, a power cycle will get it back on track.
***Sometimes, after the first boot, it may fail to reboot*** (the Pi/xCore’s green LED doesnt flicker anymore and remains off for >10 seconds). If that happens, a power cycle will get it back on track.

5. ***Optional: Comitup hotspot (if you skipped step 2 "Raspberry Pi Imager configuration")***<br>
If you didn't enter your Wifi settings when asked for the custom settings during Pi Imager (see step 2), or if you accidentally entered the wrong Wifi settings:
Expand All @@ -57,61 +57,68 @@ If you didn't enter your Wifi settings when asked for the custom settings during

<p align="center"><img src=".github/img/comitup_hotspot.png" style="width:50%"></p>

3. Click on your home WiFi and fill in your password.
3. Click on your home WiFi, fill in your password and click "SUBMIT".

4. The hotspot will disappear and the mower should connect to your WiFi.

6. Try pinging your mower via `ping openmower` (or the hostname you entered during Pi Imager). If the host can't be found, check your router for the mower's IP address.
6. Two minutes after the second reboot, try pinging your mower via `ping openmower` (or the hostname you entered during Pi Imager). If the host can't be found, check your router for the mower's IP address.

7. SSH into your mower via `ssh openmower@openmower` or `ssh openmower@<your hostname or mower's IP address>` (password 'openmower' or the one you entered during Raspberry Pi Imager).

8. ***Optional:***<br>
1. If you didn't configure a custom password during step 2 (Raspberry Pi Imager configuration), change your password now via `passwd`.
7. ***Optional:***<br>
1. If you didn't configure a custom password during step 2 (Raspberry Pi Imager configuration), login via SSH and change your password now via `passwd`.
2. Use `raspi-config` to change keyboard, timezone, WLAN country and the like (if not configured in Raspberry Pi Imager's custom settings).

</details>



<details>
<summary><b>Manage OpenMower stack (GUI + CLI)</b></summary>

Dockge (a container manager GUI), as well as a ttyd (a WebTerminal) are automatically pulled and started after 2-5 minutes (after final boot).
[Dockge](https://dockge.kuma.pet/) (a container manager GUI) and [ttyd](https://tsl0922.github.io/ttyd/) (a web terminal)
are bundled with the OpenMowerOS image and are unpacked and installed during the final second‑boot step.
This may take about 2 minutes.

Please wait while the Pi/xCore’s green LED is flickering or steadily on.

The WebTerminal is available as a lightweight alternative to SSH for running the same commands.
It can be reached via `http://openmower:7681` (adjust if you changed the hostname).
Default login credentials: openmower/openmower.

For each relevant GUI action, a CLI alternative is available via a powerful `openmower` command; both are listed below.

1. Connect to the container manager:
- GUI: Open <http://openmower:5001> (or your individual hostname if changed), and login by entering the default Dockge admin user `openmower/openmower`:
![Create Admin Account](.github/img/dockge_01.jpg)
- CLI:
- SSH into your Pi: `ssh openmower@openmower` (or your configured hostname).
- WebTerminal via URL `http://openmower:7681` (or your configured hostname).
- GUI: Open <http://openmower:5001> (or your individual hostname if changed)

2. Configure the stack (.env)
- CLI: `openmower configure env`
- GUI:
![Select and Edit Stack](.github/img/dockge_02_select_and_edit.jpg)
![Edit .env](.github/img/dockge_03_edit.jpg)
![Save .env](.github/img/dockge_04_save.jpg)
- CLI: `openmower configure env`

3. Start the stack (inclusive initial pull)
- GUI:
![Start Stack](.github/img/dockge_05_start.jpg)
- CLI:
3. Start the stack (including the initial pull)
- CLI: If you configured your .env file via `openmower configure env` then the stack is pulled and started automatically.<br>
If not:

```bash
openmower pull
openmower start
```
- GUI:
![Start Stack](.github/img/dockge_05_start.jpg)

4. Check status and open the OpenMower webApp
4. Check status and open the OpenMower web app
- CLI: `openmower status` should list three service names (open_mower_ros, Mosquitto and OpenMowerApp), all with status 'up'. If so, open a browser and visit `http://openmower:8080` (or your configured hostname).
- GUI:
![Stack Active](.github/img/dockge_06_active.jpg)
- CLI: `openmower status` should list three service names (open_mower_ros, Mosquitto, OpenMowerApp), all with status 'up'. If so, open a browser and visit `http://openmower:8080` (or your configured hostname).

5. Configure the ROS parameters (CLI only for now): `openmower configure ros`
5. Configure the ROS parameters (CLI only for now): `openmower configure ros` and:

1. Adapt section `gps` to your needs, but set at least `datum_lat` and `datum_lon` to the corresponding lat/lon values near your docking station. Use right‑click in [Google Maps](https://maps.google.com) to get them.
2. `ntrip_client` settings with the ones from your local RTK base or from your public NTRIP service.
3. Once (and not before 🩸) you have validated your emergency sensors, set `enable_mower` to true.

</details>

6 changes: 3 additions & 3 deletions WHATSNEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This document highlights the most relevant changes compared to previous OpenMowe
## 🆕 New

- 🖥️ [Dockge](https://dockge.kuma.pet/) GUI for container management.
- 🖥️ WebTerminal [ttyd](https://github.com/tsl0922/ttyd) provides a browser-based shell as a lightweight SSH alternative.
- 🖥️ WebTerminal [ttyd](https://tsl0922.github.io/ttyd/) provides a browser-based shell as a lightweight SSH alternative.
- 🧰 Unified `openmower` CLI for configure, pull, start, stop, status, shell, logs, …
- 🗂️ Consolidated storage layout: configs, maps, logs now in `/home/openmower`.
- 🧾 Version metadata at `/usr/share/openmoweros/version.{json,yaml,sh,txt}` (git hash, branch, describe, build timestamp).
Expand All @@ -16,15 +16,15 @@ This document highlights the most relevant changes compared to previous OpenMowe

- ⚙️ Key settings (e.g. WLAN SSID & password) configurable directly in Raspberry Pi Imager.
- ✍️ No need to pre-edit files like `/boot/openmower/openmower_version.txt` before first boot.
- 🚀 Eliminated initial large image pull (only a short 2–5 minute Dockge pull remains).
- 🚀 Eliminated initial large image pull.
- 👤 Containers run with `openmower` user permissions (no sudo needed for most operations).


## 🛠️ Under the hood (for the curious)

- 🐧 Debian Trixie (arm64) images built with [pi‑gen](https://github.com/RPi-Distro/pi-gen).
- 📁 `openmower` CLI command is in `/usr/local/bin`.
- 🐳 OpenMower stack: Mosquitto and OpenMowerApp (together with a small Nginx) now run as separate containers and are no longer built into the openmower image.
- 🐳 OpenMower stack: Mosquitto and OpenMowerApp (together with a small Nginx) now run as separate containers and are no longer built into the open_mower_ros image.
- 📶 WLAN is managed by NetworkManager.
- 🔌 LAN is managed by ifupdown.
- 📡 DHCP for the internal (xCore) LAN is handled by dnsmasq.
Expand Down
7 changes: 7 additions & 0 deletions stage-openmower/30-docker/00-run-chroot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,10 @@ cat > /etc/docker/daemon.json <<'EOF'
}
EOF
fi

# Bundled docker images
mkdir -p /opt/docker-images
chmod -R u=rwX,g=rX,o=rX /opt/docker-images

# Ensure the preloader runs on boot (after docker)
systemctl enable docker-preload-images.service
28 changes: 28 additions & 0 deletions stage-openmower/30-docker/00-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash -e

STAGE_DIR="$(dirname "$0")"

install -m 0644 -D "$STAGE_DIR/files/etc/systemd/system/docker-preload-images.service" "$ROOTFS_DIR/etc/systemd/system/docker-preload-images.service"

# Install bundled docker images
install -d -m 0755 "$ROOTFS_DIR/opt/docker-images"
cp -a "$STAGE_DIR/files/opt/docker-images/." "$ROOTFS_DIR/opt/docker-images/"

# Reassemble split files into their base
DEST="$ROOTFS_DIR/opt/docker-images"
if compgen -G "$DEST"/*.tar.gz.part* > /dev/null; then
# Build unique list of basenames without the .partNN suffix
for base in $(for f in "$DEST"/*.tar.gz.part*; do echo "${f%.part*}"; done | sort -u); do
rm -f "${base}"
for p in $(ls -1 "${base}".part* 2>/dev/null | sort -V); do
cat "$p" >> "${base}"
done
rm -f "${base}".part*
done
fi

# Uncompress all .tar.gz files to .tar. OS image gets ZIPped anyway but preload service only needs docker load them
for gz in "$DEST"/*.tar.gz; do
[ -e "$gz" ] || break
gunzip -f "$gz"
done
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[Unit]
Description=Preload bundled Docker images (*.tar) into local Docker
After=docker.service
Requires=docker.service
Before=dockge.service webterminal.service
ConditionDirectoryNotEmpty=/opt/docker-images

[Service]
Type=oneshot
TimeoutStartSec=4min
ExecStart=/bin/sh -e -c '\
dir=/opt/docker-images; \
# Load all .tar and delete on success \
for tar in "$dir"/*.tar; do \
[ -e "$tar" ] || break; \
if docker load -i "$tar"; then rm -f "$tar"; fi; \
done \
'

[Install]
WantedBy=multi-user.target
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
[Unit]
Description=Dockge
Wants=network-online.target docker.service
After=network-online.target docker.service
Wants=network-online.target docker.service docker-preload-images.service time-sync.target
After=network-online.target docker.service docker-preload-images.service time-sync.target
StartLimitIntervalSec=10min
StartLimitBurst=10

[Service]
Type=oneshot
WorkingDirectory=/opt/dockge
RemainAfterExit=yes
TimeoutStartSec=10min
ExecStartPre=/bin/sh -c 'H="$(hostname -f 2>/dev/null || hostname || cat /etc/hostname)"; EF="/opt/stacks/openmower/.env"; if [ -f "$EF" ]; then if grep -qE "^[[:space:]]*HOSTNAME[[:space:]]*=" "$EF"; then sed -i -E "s|^[[:space:]]*HOSTNAME[[:space:]]*=.*|HOSTNAME=$H|g" "$EF"; else echo "HOSTNAME=$H" >> "$EF"; fi; fi'
ExecStartPre=/usr/bin/docker compose pull --quiet
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
Restart=on-failure
RestartSec=30s

[Install]
WantedBy=multi-user.target
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
[Unit]
Description=WebTerminal (ttyd) for OpenMowerOS
Documentation=https://github.com/ClemensElflein/OpenMowerOS
Wants=network-online.target docker.service
After=network-online.target docker.service
Wants=network-online.target docker.service docker-preload-images.service
After=network-online.target docker.service docker-preload-images.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/stacks/webterminal
ExecStartPre=/bin/sh -c 'H="$(hostname -f 2>/dev/null || hostname || cat /etc/hostname)"; EF="/opt/stacks/webterminal/.env"; if [ -f "$EF" ]; then if grep -qE "^[[:space:]]*HOSTNAME[[:space:]]*=" "$EF"; then sed -i -E "s|^[[:space:]]*HOSTNAME[[:space:]]*=.*|HOSTNAME=$H|g" "$EF"; else echo "HOSTNAME=$H" >> "$EF"; fi; fi'
ExecStartPre=/usr/bin/docker compose pull --quiet
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=5min
Expand Down
11 changes: 0 additions & 11 deletions stage-openmower/40-openmower/00-run-chroot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,3 @@ echo "✓ Installed. Version check (if supported):"
echo "→ Installing tab completion for /usr/local/bin/openmower"
sudo -u openmower /usr/local/bin/openmower --install-completion
echo "✓ Installed"

echo "→ Updating bash prompts to show 🐳 when STACK_NAME is set"
for f in /home/openmower/.bashrc /root/.bashrc /etc/skel/.bashrc; do
cat >> "$f" <<'OMEOF'

# Prefix PS1 with docker whale if in stack context
if [ -n "$STACK_NAME" ]; then
PS1="🐳 $PS1"
fi
OMEOF
done
6 changes: 1 addition & 5 deletions stage-openmower/40-openmower/files/opt/stacks/openmower/.env
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ MOWER="CHANGE_ME"
# 0_9_X_MPU9250: MPU9250 with 0.9.x mainboard
FIRMWARE="CHANGE_ME"

# Select your ESC type
# ESC type (only relevant for V1 Hardware):
# Supported values as of today:
# xesc_mini: for the STM32 version (VESC)
# xesc_mini_w_r4ma: for the STM32 version (VESC),
Expand Down Expand Up @@ -102,7 +102,3 @@ DEBUG=True
# Get changed by dockge.service
# on each start
HOSTNAME="openmower.local"

#
# Currently only used for docker shell prompt
STACK_NAME="openmower"
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ services:
image: eclipse-mosquitto:2
container_name: Mosquitto
restart: unless-stopped
user: mosquitto:mosquitto
ports:
- "1883:1883"
- "9001:9001"
Expand Down