OctoPrint with multiple 3D printers

12 minute read

OctoPrints
One of the most useful tools when managing a 3D printer is OctoPrint, since among several things it allows you to manage your printer from a web interface, as well as adding a lot of functionality (for example, monitoring and management of the printer through Telegram, plugins to generate timelapse videos of the prints, or even detect when there are spaghetti-like problems in our prints using another plugin with AI).

Multiple printers

But what if we have more than one printer? Can OctoPrint manage more than one printer?

The short answer is: NO! The software itself is not designed for multiple printers, but you can find alternative ways to have more than one instance of OctoPrint running on your device or computer.

There are several ways to do this, for example you can modify the OctoPi scripts to add more instances, create scripts that launch multiple instances of the same program, but the option I ended up using was Docker containers.

Raspberry Pi + Ubuntu

In my case I have a Raspberry Pi 4, with 8 GB of RAM, more than enough to run multiple instances of OctoPrint.

I originally had a specific operating system installed on my Raspberry called OctoPi, a Debian derivative with OctoPrint as the main program that is launched when the Raspberry Pi is turned on, but as I wanted to use containers I decided to use Ubuntu, which general purposed distribution where you can surely find everything you need to work with containers.

Requirements and components:

For this setup I used the following:

  • Impresora 3D - Artillery Genius Pro
  • Impresora 3D - Artillery SideWinder X2
  • 2x WebCam - Logitech C270 (one per printer)
  • Raspberry Pi 4 - 8G RAM
  • SD Card 64 GB
  • Ubuntu 22.04 LTS ARM 64 bits
  • Docker 20.10.17
  • OctoPrint lastest (1.8.3)

Ubuntu Installation

Nowadays is easier than ever to install Ubuntu on the Raspberry Pi, you just have to download Raspberry Pi Imager, which will generate the image on your SD card, select the operating system, which in this case is Ubuntu 22.04 LTS and you’re done. Raspberry Pi Imager is avialable for Linux, Mac and Windows, so you can generate the image on the operating system that suits you best.

Raspberry Pi Imager

From this very software you will have the possibility to configure the password of the WiFi network in case you are using the Raspberry wirelessly, so you don’t need to use a television set and a keyboard (headless), or manipulate the SD card to configure the networking. Since I’m using it headless, that’s what I did and then I connected via ssh.

WiFi Settings

For the headless setup the trick is to let the Ubuntu initialization scripts run and then connect via ssh. Now if you see that you can’t connect via ssh you can look for an HDMI cable and a keyboard to check what might be going on.

Software installation on Ubuntu

Apart from Docker we are going to need docker-compose, and the utilities that will help us install them.

Docker and docker-compose

To install docker you need to download it using the script. You will need to loging in as the administrator user with the root account:

pi@octopi:~$ sudo su
root@octopi:~# 

root@octopi:~# apt install curl
root@octopi:~# curl -sSL https://get.docker.com | sh

root@octopi:~# apt update
root@octopi:~# apt install docker-compose

OctoPrint with one printer

The first thing you need is to create a folder for OctoPrint, and then go into it:

root@octopi:~# mkdir octoprint
root@octopi:~# 
root@octopi:~# cd octoprint/
root@octopi:~/octoprint#

Let’s start with a printer, in this case I’ll start with the Artillery Genius Pro. For this you can create a folder that identifies this printer:

root@octopi:~# mkdir geniuspro
root@octopi:~# chmod -R 777 geniuspro/

It’s important to know the actual path because you’ll use it later in the container definition along with the name of this folder. To do this, execute the following command:

root@octopi:~# pwd
/root/octoprint

With this you can indicate the files of this first instance of OctoPrint will reside in /root/octoprint/geniuspro

Now to configure the printer you need to use docker-compose so you need to create the docker-compose.yml file with the following information:

version: '2.4'

services:
  geniuspro:
    image: octoprint/octoprint
    restart: unless-stopped
    ports:
      - 5000:5000
      - 8080:8080
    devices:
      - /dev/ttyACM0:/dev/ttyACM0
      - /dev/video0:/dev/video0
    volumes:
      - /root/octoprint/geniuspro:/octoprint
    # uncomment the lines below to ensure camera streaming is enabled when
    # you add a video device
    environment:
      - ENABLE_MJPG_STREAMER=true
      - CAMERA_DEV=/dev/video0
      - MJPG_STREAMER_INPUT="-y -n -r 1280x960 -f 30

