VintageNetECM (vintage_net_ecm v0.2.0)

Copy Markdown

A 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.Modem so 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

Like utc_now/1, but also reports the network's timezone.

Ask the modem on ifname what time the network says it is, in UTC.

Functions

network_time(ifname)

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

utc_now(ifname)

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