Converting SBC into Network 3D Print Server

Using an old SBC like a Raspberry Pi as some kind of network (3D) print server for simple USB printers (or any USB device) without network capabilities. The setup is quite easy and can be done in 30 minutes. 
 
In my case I will use an old odroid-mc1 for my Flashforge Creator Pro 2 3D printer. The printer is connected to the odroid-mc1 via USB and the odroid-mc1 is connected via wired ethernet to my network. For the remote USB functionality I usethe  VirtualHere "trial" edition which allows sharing one device  and its Windows client has a nice GUI. Download the client from: https://www.virtualhere.com/usb_client_software. There are open source USBIP solutions, but Virtualhere is much more convenient and IMHO also more reliable. This print server costs me 2.2 Watt all the time, which is much less than the 15 Watt the printer draws when idle. 
 
On the client side, just run the downloaded client and you can configure a server connection. Then a list of connected USB devices is shown and you can connect/disconnect via right click on each device. When connected, a USB device will behave as it would have been connected directly to your PC. You even need the same drivers as for a locally connected device.
 
 
Required skills: 
- Knowing the model of your SBC
- Writing Image to SD card
- Basic shell/command line experience
- Logging viah SSH
- Edit config files 
 

Basic installation

  1. Download armbian for your device from https://www.armbian.com/download/ the CLI editions are sufficient and have no UI. If you cannot find your device or device version, check something similar named or the first model from your model line, e.g. if you have a Raspberry Pi 3, check the Raspberry Pi download, you device revision may be listed unter "Compatible".  The filter does not show all manufacturer, so just scroll the list down. For my odroid-mc1 I have to scroll down and choose Odroid XU4 / HCx. Read the notes on the page, there might be important settings, as for my odroid-mc1 I can configure an optimized board configuration after installation. 
  2. Flash Armbian Image to sdcard, e.g. BalenaEtcher
  3. Boot Armbian, wait until it is online, this may take some time. I check this by looking at my DHCP logs from my dnsmasq server, you may also get the IP address from your router if  the name registration resolution is not working 
  4. Login as root (any ssh client like PuTTY on windows),password 1234.
    1. Change the root password
    2. Choose your shell
    3. Cancel user account creation with ctrl-c or create a user account if you prefer to type sudo all the time. Your choice.
  5. Do apt update && apt dist-upgrade && reboot
  6. Edit the hostname in the file /etc/hostname. nano is installed, vi needs to be installed with apt install vim or your prefered vi version. My guess the hostname is always the model name. 
  7. If you haven't applied any recommended changes for your machine, do it now, for me it required a reboot, so afterwards the device should be available under the new hostname. 
  8. Install VirtualHere Server on the device by running curl https://raw.githubusercontent.com/virtualhere/script/main/install_server | sh . If you aren't root, write sudo before the sh (not the curl!) as usual. The service is automatically started. On your client, install the VirtualHere client. For some reason he cannot find the server automatically, so I have to add it manually by right clicking on USB Hubs and do "Specify Hubs". Add here your choosen hostname or IP adress + the default port. If any device is connected to your new print server, it should be visible where and with right click yoo can connect it.
  9. Do a reboot with the command reboot and test if  it comes up again. 
  10. If you have connected any USB device, starting the client on the PC and scanning (did not work for me) or configuring a host manually should should the connected USB devices. 
  11. You are ready to use the now "networked" device. Oh and it's by default not encrypted nor password protected. 

Some minor hardening

If you want to power off the server by simply cutting its power, the root filesystem won't be happy about this, even with a journal. To make this safe, we'll make the filesystem read-only, and overlay it with a in-memory filesystem, so logs and other runtime stuff can be written, but every change will be gone after reboot as the underlying filesystem is readonly. 
  1. apt install overlayroot
  2. edit /etc/overlayroot.conf and change the last line to overlayroot="tmpfs"
  3. reboot
By default the system will be updated automatically by unattended-upgrade which is now a little pointless as the updates will be gone after reboot.. As a good exercise on how to make changes, we will remove it and some other packages. 
The normal worklow is the command sequence
  1. overlayroot-chroot
  2. make your changes
  3. exit 
Only this particular session can write to the root filesystem, and with exit you end this session. But I had trouble doing this, often getting the following messages, which make me a bit nervous: 
mount: /media/root-ro: mount point is busy.
ERROR: Note that [/media/root-ro] is still mounted read/write
This happend even with sync before exiting or even when Doing minor changes like config file ediiting.
 
My recommendation is this procedure:
  1. overlayroot-chroot
  2. edit /etc/overlayroot.conf and comment out the line we added earlier by prepending a hash sign. Then do a clean reboot and after that the filesystem is writable again for everybody
  3. reboot
  4. make your changes 
  5. remove the comment hash sign in /etc/overlayroot.conf
  6. reboot
So, back to the uninstallation. run apt autoremove <packages>. I recommend to uninstall these packages
  • Automated updates: unattended-upgrade
  • WiFi functionality: wireless-regdb wireless-tools wpasupplicant
  • Development stuff: gcc-12 cpp-12 gcc-11 git
  • When not running from a real SSD or HDD: smartmontools
  • NFS (this is not file server): rpcbind
Now the system is down to only 100MB RAM consumption, mostly for systemd & networkmanager
 
