dockermacosarduinoserial-portavrdude

How to use Arduino CLI from within a Linux Docker container on Mac host


I am attempting to use the Arduino CLI within a Docker container on a Mac host. My setup allows for communication over serial, but I'm encountering issues getting the Arduino CLI to function correctly.

Context

Due to the limitation on Mac that prevents passing a serial device directly to a Docker container (refer to this open issue from 2016), I'm using socat to bridge the serial connection over TCP and back to serial within the container.

Setup

Docker Command

The Docker command I'm using includes the --add-host=host.docker.internal:host-gateway option to forward all TCP traffic to the container from the host:

docker run -it --rm \
    -v "$HOME:$HOME" \
    -e "HOME=$HOME" \
    -w "$PWD" \
    -e "DISPLAY=host.docker.internal:0" \
    --add-host=host.docker.internal:host-gateway \
    --user root \
    openmodelica-screen bash

Socat Commands

On the Mac host, I'm connecting the Arduino on /dev/cu.usbmodem14201 to TCP port 9000:

socat TCP-LISTEN:9000,reuseaddr,fork FILE:/dev/cu.usbmodem14201,raw

Within the Linux container, I'm creating a serial port from TCP port 9000, which I can find with ls /dev/pts, usually /dev/pts/2:

socat tcp:host.docker.internal:9000,reuseaddr pty,raw,echo=0 &

Issue

I have a simple Arduino program that returns a temperature reading upon receiving a command. I can successfully communicate with it using picocom:

picocom -b 9600 /dev/pts/2

However, I noticed picocom sends each key press instantly without waiting for a carriage return.

When I attempt to upload a sketch using the Arduino CLI, I encounter an error:

./arduino-cli upload -p /dev/pts/2 --fqbn arduino:avr:leonardo MyFirstSketch

Error:

Connecting to programmer: .
Found programmer: Id = "Invalid"; type = l
    Software Version = I.n; Hardware Version = v.a
avrdude: error: buffered memory access not supported. Maybe it isn't
a butterfly/AVR109 but a AVR910 device?
avrdude: initialization failed, rc=-1
         Double check connections and try again, or use -F to override
         this check.

avrdude: error: programmer did not respond to command: leave prog mode
avrdude: error: programmer did not respond to command: exit bootloader
Failed uploading: uploading error: exit status 1

Question

I suspect the issue lies in the serial to TCP conversion and back. Could anyone provide tips or tricks for using socat in this context to ensure compatibility with the Arduino programmer? Any advice on resolving this error would be greatly appreciated.


Solution

  • I wanted to share my solution for the above question with the caveat that it may not be completely satisfying because it involves a physical step with pressing the reset button to the Arduino Leonardo before being able to program it.

    Mac host

    Ser2net server

    On the mac host, brew install ser2net and create file /usr/local/etc/ser2net/ser2net.yaml with this content:

    %YAML 1.1
    ---
    define: &confver 1.0
    
    connection: &id001
      accepter: tcp,9000
      connector: serialdev,/dev/cu.usbmodem14201,57600n81,xonxoff,rtscts
      options:
        kickolduser: true
    

    Then, to serve the Arduino on port 9000, start the server with this line, where -n says not to send to the background (I am running in screen):

    /usr/local/Cellar/ser2net/4.6.1/sbin/ser2net -c /usr/local/etc/ser2net/ser2net.yaml -n
    

    Linux container

    Socat client (optional for avrdude)

    Inside the Linux container, I am running this in screen to listen to port 9000 and turn that into a virtual serial port /dev/ttyS0:

    socat -d -d pty,link=/dev/ttyS0,rawer,b57600 tcp:host.docker.internal:9000
    

    Arduino-cli

    I used arduino-cli to compile the binaries to the MyFirstSketch folder:

    arduino-cli compile --fqbn arduino:avr:leonardo --export-binaries MyFirstSketch -v -v
    

    Avrdude

    Then, I used avrdude to send the binary to the Arduino. arduino-cli uses avrdude internally, so it is sometimes easier to use avrdude directly to be able to specify additional options. On the Arduino Leonardo, I found it necessary to press reset on the board first before this command.

    avrdude -v -V -patmega32u4 -cavr109 "-Pnet:host.docker.internal:9000" -b57600 -D "-Uflash:w:MyFirstSketch/build/arduino.avr.leonardo/MyFirstSketch.hex:i"
    

    and you should see this satifying message at the end:

    Writing | ################################################## | 100% 1.01s
    
    avrdude: 7024 bytes of flash written
    
    avrdude: safemode: lfuse reads as FF
    avrdude: safemode: hfuse reads as D8
    avrdude: safemode: efuse reads as CB
    avrdude: safemode: Fuses OK (E:CB, H:D8, L:FF)
    
    avrdude done.  Thank you.
    

    Unsatisfactory issues

    I wanted to share my solution, even if it is less than ideal, because it took me a lot of work to get to this point, and I think your project may benefit by some of the concepts and tools mentioned here.

    Virtual device

    I wasn't able to get avrdude to work with the virtual device /dev/ttyS0 that I set up earlier on socat, but /dev/ttyS0 works with serial programs like picocom.

    To set this up properly, I would need to find a better way to use/edit the linux kernel used by the docker container, and that proved difficult.

    Reset button

    To a programmer, it's not satifying to have a step that requires a human button press. The reset signal could be sent to the Arduino from the Mac by sending stty -f /dev/cu.usbmodem14201 1200 to the Arduino, but stty -F /dev/ttyS0 1200 did not work inside the Linux container.

    Possible solution to both issues above

    One way around this is to use an RFC2217 compatible telnet signal, but this requires a compliant server and client. Ser2net is compliant with RFC2217, but it is hard to find a client that is. There is a client called ttynvt that is RFC2217 compliant, but it also required doing stuff with the Linux kernel.

    With RFC2217, you can control/change the baud rate of the virtual serial device, and this is necessary to program the Arduino Leonardo. I would suggest looking into other Arduino board types or other microcontrollers if you are looking to program an Arduino inside of a docker container and using a Mac host.