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, enableBR2_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 andBR2_PACKAGE_WPA_SUPPLICANT_HOTSPOT
is enabled in Buildroot.
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.
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
- Seewpa_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 thesimple
wpa module:learn
{:learn, args}
args is a string to be passed to thelearn
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
or:ap
mode, use this channel frequency (in MHz). For example, specify 2412 for channel 1.:ieee80211w
- Whether management frame protection is enabled. Set to0
,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"]
}
}
})
An example of configuring the Access Point's frequency is as follows. Consult WLAN Channels for the appropriate channels for your device.
iex> VintageNet.configure("wlan0", %{
type: VintageNetWiFi,
vintage_net_wifi: %{
networks: [
%{
mode: :ap,
ssid: "test ssid",
frequency: 5180, # Creates a 5 GHz wifi network if supported by device
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.
In addition to the common vintage_net
properties for all interface types, this
technology reports the following:
Property | Values | Description |
---|---|---|
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.
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.
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"
}}
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.