I had issues like connection losses, reconnection issues, even resets of the printer when trying to connect to the USB device. In the end I solved this issue by disabling the power management for the network chip. Long story cut short: 
  1. Make the root file system writable (see above) 
  2. echo 'SUBSYSTEM=="net", ATTR{power/control}="on"' >>  /etc/udev/rules.d/99-nic-powermanagement.rules
  3. reboot
This is the most generic solution and should work for almost any network device.
 
Now throw the SBC it into some corner and forget it. 
 
Rubrik: 

Disabling power management for network card with udev

I tried to disable power saving on the "network card" in my odroid-mc1 (SBC like Raspberry Pi) which I use as network print server as my 3D printer can be controlled via  USB and SD card . Per default powersaving is on and I assume this is cause of various USB-over-IP issues when the system is idle.
 
# lsusb
Bus 006 Device 002: ID 0bda:8153 Realtek Semiconductor Corp. RTL8153 Gigabit Ethernet Adapter
 
The interface name is enx001e06373d78 and can be found in /sys/class/net/enx001e06373d78 and with the power management setting in power/control: 
# cat /sys/class/net/enx001e06373d78/power/control
auto
 
auto means activated power management and on is no power powermanagement as per https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-power
 
As usual, settings in sysfs are not persisted and gone after a reboot or reconnect, it's not configurable via sysctl, so the most generic way is in udev and doing so makes it work with other USB ports, other devices, multiple device of the same type, etc. 
 
Either edit an existing udev rule file in /etc/udev/rules.d/ or a create a new one like 99-my-powersettings.rules. The order in which udev rules are processed is sorted by the number in the file name and you want to do this after your network card is configured, so put it at the end. 
 
Udev rules consist of conditions or "filters" and actions. Here are 3 rules that set power/control to on on the odroid-mc1: 
 
# Condition: is network device, actions: write on into its power/control sysfs file (if it exists..)
#SUBSYSTEM=="net", ATTR{power/control}="on"

# Conditions: is network device and has productId 8513, actions: write on into its power/control sysfs file
SUBSYSTEM=="net", ATTRS{idProduct}=="8153" , ATTR{power/control}="on"

# Conditions: is network device and has productId 8513 and power/control is set to auto, then set it to on
#SUBSYSTEM=="net", ATTRS{idProduct}=="8153", ATTR{power/control}=="auto", ATTR{power/control}="on"
 
Parts with == are one type of conditions and single equal sign set values. You cat get a list of possible conditions with 
# udevadm info -a -p /sys/class/net/enx001e06373d78
Be as generic or specific as you like. 
 
Now the funny part and pitfall. It took me some time to get this working, the setting was never applied. Other people had the same issue, sometimes there was a solution provided which did not work.. The issue was that I tried to apply the setting to the "USB device", not the "network device". That does that mean. 
 
In /etc/udev/rules.d/50-usb-realtek-net.rules is a rule (slightly modified for easier reading) for the NIC on the system: 
SUBSYSTEM=="usb", ATTR{idVendor}=="0bda", ATTR{idProduct}=="8153", ATTR{bConfigurationValue}!="$env{REALTEK_NIC_MODE}", ATTR{bConfigurationValue}="$env{REALTEK_NIC_MODE}"
which sets some NIC Mode, whatever this means. But this is my network chip, so I just add ATTR{power/control}="on" and it should never ever go into power management mode again? 
 
Wrong. This operates on the USB endpoint: 
# readlink -f /sys/class/net/enx001e06373d78/device
/sys/devices/platform/soc/soc:usb3-1/12400000.dwc3/xhci-hcd.8.auto/usb6/6-1/6-1:1.0 
 
or shorter /sys/bus/usb/devices/6-1:1.0 which has the referenced bConfigurationValue: 
# ls -al /sys/bus/usb/devices/6-1:1.0/
total 0
drwxr-xr-x 7 root root    0 Jul 15 04:46 .
drwxr-xr-x 6 root root    0 Jul 15 04:46 ..
-rw-r--r-- 1 root root 4096 Jul 15 04:51 authorized
-r--r--r-- 1 root root 4096 Jul 15 04:51 bAlternateSetting
-r--r--r-- 1 root root 4096 Jul 15 04:46 bInterfaceClass
-r--r--r-- 1 root root 4096 Jul 15 04:46 bInterfaceNumber
-r--r--r-- 1 root root 4096 Jul 15 04:51 bInterfaceProtocol
-r--r--r-- 1 root root 4096 Jul 15 04:51 bInterfaceSubClass
-r--r--r-- 1 root root 4096 Jul 15 04:51 bNumEndpoints
...
 
and it does not even have a power/control setting: 
 
# ls -al /sys/bus/usb/devices/6-1:1.0/power
total 0
drwxr-xr-x 2 root root    0 Jul 15 04:46 .
drwxr-xr-x 7 root root    0 Jul 15 04:46 ..
-rw-r--r-- 1 root root 4096 Jul 15 04:51 async
-r--r--r-- 1 root root 4096 Jul 15 04:51 runtime_active_kids
-r--r--r-- 1 root root 4096 Jul 15 04:51 runtime_enabled
-r--r--r-- 1 root root 4096 Jul 15 04:51 runtime_status
-r--r--r-- 1 root root 4096 Jul 15 04:51 runtime_usage
 
And it's not even a network card, remember the NIC was in /sys/class/net, but the SUBSYSTEM in the rule is usb.. So my rule worked on the wrong device, on the "usb device" instead of the network device..
 
A few hours wasted on my first udev adventure & a lesson learned. Always work on the correct device. 
 
Rubrik: