Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
flacks committed Mar 27, 2024
0 parents commit 1d78b5a
Show file tree
Hide file tree
Showing 8 changed files with 959 additions and 0 deletions.
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# prusa-cam

![Snapshot of `prusa-cam`](snapshot.png)

I set up a Logitech C920 webcam [inside my Prusa enclosure](https://www.printables.com/model/433908-logitech-c920-original-prusa-enclosure-mount) to be able to monitor my prints using Prusa Connect. `prusa-cam` is a Bash script that POSTs snapshots from your webcam to the Prusa Connect API in 10 second intervals, which I believe is the minimum time generally allowed by Prusa. It only sends snapshots when your printer is online and actively printing. It pings your printer locally and runs as a systemd service, and logs if things are successful or not, resetting states if failures occur.

![Camera inside printer enclosure](camera.jpg)

![View from camera](cheese.jpg)

## Dependencies

- curl
- jq
- v4l2-ctl
- ffmpeg

## Setup

- Copy `prusa-cam` to somewhere like `"$HOME/.local/bin/"`
- Copy the systemd service to `"$HOME/.config/systemd/user/"`
- Edit the systemd service with the full path to the script
- Copy `env.example` to `"$HOME/.config/prusa-cam/env"`
- Edit the file with your settings
- Get your printer API key from your printer (PrusaLink API key)
- Get your camera name from `v4l2-ctl --list-devices`
- Generate a fingerprint with something like `pwgen 32 1 | base64`
- Get your token from Prusa Connect > Camera > Other cameras
- Run `prusa-cam`, ensure you're getting successes
- Note: `prusa-cam` will only start POSTing snapshots if your printer is actively printing
- `prusa-cam` will let you know if one of the critical dependencies is missing, or if your config is missing required fields
- Run `systemctl --user enable --now prusa-cam`
Binary file added camera.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cheese.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash

: "${PRINTER_NAME:=mk4}"
: "${PRINTER_IP:=192.168.1.10}"
# Fetch printer API key from your printer (PrusaLink API key)
: "${PRINTER_API_KEY:=some_api_key}"
# Fetch from `v4l2-ctl --list-devices`
: "${CAMERA_NAME:=HD Pro Webcam C920}"
# Generate with something like `pwgen 32 1 | base64`
: "${FINGERPRINT:=some_fingerprint}"
# Fetch from Prusa Connect > Camera > Other cameras
: "${TOKEN:=some_token}"
230 changes: 230 additions & 0 deletions prusa-cam
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#!/usr/bin/env bash

: "${CONFIG:=$HOME/.config/prusa-cam/env}"

if ! [[ -f $CONFIG ]]; then
echo "$CONFIG doesn't exist, quitting"
exit 1
fi

# shellcheck disable=SC1090
source "$CONFIG"

: "${PRINTER_STATUS_URL:=api/v1/status}"
: "${SNAPSHOT_DIR:=$(mktemp -d --suffix=-prusa-cam)}"
: "${SNAPSHOT_FILENAME:=output.jpg}"
: "${SNAPSHOT_URL:=https://webcam.connect.prusa3d.com/c/snapshot}"
: "${DELAY_SECONDS:=10}"

config_check() {
if [[ -z $PRINTER_NAME ]]; then
echo Config missing PRINTER_NAME
exit 1
elif [[ -z $PRINTER_IP ]]; then
echo Config missing PRINTER_IP
exit 1
elif [[ -z $PRINTER_API_KEY ]]; then
echo Config missing PRINTER_API_KEY
exit 1
elif [[ -z $CAMERA_NAME ]]; then
echo Config missing CAMERA_NAME
exit 1
elif [[ -z $FINGERPRINT ]]; then
echo Config missing FINGERPRINT
exit 1
elif [[ -z $TOKEN ]]; then
echo Config missing TOKEN
exit 1
fi
}

dependency_check() {
if ! command -v curl >/dev/null; then
echo OS missing curl... wtf
exit 1
elif ! command -v jq >/dev/null; then
echo OS missing jq
exit 1
elif ! command -v v4l2-ctl >/dev/null; then
echo OS missing v4l2-ctl
exit 1
elif ! command -v ffmpeg >/dev/null; then
echo OS missing ffmpeg
exit 1
fi
}

get_camera_device() {
CAMERA_DEVICE=$(v4l2-ctl --list-devices |
grep -A 1 "$CAMERA_NAME" |
tail -n 1 |
tr -d \\t)

if [[ -z $CAMERA_DEVICE ]]; then
echo "Could not get camera device"
exit 1
fi
}

quit() {
rm -r "$SNAPSHOT_DIR"
exit 1
}

trap quit SIGINT SIGTERM

is_printer_online() {
if ping \
-c 1 \
"$PRINTER_IP" \
>/dev/null 2>&1; then
return 0
else
return 1
fi
}

is_printer_active() {
PRINTER_STATE=$(curl \
-H "X-Api-Key: $PRINTER_API_KEY" \
-s \
"$PRINTER_IP/$PRINTER_STATUS_URL" |
jq -r .printer.state)

if [[ $PRINTER_STATE = 'PRINTING' ]]; then
return 0
else
return 1
fi
}

get_snapshot() {
if ffmpeg \
-loglevel quiet \
-stats \
-y \
-f v4l2 \
-i "$CAMERA_DEVICE" \
-f image2 \
-vframes 1 \
-pix_fmt yuyv422 \
"$SNAPSHOT_DIR/$SNAPSHOT_FILENAME" \
2>/dev/null; then
return 0
else
return 1
fi
}

post_snapshot() {
if curl \
-X PUT \
-H "Accept: */*" \
-H "Content-Type: image/jpg" \
-H "Fingerprint: $FINGERPRINT" \
-H "Token: $TOKEN" \
--data-binary "@$SNAPSHOT_DIR/$SNAPSHOT_FILENAME" \
--no-progress-meter \
--compressed \
"$SNAPSHOT_URL"; then
return 0
else
return 1
fi
}

main() {
while true; do
if is_printer_online; then
unset PRINTER_NOT_ONLINE_SHOWN

if [[ -z $PRINTER_ONLINE_SHOWN ]]; then
echo "$PRINTER_NAME online"
PRINTER_ONLINE_SHOWN=1
fi

if is_printer_active; then
unset PRINTER_NOT_ACTIVE_SHOWN

if [[ -z $PRINTER_ACTIVE_SHOWN ]]; then
echo "$PRINTER_NAME active"
PRINTER_ACTIVE_SHOWN=1
fi

if get_snapshot; then
unset FFMPEG_FAILURE_SHOWN

if [[ -z $FFMPEG_SUCCESS_SHOWN ]]; then
echo "Successfully capturing snapshots"
FFMPEG_SUCCESS_SHOWN=1
fi

if post_snapshot; then
unset CLOUD_CONNECT_FAILURE_SHOWN

if [[ -z $CLOUD_CONNECT_SUCCESS_SHOWN ]]; then
echo "Successfully POSTing to Prusa Connect"
CLOUD_CONNECT_SUCCESS_SHOWN=1
fi
else
unset CLOUD_CONNECT_SUCCESS_SHOWN

if [[ -z $CLOUD_CONNECT_FAILURE_SHOWN ]]; then
echo "POSTing to Prusa Connect returned an error"
CLOUD_CONNECT_FAILURE_SHOWN=1
fi
fi
else
unset FFMPEG_SUCCESS_SHOWN

if [[ -z $FFMPEG_FAILURE_SHOWN ]]; then
echo "FFmpeg returned an error"
FFMPEG_FAILURE_SHOWN=1
fi

unset CLOUD_CONNECT_SUCCESS_SHOWN
unset CLOUD_CONNECT_FAILURE_SHOWN
fi
else
unset PRINTER_ACTIVE_SHOWN

if [[ -z $PRINTER_NOT_ACTIVE_SHOWN ]]; then
echo "$PRINTER_NAME not active"
PRINTER_NOT_ACTIVE_SHOWN=1
fi

unset FFMPEG_SUCCESS_SHOWN
unset FFMPEG_FAILURE_SHOWN

unset CLOUD_CONNECT_SUCCESS_SHOWN
unset CLOUD_CONNECT_FAILURE_SHOWN
fi
else
unset PRINTER_ONLINE_SHOWN

if [[ -z $PRINTER_NOT_ONLINE_SHOWN ]]; then
echo "$PRINTER_NAME offline"
PRINTER_NOT_ONLINE_SHOWN=1
fi

unset PRINTER_ACTIVE_SHOWN
unset PRINTER_NOT_ACTIVE_SHOWN

unset FFMPEG_SUCCESS_SHOWN
unset FFMPEG_FAILURE_SHOWN

unset CLOUD_CONNECT_SUCCESS_SHOWN
unset CLOUD_CONNECT_FAILURE_SHOWN
fi

sleep "$DELAY_SECONDS"
done
}

config_check

dependency_check

get_camera_device

main
11 changes: 11 additions & 0 deletions prusa-cam.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=Prusa Cam
After=network-online.target

[Service]
ExecStart=/home/user/.local/bin/prusa-cam
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
Binary file added snapshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1d78b5a

Please sign in to comment.