VintageNetECM (vintage_net_ecm v0.2.0)
Copy MarkdownA VintageNet technology for cellular modems whose data plane is a USB CDC-ECM
(or RNDIS) network interface — e.g. the Quectel EG800Q in usbnet ECM mode.
Unlike VintageNetMobile, which dials a PPP link over a serial AT port, an
ECM-class modem exposes an ordinary Ethernet-like netdev (usb0/usb1/wwan0)
whose IP/DNS/route are obtained over DHCP once the modem's internal data call is
up. VintageNetECM therefore composes VintageNetEthernet for the IP plane and
adds an AT-control sidecar (VintageNetECM.ATController) that:
- ensures the radio is on (
AT+CFUN=1), sets the APN (AT+CGDCONT), and selects the operator automatically (AT+COPS=0), - waits for the modem to register (
+CEREG-> 1/5), - brings the ECM data call up via the configured
VintageNetECM.Modemso the netdev gets carrier and DHCP can complete, - monitors registration/signal and republishes them under
["interface", ifname, "mobile", ...], - deactivates the data call on teardown.
The vendor-specific AT details live behind the VintageNetECM.Modem behaviour. The
default implementation, VintageNetECM.Modem.Quectel, targets Quectel usbnet ECM
modems such as the EG800Q.
Configuration
VintageNet.configure("usb1", %{
type: VintageNetECM,
vintage_net_ecm: %{
service_providers: [%{apn: "super"}],
at_tty: "ttyUSB2",
context_id: 1,
modem: VintageNetECM.Modem.Quectel
}
}):modem selects the VintageNetECM.Modem implementation (defaults to
VintageNetECM.Modem.Quectel). :at_tty defaults to that modem's
default_at_tty/0. :ipv4 defaults to %{method: :dhcp} (the only sensible mode
for ECM). Other VintageNetEthernet options (:dhcpd, static :ipv4,
:mac_address) pass through to the composed ethernet config.
Summary
Functions
@spec network_time(VintageNet.ifname()) :: {:ok, VintageNetECM.Modem.network_time()} | {:error, term()}
Like utc_now/1, but also reports the network's timezone.
iex> VintageNetECM.network_time("usb1")
{:ok, %{utc: ~U[2026-08-08 17:04:31Z], utc_offset: -25200, dst_offset: 3600}}:utc_offset is the local timezone's offset from UTC in seconds, of which
:dst_offset seconds are daylight saving.
@spec utc_now(VintageNet.ifname()) :: {:ok, DateTime.t()} | {:error, term()}
Ask the modem on ifname what time the network says it is, in UTC.
iex> VintageNetECM.utc_now("usb1")
{:ok, ~U[2026-08-08 17:04:31Z]}This is a live AT query rather than a published property, so it costs a round trip
to the modem and can fail: {:error, :not_running} if ifname isn't configured for
VintageNetECM, {:error, :not_synchronized} if the modem hasn't picked the time up
from the network yet.
The local timezone is published as ["interface", ifname, "mobile", "timezone"] —
see VintageNetECM.ATController for the full property list.