This file defines various things related to OctoPrint, such as the webcam and the printer itself. For example, image indicates which Docker image to use for the geniuspro service.

Notice the ports definitions, where 5000 will be used for the web interface and 8080 for webcam streaming video.

On the other hand, the printer is defined as a device in /dev/ttyACM0, and the webcam in /dev/video0

Something to keep in mind and that differs from the example on which I based, is that the streamer is now part of the Docker image and is served on port 8080, so there is no need to have a separate definition of another Docker image pointing to the streamer.

volumes has the reference to the printer directory that you had created as /root/octoprint/geniuspro, which will be mapped into the container as the /octoprint folder. That is, when we launch the container all the OctoPrint files for this printer will be in the /root/octoprint/geniuspro path of the operating system.

Finally, environment has the streamer parameters, like webcam device from the container. In this case both the OS and the container match the path /dev/video0. Here you also need to provide you webcam parameters, in my case I indicated that the resolution is 1280x960 at 30 frames per second, which is what corresponds to my Logitech C270. For other models you can consult the supported cameras list page.

Once the docker-compose.yml file has been created with the information shown above, all you have to do is start the service with Docker:

root@octopi:~/octoprint# docker-compose up -d
  
Creating octoprint_geniuspro_1 ... done

With this we can access the OctoPrint web interface from the Raspberry Pi on the port that we had indicated, in my case the url is http://192.168.68.102:5000:

OctoPrint Web 1

Adding a second printer

To add a second printer, you just have to replicate the configuration you had before, but specifying the new printer and the corresponding video camera. So for my Artillery Sidewinder X2 this would be:

version: '2.4'

services:
  geniuspro:
    image: octoprint/octoprint
    restart: unless-stopped
    ports:
      - 5000:5000
      - 8080:8080
    devices:
      - /dev/ttyACM0:/dev/ttyACM0
      - /dev/video0:/dev/video0
    volumes:
      - /root/octoprint/geniuspro:/octoprint
    # uncomment the lines below to ensure camera streaming is enabled when
    # you add a video device
    environment:
      - ENABLE_MJPG_STREAMER=true
      - CAMERA_DEV=/dev/video0
      - MJPG_STREAMER_INPUT="-y -n -r 1280x960 -f 30
 
  sidewinder_x2:
    image: octoprint/octoprint
    restart: unless-stopped
    ports:
      - 5000:5000
      - 8080:8080
    devices:
      - /dev/ttyACM1:/dev/ttyACM0
      - /dev/video2:/dev/video0
    volumes:
      - /root/octoprint/geniuspro:/octoprint
    # uncomment the lines below to ensure camera streaming is enabled when
    # you add a video device
    environment:
      - ENABLE_MJPG_STREAMER=true
      - CAMERA_DEV=/dev/video0
      - MJPG_STREAMER_INPUT="-y -n -r 1280x960 -f 30

Take into account the phycal printer is identified as /dev/ttyACM1 in the Raspberry , but the container internally it will be see it as /dev/ttyACM0. The same goes for the video camera, where it is physically identified as /dev/video2 but internally the container will see it as /dev/video0.

Once the configuration has been created, the container must be run as follows:

root@octopi:~/octoprint# docker-compose up -d
  
octoprint_geniuspro_1 is up-to-date
Creating octoprint_sidewinderx2_1 ... done

This will now allow access to the second instance of OctoPrint on port 5001:

OctoPrint Web 2

How to differentiate printers?

In the example above I used the devices /dev/ttyACM0 and /dev/ttyACM1 to reference my first printer, the Artillery Genius Pro, and my second printer, the Artillery SideWinder X2, but the operating system not always takes that order, since it will depend on which one you had turned on first. In order to identify the printers regardless of the connection order we can create a symbolic link using udev with the manufacturer and product information.

For this, you have first list the USB devices:

root@octopi:~/octoprint# lsusb
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 004: ID 046d:0825 Logitech, Inc. Webcam C270
Bus 001 Device 003: ID 046d:0825 Logitech, Inc. Webcam C270
Bus 001 Device 015: ID 0483:5740 STMicroelectronics Virtual COM Port
Bus 001 Device 016: ID 0483:5740 STMicroelectronics Virtual COM Port
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

There are two STMicroelectronics Virtual COM Port devices here, corresponding to the two printers, but which is which? We are going to ask for the detail of these devices:

 root@octopi:~/octoprint# lsusb -v -d 0483:5740

