Using 4G LTE wireless modems on a Raspberry Pi

For a recent project, I needed to add cellular connectivity to a Raspberry Pi (actually, an entire cluster... but that's a story for a future time!).

Raspberry Pi 4 model B with 4G LTE wireless Quectel modem and antenna and USB adapter

I figured I'd document the process in this blog post so people who follow in my footsteps don't need to spend quite as much time researching. This post is the culmination of 40+ hours of reading, testing, and head-scratching.

There doesn't seem to be any good central resource for "4G LTE and Linux" out there, just a thousand posts about the ABC's of getting an Internet connection working through a 4G modem—but with precious little explanation about why or how it works. (Or why someone should care about random terms like PPP, ECM, QMI, or MBIM, or why someone would choose qmi_wwan over cdc_ether, or ... I could go on).

Hopefully you can learn something from my notes. Or point out places where I'm glaringly wrong :)

Home Assistant Yellow - Pi-powered local automation

I've dipped my toes in 'smart home' automation in the past.

Typically I approach 'smart' and 'IoT' devices as a solution to one simple problem, instead of trying to do 'all the things'.

For example, I wanted to make it easy for my kids to control a home theater with four different devices and complex audio/visual routing, so I bought a Harmony remote and programmed it to control TV, a game console, an Apple TV, and radio. I don't want Logitech to start controlling other aspects of my house, or to give intruders an avenue by which they could invade my home's network.

However, many smart devices require a persistent Internet connection to use them, and that I cannot abide.

Home Assistant Yellow - inside enclosure

Network interface routing priority on a Raspberry Pi

52Pi Raspberry Pi Compute Module 4 Router Board

As I start using Raspberry Pis for more and more network routing activities—especially as the Compute Module 4 routers based on Debian, OpenWRT, and VyOS have started appearing—I've been struggling with one particular problem: how can I set routing priorities for network interfaces?

Now, this is a bit of a loaded question. You could dive right into routing tables and start adding and deleting routes from the kernel. You could mess with subnets, modify firewalls, and futz with iptables.

But in my case, my need was simple: I wanted to test the speed of a specific interface, either from one computer to another, or over the Internet (e.g. via speedtest-cli).

The problem is, even if you try limiting an application to a specific IP address (each network interface has its own), the Linux kernel will choose whatever network route it deems the best.

SSH and HTTP to a Raspberry Pi behind CG-NAT

For a project I'm working on, I'll have a Raspberry Pi sitting behind a 4G LTE modem:

Raspberry Pi 4 with 4G LTE modem and antenna on desk

This modem is on AT&T's network, but regardless of the provider, unless you're willing to pay hundreds or thousands of dollars a month for a SIM with a public IP address, the Internet connection will be running behind CG-NAT.

What this means is there's no publicly routable address for the Pi—you can't access it from the public Internet, since it's only visible inside the cell network's private network.

There are a few different ways people have traditionally dealt with accessing devices running through CG-NAT connections:

  1. Using a VPN
  2. Using a one-off tool like ngrok
  3. Using reverse tunnels, often via SSH

And after weighing the pros and cons, I decided to go with option 3, since—for my needs—I want to have two ports open back to the Raspberry Pi:

Quick 'Hello World' HTTP deployment for testing K3s and Traefik

Recently I needed to test the full HTTP stack between a Kubernetes cluster's member nodes and an external Internet routing setup, and so I wanted to quickly install K3s (which includes Traefik by default, and load balances through ports 80 and 443 on all nodes), then get a quick 'hello world' web page up, so I could see if the traffic was routing properly all the way from the external host through to a running container exposed via Traefik Ingress.

Here's how I set up a basic 'Hello World' web page on my K3s cluster:

First, I created an HTML file to be stored as a ConfigMap. Create a file named index.html with the following contents:

  <title>Hello World!</title>
<body>Hello World!</body>

Create a ConfigMap with the HTML from the file you just created:

$ kubectl create configmap hello-world --from-file index.html

Save the following to Kubernetes resource definitions into a file named hello-world.yml:

Autofocus on a Pi - ArduCam's new 16MP camera

ArduCam with other Raspberry Pi Cameras - v2 HQ and Autofocus 16MP

ArduCam recently completed a successful crowdfunding campaign for a 16 megapixel Raspberry Pi camera with built-in autofocus.

The camera is on a board with the same footprint as the Pi Camera V2, but it has a Sony IMX519 image sensor with twice the resolution (16 Mpix vs 8 Mpix) and a larger image sensor (1/2.53" vs 1/4"), a slightly nicer lens, and the headline feature: a built-in autofocus motor.

Autofocus performance

Getting right into the meat of it: autofocus works, with some caveats.

First, the good. Autofocus is quick to acquire focus in many situations, especially in well-lit environments with one main subject. Using ArduCam's fork of libcamera-still or libcamera-vid, you only need to pass in --autofocus and the camera will snap into focus immediately.