View Source README

vintage net logo

Hex version API docs CircleCI Coverage Status

VintageNetWiFi makes it easy to add WiFi support for your device. This can be as simple as connecting to a WiFi access point or starting a WiFi access point so that other computers can connect directly.

You will need a WiFi module to use this library. If you're using Nerves, the official Raspberry Pi and Beaglebone systems contain WiFi drivers for built-in modules. If you are using a USB WiFi module, make sure that the Linux device driver for that module is loaded and any required firmware is available.

Once that's done, all that you need to do is add :vintage_net_wifi to your mix dependencies like this:

def deps do
  [
    {:vintage_net_wifi, "~> 0.12.0", targets: @all_targets}
  ]
end

VintageNetWiFi also requires that the wpa_supplicant package and necessary WiFi kernel modules are included in the system. All officially supported Nerves systems that run on hardware with WiFI should work.

In Buildroot, check that BR2_PACKAGE_WPA_SUPPLICANT is enabled. Even if you don't plan to use WPA3, enable BR2_PACKAGE_WPA_SUPPLICANT_WPA3 as well so that the generic WiFi configurations don't fail due to parse errors.

If you are using access point mode, check that CONFIG_UDHCPD is enabled in Busybox and BR2_PACKAGE_WPA_SUPPLICANT_HOTSPOT is enabled in Buildroot.

Usage

The easiest way to configure WiFi is to using VintageNetWiFi.quick_configure/2. For example:

iex> VintageNetWiFi.quick_configure("my_access_point", "secret_passphrase")
:ok

Using VintageNet.info to check whether you're connected. If there's no connection and you think there should be one, try watching the logs. On Nerves, the normal ways are to run RingLogger.next, RingLogger.viewer or log_attach/log_detach from an IEx prompt. (Hopefully the console or a wired network interface works)

The second easiest way to create WiFi configurations is to use the helper functions in VintageNetWiFi.Cookbook. Check out the module documentation for the various configurations.

See the VintageNetWiFi.quick_configure/2 documentation for details on WPA3 support.

Advanced usage

WiFi network interfaces typically have names like "wlan0" or "wlan1" when using Nerves. Most of the time, there's only one WiFi interface and its "wlan0". Some WiFi adapters expose separate interfaces for 2.4 GHz and 5 GHz and they can be configured independently.

An example WiFi configuration looks like this:

config :vintage_net,
  config: [
    {"wlan0",
      %{
        type: VintageNetWiFi,
        vintage_net_wifi: %{
          networks: [
            %{
              key_mgmt: :wpa_psk,
              ssid: "my_network_ssid",
              psk: "a_passphrase_or_psk",
            }
          ]
        },
        ipv4: %{method: :dhcp},
      }
    }
  ]

The :ipv4 key is handled by vintage_net to set the IP address on the connection. Most of the time, you'll want to use DHCP to dynamically get an IP address.

The :vintage_net_wifi key has the following common fields:

  • :ap_scan - See wpa_supplicant documentation. The default for this, 1, should work for nearly all users.
  • :bgscan - Periodic background scanning to support roaming within an ESS.
    • :simple
    • {:simple, args} - args is a string to be passed to the simple wpa module
    • :learn
    • {:learn, args} args is a string to be passed to the learn wpa module
  • :passive_scan
    • 0: Do normal scans (allow active scans) (default)
    • 1: Do passive scans.
  • :regulatory_domain: Two character country code. Technology configuration will take priority over Application configuration
  • :networks - A list of Wi-Fi networks to configure. In client mode, VintageNet connects to the first available network in the list. In host mode, the list should have one entry with SSID and password information.
    • :mode -
      • :infrastructure (default) - Normal operation. Associate with an AP
      • :ap - access point mode
      • :ibss - peer to peer mode (not supported)
      • :p2p_go - P2P Go mode (not supported)
      • :p2p_group_formation - P2P Group Formation mode (not supported)
      • :mesh - mesh mode
    • :ssid - The SSID for the network
    • :key_mgmt - WiFi security mode (:wpa_psk for WPA2, :none for no password or WEP, :sae for pure WPA3, or :wpa_psk_sha256 for WPA2 with SHA256). Not used if :allowed_key_mgmt is set.
    • :allowed_key_mgmt - A list of allowed WiFi security modes. See :key_mgmt for options. Supported in v0.12.1+. VintageNetWiFi's configuration normalizer automatically sets :key_mgmt to the first option in the list for backwards compatibility with v0.12.0 and earlier.
    • :psk - A WPA2 passphrase or the raw PSK. If a passphrase is passed in, it will be converted to a PSK and discarded.
    • :sae_password - A password for use with SAE authentication. This is similar to a passphrase that you could supply to :psk, but it has less length restrictions.
    • :priority - The priority to set for a network if you are using multiple network configurations
    • :scan_ssid - Scan with SSID-specific Probe Request frames (this can be used to find APs that do not accept broadcast SSID or use multiple SSIDs; this will add latency to scanning, so enable this only when needed)
    • :frequency - When in :ibss mode, use this channel frequency (in MHz). For example, specify 2412 for channel 1.
    • :ieee80211w - Whether management frame protection is enabled. Set to 0, 1, 2 or :disabled, :optional, :required.

