Packages

An example open source Elixir application.

Current section

Files

Jump to
elevator lib network.ex
Raw

lib/network.ex

defmodule Network do
@moduledoc """
Broadcasting of IP and listening to other IPs.
"""
use GenServer, restart: :permanent
require Logger
@udpPort Application.compile_env(:elevator, :udpPort)
@broadcastIP Application.compile_env(:elevator, :broadcastIP)
@broadcastRate Application.compile_env(:elevator, :broadcastRate)
# Public functions
# --------------------------------------------
@doc "Starts the GenServer."
def start_link([port]), do: GenServer.start_link(__MODULE__, [port], name: __MODULE__)
@doc "Initialize the GenServer by starting node and opening UDP socket at port `@udpPort`."
def init([port]) do
Node.start(getElevatorID(port))
Node.set_cookie(:safari)
baseOptList = [{:broadcast, true}, {:active, true}, {:reuseaddr, true}]
# in order to reuse a socket/port for udp broadcast, extra options need to be passed for non-linus OS-s (only relevant for multiple elevators on one PC):
fullOptList =
case :os.type() do
{:unix, :darwin} -> [{:raw, 0xFFFF, 0x0200, <<1::32-native>>} | baseOptList]
{:win32, :nt} -> [{:raw, 0xFFFF, 0x0004, <<1::32-native>>} | baseOptList]
_ -> baseOptList
end
{:ok, socket} = :gen_udp.open(@udpPort, fullOptList)
# Begin broadcasting loop
send(self(), :nudge)
{:ok, socket}
end
@doc "Closes the UDP socket upon termination of GenServer."
def terminate(_, socket) do
:gen_udp.close(socket)
Node.stop()
end
def handle_cast(:kil, socket) do
Process.exit(self(), :normal)
{:noreply, socket}
end
def kil() do
GenServer.cast(__MODULE__, :kil)
end
# API
# --------------------------------------------
@doc "Handles broadcasting of UDP messages"
def handle_info(:nudge, socket) do
broadcast(socket)
# Call self again after @broadcastRate[ms]
Process.send_after(__MODULE__, :nudge, @broadcastRate)
{:noreply, socket}
end
@doc "Handles received UDP messages"
def handle_info({:udp, _, _, _, nodeIDString}, socket) do
# Try connect to Node ID
nodeID =
nodeIDString
|> to_string()
|> String.to_atom()
unless nodeID in [node() | Node.list()] do
Logger.info("trying to connect to node #{inspect(nodeID)}")
connResponse = Node.ping(nodeID)
if connResponse == :pang do
Logger.error("Couldn't connect to node #{nodeID}.")
else
# get BackupHandler, orderWatchdog and OrderHandler to retrieve the orders, as there may have been orders previously if the node is being restarted
BackupHandler.multiTriggerLogPush()
OrderWatchdog.multiTriggerImpatientOrderPush()
OrderHandler.multiTriggerRetrieveBackup()
end
end
{:noreply, socket}
end
# Private Functions
# --------------------------------------------
@doc "Broadcast the IP address to UDP port."
defp broadcast(socket) do
msg = to_string(node())
# Broadcast message
:gen_udp.send(socket, @broadcastIP, @udpPort, msg)
end
# Creates a valid node name by retreiving IP address.
defp getElevatorID(num \\ "") do
# Gets info about IP addresses and extracts global/local IP
{:ok, [{ipTuple, _, _} | _tail]} = :inet.getif()
# Cast IP to correct format
with {field1, field2, field3, field4} <- ipTuple do
String.to_atom("gr15elevator#{num}@#{field1}.#{field2}.#{field3}.#{field4}")
end
end
end