mirror of
https://github.com/teslamate-org/teslamate.git
synced 2026-01-24 21:06:08 +08:00
Support multiple cars & overhaul table structure
This commit is contained in:
+15
-4
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
+4
-1
@@ -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
|
||||
+1
-2
@@ -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
-2
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -24,9 +24,6 @@ defmodule TeslaMate.VehicleCase do
|
||||
opts}
|
||||
)
|
||||
|
||||
assert_receive :close_charging_state
|
||||
assert_receive :close_drive_state
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user