These keys fairly directly map to the keys in the official docs. VintageNetWiFi performs some checks on the keys to avoid typos and other easy mistakes from breaking the wpa_supplicant.conf file. To inspect the generated configuration, run File.read("/tmp/vintage_net/wpa_supplicant.conf.wlan0").

If you do not want VintageNetWiFi to generate a wpa_supplicant.conf file for you, you can specify the contents for yourself by using the :wpa_supplicant_conf key. For example,

iex> VintageNet.configure("wlan0", %{
      type: VintageNetWiFi,
      vintage_net_wifi: %{
        wpa_supplicant_conf: """
        network={
          ssid="home"
          key_mgmt=WPA-PSK
          psk="very secret passphrase"
        }
        """
      },
      ipv4: %{method: :dhcp}
    })

Please note that the syntax of the :wpa_supplicant_conf key is NOT validated by VintageNet and we do not recommend them method unless you are troubleshooting the wpa_supplicant or are working on a new feature.

WPA PSK example:

iex> VintageNet.configure("wlan0", %{
      type: VintageNetWiFi,
      vintage_net_wifi: %{
        networks: [
          %{
            key_mgmt: :wpa_psk,
            psk: "a_passphrase_or_psk",
            ssid: "my_network_ssid"
          }
        ]
      },
      ipv4: %{method: :dhcp}
    })

WEP example:

iex> VintageNet.configure("wlan0", %{
      type: VintageNetWiFi,
      vintage_net_wifi: %{
        networks: [
          %{
            ssid: "my_network_ssid",
            wep_key0: "42FEEDDEAFBABEDEAFBEEFAA55",
            key_mgmt: :none,
            wep_tx_keyidx: 0
          }
        ]
      },
      ipv4: %{method: :dhcp}
    })

WPA3-only example:

iex> VintageNet.configure("wlan0", %{
      type: VintageNetWiFi,
      ipv4: %{method: :dhcp},
      vintage_net_wifi: %{
        networks: [
          %{
            key_mgmt: :sae,
            ssid: "my_network_ssid",
            sae_password: "a_password",
            ieee80211w: 2
          }
        ]
      }
    })

WPA2 w/ SHA256 example:

iex> VintageNet.configure("wlan0", %{
      type: VintageNetWiFi,
      ipv4: %{method: :dhcp},
      vintage_net_wifi: %{
        networks: [
          %{
            key_mgmt: :wpa_psk_sha256,
            ssid: "my_network_ssid",
            psk: "a_password",
            ieee80211w: 2
          }
        ]
      }
    })

Enterprise Wi-Fi (WPA-EAP) support mostly passes through to the wpa_supplicant. Instructions for enterprise network for Linux should map. For example:

iex> VintageNet.configure("wlan0", %{
      type: VintageNetWiFi,
      vintage_net_wifi: %{
        networks: [
          %{
            ssid: "testing",
            key_mgmt: :wpa_eap,
            pairwise: "CCMP TKIP",
            group: "CCMP TKIP",
            eap: "PEAP",
            identity: "user1",
            password: "supersecret",
            phase1: "peapver=auto",
            phase2: "MSCHAPV2"
          }
        ]
      },
      ipv4: %{method: :dhcp}
})

Network adapters that can run as an Access Point can be configured as follows:

iex> VintageNet.configure("wlan0", %{
      type: VintageNetWiFi,
      vintage_net_wifi: %{
        networks: [
          %{
            mode: :ap,
            ssid: "test ssid",
            key_mgmt: :none
          }
        ]
      },
      ipv4: %{
        method: :static,
        address: "192.168.24.1",
        netmask: "255.255.255.0"
      },
      dhcpd: %{
        start: "192.168.24.2",
        end: "192.168.24.10",
        options: %{
          dns: ["1.1.1.1", "1.0.0.1"],
          subnet: "255.255.255.0",
          router: ["192.168.24.1"]
        }
      }
})

If your device may be installed in different countries, you should override the default regulatory domain to the desired country at runtime. VintageNet uses the global domain by default and that will restrict the set of available WiFi frequencies in some countries. For example:

iex> VintageNet.configure("wlan0", %{
      type: VintageNetWiFi,
      vintage_net_wifi: %{
        regulatory_domain: "US",
        networks: [
          %{
            ssid: "testing",
            key_mgmt: :wpa_psk,
            psk: "super secret"
          }
        ]
      },
      ipv4: %{method: :dhcp}
})

