Support multiple cars & overhaul table structure

This commit is contained in:
Adrian Kumpf
2019-04-02 00:21:33 +02:00
parent f87af0877c
commit 7c25138e74
33 changed files with 794 additions and 541 deletions
+15 -4
View File
@@ -117,12 +117,23 @@ defmodule TeslaMate.Api do
end
def get_vehicle_with_state(_id) do
# {:error, :unavailable}
{:ok,
%Vehicle{
state: "online",
charge_state: %Vehicle.State.Charge{},
charge_state: %Vehicle.State.Charge{
# timestamp: DateTime.utc_now() |> DateTime.to_unix(:microsecond),
# charging_state: "Unplugged",
# charger_power: 22,
# battery_level: 16,
# charge_energy_added: 0.5,
# ideal_battery_range: 59.95
},
drive_state: %Vehicle.State.Drive{
timestamp: DateTime.utc_now() |> DateTime.to_unix(:microsecond),
# shift_state: "N",
# speed: 50,
latitude: 0.0,
longitude: 0.0
},
@@ -133,9 +144,9 @@ defmodule TeslaMate.Api do
def list_vehicles do
m3 = %Vehicle{
id: 0,
id: 1000,
state: "online",
vehicle_id: 0,
vehicle_id: 1010,
display_name: "Tesla!M3",
option_codes: ["MDL3", "BT37", "DV4W"]
}
@@ -144,7 +155,7 @@ defmodule TeslaMate.Api do
end
@impl true
def init(opts) do
def init(_opts) do
{:ok, nil}
end
end
+118 -188
View File
@@ -6,245 +6,175 @@ defmodule TeslaMate.Log do
import Ecto.Query, warn: false
alias TeslaMate.Repo
alias TeslaMate.Log.Position
## Car
def insert_position(attrs) do
with {:ok, _} <- %Position{} |> Position.changeset(attrs) |> Repo.insert() do
:ok
end
alias TeslaMate.Log.Car
def list_cars do
Repo.all(Car)
end
def get_last_position_id! do
Position
|> select([p], max(p.id))
|> Repo.one!()
def get_car!(id) do
Repo.get!(Car, id)
end
def get_car_by_eid(eid) do
Repo.get_by(Car, eid: eid)
end
def create_car(%{eid: eid, vid: vid} = attrs) do
%Car{eid: eid, vid: vid}
|> Car.changeset(attrs)
|> Repo.insert()
end
def update_car(%Car{} = car, attrs) do
car
|> Car.changeset(attrs)
|> Repo.update()
end
## State
alias TeslaMate.Log.State
def start_state(state) do
case get_current_state() do
%{state: ^state} ->
def start_state(car_id, state) do
case get_current_state(car_id) do
%State{state: ^state} ->
:ok
%{state: _state} ->
last_position_id = get_last_position_id!()
attrs = %{
state: state,
start_date: DateTime.utc_now(),
start_position_id: last_position_id
}
with :ok <- close_state(last_position_id),
{:ok, _} <- create_state(attrs) do
%State{} = s ->
with {:ok, _} <- s |> State.changeset(%{end_date: DateTime.utc_now()}) |> Repo.update(),
{:ok, _} <- create_state(car_id, %{state: state, start_date: DateTime.utc_now()}) do
:ok
end
nil ->
last_position_id = get_last_position_id!()
attrs = %{
state: state,
start_date: DateTime.utc_now(),
start_position_id: last_position_id
}
with {:ok, _} <- create_state(attrs) do
with {:ok, _} <- create_state(car_id, %{state: state, start_date: DateTime.utc_now()}) do
:ok
end
end
end
def close_state(position_id) do
end_date = DateTime.utc_now()
result =
State
|> where([s], is_nil(s.end_date))
|> update(set: [end_date: ^end_date, end_position_id: ^position_id])
|> Repo.update_all([])
case result do
{0, nil} -> {:erorr, :no_state_to_be_closed}
{1, nil} -> :ok
{n, nil} -> {:error, {:closed_multiple_states, n}}
end
end
def get_current_state do
defp get_current_state(car_id) do
State
|> select([:state])
|> where([s], is_nil(s.end_date))
|> where([s], ^car_id == s.car_id and is_nil(s.end_date))
|> Repo.one()
end
defp create_state(attrs \\ %{}) do
%State{}
defp create_state(car_id, attrs) do
%State{car_id: car_id}
|> State.changeset(attrs)
|> Repo.insert()
end
defp update_state(%State{} = state, attrs) do
state
|> State.changeset(attrs)
|> Repo.update()
end
## Position
alias TeslaMate.Log.DriveState
alias TeslaMate.Log.Position
def start_drive_state do
last_position_id = get_last_position_id!()
attrs = %{start_date: DateTime.utc_now(), start_position_id: last_position_id}
with {:ok, _} <- create_drive_state(attrs) do
def insert_position(car_id, attrs) do
with {:ok, _} <-
%Position{car_id: car_id, trip_id: Map.get(attrs, :trip_id)}
|> Position.changeset(attrs)
|> Repo.insert() do
:ok
end
end
def close_drive_state do
start_position_id =
case get_last_drive_state() do
%{start_position_id: start_position_id} -> start_position_id
# Assumption there is always one position with id 1
nil -> 1
end
## Trip
end_date = DateTime.utc_now()
last_position_id = get_last_position_id!()
alias TeslaMate.Log.Trip
result =
DriveState
|> where([s], is_nil(s.end_date))
|> update(set: [end_date: ^end_date, end_position_id: ^last_position_id])
|> Repo.update_all([])
|> case do
{0, nil} -> {:erorr, :no_drive_state_to_be_closed}
{1, nil} -> :ok
{n, nil} -> {:error, {:closed_multiple_states, n}}
end
with :ok <- result do
if start_position_id != 1 do
:ok = update_drive_statistics(start_position_id, last_position_id)
end
:ok
def start_trip(car_id) do
with {:ok, %Trip{id: id}} <-
%Trip{car_id: car_id}
|> Trip.changeset(%{start_date: DateTime.utc_now()})
|> Repo.insert() do
{:ok, id}
end
end
def get_last_drive_state do
DriveState
|> select([:start_position_id])
|> where([d], is_nil(d.end_date))
|> Repo.one()
def close_trip(trip_id) do
# TODO statistics
attrs = %{}
with {:ok, _trip} <-
Trip
|> Repo.get!(trip_id)
|> Trip.changeset(attrs)
|> Repo.update() do
:ok
end
# statistics =
# Position
# |> select([p], %{
# outside_temp_avg: fragment("?::float", avg(p.outside_temp)),
# speed_max: max(p.speed),
# speed_min: min(p.speed),
# power_max: max(p.power),
# power_min: min(p.power),
# power_avg: fragment("?::float", avg(p.power))
# })
# |> where(
# [p],
# car_id == p.car_id and ^start_position_id <= p.id and p.id <= ^end_position_id
# )
# |> Repo.one!()
# |> Map.to_list()
# result =
# Trip
# |> where(
# [d],
# car_id == d.car_id and d.start_position_id == ^start_position_id and
# d.end_position_id == ^end_position_id
# )
# |> update(set: ^statistics)
# |> Repo.update_all([])
# case result do
# {0, nil} -> {:erorr, :no_trips_to_be_updated}
# {1, nil} -> :ok
# end
end
defp update_drive_statistics(start_position_id, end_position_id) do
statistics =
alias TeslaMate.Log.{ChargingProcess, Charge}
def start_charging_process(car_id) do
# TODO remove when combined with other actions
last_position_id =
Position
|> select([p], %{
outside_temp_avg: fragment("?::float", avg(p.outside_temp)),
speed_max: max(p.speed),
speed_min: min(p.speed),
power_max: max(p.power),
power_min: min(p.power),
power_avg: fragment("?::float", avg(p.power))
})
|> where([p], ^start_position_id <= p.id and p.id <= ^end_position_id)
|> select([p], max(p.id))
|> where(car_id: ^car_id)
|> Repo.one!()
|> Map.to_list()
result =
DriveState
|> where(
[d],
d.start_position_id == ^start_position_id and d.end_position_id == ^end_position_id
)
|> update(set: ^statistics)
|> Repo.update_all([])
case result do
{0, nil} -> {:erorr, :no_drive_states_to_be_updated}
{1, nil} -> :ok
with {:ok, %ChargingProcess{id: id}} <-
%ChargingProcess{car_id: car_id, position_id: last_position_id}
|> ChargingProcess.changeset(%{start_date: DateTime.utc_now()})
|> Repo.insert() do
{:ok, id}
end
end
defp create_drive_state(attrs \\ %{}) do
%DriveState{}
|> DriveState.changeset(attrs)
|> Repo.insert()
end
defp update_drive_state(%DriveState{} = drive_state, attrs) do
drive_state
|> DriveState.changeset(attrs)
|> Repo.update()
end
alias TeslaMate.Log.{ChargingState, Charge}
def insert_charge(attrs) do
with {:ok, _} <- %Charge{} |> Charge.changeset(attrs) |> Repo.insert() do
def insert_charge(process_id, attrs) do
with {:ok, _} <-
%Charge{charging_process_id: process_id}
|> Charge.changeset(attrs)
|> Repo.insert() do
:ok
end
end
def get_last_charge_id! do
Charge
|> select([p], max(p.id))
|> Repo.one!()
end
def close_charging_process(process_id) do
# TODO calculate statistics
def start_charging_state do
# TODO combine with insert_charge && insert_position
last_position_id = get_last_position_id!()
last_charge_id = get_last_charge_id!()
attrs = %{
start_date: DateTime.utc_now(),
position: last_position_id,
charge_start_id: last_charge_id
}
with {:ok, _} <- create_charging_state(attrs) do
with {:ok, _} <-
ChargingProcess
|> Repo.get!(process_id)
|> ChargingProcess.changeset(%{end_date: DateTime.utc_now()})
|> Repo.update() do
:ok
end
end
def close_charging_state do
last_charge_id = get_last_charge_id!()
end_date = DateTime.utc_now()
result =
ChargingState
|> where([c], is_nil(c.end_date))
|> update(set: [end_date: ^end_date, charge_end_id: ^last_charge_id])
|> Repo.update_all([])
case result do
{0, nil} -> {:erorr, :no_charging_state_to_be_closed}
{1, nil} -> :ok
{n, nil} -> {:error, {:closed_multiple_charging_states, n}}
end
end
defp create_charging_state(attrs \\ %{}) do
%ChargingState{}
|> ChargingState.changeset(attrs)
|> Repo.insert()
end
defp update_charging_state(%ChargingState{} = charging_state, attrs) do
charging_state
|> ChargingState.changeset(attrs)
|> Repo.update()
end
defp update_charge(%Charge{} = charge, attrs) do
charge
|> Charge.changeset(attrs)
|> Repo.update()
end
end
+28
View File
@@ -0,0 +1,28 @@
defmodule TeslaMate.Log.Car do
use Ecto.Schema
import Ecto.Changeset
alias TeslaMate.Log.{ChargingProcess, Position, Trip}
schema "cars" do
field :efficiency, :float
field :eid, :integer
field :model, :string
field :vid, :integer
has_many :charging_processes, ChargingProcess
has_many :positions, Position
has_many :trips, Trip
timestamps()
end
@doc false
def changeset(car, attrs) do
car
|> cast(attrs, [:model, :efficiency])
|> validate_required([:eid, :vid, :model, :efficiency])
|> unique_constraint(:eid)
|> unique_constraint(:vid)
end
end
+12 -6
View File
@@ -2,38 +2,44 @@ defmodule TeslaMate.Log.Charge do
use Ecto.Schema
import Ecto.Changeset
alias TeslaMate.Log.ChargingProcess
schema "charges" do
field :battery_level, :float
field :battery_level, :integer
field :charge_energy_added, :float
field :charger_actual_current, :integer
field :charger_phases, :integer, default: 1
field :charger_power, :float
field :charger_voltage, :integer
field :date, :utc_datetime
field :ideal_battery_range, :float
field :ideal_battery_range_km, :float
field :outside_temp, :float
belongs_to :charging_process, ChargingProcess
end
@doc false
def changeset(charge, attrs) do
charge
|> cast(attrs, [
:date,
:battery_level,
:charge_energy_added,
:charger_power,
:date,
:ideal_battery_range,
:ideal_battery_range_km,
:charger_voltage,
:charger_phases,
:charger_actual_current,
:outside_temp
])
|> validate_required([
:date,
:charging_process_id,
:battery_level,
:charge_energy_added,
:charger_power,
:date,
:ideal_battery_range
:ideal_battery_range_km
])
|> foreign_key_constraint(:charging_process_id)
end
end
+34
View File
@@ -0,0 +1,34 @@
defmodule TeslaMate.Log.ChargingProcess do
use Ecto.Schema
import Ecto.Changeset
alias TeslaMate.Log.{Charge, Car, Position}
schema "charging_processes" do
field :start_date, :utc_datetime
field :end_date, :utc_datetime
# TODO add fields
field :charge_energy_added, :float
# complete_date
belongs_to(:car, Car)
belongs_to(:position, Position)
has_many :charges, Charge
end
@doc false
def changeset(charging_state, attrs) do
charging_state
|> cast(attrs, [
:position_id,
:start_date,
:end_date,
:charge_energy_added
])
|> validate_required([:car_id, :start_date])
|> foreign_key_constraint(:car_id)
|> foreign_key_constraint(:position_id)
end
end
-35
View File
@@ -1,35 +0,0 @@
defmodule TeslaMate.Log.ChargingState do
use Ecto.Schema
import Ecto.Changeset
alias TeslaMate.Log.Charge
schema "charging_states" do
field :start_date, :utc_datetime
field :end_date, :utc_datetime
field :unplug_date, :utc_datetime
field :charge_energy_added, :float
belongs_to(:position, Position, foreign_key: :position_id)
belongs_to(:charge_start, Charge, foreign_key: :charge_start_id)
belongs_to(:charge_end, Charge, foreign_key: :charge_end_id)
end
@doc false
def changeset(charging_state, attrs) do
charging_state
|> cast(attrs, [
:start_date,
:end_date,
:unplug_date,
:charge_energy_added,
:position_id,
:charge_start_id,
:charge_end_id
])
|> validate_required([:start_date])
|> foreign_key_constraint(:position_id)
|> foreign_key_constraint(:charge_start_id)
|> foreign_key_constraint(:charge_end_id)
end
end
-30
View File
@@ -1,30 +0,0 @@
defmodule TeslaMate.Log.DriveState do
use Ecto.Schema
import Ecto.Changeset
alias TeslaMate.Log.Position
schema "drive_states" do
field :start_date, :utc_datetime
field :end_date, :utc_datetime
field :outside_temp_avg, :float
field :speed_max, :integer
field :speed_min, :integer
field :power_max, :float
field :power_min, :float
field :power_avg, :float
belongs_to(:start_position, Position, foreign_key: :start_position_id)
belongs_to(:end_position, Position, foreign_key: :end_position_id)
end
@doc false
def changeset(drive_state, attrs) do
drive_state
|> cast(attrs, [:start_date, :end_date, :start_position_id, :end_position_id])
|> validate_required([:start_date, :start_position_id])
|> foreign_key_constraint(:start_position_id)
|> foreign_key_constraint(:end_position_id)
end
end
+11 -3
View File
@@ -2,17 +2,23 @@ defmodule TeslaMate.Log.Position do
use Ecto.Schema
import Ecto.Changeset
alias TeslaMate.Log.{Car, Trip}
schema "positions" do
field :date, :utc_datetime
field :latitude, :float
field :longitude, :float
field :speed, :integer
field :power, :integer
field :odometer, :float
field :ideal_battery_range, :float
field :ideal_battery_range_km, :float
field :battery_level, :float
field :outside_temp, :float
field :altitude, :float
belongs_to(:car, Car)
belongs_to(:trip, Trip)
end
@doc false
@@ -25,11 +31,13 @@ defmodule TeslaMate.Log.Position do
:speed,
:power,
:odometer,
:ideal_battery_range,
:ideal_battery_range_km,
:battery_level,
:outside_temp,
:altitude
])
|> validate_required([:date, :latitude, :longitude])
|> validate_required([:car_id, :date, :latitude, :longitude])
|> foreign_key_constraint(:car_id)
|> foreign_key_constraint(:trip_id)
end
end
+4 -7
View File
@@ -3,7 +3,7 @@ defmodule TeslaMate.Log.State do
import Ecto.Changeset
alias __MODULE__.State
alias TeslaMate.Log.Position
alias TeslaMate.Log.{Car}
schema "states" do
field :state, State
@@ -11,16 +11,13 @@ defmodule TeslaMate.Log.State do
field :start_date, :utc_datetime
field :end_date, :utc_datetime
belongs_to(:start_position, Position, foreign_key: :start_position_id)
belongs_to(:end_position, Position, foreign_key: :end_position_id)
belongs_to(:car, Car)
end
@doc false
def changeset(state, attrs) do
state
|> cast(attrs, [:state, :start_date, :end_date, :start_position_id, :end_position_id])
|> validate_required([:state, :start_date])
|> foreign_key_constraint(:start_position_id)
|> foreign_key_constraint(:end_position_id)
|> cast(attrs, [:state, :start_date, :end_date])
|> validate_required([:car_id, :state, :start_date])
end
end
+66
View File
@@ -0,0 +1,66 @@
defmodule TeslaMate.Log.Trip do
use Ecto.Schema
import Ecto.Changeset
alias TeslaMate.Log.{Position, Car}
schema "trips" do
field :start_date, :utc_datetime
field :end_date, :utc_datetime
field :outside_temp_avg, :float
field :speed_max, :integer
field :speed_min, :integer
field :power_max, :float
field :power_min, :float
field :power_avg, :float
field :start_range_km, :float
field :end_range_km, :float
field :start_km, :float
field :end_km, :float
field :distance, :float
field :duration_min, :integer
# TODO Address ref
field :start_address, :string
field :end_address, :string
field :consumption_kWh, :float
field :consumption_kWh_100km, :float
belongs_to(:car, Car)
has_many :positions, Position
end
@doc false
def changeset(trip, attrs) do
trip
|> cast(attrs, [
:start_date,
:end_date,
:outside_temp_avg,
:speed_max,
:speed_min,
:power_max,
:power_min,
:power_avg,
:start_range_km,
:end_range_km,
:start_km,
:end_km,
:distance,
:duration_min,
:start_address,
:end_address,
:consumption_kWh,
:consumption_kWh_100km
])
|> validate_required([:car_id, :start_date])
|> foreign_key_constraint(:car_id)
end
end
+63 -65
View File
@@ -9,11 +9,11 @@ defmodule TeslaMate.Vehicles.Vehicle do
alias TeslaApi.Vehicle.State
alias TeslaApi.Vehicle
import Core.Dependency, only: [call: 3, call: 2]
import Core.Dependency, only: [call: 3]
defstruct id: nil,
car_id: nil,
vehicle_id: nil,
properties: nil,
last_shift_state: nil,
last_used: nil,
sleep_between: %{from: nil, to: nil},
@@ -39,9 +39,7 @@ defmodule TeslaMate.Vehicles.Vehicle do
def init(opts) do
%Vehicle{} = vehicle = Keyword.fetch!(opts, :vehicle)
properties = Identification.properties(vehicle)
Logger.info("Found Vehicle '#{vehicle.display_name}' [#{inspect(properties)}]")
Logger.info("Found Vehicle '#{vehicle.display_name}'")
deps = %{
log: Keyword.get(opts, :log, Log),
@@ -51,33 +49,36 @@ defmodule TeslaMate.Vehicles.Vehicle do
data = %Data{
id: vehicle.id,
vehicle_id: vehicle.vehicle_id,
properties: properties,
last_used: DateTime.utc_now(),
sudpend_after_idle_min: Keyword.get(opts, :sudpend_after_idle_min, 15),
sudpend_after_idle_min: Keyword.get(opts, :sudpend_after_idle_min, 10),
suspend_min: Keyword.get(opts, :suspend_min, 21),
deps: deps
}
{:ok, :start, data, {:next_event, :internal, :init}}
{:ok, :start, data, {:next_event, :internal, {:init, vehicle}}}
end
@impl true
def handle_event(:internal, :init, :start, data) do
# create_car if not exists
def handle_event(:internal, {:init, vehicle}, :start, data) do
{:ok, %Log.Car{id: car_id}} =
case call(data.deps.log, :get_car_by_eid, [vehicle.id]) do
nil ->
properties = Identification.properties(vehicle)
# DBHelper.getLastTrip()
call(data.deps.log, :create_car, [
%{
eid: vehicle.id,
vid: vehicle.vehicle_id,
model: properties.model,
efficiency: properties.efficiency
}
])
case call(data.deps.log, :close_charging_state) do
{:erorr, :no_charging_state_to_be_closed} -> :ok
:ok -> :ok
end
car ->
{:ok, car}
end
case call(data.deps.log, :close_drive_state) do
{:erorr, :no_drive_state_to_be_closed} -> :ok
:ok -> :ok
end
{:keep_state_and_data, {:next_event, :internal, :fetch}}
{:keep_state, %Data{data | car_id: car_id}, {:next_event, :internal, :fetch}}
end
# TODO
@@ -87,6 +88,7 @@ defmodule TeslaMate.Vehicles.Vehicle do
# - geofecnces
# - mqtt
# - indices
# - cron job which "closes" drives & charging_processes
def handle_event(event, :fetch, state, data) when event in [:state_timeout, :internal] do
case fetch(data, expected_state: state) do
@@ -118,7 +120,7 @@ defmodule TeslaMate.Vehicles.Vehicle do
def handle_event(:internal, {:update, :asleep}, :start, data) do
Logger.info("Start / :asleep")
:ok = call(data.deps.log, :start_state, [:asleep])
:ok = call(data.deps.log, :start_state, [data.car_id, :asleep])
{:next_state, :asleep, data, schedule_fetch()}
end
@@ -126,18 +128,18 @@ defmodule TeslaMate.Vehicles.Vehicle do
def handle_event(:internal, {:update, :offline}, :start, data) do
Logger.info("Start / :offline")
:ok = call(data.deps.log, :start_state, [:offline])
:ok = call(data.deps.log, :start_state, [data.car_id, :offline])
{:keep_state_and_data, schedule_fetch()}
end
def handle_event(:internal, {:update, {:online, vehicle_state}}, :start, data) do
def handle_event(:internal, event, :start, data) do
Logger.info("Start / :online")
:ok = insert_position(vehicle_state, data)
:ok = call(data.deps.log, :start_state, [:online])
:ok = call(data.deps.log, :start_state, [data.car_id, :online])
{:next_state, :online, %Data{data | last_used: DateTime.utc_now()}, schedule_fetch()}
{:next_state, :online, %Data{data | last_used: DateTime.utc_now()},
{:next_event, :internal, event}}
end
### :online
@@ -154,23 +156,21 @@ defmodule TeslaMate.Vehicles.Vehicle do
when shift_state in ["D", "N", "R"] ->
Logger.info("Start / :driving")
:ok = insert_position(vehicle_state, data)
:ok = call(data.deps.log, :start_drive_state)
# wh.StartStreamThread(); // for altitude
{:ok, trip_id} = call(data.deps.log, :start_trip, [data.car_id])
:ok = insert_position(vehicle_state, data, trip_id: trip_id)
{:next_state, {:driving, shift_state}, %Data{data | last_used: DateTime.utc_now()},
{:next_state, {:driving, trip_id}, %Data{data | last_used: DateTime.utc_now()},
schedule_fetch(5)}
%{charge_state: %State.Charge{charging_state: charging_state}}
when charging_state in ["Complete", "Charging"] ->
Logger.info("Start / :charging")
:ok = insert_charge(vehicle_state, data)
:ok = insert_position(vehicle_state, data)
:ok = call(data.deps.log, :start_charging_state)
{:ok, charging_process_id} = call(data.deps.log, :start_charging_process, [data.car_id])
{:next_state, {:charging, charging_state}, %Data{data | last_used: DateTime.utc_now()},
schedule_fetch(5)}
{:next_state, {:charging, charging_state, charging_process_id},
%Data{data | last_used: DateTime.utc_now()}, schedule_fetch(5)}
_ ->
suspend? =
@@ -186,9 +186,6 @@ defmodule TeslaMate.Vehicles.Vehicle do
{true, %State.Climate{is_preconditioning: _}} ->
Logger.info("Start / :suspend")
# Insert last position before sleep
:ok = insert_position(vehicle_state, data)
{:next_state, :start, data, schedule_fetch(data.suspend_min, :minutes)}
{false, _} ->
@@ -199,20 +196,20 @@ defmodule TeslaMate.Vehicles.Vehicle do
### :charging
def handle_event(:internal, {:update, :offline}, {:charging, _}, _data) do
def handle_event(:internal, {:update, :offline}, {:charging, _, _}, _data) do
Logger.warn("Vehicle went offline while charging")
{:keep_state_and_data, schedule_fetch()}
end
def handle_event(:internal, {:update, {:online, vehicle_state}}, {:charging, last}, data) do
def handle_event(:internal, {:update, {:online, vehicle_state}}, {:charging, last, pid}, data) do
case {vehicle_state.charge_state.charging_state, last} do
{"Charging", _} ->
:ok = insert_charge(vehicle_state, data)
:ok = insert_charge(pid, vehicle_state, data)
next_check_in = round(1000 / Map.get(vehicle_state.charge_state, :charge_power, 100))
{:next_state, {:charging, "Charging"}, %Data{data | last_used: DateTime.utc_now()},
{:next_state, {:charging, "Charging", pid}, %Data{data | last_used: DateTime.utc_now()},
schedule_fetch(next_check_in)}
{"Complete", "Complete"} ->
@@ -221,9 +218,9 @@ defmodule TeslaMate.Vehicles.Vehicle do
{"Complete", _} ->
Logger.info("Charging complete")
:ok = insert_charge(vehicle_state, data)
:ok = insert_charge(pid, vehicle_state, data)
{:next_state, {:charging, "Complete"}, %Data{data | last_used: DateTime.utc_now()},
{:next_state, {:charging, "Complete", pid}, %Data{data | last_used: DateTime.utc_now()},
schedule_fetch()}
{charging_state, last_charging_state} ->
@@ -231,8 +228,7 @@ defmodule TeslaMate.Vehicles.Vehicle do
"Charging ended (?): #{inspect(charging_state)} | Before: #{last_charging_state}"
)
:ok = insert_position(vehicle_state, data)
:ok = call(data.deps.log, :close_charging_state)
:ok = call(data.deps.log, :close_charging_process, [pid])
{:next_state, :start, %Data{data | last_used: DateTime.utc_now()}, schedule_fetch(5)}
end
@@ -240,28 +236,26 @@ defmodule TeslaMate.Vehicles.Vehicle do
### :driving
def handle_event(:internal, {:update, :offline}, {:driving, _}, _data) do
def handle_event(:internal, {:update, :offline}, {:driving, _trip_id}, _data) do
Logger.warn("Vehicle went offline while driving")
{:keep_state_and_data, schedule_fetch()}
end
def handle_event(:internal, {:update, {:online, vehicle_state}}, {:driving, _}, data) do
def handle_event(:internal, {:update, {:online, vehicle_state}}, {:driving, trip_id}, data) do
case vehicle_state.drive_state.shift_state do
shift_state when shift_state in ["D", "R", "N"] ->
:ok = insert_position(vehicle_state, data)
:ok = insert_position(vehicle_state, data, trip_id: trip_id)
{:next_state, {:driving, shift_state}, %Data{data | last_used: DateTime.utc_now()},
schedule_fetch(5)}
{:keep_state, %Data{data | last_used: DateTime.utc_now()}, schedule_fetch(5)}
"P" ->
{:keep_state, %Data{data | last_used: DateTime.utc_now()}, schedule_fetch(10)}
nil ->
Logger.info("Driving ended")
Logger.info("Trip ended")
# wh.StopStreaming();
:ok = call(data.deps.log, :close_drive_state)
:ok = call(data.deps.log, :close_trip, [trip_id])
{:next_state, :start, %Data{data | last_used: DateTime.utc_now()}, schedule_fetch(5)}
end
@@ -279,12 +273,11 @@ defmodule TeslaMate.Vehicles.Vehicle do
{:keep_state_and_data, schedule_fetch()}
end
def handle_event(:internal, {:update, {:online, vehicle_state}}, :asleep, data) do
def handle_event(:internal, {:update, {:online, _}} = event, :asleep, data) do
Logger.info("Vehicle woke up")
:ok = insert_position(vehicle_state, data)
{:next_state, :start, %Data{data | last_used: DateTime.utc_now()}, schedule_fetch(5)}
{:next_state, :start, %Data{data | last_used: DateTime.utc_now()},
{:next_event, :internal, event}}
end
# Private
@@ -294,7 +287,7 @@ defmodule TeslaMate.Vehicles.Vehicle do
case expected_state do
:online -> true
{:driving, _} -> true
{:charging, _} -> true
{:charging, _, _} -> true
:asleep -> false
:start -> false
end
@@ -321,8 +314,11 @@ defmodule TeslaMate.Vehicles.Vehicle do
end
end
defp insert_position(%Vehicle{drive_state: %State.Drive{}} = state, data) do
defp insert_position(%Vehicle{drive_state: %State.Drive{}} = state, data, opts \\ []) do
trip_id = Keyword.get(opts, :trip_id)
attrs = %{
trip_id: trip_id,
date: DateTime.from_unix!(state.drive_state.timestamp, :microsecond),
latitude: state.drive_state.latitude,
longitude: state.drive_state.longitude,
@@ -331,14 +327,15 @@ defmodule TeslaMate.Vehicles.Vehicle do
battery_level: Map.get(state.charge_state, :battery_level),
outside_temp: Map.get(state.climate_state, :outside_temp),
odometer: Map.get(state.vehicle_state, :odometer),
ideal_battery_range: Map.get(state.charge_state, :ideal_battery_range),
# TODO convert
ideal_battery_range_km: Map.get(state.charge_state, :ideal_battery_range),
altitude: nil
}
:ok = call(data.deps.log, :insert_position, [attrs])
:ok = call(data.deps.log, :insert_position, [data.car_id, attrs])
end
defp insert_charge(%Vehicle{charge_state: %State.Charge{}} = state, data) do
defp insert_charge(process_id, %Vehicle{charge_state: %State.Charge{}} = state, data) do
attrs = %{
date: DateTime.from_unix!(state.charge_state.timestamp, :microsecond),
battery_level: state.charge_state.battery_level,
@@ -347,11 +344,12 @@ defmodule TeslaMate.Vehicles.Vehicle do
charger_phases: state.charge_state.charger_phases,
charger_power: state.charge_state.charger_power,
charger_voltage: state.charge_state.charger_voltage,
ideal_battery_range: state.charge_state.ideal_battery_range,
# TODO convert
ideal_battery_range_km: state.charge_state.ideal_battery_range,
outside_temp: Map.get(state.climate_state, :outside_temp)
}
:ok = call(data.deps.log, :insert_charge, [attrs])
:ok = call(data.deps.log, :insert_charge, [process_id, attrs])
end
defp schedule_fetch(n \\ 10, unit \\ :seconds)
@@ -0,0 +1,26 @@
defmodule TeslaMateWeb.CarController do
use TeslaMateWeb, :controller
alias TeslaMate.Log
alias TeslaMate.Log.Car
action_fallback TeslaMateWeb.FallbackController
def index(conn, _params) do
car = Log.list_cars()
render(conn, "index.json", car: car)
end
def show(conn, %{"id" => id}) do
car = Log.get_car!(id)
render(conn, "show.json", car: car)
end
def update(conn, %{"id" => id, "car" => car_params}) do
car = Log.get_car!(id)
with {:ok, %Car{} = car} <- Log.update_car(car, car_params) do
render(conn, "show.json", car: car)
end
end
end
+5 -4
View File
@@ -19,8 +19,9 @@ defmodule TeslaMateWeb.Router do
get "/", PageController, :index
end
# Other scopes may use custom stacks.
# scope "/api", TeslaMateWeb do
# pipe_through :api
# end
scope "/api", TeslaMateWeb do
pipe_through :api
resources "/car", CarController, only: [:index, :show, :update]
end
end
+22
View File
@@ -0,0 +1,22 @@
defmodule TeslaMateWeb.CarView do
use TeslaMateWeb, :view
alias TeslaMateWeb.CarView
def render("index.json", %{car: car}) do
%{data: render_many(car, CarView, "car.json")}
end
def render("show.json", %{car: car}) do
%{data: render_one(car, CarView, "car.json")}
end
def render("car.json", %{car: car}) do
%{
id: car.id,
eid: car.eid,
vid: car.vid,
model: car.model,
efficiency: car.efficiency
}
end
end
+1 -1
View File
@@ -61,7 +61,7 @@ defmodule TeslaMate.MixProject do
[
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate", "test"]
test: ["ecto.create --quiet", "ecto.migrate", "test --no-start"]
]
end
end
@@ -0,0 +1,17 @@
defmodule TeslaMate.Repo.Migrations.CreateCar do
use Ecto.Migration
def change do
create table(:cars) do
add(:eid, :bigint, null: false)
add(:vid, :bigint, null: false)
add(:model, :string, null: false)
add(:efficiency, :float, null: false)
timestamps()
end
create(unique_index(:cars, :eid))
create(unique_index(:cars, :vid))
end
end
@@ -0,0 +1,28 @@
defmodule TeslaMate.Repo.Migrations.CreateTrips do
use Ecto.Migration
def change do
create table(:trips) do
add(:start_date, :utc_datetime, null: false)
add(:end_date, :utc_datetime)
add(:outside_temp_avg, :float)
add(:speed_max, :integer)
add(:speed_min, :integer)
add(:power_max, :float)
add(:power_min, :float)
add(:power_avg, :float)
add(:start_range_km, :float)
add(:end_range_km, :float)
add(:start_km, :float)
add(:end_km, :float)
add(:distance, :float)
add(:duration_min, :integer)
add(:start_address, :string)
add(:end_address, :string)
add(:consumption_kWh, :float)
add(:consumption_kWh_100km, :float)
add(:car_id, references(:cars), null: false)
end
end
end
@@ -9,10 +9,13 @@ defmodule TeslaMate.Repo.Migrations.CreatePositions do
add(:speed, :integer)
add(:power, :integer)
add(:odometer, :float)
add(:ideal_battery_range, :float)
add(:ideal_battery_range_km, :float)
add(:battery_level, :float)
add(:outside_temp, :float)
add(:altitude, :float)
add(:car_id, references(:cars), null: false)
add(:trip_id, references(:trips))
end
end
end
@@ -12,8 +12,7 @@ defmodule TeslaMate.Repo.Migrations.CreateStates do
add(:start_date, :utc_datetime, null: false)
add(:end_date, :utc_datetime)
add(:start_position_id, references(:positions))
add(:end_position_id, references(:positions))
add(:car_id, references(:cars), null: false)
end
end
@@ -0,0 +1,15 @@
defmodule TeslaMate.Repo.Migrations.CreateChargingProcesses do
use Ecto.Migration
def change do
create table(:charging_processes) do
add(:start_date, :utc_datetime, nul: false)
add(:end_date, :utc_datetime)
add(:charge_energy_added, :float)
add(:car_id, references(:cars), null: false)
add(:position_id, references(:positions))
end
end
end
@@ -4,14 +4,16 @@ defmodule TeslaMate.Repo.Migrations.CreateCharges do
def change do
create table(:charges) do
add(:date, :utc_datetime, null: false)
add(:ideal_battery_range, :float, null: false)
add(:battery_level, :float, null: false)
add(:ideal_battery_range_km, :float, null: false)
add(:battery_level, :integer, null: false)
add(:charge_energy_added, :float, null: false)
add(:charger_power, :float, null: false)
add(:charger_voltage, :integer)
add(:charger_phases, :integer)
add(:charger_actual_current, :integer)
add(:outside_temp, :float)
add(:charging_process_id, references(:charging_processes), null: false)
end
end
end
@@ -1,20 +0,0 @@
defmodule TeslaMate.Repo.Migrations.CreateDriveStates do
use Ecto.Migration
def change do
create table(:drive_states) do
add(:start_date, :utc_datetime, null: false)
add(:end_date, :utc_datetime)
add(:outside_temp_avg, :float)
add(:speed_max, :integer)
add(:speed_min, :integer)
add(:power_max, :float)
add(:power_min, :float)
add(:power_avg, :float)
add(:start_position_id, references(:positions), null: false)
add(:end_position_id, references(:positions))
end
end
end
@@ -1,17 +0,0 @@
defmodule TeslaMate.Repo.Migrations.CreateChargingStates do
use Ecto.Migration
def change do
create table(:charging_states) do
add(:start_date, :utc_datetime, nul: false)
add(:end_date, :utc_datetime)
add(:unplug_date, :utc_datetime)
add(:charge_energy_added, :float)
add(:position_id, references(:positions))
add(:charge_start_id, references(:charges))
add(:charge_end_id, references(:charges))
end
end
end
+2
View File
@@ -33,6 +33,8 @@ defmodule TeslaMateWeb.ConnCase do
Ecto.Adapters.SQL.Sandbox.mode(TeslaMate.Repo, {:shared, self()})
end
{:ok, _} = TeslaMateWeb.Endpoint.start_link()
{:ok, conn: Phoenix.ConnTest.build_conn()}
end
end
+40 -7
View File
@@ -4,22 +4,37 @@ defmodule LogMock do
defstruct [:pid]
alias __MODULE__, as: State
alias TeslaMate.Log.Car
# API
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: Keyword.fetch!(opts, :name))
end
def start_state(name, state), do: GenServer.call(name, {:start_state, state})
def get_car_by_eid(name, eid), do: GenServer.call(name, {:get_car_by_eid, eid})
def create_car(name, car), do: GenServer.call(name, {:create_car, car})
def start_drive_state(name), do: GenServer.call(name, :start_drive_state)
def close_drive_state(name), do: GenServer.call(name, :close_drive_state)
def start_state(name, car_id, state), do: GenServer.call(name, {:start_state, car_id, state})
def start_charging_state(name), do: GenServer.call(name, :start_charging_state)
def close_charging_state(name), do: GenServer.call(name, :close_charging_state)
def start_trip(name, car_id), do: GenServer.call(name, {:start_trip, car_id})
def close_trip(name, trip_id), do: GenServer.call(name, {:close_trip, trip_id})
def insert_position(name, attrs), do: GenServer.call(name, {:insert_position, attrs})
def insert_charge(name, attrs), do: GenServer.call(name, {:insert_charge, attrs})
def start_charging_process(name, car_id) do
GenServer.call(name, {:start_charging_process, car_id})
end
def close_charging_process(name, process_id) do
GenServer.call(name, {:close_charging_process, process_id})
end
def insert_position(name, car_id, attrs) do
GenServer.call(name, {:insert_position, car_id, attrs})
end
def insert_charge(name, car_id, attrs) do
GenServer.call(name, {:insert_charge, car_id, attrs})
end
# Callbacks
@@ -29,6 +44,24 @@ defmodule LogMock do
end
@impl true
def handle_call({:get_car_by_eid, _eid}, _from, state) do
{:reply, nil, state}
end
def handle_call({:create_car, car}, _from, state) do
{:reply, {:ok, struct(Car, Map.put(car, :id, 999))}, state}
end
def handle_call({:start_charging_process, _car_id} = action, _from, %State{pid: pid} = state) do
send(pid, action)
{:reply, {:ok, 99}, state}
end
def handle_call({:start_trip, _car_id} = action, _from, %State{pid: pid} = state) do
send(pid, action)
{:reply, {:ok, 111}, state}
end
def handle_call(action, _from, %State{pid: pid} = state) do
send(pid, action)
{:reply, :ok, state}
-3
View File
@@ -24,9 +24,6 @@ defmodule TeslaMate.VehicleCase do
opts}
)
assert_receive :close_charging_state
assert_receive :close_drive_state
:ok
end
+109 -48
View File
@@ -1,7 +1,7 @@
defmodule TeslaMate.LogTest do
use TeslaMate.DataCase
# alias TeslaMate.Log
alias TeslaMate.Log
# describe "positions" do
# alias TeslaMate.Log.Position
@@ -129,73 +129,73 @@ defmodule TeslaMate.LogTest do
# end
# end
# describe "drive_states" do
# alias TeslaMate.Log.DriveState
# describe "trips" do
# alias TeslaMate.Log.Trip
# @valid_attrs %{end_date: "2010-04-17T14:00:00Z", end_position_id: 42, start_date: "2010-04-17T14:00:00Z", start_position_id: 42}
# @update_attrs %{end_date: "2011-05-18T15:01:01Z", end_position_id: 43, start_date: "2011-05-18T15:01:01Z", start_position_id: 43}
# @invalid_attrs %{end_date: nil, end_position_id: nil, start_date: nil, start_position_id: nil}
# def drive_state_fixture(attrs \\ %{}) do
# {:ok, drive_state} =
# def trip_fixture(attrs \\ %{}) do
# {:ok, trip} =
# attrs
# |> Enum.into(@valid_attrs)
# |> Log.create_drive_state()
# |> Log.create_trip()
# drive_state
# trip
# end
# test "list_drive_states/0 returns all drive_states" do
# drive_state = drive_state_fixture()
# assert Log.list_drive_states() == [drive_state]
# test "list_trips/0 returns all trips" do
# trip = trip_fixture()
# assert Log.list_trips() == [trip]
# end
# test "get_drive_state!/1 returns the drive_state with given id" do
# drive_state = drive_state_fixture()
# assert Log.get_drive_state!(drive_state.id) == drive_state
# test "get_trip!/1 returns the trip with given id" do
# trip = trip_fixture()
# assert Log.get_trip!(trip.id) == trip
# end
# test "create_drive_state/1 with valid data creates a drive_state" do
# assert {:ok, %DriveState{} = drive_state} = Log.create_drive_state(@valid_attrs)
# assert drive_state.end_date == DateTime.from_naive!(~N[2010-04-17T14:00:00Z], "Etc/UTC")
# assert drive_state.end_position_id == 42
# assert drive_state.start_date == DateTime.from_naive!(~N[2010-04-17T14:00:00Z], "Etc/UTC")
# assert drive_state.start_position_id == 42
# test "create_trip/1 with valid data creates a trip" do
# assert {:ok, %Trip{} = trip} = Log.create_trip(@valid_attrs)
# assert trip.end_date == DateTime.from_naive!(~N[2010-04-17T14:00:00Z], "Etc/UTC")
# assert trip.end_position_id == 42
# assert trip.start_date == DateTime.from_naive!(~N[2010-04-17T14:00:00Z], "Etc/UTC")
# assert trip.start_position_id == 42
# end
# test "create_drive_state/1 with invalid data returns error changeset" do
# assert {:error, %Ecto.Changeset{}} = Log.create_drive_state(@invalid_attrs)
# test "create_trip/1 with invalid data returns error changeset" do
# assert {:error, %Ecto.Changeset{}} = Log.create_trip(@invalid_attrs)
# end
# test "update_drive_state/2 with valid data updates the drive_state" do
# drive_state = drive_state_fixture()
# assert {:ok, %DriveState{} = drive_state} = Log.update_drive_state(drive_state, @update_attrs)
# assert drive_state.end_date == DateTime.from_naive!(~N[2011-05-18T15:01:01Z], "Etc/UTC")
# assert drive_state.end_position_id == 43
# assert drive_state.start_date == DateTime.from_naive!(~N[2011-05-18T15:01:01Z], "Etc/UTC")
# assert drive_state.start_position_id == 43
# test "update_trip/2 with valid data updates the trip" do
# trip = trip_fixture()
# assert {:ok, %Trip{} = trip} = Log.update_trip(trip, @update_attrs)
# assert trip.end_date == DateTime.from_naive!(~N[2011-05-18T15:01:01Z], "Etc/UTC")
# assert trip.end_position_id == 43
# assert trip.start_date == DateTime.from_naive!(~N[2011-05-18T15:01:01Z], "Etc/UTC")
# assert trip.start_position_id == 43
# end
# test "update_drive_state/2 with invalid data returns error changeset" do
# drive_state = drive_state_fixture()
# assert {:error, %Ecto.Changeset{}} = Log.update_drive_state(drive_state, @invalid_attrs)
# assert drive_state == Log.get_drive_state!(drive_state.id)
# test "update_trip/2 with invalid data returns error changeset" do
# trip = trip_fixture()
# assert {:error, %Ecto.Changeset{}} = Log.update_trip(trip, @invalid_attrs)
# assert trip == Log.get_trip!(trip.id)
# end
# test "delete_drive_state/1 deletes the drive_state" do
# drive_state = drive_state_fixture()
# assert {:ok, %DriveState{}} = Log.delete_drive_state(drive_state)
# assert_raise Ecto.NoResultsError, fn -> Log.get_drive_state!(drive_state.id) end
# test "delete_trip/1 deletes the trip" do
# trip = trip_fixture()
# assert {:ok, %Trip{}} = Log.delete_trip(trip)
# assert_raise Ecto.NoResultsError, fn -> Log.get_trip!(trip.id) end
# end
# test "change_drive_state/1 returns a drive_state changeset" do
# drive_state = drive_state_fixture()
# assert %Ecto.Changeset{} = Log.change_drive_state(drive_state)
# test "change_trip/1 returns a trip changeset" do
# trip = trip_fixture()
# assert %Ecto.Changeset{} = Log.change_trip(trip)
# end
# end
# describe "charging_states" do
# alias TeslaMate.Log.ChargingState
# alias TeslaMate.Log.ChargingProcess
# @valid_attrs %{end_date: "2010-04-17T14:00:00Z", start_date: "2010-04-17T14:00:00Z"}
# @update_attrs %{end_date: "2011-05-18T15:01:01Z", start_date: "2011-05-18T15:01:01Z"}
@@ -221,7 +221,7 @@ defmodule TeslaMate.LogTest do
# end
# test "create_charging_state/1 with valid data creates a charging_state" do
# assert {:ok, %ChargingState{} = charging_state} = Log.create_charging_state(@valid_attrs)
# assert {:ok, %ChargingProcess{} = charging_state} = Log.create_charging_state(@valid_attrs)
# assert charging_state.end_date == DateTime.from_naive!(~N[2010-04-17T14:00:00Z], "Etc/UTC")
# assert charging_state.start_date == DateTime.from_naive!(~N[2010-04-17T14:00:00Z], "Etc/UTC")
# end
@@ -232,7 +232,7 @@ defmodule TeslaMate.LogTest do
# test "update_charging_state/2 with valid data updates the charging_state" do
# charging_state = charging_state_fixture()
# assert {:ok, %ChargingState{} = charging_state} = Log.update_charging_state(charging_state, @update_attrs)
# assert {:ok, %ChargingProcess{} = charging_state} = Log.update_charging_state(charging_state, @update_attrs)
# assert charging_state.end_date == DateTime.from_naive!(~N[2011-05-18T15:01:01Z], "Etc/UTC")
# assert charging_state.start_date == DateTime.from_naive!(~N[2011-05-18T15:01:01Z], "Etc/UTC")
# end
@@ -245,7 +245,7 @@ defmodule TeslaMate.LogTest do
# test "delete_charging_state/1 deletes the charging_state" do
# charging_state = charging_state_fixture()
# assert {:ok, %ChargingState{}} = Log.delete_charging_state(charging_state)
# assert {:ok, %ChargingProcess{}} = Log.delete_charging_state(charging_state)
# assert_raise Ecto.NoResultsError, fn -> Log.get_charging_state!(charging_state.id) end
# end
@@ -258,9 +258,9 @@ defmodule TeslaMate.LogTest do
# describe "charges" do
# alias TeslaMate.Log.Charge
# @valid_attrs %{battery_level: 120.5, charge_energy_added: 120.5, charger_actual_current: 42, charger_phases: 42, charger_power: 120.5, charger_voltage: 42, date: "2010-04-17T14:00:00Z", ideal_battery_range: 120.5, outside_temp: 120.5}
# @update_attrs %{battery_level: 456.7, charge_energy_added: 456.7, charger_actual_current: 43, charger_phases: 43, charger_power: 456.7, charger_voltage: 43, date: "2011-05-18T15:01:01Z", ideal_battery_range: 456.7, outside_temp: 456.7}
# @invalid_attrs %{battery_level: nil, charge_energy_added: nil, charger_actual_current: nil, charger_phases: nil, charger_power: nil, charger_voltage: nil, date: nil, ideal_battery_range: nil, outside_temp: nil}
# @valid_attrs %{battery_level: 120.5, charge_energy_added: 120.5, charger_actual_current: 42, charger_phases: 42, charger_power: 120.5, charger_voltage: 42, date: "2010-04-17T14:00:00Z", ideal_battery_range_km: 120.5, outside_temp: 120.5}
# @update_attrs %{battery_level: 456.7, charge_energy_added: 456.7, charger_actual_current: 43, charger_phases: 43, charger_power: 456.7, charger_voltage: 43, date: "2011-05-18T15:01:01Z", ideal_battery_range_km: 456.7, outside_temp: 456.7}
# @invalid_attrs %{battery_level: nil, charge_energy_added: nil, charger_actual_current: nil, charger_phases: nil, charger_power: nil, charger_voltage: nil, date: nil, ideal_battery_range_km: nil, outside_temp: nil}
# def charge_fixture(attrs \\ %{}) do
# {:ok, charge} =
@@ -290,7 +290,7 @@ defmodule TeslaMate.LogTest do
# assert charge.charger_power == 120.5
# assert charge.charger_voltage == 42
# assert charge.date == DateTime.from_naive!(~N[2010-04-17T14:00:00Z], "Etc/UTC")
# assert charge.ideal_battery_range == 120.5
# assert charge.ideal_battery_range_km == 120.5
# assert charge.outside_temp == 120.5
# end
@@ -308,7 +308,7 @@ defmodule TeslaMate.LogTest do
# assert charge.charger_power == 456.7
# assert charge.charger_voltage == 43
# assert charge.date == DateTime.from_naive!(~N[2011-05-18T15:01:01Z], "Etc/UTC")
# assert charge.ideal_battery_range == 456.7
# assert charge.ideal_battery_range_km == 456.7
# assert charge.outside_temp == 456.7
# end
@@ -329,4 +329,65 @@ defmodule TeslaMate.LogTest do
# assert %Ecto.Changeset{} = Log.change_charge(charge)
# end
# end
describe "car" do
alias TeslaMate.Log.Car
@valid_attrs %{efficiency: 120.5, eid: 42, model: "some model", vid: 42}
@update_attrs %{
efficiency: 456.7,
model: "some updated model",
eid: 43,
vid: 43
}
@invalid_attrs %{efficiency: nil, eid: nil, model: nil, vid: nil}
def car_fixture(attrs \\ %{}) do
{:ok, car} =
attrs
|> Enum.into(@valid_attrs)
|> Log.create_car()
car
end
test "list_cars/0 returns all car" do
car = car_fixture()
assert Log.list_cars() == [car]
end
test "get_car!/1 returns the car with given id" do
car = car_fixture()
assert Log.get_car!(car.id) == car
end
test "create_car/1 with valid data creates a car" do
assert {:ok, %Car{} = car} = Log.create_car(@valid_attrs)
assert car.efficiency == 120.5
assert car.eid == 42
assert car.model == "some model"
assert car.vid == 42
end
test "create_car/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Log.create_car(@invalid_attrs)
end
test "update_car/2 with valid data updates the car" do
car = car_fixture()
assert {:ok, %Car{} = car} = Log.update_car(car, @update_attrs)
assert car.efficiency == 456.7
assert car.model == "some updated model"
assert car.eid == 42
assert car.vid == 42
end
test "update_car/2 with invalid data returns error changeset" do
car = car_fixture()
assert {:error, %Ecto.Changeset{}} = Log.update_car(car, @invalid_attrs)
assert car == Log.get_car!(car.id)
end
end
end
@@ -12,9 +12,10 @@ defmodule TeslaMate.Vehicles.Vehicle.ChargingTest do
)
end
@tag :only
test "logs a full charging cycle", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :microsecond)
now_ts = DateTime.to_unix(now, :millisecond)
events = [
{:ok, %TeslaApi.Vehicle{state: "online"}},
@@ -30,20 +31,16 @@ defmodule TeslaMate.Vehicles.Vehicle.ChargingTest do
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:insert_position, %{date: ^now, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
assert_receive :start_charging_state
assert_receive {:insert_position, %{date: _, latitude: 0.0, longitude: 0.0}}
assert_receive {:insert_charge, %{date: _, charger_power: nil}}
assert_receive {:insert_charge, %{date: _, charger_power: 125}}
assert_receive {:insert_charge, %{date: _, charger_power: 120}}
assert_receive {:insert_charge, %{date: _, charger_power: 0}}
assert_receive :close_charging_state
assert_receive {:insert_position, 999, %{date: _, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_charging_process, 999}
assert_receive {:insert_charge, 99, %{date: _, charger_power: 125}}
assert_receive {:insert_charge, 99, %{date: _, charger_power: 120}}
assert_receive {:insert_charge, 99, %{date: _, charger_power: 0}}
assert_receive {:close_charging_process, 99}
assert_receive {:insert_position, %{date: _, latitude: 0.0, longitude: 0.0, speed: nil}}
assert_receive {:insert_position, %{date: _, latitude: 0.2, longitude: 0.2, speed: nil}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
refute_receive _
end
@@ -51,7 +48,7 @@ defmodule TeslaMate.Vehicles.Vehicle.ChargingTest do
@tag :capture_log
test "handles a connection loss when charging", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :microsecond)
now_ts = DateTime.to_unix(now, :millisecond)
events = [
{:ok, %TeslaApi.Vehicle{state: "online"}},
@@ -71,27 +68,23 @@ defmodule TeslaMate.Vehicles.Vehicle.ChargingTest do
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:insert_position, %{date: ^now, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
assert_receive :start_charging_state
assert_receive {:insert_position, %{date: _, latitude: 0.0, longitude: 0.0}}
assert_receive {:insert_charge, %{date: _, charger_power: nil}}
assert_receive {:insert_charge, %{date: _, charger_power: 125}}
assert_receive {:insert_charge, %{date: _, charger_power: 120}}
assert_receive {:insert_charge, %{date: _, charger_power: 0}}
assert_receive :close_charging_state
assert_receive {:insert_position, 999, %{date: _, latitude: 0.0, longitude: 0.0, speed: nil}}
assert_receive {:start_charging_process, 999}
assert_receive {:insert_charge, 99, %{date: _, charger_power: 125}}
assert_receive {:insert_charge, 99, %{date: _, charger_power: 120}}
assert_receive {:insert_charge, 99, %{date: _, charger_power: 0}}
assert_receive {:close_charging_process, 99}
assert_receive {:insert_position, %{date: _, latitude: 0.0, longitude: 0.0, speed: nil}}
assert_receive {:insert_position, %{date: _, latitude: 0.2, longitude: 0.2, speed: nil}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
refute_receive _
end
test "Transitions directly into charging state", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :microsecond)
now_ts = DateTime.to_unix(now, :millisecond)
events = [
{:ok, %TeslaApi.Vehicle{state: "online"}},
@@ -100,13 +93,13 @@ defmodule TeslaMate.Vehicles.Vehicle.ChargingTest do
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:insert_position, %{date: ^now, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
assert_receive :start_charging_state
assert_receive {:insert_position, %{date: _, latitude: 0.0, longitude: 0.0}}
assert_receive {:insert_charge, %{date: _, charger_power: 22}}
assert_receive {:insert_charge, %{date: _, charger_power: 22}}
assert_receive {:insert_position, 999, %{date: _, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_charging_process, 999}
assert_receive {:insert_charge, 99, %{date: _, charger_power: 22}}
assert_receive {:insert_charge, 99, %{date: _, charger_power: 22}}
# ...
refute_received _
@@ -1,44 +1,42 @@
defmodule TeslaMate.Vehicles.Vehicle.DrivingTest do
use TeslaMate.VehicleCase, async: true
defp drive_event(ts, shift_state, speed) do
defp drive_event(ts, shift_state, speed_mph) do
vehicle_full(
drive_state: %{
timestamp: ts,
latitude: 0.1,
longitude: 0.1,
shift_state: shift_state,
speed: speed
speed: speed_mph
}
)
end
test "logs a full drive", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :microsecond)
now_ts = DateTime.to_unix(now, :millisecond)
events = [
{:ok, %TeslaApi.Vehicle{state: "online"}},
{:ok, vehicle_full(drive_state: %{timestamp: now_ts, latitude: 0.0, longitude: 0.0})},
{:ok, drive_event(now_ts + 1, "D", 50)},
{:ok, drive_event(now_ts + 2, "N", 0)},
{:ok, drive_event(now_ts + 1, "D", 60)},
{:ok, drive_event(now_ts + 2, "N", 30)},
{:ok, drive_event(now_ts + 3, "R", -5)},
{:ok, vehicle_full(drive_state: %{timestamp: now_ts, latitude: 0.2, longitude: 0.2})}
]
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:insert_position, %{date: ^now, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
assert_receive :start_drive_state
assert_receive {:insert_position, %{date: _, latitude: 0.1, longitude: 0.1, speed: 50}}
assert_receive {:insert_position, %{date: _, latitude: 0.1, longitude: 0.1, speed: 0}}
assert_receive {:insert_position, %{date: _, latitude: 0.1, longitude: 0.1, speed: -5}}
assert_receive :close_drive_state
assert_receive {:start_trip, 999}
assert_receive {:insert_position, 999, %{longitude: 0.1, speed: 97.0, trip_id: 111}}
assert_receive {:insert_position, 999, %{longitude: 0.1, speed: 48.0, trip_id: 111}}
assert_receive {:insert_position, 999, %{longitude: 0.1, speed: -8.0, trip_id: 111}}
assert_receive {:close_trip, 111}
assert_receive {:insert_position, %{date: _, latitude: 0.2, longitude: 0.2, speed: nil}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
refute_receive _
end
@@ -46,7 +44,7 @@ defmodule TeslaMate.Vehicles.Vehicle.DrivingTest do
@tag :capture_log
test "handles a connection loss when driving", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :microsecond)
now_ts = DateTime.to_unix(now, :millisecond)
events = [
{:ok, %TeslaApi.Vehicle{state: "online"}},
@@ -63,24 +61,22 @@ defmodule TeslaMate.Vehicles.Vehicle.DrivingTest do
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:insert_position, %{date: ^now, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
assert_receive :start_drive_state
assert_receive {:insert_position, %{date: _, latitude: 0.1, longitude: 0.1, speed: 50}}
assert_receive {:insert_position, %{date: _, latitude: 0.1, longitude: 0.1, speed: 55}}
assert_receive {:insert_position, %{date: _, latitude: 0.1, longitude: 0.1, speed: 40}}
assert_receive :close_drive_state
assert_receive {:start_trip, 999}
assert_receive {:insert_position, 999, %{longitude: 0.1, speed: 80.0, trip_id: 111}}
assert_receive {:insert_position, 999, %{longitude: 0.1, speed: 89.0, trip_id: 111}}
assert_receive {:insert_position, 999, %{longitude: 0.1, speed: 64.0, trip_id: 111}}
assert_receive {:close_trip, 111}
assert_receive {:insert_position, %{date: _, latitude: 0.2, longitude: 0.2, speed: nil}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
refute_receive _
end
test "Transitions directly into driving state", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :microsecond)
now_ts = DateTime.to_unix(now, :millisecond)
events = [
{:ok, %TeslaApi.Vehicle{state: "online"}},
@@ -89,14 +85,29 @@ defmodule TeslaMate.Vehicles.Vehicle.DrivingTest do
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:insert_position, %{date: ^now, latitude: 0.1, longitude: 0.1}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
assert_receive :start_drive_state
assert_receive {:insert_position, %{date: _, latitude: 0.1, longitude: 0.1, speed: 0}}
assert_receive {:insert_position, %{date: _, latitude: 0.1, longitude: 0.1, speed: 0}}
assert_receive {:start_trip, 999}
assert_receive {:insert_position, 999, %{longitude: 0.1, speed: 0.0, trip_id: 111}}
assert_receive {:insert_position, 999, %{longitude: 0.1, speed: 0.0, trip_id: 111}}
# ...
refute_received _
end
test "shift state P does not trigger driving state", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :millisecond)
events = [
{:ok, %TeslaApi.Vehicle{state: "online"}},
{:ok, drive_event(now_ts, "P", 0)}
]
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:start_state, 999, :online}
refute_receive _
end
end
@@ -3,7 +3,7 @@ defmodule TeslaMate.Vehicles.Vehicle.SuspendTest do
test "suspends after idling", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :microsecond)
now_ts = DateTime.to_unix(now, :millisecond)
suspendable =
vehicle_full(
@@ -27,13 +27,9 @@ defmodule TeslaMate.Vehicles.Vehicle.SuspendTest do
suspend_min: suspend_ms
)
assert_receive {:insert_position, %{date: ^now, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_state, :online}
assert_receive {:insert_position, %{date: _, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_state, 999, :online}
refute_receive _, sudpend_after_idle_ms + suspend_ms - 20
assert_receive {:start_state, :asleep}
assert_receive {:start_state, 999, :asleep}
refute_receive _
end
@@ -41,7 +37,7 @@ defmodule TeslaMate.Vehicles.Vehicle.SuspendTest do
@tag :capture_log
test "does not suspend if preconditioning", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :microsecond)
now_ts = DateTime.to_unix(now, :millisecond)
not_supendable =
vehicle_full(
@@ -63,8 +59,7 @@ defmodule TeslaMate.Vehicles.Vehicle.SuspendTest do
suspend_min: suspend_ms
)
assert_receive {:insert_position, %{date: ^now, latitude: 0.0, longitude: 0.0}}
assert_receive {:start_state, :online}
assert_receive {:start_state, 999, :online}
# Stays online
refute_receive _, round(suspend_ms * 1.5)
+22 -25
View File
@@ -3,19 +3,20 @@ defmodule TeslaMate.Vehicles.VehicleTest do
describe "start" do
@tag :capture_log
test "handles unkown state", %{test: name} do
test "handles unkown and faulty states", %{test: name} do
events = [
{:ok, %TeslaApi.Vehicle{state: "unknown"}}
{:ok, %TeslaApi.Vehicle{state: "unknown"}},
{:error, %TeslaApi.Error{message: "boom"}}
]
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0, vehicle_id: 1001}, events)
refute_receive _
end
test "handles online state", %{test: name} do
now = DateTime.utc_now()
now_ts = DateTime.to_unix(now, :microsecond)
now_ts = DateTime.to_unix(now, :millisecond)
events = [
{:ok, %TeslaApi.Vehicle{state: "online"}},
@@ -24,21 +25,21 @@ defmodule TeslaMate.Vehicles.VehicleTest do
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:insert_position,
%{
altitude: nil,
battery_level: nil,
date: ^now,
ideal_battery_range: nil,
latitude: 0.0,
longitude: 0.0,
odometer: nil,
outside_temp: nil,
power: nil,
speed: nil
}}
assert_receive {:start_state, 999, :online}
assert_receive {:start_state, :online}
# assert_receive {:insert_position,
# %{
# altitude: nil,
# battery_level: nil,
# date: ^now,
# ideal_battery_range_km: nil,
# latitude: 0.0,
# longitude: 0.0,
# odometer: nil,
# outside_temp: nil,
# power: nil,
# speed: nil
# }}
refute_receive _
end
@@ -50,13 +51,9 @@ defmodule TeslaMate.Vehicles.VehicleTest do
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:start_state, :offline}
assert_receive {:start_state, :offline}
assert_receive {:start_state, :offline}
assert_receive {:start_state, :offline}
# ...
assert_receive {:start_state, 999, :offline}
refute_received _
refute_receive _
end
test "handles asleep state", %{test: name} do
@@ -66,7 +63,7 @@ defmodule TeslaMate.Vehicles.VehicleTest do
:ok = start_vehicle(name, %TeslaApi.Vehicle{id: 0}, events)
assert_receive {:start_state, :asleep}
assert_receive {:start_state, 999, :asleep}
refute_receive _
end
@@ -0,0 +1,65 @@
defmodule TeslaMateWeb.CarControllerTest do
use TeslaMateWeb.ConnCase
alias TeslaMate.Log
alias TeslaMate.Log.Car
@create_attrs %{
efficiency: 120.5,
eid: 42,
model: "some model",
vid: 42
}
@update_attrs %{
efficiency: 456.7,
eid: 43,
model: "some updated model",
vid: 43
}
@invalid_attrs %{efficiency: nil, eid: nil, model: nil, vid: nil}
def fixture(:car) do
{:ok, car} = Log.create_car(@create_attrs)
car
end
setup %{conn: conn} do
{:ok, conn: put_req_header(conn, "accept", "application/json")}
end
describe "index" do
test "lists all car", %{conn: conn} do
conn = get(conn, Routes.car_path(conn, :index))
assert json_response(conn, 200)["data"] == []
end
end
describe "update car" do
setup [:create_car]
test "renders car when data is valid", %{conn: conn, car: %Car{id: id} = car} do
conn = put(conn, Routes.car_path(conn, :update, car), car: @update_attrs)
assert %{"id" => ^id} = json_response(conn, 200)["data"]
conn = get(conn, Routes.car_path(conn, :show, id))
assert %{
"id" => id,
"efficiency" => 456.7,
"model" => "some updated model",
"eid" => 42,
"vid" => 42
} = json_response(conn, 200)["data"]
end
test "renders errors when data is invalid", %{conn: conn, car: car} do
conn = put(conn, Routes.car_path(conn, :update, car), car: @invalid_attrs)
assert json_response(conn, 422)["errors"] != %{}
end
end
defp create_car(_) do
car = fixture(:car)
{:ok, car: car}
end
end
+10
View File
@@ -1,3 +1,13 @@
Application.load(:tesla_mate)
for app <- Application.spec(:tesla_mate, :applications) do
{:ok, _} = Application.ensure_all_started(app)
end
# :ok = Application.ensure_all_started(TeslaMate.Repo)
TeslaMate.Repo.start_link()
ExUnit.start()
# Ecto.Adapters.SQL.Sandbox.mode(TeslaMate.Repo, :manual)