Bus 001 Device 015: ID 0483:5740 STMicroelectronics Virtual COM Port
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            2 Communications
  bDeviceSubClass         2 Abstract (modem)
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x0483 STMicroelectronics
  idProduct          0x5740 Virtual COM Port
  bcdDevice            0.00
  iManufacturer           1 STMicroelectronics
  iProduct                2 ARTILLERY_RUBY CDC in FS Mode
  iSerial                 3 386F39543538  
...
  
Bus 001 Device 016: ID 0483:5740 STMicroelectronics Virtual COM Port
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            2 Communications
  bDeviceSubClass         2 Abstract (modem)
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x0483 STMicroelectronics
  idProduct          0x5740 Virtual COM Port
  bcdDevice            0.00
  iManufacturer           1 STMicroelectronics
  iProduct                2 ARTILLERY_RUBY CDC in FS Mode
  iSerial                 3 3594398F3538  
...

What differentiates one printer from another is the serial number of the device. Since the Artillery Genius Pro was initially mapped to /dev/ttyACM0, we can check what its serial number is:

root@octopi:~/octoprint# udevadm info -a -n /dev/ttyACM0 | grep serial
    ATTRS{serial}=="3594398F3538"
    ATTRS{serial}=="0000:01:00.0"

Now to see the serial code of the Artillery Sidewinder X2 you have to consult the device /dev/ttyACM1:


root@octopi:~/octoprint# udevadm info -a -n /dev/ttyACM1 | grep serial
    ATTRS{serial}=="386F39543538"
    ATTRS{serial}=="0000:01:00.0" 

For udev to recognize these rules we must create the following file /etc/udev/rules.d/40-printers.rules , with this content:

 # Genius Pro
KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ATTRS{serial}=="3594398F3538", SYMLINK="ttyGeniusPro"

# Sidewinder X2
KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ATTRS{serial}=="386F39543538", SYMLINK="ttySidewinderX2"

Then udev must be restarted to take these changes:

root@octopi:~/octoprint# udevadm control --reload-rules && udevadm trigger

This way the printers will be mapped as follows:

root@octopi:~/octoprint# ls -l  /dev/tty{GeniusPro,Sidewinder}*
lrwxrwxrwx 1 root root 7 Aug 22 21:10 /dev/ttyGeniusPro -> ttyACM0
lrwxrwxrwx 1 root root 7 Aug 22 21:10 /dev/ttySidewinderX2 -> ttyACM1

So the Docker configuration can be modified to reflect these changes:

version: '2.4'

services:
  geniuspro:
    image: octoprint/octoprint
    restart: unless-stopped
    ports:
      - 5000:5000
      - 8080:8080
    devices:
      - /dev/ttyGeniusPro:/dev/ttyACM0
      - /dev/video0:/dev/video0
    volumes:
      - /root/octoprint/geniuspro:/octoprint
    # uncomment the lines below to ensure camera streaming is enabled when
    # you add a video device
    environment:
      - ENABLE_MJPG_STREAMER=true
      - CAMERA_DEV=/dev/video0
      - MJPG_STREAMER_INPUT="-y -n -r 1280x960 -f 30

  sidewinderx2:
    image: octoprint/octoprint
    restart: unless-stopped
    ports:
      - 5001:5000
      - 8081:8080
    devices:
      - /dev/ttySidewinderX2:/dev/ttyACM0
      - /dev/video2:/dev/video0
    volumes:
      - /root/octoprint/sidewinderx2:/octoprint
    # uncomment the lines below to ensure camera streaming is enabled when
    # you add a video device
    environment:
      - ENABLE_MJPG_STREAMER=true
      - CAMERA_DEV=/dev/video0
      - MJPG_STREAMER_INPUT="-y -n -r 1280x960 -f 30  

Webcams

Unlike printers which have different serial numbers, I have two Logitech C270 webcams that are exactly the same model. How can we differente them? How can we make the OS to take the right camera?

Again we do the analysis with lsusb:

root@octopi:~/octoprint# lsusb
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 004: ID 046d:0825 Logitech, Inc. Webcam C270
Bus 001 Device 003: ID 046d:0825 Logitech, Inc. Webcam C270
Bus 001 Device 015: ID 0483:5740 STMicroelectronics Virtual COM Port
Bus 001 Device 016: ID 0483:5740 STMicroelectronics Virtual COM Port
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Both cameras are on the same bus but identified as different USB devices, which we can take advantage of. In this case we will not use the product id to differentiate them but the USB device id. We’ll be referencing the video4linux subsystem instead of tty, so we’ll be using KERNELS instead of KERNEL to differentiate cameras. For example, to find out which USB device the camera in /dev/video0 is on, we can run the following:

