Current section
Files
Jump to
Current section
Files
lib/xplane_instance.ex
defmodule XPlane.Instance do
@moduledoc """
Represent a running instance of X-Plane and provide a GenServer to monitor
the local network for X-Plane multicast "beacon" messages and return them
as a list.
## Example
```
iex> XPlane.Instance.start
{:ok, #PID<0.138.0>}
iex> XPlane.Instance.list
[
%XPlane.Instance{
computer_name: "Starboard",
host: :xplane,
ip: {192, 168, 0, 58},
addr: "192.168.0.58",
major_version: 1,
minor_version: 1,
port: 49000,
role: :extern_visual,
seconds_since_seen: 0,
version_number: 105101
}
]
iex> XPlane.Instance.stop
:ok
```
"""
defstruct [
:ip, # IP Address of X-Plane instance as tuple
:addr, # Same as dot separated string
:major_version, # 1 at the time of X-Plane 10.40
:minor_version, # 1 at the time of X-Plane 10.40
:version_number, # 104103 for X-Plane 10.41r3
:host, # :xplane | :planemaker
:role, # :master | :extern_visual | :ios
:port, # Port number X-Plane is listening on, 49000 by default
:computer_name, # Hostname of the computer
:seconds_since_seen] # Time since last beacon multicast received in seconds
@beacon_addr {239, 255, 1, 1}
@beacon_port 49707
@zeros_addr {0, 0, 0, 0}
@startup_grace_period 1000
use GenServer
# API
def start(opts \\ []) do
result = GenServer.start(__MODULE__, :ok, [name: __MODULE__] ++ opts)
:timer.sleep(@startup_grace_period) # Allow time for beacons to be picked up
result
end
def list() do
now = :erlang.system_time(:second)
Enum.map(
GenServer.call(__MODULE__, :list) |> Map.to_list,
fn {ip, {major_version, minor_version, host, version_number,
role, port, computer_name, last_seen}} ->
%XPlane.Instance{
ip: ip,
addr: (ip |> Tuple.to_list |> Enum.join(".")),
major_version: major_version,
minor_version: minor_version,
version_number: version_number,
host: [nil, :xplane, :planemaker] |> Enum.at(host),
role: [nil, :master, :extern_visual, :ios] |> Enum.at(role),
port: port,
computer_name: binary_part(computer_name, 0, byte_size(computer_name) - 1),
seconds_since_seen: now - last_seen
}
end
)
end
def stop() do
GenServer.cast(__MODULE__, :stop)
end
# GenServer callbacks
def init(:ok) do
udp_options = [
:binary,
active: true,
add_membership: {@beacon_addr, @zeros_addr},
multicast_if: @zeros_addr,
multicast_loop: false,
multicast_ttl: 4,
reuseaddr: true
]
{:ok, _sock} = :gen_udp.open(@beacon_port, udp_options)
{:ok, %{}}
end
def handle_info({:udp, _sock, sender_ip, _sender,
<<"BECN\0",
major_version::unsigned,
minor_version::unsigned,
host::native-integer-32,
version_number::native-integer-32,
role::unsigned-native-32,
port::unsigned-native-16,
computer_name::binary>>}, state) do
{
:noreply,
state
|> Map.put(
sender_ip, {
major_version,
minor_version,
host,
version_number,
role,
port,
computer_name,
:erlang.system_time(:second)
}
)
}
end
def handle_call(:list, _from, state) do
{:reply, state, state}
end
def handle_cast(:stop, state) do
{:stop, :normal, state}
end
end