OctoPrint con múltiple impresoras 3D

12 minute read

OctoPrints
Una de las herramientas más útiles a la hora de gestionar una impresora 3D es OctoPrint, ya que entre varias cosas permite administrar tu impresora desde una interfaz web, así como agregar un montón de funcionalidades (por ejemplo, monitoreo y gestión de la impresora a través de Telegram, plugins para generar los videos timelapse de las impresiones, o incluso detectar cuando hay problemas tipo espagueti en nuestra impresiones usando otro plugin con AI).

Múltiples impresoras

¿Pero qué pasa si tenemos más de una impresora? ¿Puede OctoPrint gestionar mas de una impresora?

La respuesta corta es: NO! El software en sí no está diseñado para múltiples impresoras, pero se puede buscar formas alternativas para tener más de una instancia de OctoPrint corriendo en nuestro dispositivo o computadora.

Existen varias formas para esto, por ejemplo se puede modificar los scripts de OctoPi para agregar más instancias, crear scripts que lancen varias instancias del mismo programa , pero la opción que terminé usando fue la de contenedores Docker.

Raspberry Pi + Ubuntu

En mi caso tengo una Raspberry Pi 4, de 8 GB de RAM, más que suficiente para correr múltiples instancias de OctoPrint.

En principio tenía instalado en mi Raspberry un sistema operativo específico llamado OctoPi, un derivado de Debian con OctoPrint como el programa principal que se lanza cuando se enciende el dispositivo, pero al querer tener contenedores pensé en usar Ubuntu que es una distribución menos especializada y donde seguramente se puede encontrar todo lo necesario para trabajar con contenedores.

Requisitos y componentes:

Para esta configuración usé lo siguiente:

  • Impresora 3D - Artillery Genius Pro
  • Impresora 3D - Artillery SideWinder X2
  • 2x WebCam - Logitech C270 (una por impresora)
  • 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)

Instalación de Ubuntu

Para instalar Ubuntu en la Raspberry Pi ahora es más sencillo que nunca, solo tienes que descargar Raspberry Pi Imager, el cual generará la imagen en tu tarjeta SD, seleccionar el sistema operativo, que en este caso es Ubuntu 22.04 LTS y listo. Hay versiones de Raspberry Pi Imager para Linux, Mac y Windows, así que podrás generar la imagen en el sistema operativo que más te convenga.

Raspberry Pi Imager

Desde este mismo software vas a tener la posibilidad de configurar la contraseña de la red WiFi en caso de que la estés usando la Raspberry de forma inalámbrica y que no quieras usar un televisor y un teclado (headless), o tocar la SD para configurar la red. Como yo lo estoy usando headless, fue lo que hice y luego me conecté por ssh.

WiFi Settings

Para la configuración headless el truco es dejar que corran los scripts de inicialización de Ubuntu y luego conectarte por ssh. Ahora si ves que no puedes conectarse por ssh puedes buscar un cable HDMI y un teclado para revisar qué pueda estar pasando.

Instalación del software en Ubuntu

Aparte de Docker vamos a necesitar, docker-compose, y los utilitarios que nos ayudarán a instalarlos.

Docker y docker-compose

Para instalar docker debemos descargarlo usando el script. Necesitaremos entrar como usuario administrador con la cuenta root:

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 con una impresora

Lo primero que haremos es crear un carpeta para OctoPrint, y luego entrar en ella:

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

Vamos a empezar con una impresora, en este caso empezaré con la Artillery Genius Pro. Para esto creamos una carpeta que identifique a esta impresora:

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

Es importante saber la ruta actual porque luego la usaremos en la definición del contenedor junto con el nombre de esta carpeta. Para ello ejecutamos la siguiente orden:

root@octopi:~# pwd
/root/octoprint

Ya con esto podemos indicar luego que los archivos de esta primera instancia de OctoPrint residirán en /root/octoprint/geniuspro.

Ahora para configurar la impresora vamos a usar docker-compose por lo que creamos el archivo docker-compose.yml con la siguiente información:

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

Este archivo define varias cosas relacionadas a OctoPrint, como la cámara web y la impresora como tal. Por ejemplo, con image indicamos cuál es la imagen Docker a usar para el servicio geniuspro.

En esta definición hay que destacar los puertos, los cuales serán usados para acceder a los servicios, siendo el 5000 para la interfaz web y el 8080 para el streaming de video de la cámara web.

Por otro lado se define como dispositivo la impresora en /dev/ttyACM0, y la cámara web en /dev/video0

Algo a tener en cuenta y que se diferencia del ejemplo en cuál me basé, es que el streamer ahora es parte de la imagen Docker y es atendido en el puerto 8080, por los que no hace falta tener una definición aparte a otra imagen Docker apuntando al streamer.

Luego en volumes es donde se hace la referencia al directorio de la impresora que habíamos creado en la ruta /root/octoprint/geniuspro, que será mapeada dentro del contenedor en la carpeta /octoprint. Es decir, cuando levantemos el contenedor todos los archivos de OctoPrint para esta impresora quedarán en la ruta /root/octoprint/geniuspro del sistema operativo.

Por último, en environment definimos los parámetros para el streamer, pasando la información del dispositivo de la cámara web referido desde el contenedor, que en este caso coincide con el del sistema operativo afitrión, es decir, /dev/video0. También le proporcionamos los parámetros de la cámara, en mi caso le indico que la resolución es de 1280x960 a 30 frames por segundos, que es lo que corresponde com mi Logitech C270. Para otros modelos puedes consultar el listado desde la página de cámaras soportadas.