root@octopi:~/octoprint# udevadm info -a -p  $(udevadm info -q path -n /dev/video0) | grep KERNELS
    KERNELS=="1-1.4:1.0"
    KERNELS=="1-1.4"
    KERNELS=="1-1"
    KERNELS=="usb1"
    KERNELS=="0000:01:00.0"
    KERNELS=="0000:00:00.0"
    KERNELS=="pci0000:00"
    KERNELS=="fd500000.pcie"
    KERNELS=="scb"
    KERNELS=="platform"

For the other camera we do the same:

root@octopi:~/octoprint# udevadm info -a -p  $(udevadm info -q path -n /dev/video2) | grep KERNELS
    KERNELS=="1-1.3:1.0"
    KERNELS=="1-1.3"
    KERNELS=="1-1"
    KERNELS=="usb1"
    KERNELS=="0000:01:00.0"
    KERNELS=="0000:00:00.0"
    KERNELS=="pci0000:00"
    KERNELS=="fd500000.pcie"
    KERNELS=="scb"
    KERNELS=="platform"

Now with this information we can modify the udev rules as follows:

# Genius Pro
KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ATTRS{serial}=="3594398F3538", SYMLINK="ttyGeniusPro"
SUBSYSTEM=="video4linux", KERNELS=="1-1.3", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0825", SYMLINK="videoGeniusPro"

# Sidewinder X2
KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ATTRS{serial}=="386F39543538", SYMLINK="ttySidewinderX2"
SUBSYSTEM=="video4linux", KERNELS=="1-1.4", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0825", SYMLINK="videoSidewinderX2"

Remember to reset the udev rules with:

root@octopi:~/octoprint# udevadm control --reload-rules && udevadm trigger

So now we have the cameras mapped to /dev/videoGeniusPro and /dev/videoSidewinderX2 respectively. Now we can modify the docker-compose.yml file to take this new configuration:

version: '2.4'

services:
  geniuspro:
    image: octoprint/octoprint
    restart: unless-stopped
    ports:
      - 5000:5000
      - 8080:8080
    devices:
      - /dev/ttyGeniusPro:/dev/ttyACM0
      - /dev/videoGeniusPro:/dev/video0
    volumes:
      - /root/octoprint/geniuspro:/octoprint
    # uncomment the lines below to ensure camera streaming is enabled when
    # you add a video device
    environment:
      - ENABLE_MJPG_STREAMER=true
      - CAMERA_DEV=/dev/video0
      - MJPG_STREAMER_INPUT="-y -n -r 1280x960 -f 30

  sidewinderx2:
    image: octoprint/octoprint
    restart: unless-stopped
    ports:
      - 5001:5000
      - 8081:8080
    devices:
      - /dev/ttySidewinderX2:/dev/ttyACM0
      - /dev/videoSidewinderX2:/dev/video0
    volumes:
      - /root/octoprint/sidewinderx2:/octoprint
    # uncomment the lines below to ensure camera streaming is enabled when
    # you add a video device
    environment:
      - ENABLE_MJPG_STREAMER=true
      - CAMERA_DEV=/dev/video0
      - MJPG_STREAMER_INPUT="-y -n -r 1280x960 -f 30

Performance with two instances of OctoPrint

Probably you are wondering about the consumption of these containers in the operating system and if the Raspberry can handle it. Here is an image of htop with the two containers above printing to each of the printers at the same time.

htop

As you can see the Raspberry has a lot of resources available, since of the 8GB of RAM it is only consuming 0.5 GB including the operating system. From this we can draw two conclusions: the first one is that more printers can be added without problems, the only limit is the number of USB ports offered by the Raspberry (4 in my model), but you could add a USB hub to add more ports; and the second conclusion is that for two printers you could use a Raspberry with less RAM, for example 2 GB or 4 GB, and thus you would save a little.

What does the Raspberry Pi and printers look like?

To finish this article I leave you a photo of my printers connected to the RaspBerry Pi:

3D printers

On the smaller printer you can see a small gray box that is the Raspberry Pi, next to the two web cameras.

References

Leave a Comment