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
25 changes: 18 additions & 7 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

jobs:
custom-firmware-build:
runs-on: ubuntu-24.04
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v3
Expand All @@ -31,19 +31,30 @@ jobs:
key: kernels-${{ runner.os }}-${{ runner.arch }}-${{ env.KERNEL_SHA }}

- name: Install build dependencies
run:
sudo apt-get update -y && sudo apt-get install -y build-essential cmake gcc-aarch64-linux-gnu
run: |
sudo apt-get update -y &&
sudo apt-get install -y build-essential cmake \
gcc-aarch64-linux-gnu pkg-config squashfs-tools git-core \
bc libssl-dev

- name: Download firmware
run: make firmware

- name: Build with Custom
run: make custom_firmware CUSTOM_FIRMWARE_FILE=firmware_custom.bin
- name: Build with Basic firmware
run: make basic_firmware BASIC_FIRMWARE_FILE=firmware_basic.bin

- name: Build with Extended firmware
run: make extended_firmware EXTENDED_FIRMWARE_FILE=firmware_extended.bin

- name: Clear Kernel Git Repo
run: git -C tmp/kernel/ clean -fdx

- uses: actions/upload-artifact@v4
with:
name: custom-build
path: firmware_custom.bin
name: basic-build
path: firmware_basic.bin

- uses: actions/upload-artifact@v4
with:
name: extended-build
path: firmware_extended.bin
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ all: tools
# ================= Build Tools =================

DEBUG_FIRMWARE_FILE := firmware/firmware_debug.bin
CUSTOM_FIRMWARE_FILE := firmware/firmware_custom.bin
BASIC_FIRMWARE_FILE := firmware/firmware_basic.bin
EXTENDED_FIRMWARE_FILE := firmware/firmware_extended.bin

$(DEBUG_FIRMWARE_FILE): firmware/$(FIRMWARE_FILE) tools
./scripts/enable_debug_misc.sh $< tmp/debug $@

$(CUSTOM_FIRMWARE_FILE): firmware/$(FIRMWARE_FILE) tools
./scripts/create_custom_firmware.sh $< tmp/custom $@
$(BASIC_FIRMWARE_FILE): firmware/$(FIRMWARE_FILE) tools
./scripts/create_firmware.sh $< tmp/basic $@ overlays/basic overlays/camera-native

custom_firmware: $(CUSTOM_FIRMWARE_FILE)
$(EXTENDED_FIRMWARE_FILE): firmware/$(FIRMWARE_FILE) tools
./scripts/create_firmware.sh $< tmp/extended $@ overlays/basic overlays/camera-new

basic_firmware: $(BASIC_FIRMWARE_FILE)
extended_firmware: $(EXTENDED_FIRMWARE_FILE)
debug_firmware: $(DEBUG_FIRMWARE_FILE)
extract_firmware: firmware/$(FIRMWARE_FILE) tools
./scripts/extract_squashfs.sh $< tmp/extracted
Expand Down
39 changes: 33 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,49 @@ This project provides utilities to work with Snapmaker U1 firmware images.
It enables creating custom firmware builds, and enabling debug features,
like SSH access.

## Features
## Firmware Variants

| Variant | Description |
|---------|-------------|
| **Basic** | SSH, USB ethernet, native camera (~1Hz in Fluidd) |
| **Extended** | Basic + new camera stack with HW-accelerated streams |

### Common Features

- Extract firmware images (SquashFS rootfs)
- Create custom firmware with modifications
- Enable SSH: `root/snapmaker` and `lava/snapmaker`
- Enable DHCP on USB ethernet adapters
- Disable WiFi power saving
- Expose camera feed in Fluidd (~1Hz)

### Extended Camera

The extended firmware replaces the native camera with a hardware-accelerated
stack using the Rockchip MPP/VPU.

Camera endpoints available at `http://<ip>/webcam/`:

| Endpoint | Description |
|----------|-------------|
| `/webcam/snapshot.jpg` | Built-in: JPEG snapshot |
| `/webcam/stream.mjpg` | Built-in: MJPEG stream (~15fps) |
| `/webcam/stream.h264` | Built-in: H264 stream (raw) |
| `/webcam/player` | Built-in: H264 web player |
| `/webcam2/snapshot.jpg` | USB: JPEG snapshot |
| `/webcam2/stream.mjpg` | USB: MJPEG stream (~15fps) |
| `/webcam2/stream.h264` | USB: H264 stream (raw) |
| `/webcam2/player` | USB: H264 web player |

Fluidd automatically picks up the webcam configuration.

If USB camera is connected it will be exposed under `/webcam2/`.
Requires reboot.

## Pre-builts

