All telemetry going again in web app.

This commit is contained in:
James Harton 2020-05-14 22:05:26 +12:00
parent 1ea1d5477f
commit 679f2e1913
10 changed files with 175 additions and 68 deletions

View file

@ -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

View file

@ -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

View file

@ -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}")

View file

@ -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

View file

@ -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) %>

View file

@ -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

View file

@ -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

View 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

View 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>

View file

@ -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