All telemetry going again in web app.
This commit is contained in:
parent
1ea1d5477f
commit
679f2e1913
10 changed files with 175 additions and 68 deletions
|
@ -8,11 +8,11 @@ defmodule Augie.Sensor.IMU do
|
||||||
|
|
||||||
@type t :: %IMU{
|
@type t :: %IMU{
|
||||||
orientation: Quaternion.t(),
|
orientation: Quaternion.t(),
|
||||||
accelerometer: Vector.t(),
|
# accelerometer: Vector.t(),
|
||||||
magnetometer: Vector.t(),
|
# magnetometer: Vector.t(),
|
||||||
gyroscope: Vector.t(),
|
# gyroscope: Vector.t(),
|
||||||
gravity: Vector.t(),
|
# gravity: Vector.t(),
|
||||||
linear_acceleration: Vector.t(),
|
# linear_acceleration: Vector.t(),
|
||||||
temperature: float
|
temperature: float
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,11 +22,11 @@ defmodule Augie.Sensor.IMU do
|
||||||
@type t :: %__MODULE__{x: float, y: float, z: float, w: float}
|
@type t :: %__MODULE__{x: float, y: float, z: float, w: float}
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule Vector do
|
# defmodule Vector do
|
||||||
@moduledoc "Holds the `x`, `y` and `z` values for a vector."
|
# @moduledoc "Holds the `x`, `y` and `z` values for a vector."
|
||||||
defstruct ~w[x y z]a
|
# defstruct ~w[x y z]a
|
||||||
@type t :: %__MODULE__{x: float, y: float, z: float}
|
# @type t :: %__MODULE__{x: float, y: float, z: float}
|
||||||
end
|
# end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Convert incoming telemetry into a structure.
|
Convert incoming telemetry into a structure.
|
||||||
|
@ -37,21 +37,21 @@ defmodule Augie.Sensor.IMU do
|
||||||
orientation_y,
|
orientation_y,
|
||||||
orientation_z,
|
orientation_z,
|
||||||
orientation_w,
|
orientation_w,
|
||||||
accelerometer_x,
|
# accelerometer_x,
|
||||||
accelerometer_y,
|
# accelerometer_y,
|
||||||
accelerometer_z,
|
# accelerometer_z,
|
||||||
magnetometer_x,
|
# magnetometer_x,
|
||||||
magnetometer_y,
|
# magnetometer_y,
|
||||||
magnetometer_z,
|
# magnetometer_z,
|
||||||
gyroscope_x,
|
# gyroscope_x,
|
||||||
gyroscope_y,
|
# gyroscope_y,
|
||||||
gyroscope_z,
|
# gyroscope_z,
|
||||||
gravity_x,
|
# gravity_x,
|
||||||
gravity_y,
|
# gravity_y,
|
||||||
gravity_z,
|
# gravity_z,
|
||||||
linear_acceleration_x,
|
# linear_acceleration_x,
|
||||||
linear_acceleration_y,
|
# linear_acceleration_y,
|
||||||
linear_acceleration_z,
|
# linear_acceleration_z,
|
||||||
temperature
|
temperature
|
||||||
]) do
|
]) do
|
||||||
{:ok,
|
{:ok,
|
||||||
|
@ -62,15 +62,15 @@ defmodule Augie.Sensor.IMU do
|
||||||
z: orientation_z,
|
z: orientation_z,
|
||||||
w: orientation_w
|
w: orientation_w
|
||||||
},
|
},
|
||||||
accelerometer: %Vector{x: accelerometer_x, y: accelerometer_y, z: accelerometer_z},
|
# accelerometer: %Vector{x: accelerometer_x, y: accelerometer_y, z: accelerometer_z},
|
||||||
magnetometer: %Vector{x: magnetometer_x, y: magnetometer_y, z: magnetometer_z},
|
# magnetometer: %Vector{x: magnetometer_x, y: magnetometer_y, z: magnetometer_z},
|
||||||
gyroscope: %Vector{x: gyroscope_x, y: gyroscope_y, z: gyroscope_z},
|
# gyroscope: %Vector{x: gyroscope_x, y: gyroscope_y, z: gyroscope_z},
|
||||||
gravity: %Vector{x: gravity_x, y: gravity_y, z: gravity_z},
|
# gravity: %Vector{x: gravity_x, y: gravity_y, z: gravity_z},
|
||||||
linear_acceleration: %Vector{
|
# linear_acceleration: %Vector{
|
||||||
x: linear_acceleration_x,
|
# x: linear_acceleration_x,
|
||||||
y: linear_acceleration_y,
|
# y: linear_acceleration_y,
|
||||||
z: linear_acceleration_z
|
# z: linear_acceleration_z
|
||||||
},
|
# },
|
||||||
temperature: temperature
|
temperature: temperature
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
defmodule Augie.Sensor.Logger do
|
|
||||||
defstruct ~w[message at]a
|
|
||||||
alias __MODULE__
|
|
||||||
|
|
||||||
@moduledoc """
|
|
||||||
Storage struct for telemetry logs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@type t :: %Logger{message: String.t(), at: DateTime.t()}
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Convert incoming telemetry into a structure.
|
|
||||||
"""
|
|
||||||
@spec build([String.t()]) :: {:ok, Logger.t()} | {:error, any}
|
|
||||||
def build([message]) when is_binary(message),
|
|
||||||
do: {:ok, %Logger{message: message, at: DateTime.utc_now()}}
|
|
||||||
|
|
||||||
def build(_data), do: {:error, :bad_data}
|
|
||||||
end
|
|
|
@ -2,7 +2,7 @@ defmodule Augie.SerialTelemetry do
|
||||||
use GenServer
|
use GenServer
|
||||||
alias Augie.SerialTelemetry.Decoder
|
alias Augie.SerialTelemetry.Decoder
|
||||||
alias Circuits.UART
|
alias Circuits.UART
|
||||||
alias MidiProto.{Firmata, Parser}
|
alias MidiProto.Parser
|
||||||
alias Phoenix.PubSub
|
alias Phoenix.PubSub
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -40,8 +40,15 @@ defmodule Augie.SerialTelemetry do
|
||||||
{:ok, messages, parser} ->
|
{:ok, messages, parser} ->
|
||||||
for message <- messages do
|
for message <- messages do
|
||||||
case Decoder.decode(message) do
|
case Decoder.decode(message) do
|
||||||
{:ok, message} -> Logger.debug("Decoded message: #{inspect(message)}")
|
{:ok, name, message} ->
|
||||||
{:error, _} -> Logger.debug("Cannot decode message: #{inspect(message)}")
|
PubSub.broadcast(Augie.PubSub, name, message)
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Logger.warn(
|
||||||
|
"Unable to decode incoming Firmata message: #{inspect(reason)}: #{
|
||||||
|
inspect(message, limit: :infinity)
|
||||||
|
}"
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -87,7 +94,7 @@ defmodule Augie.SerialTelemetry do
|
||||||
UART.open(UART, uart,
|
UART.open(UART, uart,
|
||||||
active: true,
|
active: true,
|
||||||
speed: @baud_rate,
|
speed: @baud_rate,
|
||||||
framing: UART.Framing.MIDI,
|
framing: UART.Framing.None,
|
||||||
rx_framing_timeout: 0
|
rx_framing_timeout: 0
|
||||||
) do
|
) do
|
||||||
Logger.info("Connected to Teensy on #{uart}")
|
Logger.info("Connected to Teensy on #{uart}")
|
||||||
|
|
|
@ -1,17 +1,57 @@
|
||||||
defmodule Augie.SerialTelemetry.Decoder do
|
defmodule Augie.SerialTelemetry.Decoder do
|
||||||
|
alias Augie.Sensor.{GPS, IMU, Power}
|
||||||
alias MidiProto.Message.SystemExclusive
|
alias MidiProto.Message.SystemExclusive
|
||||||
use Bitwise
|
use Bitwise
|
||||||
|
|
||||||
def decode(%SystemExclusive{vendor_id: 1, payload: <<2, 0, lsb, msb>>}) do
|
@moduledoc """
|
||||||
value = lsb + (msb <<< 7)
|
This file is a bit of an omega mess because it uses binary pattern matching to
|
||||||
{:ok, %{name: :imu_temperature, value: value}}
|
decode the message payloads.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decode(%SystemExclusive{vendor_id: 1, payload: <<2, 0, payload::binary>>}) do
|
||||||
|
with {:ok,
|
||||||
|
<<bus_voltage::little-float-size(32), shunt_voltage::little-float-size(32),
|
||||||
|
current::little-float-size(32),
|
||||||
|
power::little-float-size(32)>>} <- depad_binary(<<>>, payload),
|
||||||
|
{:ok, update} <- Power.build([bus_voltage, shunt_voltage, current, power]) do
|
||||||
|
{:ok, "telemetry.power", update}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# def decode(%SystemExclusive{
|
def decode(%SystemExclusive{vendor_id: 1, payload: <<1, 0, payload::binary>>}) do
|
||||||
# vendor_id: 1,
|
with {:ok,
|
||||||
# payload: <<1, 0, lsb0, msb0, lsb1, msb1, lsb2, msb2, lsb3, msb3>>
|
<<ox::little-float-size(64), oy::little-float-size(64), oz::little-float-size(64),
|
||||||
# }) do
|
ow::little-float-size(64),
|
||||||
# end
|
temp::little-signed-integer-size(8)>>} <- depad_binary(<<>>, payload),
|
||||||
|
{:ok, update} <- IMU.build([ox, oy, oz, ow, temp]) do
|
||||||
|
{:ok, "telemetry.imu", update}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode(%SystemExclusive{vendor_id: 1, payload: <<0, 0, payload::binary>>}) do
|
||||||
|
with {:ok,
|
||||||
|
<<latitude::little-signed-integer-size(32), longitude::little-signed-integer-size(32),
|
||||||
|
altitude::little-signed-integer-size(32), speed::little-signed-integer-size(32),
|
||||||
|
heading::little-float-size(32), satellites::little-integer-size(8),
|
||||||
|
status::little-integer-size(8)>>} <- depad_binary(<<>>, payload),
|
||||||
|
{:ok, update} <-
|
||||||
|
GPS.build([latitude, longitude, altitude, heading, speed, satellites, status]) do
|
||||||
|
{:ok, "telemetry.gps", update}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def decode(_), do: {:error, "Unknown message"}
|
def decode(_), do: {:error, "Unknown message"}
|
||||||
|
|
||||||
|
defp depad_binary(result, ""), do: {:ok, result}
|
||||||
|
|
||||||
|
defp depad_binary(
|
||||||
|
result,
|
||||||
|
<<0::integer-size(1), lsb::integer-size(7), 0::integer-size(1), msb::integer-size(7),
|
||||||
|
rest::binary>>
|
||||||
|
) do
|
||||||
|
value = (msb <<< 7) + lsb &&& 0xFF
|
||||||
|
depad_binary(<<result::binary, value::integer-size(8)>>, rest)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp depad_binary(_result, <<_>>), do: {:error, "Lost synchronisation"}
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
<%= live_render(@socket, AugieWeb.OrientationLive, id: :orientation) %>
|
<%= live_render(@socket, AugieWeb.OrientationLive, id: :orientation) %>
|
||||||
<%= live_render(@socket, AugieWeb.TemperatureLive, id: :temperature) %>
|
<%= live_render(@socket, AugieWeb.TemperatureLive, id: :temperature) %>
|
||||||
|
<%= live_render(@socket, AugieWeb.PowerLive, id: :power) %>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell small-12 medium-6 large-4">
|
<div class="cell small-12 medium-6 large-4">
|
||||||
<%= live_render(@socket, AugieWeb.GpsLive, id: :gps) %>
|
<%= live_render(@socket, AugieWeb.GpsLive, id: :gps) %>
|
||||||
|
|
|
@ -20,7 +20,7 @@ defmodule AugieWeb.GpsLive do
|
||||||
@map_opts [size: "640x320", key: "AIzaSyBibH_1Yibm3gshxsQDUKw7mjaH9SyMrgw", maptype: "hybrid"]
|
@map_opts [size: "640x320", key: "AIzaSyBibH_1Yibm3gshxsQDUKw7mjaH9SyMrgw", maptype: "hybrid"]
|
||||||
|
|
||||||
def mount(_params, _context, socket) do
|
def mount(_params, _context, socket) do
|
||||||
if connected?(socket), do: PubSub.subscribe(Augie.PubSub, "GPS")
|
if connected?(socket), do: PubSub.subscribe(Augie.PubSub, "telemetry.gps")
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule AugieWeb.OrientationLive do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
def mount(_params, _context, socket) do
|
def mount(_params, _context, socket) do
|
||||||
if connected?(socket), do: PubSub.subscribe(Augie.PubSub, "IMU")
|
if connected?(socket), do: PubSub.subscribe(Augie.PubSub, "telemetry.imu")
|
||||||
|
|
||||||
{:ok, assign(socket, data_ready: false)}
|
{:ok, assign(socket, data_ready: false)}
|
||||||
end
|
end
|
||||||
|
|
31
webapp/lib/augie_web/live/power_live.ex
Normal file
31
webapp/lib/augie_web/live/power_live.ex
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
defmodule AugieWeb.PowerLive do
|
||||||
|
use Phoenix.LiveView
|
||||||
|
alias Augie.Sensor.Power
|
||||||
|
alias Phoenix.PubSub
|
||||||
|
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
def mount(_params, _context, socket) do
|
||||||
|
if connected?(socket), do: PubSub.subscribe(Augie.PubSub, "telemetry.power")
|
||||||
|
|
||||||
|
{:ok, assign(socket, bus_voltage: nil, shunt_voltage: nil, current: nil, power: nil)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(
|
||||||
|
%Power{
|
||||||
|
bus_voltage: bus_voltage,
|
||||||
|
shunt_voltage: shunt_voltage,
|
||||||
|
current: current,
|
||||||
|
power: power
|
||||||
|
},
|
||||||
|
socket
|
||||||
|
),
|
||||||
|
do:
|
||||||
|
{:noreply,
|
||||||
|
assign(socket,
|
||||||
|
bus_voltage: bus_voltage,
|
||||||
|
shunt_voltage: shunt_voltage,
|
||||||
|
current: current,
|
||||||
|
power: power
|
||||||
|
)}
|
||||||
|
end
|
47
webapp/lib/augie_web/live/power_live.html.leex
Normal file
47
webapp/lib/augie_web/live/power_live.html.leex
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-divider">
|
||||||
|
<h4>Power</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-section">
|
||||||
|
<div class="grid-x">
|
||||||
|
<div class="cell auto"><strong>Bus voltage</strong></div>
|
||||||
|
<div class="cell auto text-right">
|
||||||
|
<%= if @bus_voltage do %>
|
||||||
|
<%= Float.round(@bus_voltage, 2) %>V
|
||||||
|
<% else %>
|
||||||
|
-
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid-x">
|
||||||
|
<div class="cell auto"><strong>Shunt voltage</strong></div>
|
||||||
|
<div class="cell auto text-right">
|
||||||
|
<%= if @shunt_voltage do %>
|
||||||
|
<%= Float.round(@shunt_voltage, 2) %>V
|
||||||
|
<% else %>
|
||||||
|
-
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid-x">
|
||||||
|
<div class="cell auto"><strong>Current</strong></div>
|
||||||
|
<div class="cell auto text-right">
|
||||||
|
<%= if @current do %>
|
||||||
|
<%= Float.round(@current, 2) %>mA
|
||||||
|
<% else %>
|
||||||
|
-
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid-x">
|
||||||
|
<div class="cell auto"><strong>Power</strong></div>
|
||||||
|
<div class="cell auto text-right">
|
||||||
|
<%= if @power do %>
|
||||||
|
<%= Float.round(@power, 2) %>W
|
||||||
|
<% else %>
|
||||||
|
-
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -6,7 +6,7 @@ defmodule AugieWeb.TemperatureLive do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
def mount(_params, _context, socket) do
|
def mount(_params, _context, socket) do
|
||||||
if connected?(socket), do: PubSub.subscribe(Augie.PubSub, "IMU")
|
if connected?(socket), do: PubSub.subscribe(Augie.PubSub, "telemetry.imu")
|
||||||
|
|
||||||
{:ok, assign(socket, temperature: nil)}
|
{:ok, assign(socket, temperature: nil)}
|
||||||
end
|
end
|
||||||
|
|
Reference in a new issue