1. Go to [Actions](https://github.com/paxx12/SnapmakerU1/actions/workflows/build.yaml). You need GitHub account.
1. Download latest artifact for latest build.
1. Download `basic-build` or `extended-build` artifact.
1. Unpack the `.zip`
1. Put the `.bin` file onto USB device (FAT32/exFAT format).
1. Go to `About > Firmware version > Local Update > Select firmware_custom.bin`
1. Go to `About > Firmware version > Local Update > Select the .bin file`
1. Connect using `ssh root@<ip>` with `snapmaker` password.

**This will void your warranty, but you get SSH access.**
Expand Down
11 changes: 11 additions & 0 deletions overlays/camera-new/patches/02-disable-native-camera.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--- a/etc/init.d/S99camera_service
+++ b/etc/init.d/S99camera_service
@@ -3,6 +3,8 @@
# Start/stop camera_service process
#

+[ -e /oem/.camera-native ] || exit 0
+
log()
{
logger -p user.info -t "camera[$$]" -- "$1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--- a/home/lava/origin_printer_data/config/moonraker.conf
+++ b/home/lava/origin_printer_data/config/moonraker.conf
@@ -46,3 +46,9 @@
mqtt_protocol: v5
status_interval: 2
default_qos: 1
+[timelapse]
+[webcam case]
+service: iframe
+stream_url: /webcam/player
+snapshot_url: /webcam/snapshot.jpg
+aspect_ratio: 16:9
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--- a/home/lava/origin_printer_data/config/moonraker.conf
+++ b/home/lava/origin_printer_data/config/moonraker.conf
@@ -46,3 +46,11 @@
mqtt_protocol: v5
status_interval: 2
default_qos: 1
+[timelapse]
+[webcam case]
+service: mjpegstreamer-adaptive
+stream_url: /webcam/stream.mjpg
+snapshot_url: /webcam/snapshot.jpg
+target_fps: 15
+target_fps_idle: 5
+aspect_ratio: 16:9
65 changes: 65 additions & 0 deletions overlays/camera-new/root/etc/init.d/S99v4l2-mpp-mipi
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/bin/sh

[ ! -e /oem/.camera-native ] || exit 0

CMD_CAPTURE=/usr/local/bin/capture-mipi-mpp
CMD_CAPTURE_ARGS="--device /dev/video11 --format nv12 --jpeg-quality 7 --jpeg-sock /tmp/capture-mipi-jpeg.sock --mjpeg-sock /tmp/capture-mipi-mjpeg.sock --h264-sock /tmp/capture-mipi-h264.sock"

CMD_HTTP=/usr/local/bin/stream-http.py
CMD_HTTP_ARGS="--jpeg-sock /tmp/capture-mipi-jpeg.sock --mjpeg-sock /tmp/capture-mipi-mjpeg.sock --h264-sock /tmp/capture-mipi-h264.sock --bind 127.0.0.1"

CMD_MQTT=/usr/local/bin/stream-mqtt.py
CMD_MQTT_ARGS="--jpeg-sock /tmp/capture-mipi-jpeg.sock --publish-dir /home/lava/printer_data/camera"

start() {
if [ ! -e /dev/video11 ]; then
echo "MIPI camera device /dev/video11 not found, not starting."
return
fi

printf "Starting capture-mipi-mpp: "
start-stop-daemon -S -b -q -m -p /var/run/capture-mipi-mpp.pid -x $CMD_CAPTURE -- $CMD_CAPTURE_ARGS
echo "OK"

printf "Starting stream-http: "
start-stop-daemon -S -b -q -m -p /var/run/stream-mipi-http.pid -c lava -x /usr/bin/python3 -- $CMD_HTTP $CMD_HTTP_ARGS
echo "OK"

printf "Starting stream-mqtt: "
start-stop-daemon -S -b -q -m -p /var/run/stream-mipi-mqtt.pid -c lava -x /usr/bin/python3 -- $CMD_MQTT $CMD_MQTT_ARGS
echo "OK"
}

stop() {
printf "Stopping stream-mqtt: "
start-stop-daemon -K -q -p /var/run/stream-mipi-mqtt.pid
echo "OK"

printf "Stopping stream-http: "
start-stop-daemon -K -q -p /var/run/stream-mipi-http.pid
echo "OK"

printf "Stopping capture-mipi-mpp: "
start-stop-daemon -K -q -p /var/run/capture-mipi-mpp.pid
echo "OK"
}

case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
stop
sleep 1
start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac

exit 0
62 changes: 62 additions & 0 deletions overlays/camera-new/root/etc/init.d/S99v4l2-mpp-usb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/sh

[ ! -e /oem/.camera-native ] || exit 0

CMD_CAPTURE=/usr/local/bin/capture-usb-mpp
CMD_CAPTURE_ARGS="--device /dev/video18 --jpeg-sock /tmp/capture-usb-jpeg.sock --mjpeg-sock /tmp/capture-usb-mjpeg.sock --h264-sock /tmp/capture-usb-h264.sock"

CMD_HTTP=/usr/local/bin/stream-http.py
CMD_HTTP_ARGS="--jpeg-sock /tmp/capture-usb-jpeg.sock --mjpeg-sock /tmp/capture-usb-mjpeg.sock --h264-sock /tmp/capture-usb-h264.sock --bind 127.0.0.1 --port 8081"

start() {
if [ ! -e /dev/video18 ]; then
echo "USB camera device /dev/video18 not found, not starting."
return
fi

printf "Starting capture-usb-mpp: "
start-stop-daemon -S -b -q -m -p /var/run/capture-usb-mpp.pid -x $CMD_CAPTURE -- $CMD_CAPTURE_ARGS
echo "OK"

printf "Starting stream-http: "
start-stop-daemon -S -b -q -m -p /var/run/stream-usb-http.pid -c lava -x /usr/bin/python3 -- $CMD_HTTP $CMD_HTTP_ARGS
echo "OK"

printf "Starting stream-mqtt: "
start-stop-daemon -S -b -q -m -p /var/run/stream-usb-mqtt.pid -c lava -x /usr/bin/python3 -- $CMD_MQTT $CMD_MQTT_ARGS
echo "OK"
}

stop() {
printf "Stopping stream-mqtt: "
start-stop-daemon -K -q -p /var/run/stream-usb-mqtt.pid
echo "OK"

printf "Stopping stream-http: "
start-stop-daemon -K -q -p /var/run/stream-usb-http.pid
echo "OK"

printf "Stopping capture-usb-mpp: "
start-stop-daemon -K -q -p /var/run/capture-usb-mpp.pid
echo "OK"
}

case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
stop
sleep 1
start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac

exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from confighelper import ConfigHelper

class Timelapse:

def __init__(self, confighelper: ConfigHelper) -> None:
self.confighelper = confighelper
self.server = confighelper.get_server()

# setup eventhandlers and endpoints
file_manager = self.server.lookup_component("file_manager")
camera_path = file_manager.datapath.joinpath("camera")
file_manager.register_directory("timelapse",
str(camera_path),
full_access=True
)

def load_component(config: ConfigHelper) -> Timelapse:
return Timelapse(config)
22 changes: 22 additions & 0 deletions overlays/camera-new/scripts/01-v4l2-mpp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

ROOT_DIR="$(realpath "$(dirname "$0")/../../..")"

if [[ $# -ne 1 ]]; then
echo "Usage: $0 <rootfs-dir>"
exit 1
fi

set -eo pipefail

TARGET_DIR="$ROOT_DIR/tmp/v4l2-mpp"

if [[ ! -d "$TARGET_DIR" ]]; then
git clone https://github.com/paxx12/v4l2-mpp.git "$TARGET_DIR"
fi

echo ">> Compiling MPP library..."
"$TARGET_DIR/deps/compile_mpp.sh"

echo ">> Compiling v4l2-mpp applications..."
make -C "$TARGET_DIR" install DESTDIR="$1"
34 changes: 23 additions & 11 deletions scripts/create_custom_firmware.sh → scripts/create_firmware.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash

if [[ $# -ne 3 ]]; then
echo "Usage: $0 <upgrade.bin> <temp-dir> <output.bin>"
if [[ $# -lt 3 ]]; then
echo "Usage: $0 <upgrade.bin> <temp-dir> <output.bin> [overlays...]"
exit 1
fi

Expand All @@ -11,6 +11,7 @@ IN_FIRMWARE="$(realpath "$1")"
TEMP_DIR="$(realpath -m "$2")"
OUT_FIRMWARE="$(realpath -m "$3")"
ROOT_DIR="$(realpath "$(dirname "$0")/..")"
shift 3

rm -rf "$TEMP_DIR"

Expand All @@ -23,17 +24,28 @@ unsquashfs -d "$TEMP_DIR/rootfs" "$TEMP_DIR/rk-unpacked/rootfs.img"
echo ">> Add kernel modules to rootfs..."
"$ROOT_DIR/scripts/helpers/compile_kernel_modules.sh" "$TEMP_DIR/rootfs/info/config-6.1" "$ROOT_DIR/tmp/kernel" "$TEMP_DIR/rootfs/lib/modules/"

echo ">> Applying custom patches..."
for patchfile in "$ROOT_DIR/custom/patches/"*.patch; do
echo "[+] Applying patch: $(basename "$patchfile")"
patch -d "$TEMP_DIR/rootfs" -p1 < "$patchfile"
for overlay; do
echo ">> Applying overlay $overlay..."
if [[ -d "$overlay/patches/" ]]; then
for patchfile in "$overlay/patches/"*.patch; do
echo "[+] Applying patch: $(basename "$patchfile")"
patch -d "$TEMP_DIR/rootfs" -p1 < "$patchfile"
done
fi

if [[ -d "$overlay/scripts/" ]]; then
for scriptfile in "$overlay/scripts/"*.sh; do
echo "[+] Running script: $(basename "$scriptfile")"
./"$scriptfile" "$TEMP_DIR/rootfs"
done
fi

if [[ -d "$overlay/root/" ]]; then
echo ">> Copying custom files..."
cp -rv "$overlay/root/." "$TEMP_DIR/rootfs/"
fi
done

if [[ -d "$ROOT_DIR/custom/root/" ]]; then
echo ">> Copying custom files..."
cp -rv "$ROOT_DIR/custom/root/." "$TEMP_DIR/rootfs/"
fi

echo ">> Create squash filesystem..."
mksquashfs "$TEMP_DIR/rootfs" "$TEMP_DIR/rk-unpacked/rootfs-v2.img" -comp gzip

Expand Down
Loading