Una vez creado el archivo docker-compose.yml con la información mostrada arriba, solo basta con levantar el servicio con Docker:

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

Ya con esto podemos acceder a la interfaz web de OctoPrint desde la ip de la Raspberry en el puerto que le habíamos indicado, que en mi caso la url es http://192.168.68.102:5000:

OctoPrint Web 1

Agregar una segunda impresora

Para agregar una segunda impresora solo hay que replicar la configuración que teníamos antes, pero especificando la nueva impresora y la cámara de video correspondiente. Entonces para mi Artillery Sidewinder X2 esto sería:

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

Aquí hay que aclarar que en el apartado devices físicamente la impresora está identificada en la Raspberry como /dev/ttyACM1, pero al crear el contenedor internamente se identificará como /dev/ttyACM0. Lo mismo pasa con la cámara de video, donde físicamente se identifca como /dev/video2 pero internamente el contenedor la verá como /dev/video0.

Una vez creada la configuración se debe levantar el contendor de la siguiente manera:

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

Con esto ya se podrá acceder a la segunda instancia de OctoPrint en el puerto 5001:

OctoPrint Web 2

¿Cómo diferenciar las impresoras?

En el anterior ejemplo usé los dipositivos /dev/ttyACM0 y /dev/ttyACM1 para hacer referencia a mi primera impresora, la Artillery Genius Pro, y a la segunda impresora la Artillery SideWinder X2, pero el sistema operativo no siempre las tomará en ese orden, ya que dependerá de cuál encienda primero . Para poder identificar las impresoras sin importar el orden de conexión podemos crear un enlace simbólico usando udev con la información del fabricante y del producto.

Para esto primero hay que listar los dispositivos USB:

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

Acá hay dos dispositivos de STMicroelectronics Virtual COM Port, que corresponden a las dos impresoras, pero cuál es cuál? Vamos a pedir el detalle de estos dispositivos:

 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  
...

Lo que diferenica una impresora de la otra es el número serial del dispositivo. Como inicialmente la Artillery Genius Pro estaba mapeada en /dev/ttyACM0, podemos consultar cuál es su número de serial:

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

Ahora para la ver el código serial de la Artillery Sidewinder X2 se debe consultar por el dispositivo /dev/ttyACM1:


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

Para que udev reconozca estas reglas debemos crear el siguiente archivo /etc/udev/rules.d/40-printers.rules , con este contenido:

 # 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"

Luego se debe reiniciar udev para que tome estos cambios:

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

De esta manera quedarán las impresoras mapeadas de la siguiente forma:

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

Por lo que la configuración de Docker podemos modificarla para reflejar estos cambios:

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  

Cámaras de video

A diferencia de las impresoras las cuales tienen seriales distintos, tengo dos cámaras de video Logitech C270 que son exaxtamente iguales. ¿Cómo hacemos para diferenciarlas? ¿Cómo podemos hacer que siempre tome la cámara correcta?

Nuevamente hacemos el análisis con 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

Ambas cámaras están en el mismos bus pero identificadas como dispositivos USB diferentes, lo cual podemos aprovechar. En este caso no usaremos el id del producto para diferenciarlas sino el id de dispositivo USB. Tomaremos como referencia el subsystem video4linux en vez de tty, por lo que estaremos usando KERNELS en vez de KERNEL para diferenciar las cámaras. Por ejemplo, para saber en qué dispositivo USB está la cámara en /dev/video0 podemos ejecutar lo siguiente:

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"

Para la otra cámara hacemos lo mismo:

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"

Ya con esta información podemos modificar las reglas de udev de la siguiente manera:

# 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"

Recuerden reiniciar las reglas de udev con:

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

De esta forma ahora tenemos las cámaras mapeadas como /dev/videoGeniusProy /dev/videoSidewinderX2 respectivamente. Ahora podemos modificar el archivo docker-compose.yml para que tome esta nueva configuración:

version: '2.4'

services:
  geniuspro:
    image: octoprint/octoprint
    restart: unless-stopped
    ports:
      - 5000:5000
      - 8080:8080
    devices:
      - /dev/ttyGeniusPro:/dev/ttyACM0 # use `python -m serial.tools.miniterm` to see what the name is of the printer, this requires pyserial
      - /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 # use `python -m serial.tools.miniterm` to see what the name is of the printer, this requires pyserial
      - /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

Desempeño con dos instancias de OctoPrint

Seguramente te estarás preguntando sobre el consumo de estos contenedores en el sistema operativo y si las Raspberry puede manejarlo. Aquí te dejo una imagen de htop con los dos contenedores arriba imprimiendo en cada una de las impresoras a la vez.

htop

Como pordás notar la Raspberry está holgada, ya que de los 8GB de RAM apenas está consumiendo 0.5 GB incluyendo el sistema operativo. De esto podemos sacar dos conclusiones: la primera es que se pueden agregar más impresoras sin problemas, el único límite es la cantidad de puertos USB que ofrece las Raspberry (4 en mi modelo), pero podrías agregar un hub USB para agregar puertos; y lo segundo es que para dos impresoras se podría usar una Raspberry con menos RAM, por ejemplo de 2 GB o 4 GB, y así economizar un poco.

¿Cómo luce las impresoras y la Raspberry Pi?

Para cerrar este artículo les dejo una foto de mis impresoras conectadas a la RaspBerry Pi:

3D printers

En la impresora más chica puede verse una caja pequeña gris que es la Raspberry Pi, junto a las dos cámaras web.

Referencias

Leave a Comment