Last Updated on May 27, 2023.
I’m a big fan of Octoprint, and in the past I used it on a couple of Raspberry Pi computers, one instance per Pi. Those things are harder to come by these days, so as I try to get my 3d printers up and running again after a long hiatus in storage, I wanted to try to take one cheap Windows Mini-PC and see if I could install Linux on it and utilize Docker to have multiple Octoprint instances, one for each printer, all running on one PC. Each 3D Printer will also have a webcam attached. I also wanted to use an external Wifi Adapter as the built in adapter on the Mini-PC didn’t have as much power. So those are the goals:
- One PC to handle three printers
- One Octoprint instance per Printer
- USB Powered Hub to expand available ports
- Each printer has a webcam
- External WiFi Adapter (better than the internal)
- Docker containerization for ease of setup / config
This article serves as the general build and install log of that setup. It covers the basic install of Ubuntu, Docker, Octoprint, and some USB UDEV rules so we can make sure the printers and webcams always run under the expected device names.
Wiring Connections
The Mini-PC I have has two USB 3.0 and two USB 2.0 ports. I’ve got to hook up three webcams, three printers, and an external WiFi adapter, so that’s already 7. So I had one six port powered USB 3.0 hub, which I wanted to attach the 3D Printer related stuff to, use the other USB 3.0 port for the WiFi adapter, and then have the other two ports open as needed. That was the goal at least, it didn’t work out that way, but close. I had to remove one camera from the hub and use one of the USB 2.0 ports for it (perhaps I was maxing out the hub with video?). Nevertheless, that at least left one port open for something else someday. It’s going to be a headless system for the most part.
Mini-PC Linux Software and Config
This was fairly straightforward for the install side. Downloaded the latest Ubuntu server image (could have done desktop, but it’s a headless system), used Balena Etcher to pop it on an SD Card, then shoved that card right into the Windows Mini PC and started the install (wiping out everything). Once that was done, I installed open ssh (if it wasn’t installed as part of the initial OS install) so I can just use Putty to connect via my main PC for ease of copy/paste.
WiFi Adapter Compile and Install
More optional than anything, since this is an adapter that doesn’t have direct Linux support at this time. It’s better than the internal one my PC had though, so I’ll be using it. It’s the Alfa AC1900 WiFi Adapter and it requires special but easy handling.
Copy of the documentation from their website: https://docs.alfa.com.tw/Support/Linux/RTL8814AU/
sudo apt update
sudo apt install git build-essential
git clone https://github.com/aircrack-ng/rtl8812au.git
cd rtl8812au
make
sudo make install
After that I did a recycle, still wired in. I had to update my netplan config to take the new settings for the new adapter.
My internal adapter on the PC (that sucks), was named wlp2s0
. But I need the name of the new adapter, so to get that…
sudo lshw -C network
This gives me the output of network adapters, and I can see a new one beyond my default internal, which should be the adapter we just compiled drivers for:
*-network DISABLED
description: Wireless interface
physical id: 8
bus info: usb@1:3
logical name: wlx00c0caaedc2d
serial: .....
capabilities: ethernet physical wireless
configuration: broadcast=yes driver=rtl88XXau driverversion=5.15.0-72-generic multicast=yes wireless=unassociated
The logical name wlx00c0caaedc2d
is what I’m interested in. Now I’ll edit the netplan config to set both ethernet and wifi as optional interfaces and get the adapter ready to connect to my SSID.
sudo vi /etc/netplan/00-installer-config.yaml
That may bring up some default networking stuff. I pretty much replace it with these contents:
network:
ethernets:
enp1s0:
dhcp4: true
optional: true
wifis:
# wlp2s0:
# optional: true
# access-points:
# <SSID>:
# password: <PASSWORD>
# dhcp4: true
wlx00c0caaedc2d:
optional: true
access-points:
<SSID>:
password: <PASSWORD>
dhcp4: true
version: 2
After that we apply the settings with sudo netplan apply
and we’re off. I can see the adapter light blinking. I reboot with the network cable disconnected to make sure I can still connect via WiFi.
USB and UDEV rules
I want to set some UDEV rules so that we know exactly the USB ports our devices are being plugged into (included devices attached to the powered hub I bought), so that I can create aliases accordingly (i.e. I want to use /dev/ttyPrinter1
and /dev/videoPrinter1
and so on instead of assuming /dev/video0
is always the first camera.)
To do this, I open up an SSH session and type:
udevadm monitor
This will now spit large walls of text every time I plug something in or take something out of USB ports. I plugin the hub, and figure out which ports I’m going to use for which printer. The goal here is to grab the right ‘port identifier’ and use UDEV rules to map that consistently to the right device name. I will always plug the printers/cameras into the same USB ports. So I used a simple video camera and went from port to port, copying down the key line that shows which identifier the system uses for the specific port I just plugged into. You’ll see a bunch of lines, but look for the one that defines a video device like /dev/video0 and then look to the left of that line. You’ll see text like this, which I copied down into a notepad session:
...
/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4.1/1-4.1.2/1-4.1.2:1.0/ (printer-2)
...
/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4.1/1-4.1.1/1-4.1.1:1.0/ (video-printer2)
...
/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4.4/1-4.4:1.0/ (printer1)
...
/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4.3/1-4.3:1.0/ (video-printer1)
As I move the camera from port to port I see which key identifiers change (see the 1-4.1.2:1.0 becomes 1-4.1.1:1.0, or 1-4.4:1.0 as I move down the line)
I use those identifiers to build the udev rules file. I store mine in /etc/udev/rules.d/01-krystof-usb.rules
so here are my contents (as root):
SUBSYSTEM=="tty", KERNELS=="1-4.4:1.0", SYMLINK+="ttyPrinter1"
SUBSYSTEM=="tty", KERNELS=="1-4.4:1.0", RUN+="/var/lib/docker/composers/octoprint/restart-octoprint.sh octoprint1"
SUBSYSTEM=="tty", KERNELS=="1-4.1.2:1.0", SYMLINK+="ttyPrinter2"
SUBSYSTEM=="tty", KERNELS=="1-4.1.2:1.0", RUN+="/var/lib/docker/composers/octoprint/restart-octoprint.sh octoprint2"
SUBSYSTEM=="tty", KERNELS=="1-4.1.4:1.0", SYMLINK+="ttyPrinter3"
SUBSYSTEM=="tty", KERNELS=="1-4.1.4:1.0", RUN+="/var/lib/docker/composers/octoprint/restart-octoprint.sh octoprint3"
SUBSYSTEM=="video4linux", KERNELS=="1-4.3:1.0", ATTR{index}=="0", SYMLINK+="videoPrinter1"
SUBSYSTEM=="video4linux", KERNELS=="1-4.1.1:1.0", ATTR{index}=="0", SYMLINK+="videoPrinter2"
SUBSYSTEM=="video4linux", KERNELS=="1-8:1.0", ATTR{index}=="0", SYMLINK+="videoPrinter3"
The printers use subsystem TTY, and the videos use subsystem video4Linux. The “KERNELS” argument is where I put those uniquely changing identifiers from watching udevadm monitor
running, and the actions afterwards are what we want to do with that device when it’s plugged in. For my case, I want to create a symbolic link under /dev/ for the printer. (I could have named them towards the printer model, but some of my models are the same so I just went with printer1, printer2, etc. – You could easily put A B C, BobsPrinter, DavesPrinter (even if Dave’s not here), etc.
You’ll notice the lines are duplicated for the printers and then there’s a RUN option. That executes a script for docker later when the printers are plugged in. It attempts to restart the docker instances of OctoPrint when the printer is disconnected and reconnected (sometimes it OctoPrint in a container can’t detect that very well, so we basically perform a restart). That script looks like this if you want to use it (if you don’t, remove those lines) – Obviously those scripts don’t exist yet but will after I set up docker.
#!/bin/sh
sleep 10
docker stop $1
sleep 10
docker start $1
exit 0
To apply the UDEV rules, and make sure there aren’t any errors in the config:
sudo udevadm control --reload-rules && sudo udevadm trigger
You can see if it works by connecting one printer or video camera to the hub you wrote down your identifiers for and put into the rules. If you plug in the right device to the right port, you should get a /dev/ttyPrinter1
, or /dev/videoPrinter1
device available:
#Plugged the printer into USB port designated for printer 1:
erkrystof@pc-octoprint:/etc/udev/rules.d$ ls /dev/tty*Pr*
/dev/ttyPrinter1
#Unplugged the printer from USB port that I designated for printer 1 and put it in printer 2:
erkrystof@pc-octoprint:/etc/udev/rules.d$ ls /dev/tty*Pr*
/dev/ttyPrinter2
Docker and the OctoPrint Containers
Followed the standard docker install for Ubuntu:
https://docs.docker.com/engine/install/ubuntu/
I also like to use Portainer, but that’s entirely optional for you (it’s a web admin interface for Docker containers)
https://www.portainer.io/install
Once docker is installed, we need to create the docker-compose.yml file. I like to put mine under /var/lib/docker/composers/octoprint
. (I created the directory as root). I use the ‘official’ OctoPrint image, which I’ve had no issues with thus far in its current state. Read that page over to better understand what is happening below.
sudo vi /var/lib/docker/composers/octoprint/docker-compose.yml
version: "2.4"
services:
octoprint1:
container_name: octoprint1
image: octoprint/octoprint
restart: unless-stopped
ports:
- "8001:80"
devices:
- /dev/ttyPrinter1:/dev/ttyUSB0
- /dev/videoPrinter1:/dev/video0
volumes:
- ./octoprint1:/octoprint
environment:
- ENABLE_MJPG_STREAMER=true
- MJPG_STREAMER_INPUT=-n -r 1280x720
octoprint2:
container_name: octoprint2
image: octoprint/octoprint
restart: unless-stopped
ports:
- "8002:80"
devices:
- /dev/ttyPrinter2:/dev/ttyUSB0
- /dev/videoPrinter2:/dev/video0
volumes:
- ./octoprint2:/octoprint
environment:
- ENABLE_MJPG_STREAMER=true
- MJPG_STREAMER_INPUT=-n -r 1280x720
octoprint3:
container_name: octoprint3
image: octoprint/octoprint
restart: unless-stopped
ports:
- "8003:80"
devices:
- /dev/ttyPrinter3:/dev/ttyUSB0
- /dev/videoPrinter3:/dev/video0
volumes:
- ./octoprint3:/octoprint
environment:
- ENABLE_MJPG_STREAMER=true
- MJPG_STREAMER_INPUT=-n -r 1280x720
Since I’ve got three printers, and three webcams, I have three entries. Easy enough.
So then we could run the compose command from that directory (as root):
docker compose up -d
Now, if I do this without having the USB cameras and printers connected via USB, none of the instances will actually start. Each OctoPrint instance expects a video camera and tty device now before it will start. See how we map our well known aliases to /dev/ttyUSB0 and /dev/video0 for every single instance? That allows us to create multiple OctoPrint installs, but utilize different USB devices. The UDEV rules helped us map ports to well known aliases.
At this point, I hooked up the printers and video cameras to the Linux PC, recycled everything to make sure all was good there, then I ran docker compose up -d
. After that, I was able to hit the web interface on each instance at http://pc-octoprint.lan:8001 through 8003. (Your hostname may vary, that’s up to you).
Now underneath our composers directory we’ll have the three OctoPrint install directories designated under the volumes section.
Key notes on some settings in OctoPrint come from the OctoPrint Docker image page:
Webcam Setup in OctoPrint
Use the following values in the webcam & timelapse settings screen of the initial setup:
Setting | Value |
---|---|
Stream URL | /webcam/?action=stream |
Snapshot URL | http://localhost:8080/?action=snapshot |
Path to FFMPEG | /usr/bin/ffmpeg |
Container Environment based configs
There are configuration values that you pass using container --environment
options. Listed below are the options and their defaults. These are implicit in example docker-compose.yml, and if you wish to change them, refer to the docker-compose docs on setting environment variables.
variable | default |
---|---|
CAMERA_DEV | /dev/video0 (see note) |
MJPG_STREAMER_INPUT | -y -n -r 640x480 |
ENABLE_MJPG_STREAMER | false |
I tested the recycle script as well, so if I physically disconnect and reconnect, the docker instance tied to that port is recycled.
References
- https://www.reddit.com/r/octoprint/comments/mouzdf/ive_been_testing_running_multiple_instances_of/
- https://www.reddit.com/r/octoprint/comments/p3dk12/guidetutorial_multiinstance_octoprint_on_a/
- https://hub.docker.com/r/octoprint/octoprint