|
defmodule MyProject.WiFi do |
|
alias Nerves.WpaSupplicant |
|
alias Nerves.Leds |
|
require Logger |
|
use GenServer |
|
|
|
@wpa_supplicant_path "/usr/sbin/wpa_supplicant" |
|
@wpa_control_path "/var/run/wpa_supplicant" |
|
@wpa_config_file "/tmp/nerves_network_wpa.conf" |
|
@interface "wlan0" |
|
@control_pipe "#{@wpa_control_path}/#{@interface}" |
|
|
|
def start_link(networks) do |
|
GenServer.start_link(__MODULE__, networks) |
|
end |
|
|
|
def interface, do: @interface |
|
|
|
# OTP callbacks |
|
|
|
def init(networks) do |
|
{:ok, _} = Registry.register(Nerves.NetworkInterface, @interface, []) |
|
{:ok, _} = Registry.register(Nerves.Udhcpc, @interface, []) |
|
Leds.set debug: :slowblink |
|
if Enum.member?(Nerves.NetworkInterface.interfaces, @interface) do |
|
:ok = Nerves.NetworkInterface.ifup(@interface) |
|
{:ok, {:added, networks, %{wpa_pid: nil, dhcp_pid: nil}}} |
|
else |
|
{:ok, {:start, networks, %{wpa_pid: nil, dhcp_pid: nil}}} |
|
end |
|
end |
|
|
|
def handle_info({Nerves.NetworkInterface, :ifadded, _}, {:start, networks, pids}) do |
|
:ok = Nerves.NetworkInterface.ifup(@interface) |
|
{:noreply, {:added, networks, pids}} |
|
end |
|
|
|
def handle_info({Nerves.NetworkInterface, :ifchanged, %{is_up: true}}, {:added, networks, pids}) do |
|
{:ok, wpa_pid} = start_wpa_supplicant() |
|
{:ok, _} = Registry.register(Nerves.WpaSupplicant, @interface, []) |
|
setup_wifi(wpa_pid, networks) |
|
{:noreply, {:up, networks, %{pids | wpa_pid: wpa_pid}}} |
|
end |
|
|
|
def handle_info({Nerves.NetworkInterface, :ifchanged, %{is_up: false}}, {:up, networks, pids}) do |
|
stop_wpa_supplicant(pids) |
|
stop_dhcp(pids) |
|
:ok = Nerves.Network.Resolvconf.clear(Nerves.Network.Resolvconf, @interface) |
|
{:noreply, {:added, networks, %{wpa_pid: nil, dhcp_pid: nil}}} |
|
end |
|
|
|
def handle_info({Nerves.NetworkInterface, :ifremoved, _}, {:added, networks, pids}) do |
|
stop_wpa_supplicant(pids) |
|
stop_dhcp(pids) |
|
{:noreply, {:start, networks, %{wpa_pid: nil, dhcp_pid: nil}}} |
|
end |
|
|
|
def handle_info({Nerves.WpaSupplicant, :"CTRL-EVENT-CONNECTED", _}, {:up, networks, pids}) do |
|
{:ok, dhcp_pid} = start_dhcp(pids) |
|
Leds.set debug: false |
|
# Here is a good place to notify other processes of network changes |
|
# (for example, I found I need to tell my MQTT client to reconnect) |
|
{:noreply, {:connected, networks, %{pids | dhcp_pid: dhcp_pid}}} |
|
end |
|
|
|
def handle_info({Nerves.WpaSupplicant, :"CTRL-EVENT-DISCONNECTED", _}, {:connected, networks, pids}) do |
|
stop_dhcp(pids) |
|
Leds.set debug: :slowblink |
|
{:noreply, {:up, networks, %{pids | dhcp_pid: nil}}} |
|
end |
|
|
|
def handle_info({Nerves.Udhcpc, :bound, info}, state) do |
|
:ok = Nerves.NetworkInterface.setup(@interface, info) |
|
:ok = Nerves.Network.Resolvconf.setup(Nerves.Network.Resolvconf, @interface, info) |
|
Logger.debug(fn -> "WiFi - bound: #{@interface}" end) |
|
{:noreply, state} |
|
end |
|
|
|
def handle_info(message, state) do |
|
Logger.debug(fn -> "WiFi - unhandled message: #{inspect(message)}" end) |
|
{:noreply, state} |
|
end |
|
|
|
# helper functions |
|
|
|
defp start_wpa_supplicant() do |
|
if !File.exists?(@control_pipe) do |
|
File.write!(@wpa_config_file, "country=DE") |
|
{_, 0} = System.cmd(@wpa_supplicant_path, |
|
["-i#{@interface}", |
|
"-c#{@wpa_config_file}", |
|
"-C#{@wpa_control_path}", |
|
"-Dnl80211,wext", |
|
"-B"]) |
|
|
|
Logger.debug(fn -> "WiFi - started wpa_supplicant on #{@interface}" end) |
|
end |
|
:timer.sleep(300) # wait for pipe to be created |
|
WpaSupplicant.start_link(@interface, @control_pipe, name: :"Nerves.WpaSupplicant.#{@interface}") |
|
end |
|
|
|
defp stop_wpa_supplicant(pids) do |
|
%{wpa_pid: wpa_pid} = pids |
|
if is_pid(wpa_pid), do: WpaSupplicant.stop(wpa_pid) |
|
end |
|
|
|
defp start_dhcp(pids) do |
|
stop_dhcp(pids) |
|
Nerves.Network.Udhcpc.start_link(@interface) |
|
end |
|
|
|
defp stop_dhcp(pids) do |
|
%{dhcp_pid: dhcp_pid} = pids |
|
if is_pid(dhcp_pid), do: Nerves.Network.Udhcpc.stop(dhcp_pid) |
|
end |
|
|
|
defp setup_wifi(pid, networks) do |
|
WpaSupplicant.request(pid, {:REMOVE_NETWORK, :all}) |
|
networks |> Enum.each(fn {name, options} -> setup_ssid(pid, name, options) end) |
|
end |
|
|
|
defp setup_ssid(pid, name, options) do |
|
nid = WpaSupplicant.request(pid, :ADD_NETWORK) |
|
Enum.each(options, fn {key, value} -> |
|
WpaSupplicant.request(pid, {:SET_NETWORK, nid, key, value}) |
|
end) |
|
WpaSupplicant.request(pid, {:SET_NETWORK, nid, :id_str, Atom.to_string(name)}) |
|
WpaSupplicant.request(pid, {:ENABLE_NETWORK, nid}) |
|
end |
|
end |