Packages

An OTP application for reading and parsing GPS data written in Elixir. Will attach to an serial port, and provide positions to subscribers. Distributes positions using GenStage.

Current section

Files

Jump to
xgps lib port reader.ex
Raw

lib/port/reader.ex

defmodule XGPS.Port.Reader do
use GenServer
require Logger
def start_link(args) do
GenServer.start_link(__MODULE__, args)
end
def get_gps_data(pid) do
GenServer.call(pid, :get_gps_data)
end
def get_port_name(pid) do
GenServer.call(pid, :get_port_name)
end
defmodule State do
defstruct [
gps_data: nil,
pid: nil,
port_name: nil,
data_buffer: nil
]
end
def init({port_name, :init_adafruit_gps}) do
{:ok, uart_pid} = Nerves.UART.start_link
:ok = Nerves.UART.open(uart_pid, port_name, speed: 9600, active: true)
gps_data = %XGPS.GpsData{has_fix: false}
state = %State{gps_data: gps_data, pid: uart_pid, port_name: port_name, data_buffer: ""}
init_adafruit_gps(uart_pid)
{:ok, state}
end
def init({:simulate}) do
gps_data = %XGPS.GpsData{has_fix: false}
state = %State{gps_data: gps_data, pid: :simulate, port_name: :simulate, data_buffer: ""}
{:ok, state}
end
def init({port_name}) do
{:ok, uart_pid} = Nerves.UART.start_link
:ok = Nerves.UART.open(uart_pid, port_name, speed: 9600, active: true)
gps_data = %XGPS.GpsData{has_fix: false}
state = %State{gps_data: gps_data, pid: uart_pid, port_name: port_name, data_buffer: ""}
{:ok, state}
end
defp init_adafruit_gps(uart_pid) do
cmd1 = "$PMTK313,1*2E\r\n" # enable SBAS
cmd2 = "$PMTK319,1*24\r\n" # Set SBAS to not test mode
cmd3 = "$PMTK301,2*2E\r\n" # Enable SBAS to be used for DGPS
cmd4 = "$PMTK286,1*23\r\n" # Enable AIC (anti-inteference)
cmd5 = "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n" # Output only RMC & GGA
cmd6 = "$PMTK397,0*23\r\n" # Disable nav-speed threshold
Nerves.UART.write(uart_pid, cmd1)
Nerves.UART.write(uart_pid, cmd2)
Nerves.UART.write(uart_pid, cmd3)
Nerves.UART.write(uart_pid, cmd4)
Nerves.UART.write(uart_pid, cmd5)
Nerves.UART.write(uart_pid, cmd6)
end
def handle_info({:nerves_uart, port_name, "\n"}, %State{port_name: port_name, data_buffer: ""} = state) do
{:noreply, state}
end
def handle_info({:nerves_uart, port_name, "\n"}, %State{port_name: port_name} = state) do
sentence = String.strip((state.data_buffer))
Logger.debug(fn -> "Received: " <> sentence end)
parsed_data = XGPS.Parser.parse_sentence(sentence)
{updated, new_gps_data} = update_gps_data(parsed_data, state.gps_data)
send_update_event({updated, new_gps_data})
{:noreply, %{state | data_buffer: "", gps_data: new_gps_data}}
end
def handle_info({:nerves_uart, port_name, data}, %State{port_name: port_name} = state) do
data_buffer = state.data_buffer <> data
{:noreply, %{state | data_buffer: data_buffer}}
end
def handle_call(:stop, _from, state) do
{:stop, :normal, state}
end
def handle_call(:get_gps_data, _from, state) do
{:reply, state.gps_data, state}
end
def handle_call(:get_port_name, _from, state) do
{:reply, state.port_name, state}
end
def handle_cast({:command, command}, state) do
Nerves.UART.write(state.pid, (command <> "\r\n"))
{:noreply, state}
end
def terminate(_reason, state) do
Nerves.UART.close(state.pid)
Nerves.UART.stop(state.pid)
end
defp update_gps_data(%XGPS.Messages.RMC{} = rmc, gps_data) do
speed = knots_to_kmh(rmc.speed_over_groud)
new_gps_data = %{gps_data | time: rmc.time,
date: rmc.date,
latitude: rmc.latitude,
longitude: rmc.longitude,
angle: rmc.track_angle,
magvariation: rmc.magnetic_variation,
speed: speed}
{:updated, new_gps_data}
end
defp update_gps_data(%XGPS.Messages.GGA{fix_quality: 0}, gps_data) do
new_gps_data = %{gps_data | has_fix: false}
{:updated, new_gps_data}
end
defp update_gps_data(%XGPS.Messages.GGA{} = gga, gps_data) do
new_gps_data = %{gps_data | has_fix: true,
fix_quality: gga.fix_quality,
satelites: gga.number_of_satelites_tracked,
hdop: gga.horizontal_dilution,
altitude: gga.altitude,
geoidheight: gga.height_over_goeid,
latitude: gga.latitude,
longitude: gga.longitude}
{:updated, new_gps_data}
end
# Any other message types
defp update_gps_data(_message, gps_data), do: {:not_updated, gps_data}
defp knots_to_kmh(speed_in_knots) when is_float(speed_in_knots) do
speed_in_knots * 1.852
end
defp knots_to_kmh(_speed_in_knots), do: 0
defp send_update_event({:not_updated, _gps_data}), do: :ok
defp send_update_event({:updated, gps_data}) do
Logger.debug(fn -> "New gps_data: : " <> inspect(gps_data) end)
XGPS.EventManager.update(gps_data)
:ok
end
end