Network adapters that can be configured to support 80211s mesh networking can be configured as follows:

(Raspberry Pi internal WiFi modules do not support 80211s meshing)

VintageNet.configure("mesh0", %{
  type: VintageNetWiFi,
  vintage_net_wifi: %{
    user_mpm: 1,
    networks: [
      %{
        ssid: "my-mesh",
        key_mgmt: :none,
        mode: :mesh
      }
    ]
  }
})

Mesh nodes connected to external networks can set so called "meshgate" params. See this document for more information

VintageNet.configure("mesh0", %{
  type: VintageNetWiFi,
  vintage_net_wifi: %{
    user_mpm: 1,
    networks: [
      %{
        ssid: mesh_id,
        key_mgmt: :none,
        mode: :mesh,
        mesh_hwmp_rootmode: 4,
        mesh_gate_announcements: 1
      }
    ]
  }
})

Note that the example mesh configuration does not contain IP address settings. All standard IP schemes are acceptable, but which one to use depends on the network configuration. The simplest way to test the mesh network is to have every node configure a static predictable IP address. DHCP will also work, but this forces a "client/server" configuration meaning that nodes joining the network will need to decide if they should be a DHCP server or client.

Properties

In addition to the common vintage_net properties for all interface types, this technology reports the following:

PropertyValuesDescription
access_points[%AccessPoint{}]A list of access points as found by the most recent scan
clients["11:22:33:44:55:66"]A list of clients connected to the access point when using mode: :ap
current_ap%AccessPoint{}The currently associated access point
peers[%MeshPeer{}]a list of mesh peers that the current node knows about when using mode: :mesh
event%Event{}WiFi control events not otherwise handled

Access points are identified by their BSSID. Information about an access point has the following form:

%VintageNetWiFi.AccessPoint{
  band: :wifi_5_ghz,
  bssid: "8a:8a:20:88:7a:50",
  channel: 149,
  flags: [:wpa2_psk_ccmp, :ess],
  frequency: 5745,
  signal_dbm: -76,
  signal_percent: 57,
  ssid: "MyNetwork"
}

Mesh peers are identified by their BSSID. Information about a peer has the following form:

%VintageNetWiFi.MeshPeer{
  active_path_selection_metric_id: 1,
  active_path_selection_protocol_id: 1,
  age: 2339,
  authentication_protocol_id: 0,
  band: :wifi_2_4_ghz,
  beacon_int: 1000,
  bss_basic_rate_set: "10 20 55 110 60 120 240",
  bssid: "f8:a2:d6:b5:d4:07",
  capabilities: 0,
  channel: 5,
  congestion_control_mode_id: 0,
  est_throughput: 65000,
  flags: [:mesh],
  frequency: 2432,
  id: 7,
  mesh_capability: 9,
  mesh_formation_info: 2,
  mesh_id: "my-mesh",
  noise_dbm: -89,
  quality: 0,
  signal_dbm: -27,
  signal_percent: 97,
  snr: 62,
  ssid: "my-mesh",
  synchronization_method_id: 1
}

Applications can scan for access points in a couple ways. The first is to call VintageNet.scan("wlan0"), wait for a second, and then call VintageNet.get(["interface", "wlan0", "wifi", "access_points"]). This works for scanning networks once or twice. A better way is to subscribe to the "access_points" property and then call VintageNet.scan("wlan0") on a timer. The "access_points" property updates as soon as the WiFi module notifies that it is complete so applications don't need to guess how long to wait.

If you're using RingLogger (which is the default for Nerves) then you probably also want to call RingLogger.attach to receive any logs in your terminal which may include information about the wifi connection.

Events

Some wpa_supplicant events like CTRL-EVENT-ASSOC-REJECT are passed on through the "event" property to be handled outside VintageNetWifi. These events might be useful, but optional.

Signal quality info in STA (client) mode

You can send ioctl command to get information about signal level, quality and other info when connected to network in STA mode. Run:

VintageNet.ioctl("wlan0", :signal_poll)

Example output:

{:ok, %VintageNetWiFi.SignalInfo{
  center_frequency1: 2462,
  center_frequency2: 0,
  frequency: 2472,
  linkspeed: 300,
  signal_dbm: -32,
  signal_percent: 94,
  width: "40 MHz"
}}

Debugging

Unfortunately, when you're getting started for the very first time, WiFi can be quite frustrating. Error messages and logs are not all that helpful. The first debugging step is to connect to your device (over a UART or USB Gadget or maybe a wired Ethernet connection). Run:

iex> VintageNet.info

Double check that all of your parameters are set correctly. The :psk cannot be checked here, so if you suspect that's wrong, double check your config.exs. The next step is to look at log messages for connection errors. On Nerves devices, run RingLogger.next at the IEx prompt.