mirror of
https://github.com/teslamate-org/teslamate.git
synced 2026-01-24 21:06:08 +08:00
Authenticate and identify vehicles
This commit is contained in:
@@ -73,3 +73,7 @@ config :tesla_mate, TeslaMate.Repo,
|
||||
database: "tesla_mate_dev",
|
||||
hostname: "localhost",
|
||||
pool_size: 10
|
||||
|
||||
config :tesla_mate, :tesla_auth,
|
||||
username: System.get_env("USERNAME"),
|
||||
password: System.get_env("PASSWOrD")
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
defmodule TeslaMate.Api do
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
import Core.Dependency, only: [call: 3]
|
||||
alias TeslaApi.{Auth, Error, Vehicle}
|
||||
|
||||
defstruct auth: nil,
|
||||
deps: %{}
|
||||
|
||||
alias __MODULE__, as: State
|
||||
|
||||
@name __MODULE__
|
||||
|
||||
if Mix.env() === :prod do
|
||||
# API
|
||||
|
||||
def start_link(opts) do
|
||||
GenServer.start_link(__MODULE__, opts, name: Keyword.get(opts, :name, @name))
|
||||
end
|
||||
|
||||
def list_vehicles(name \\ @name) do
|
||||
GenServer.call(name, :list_vehicles)
|
||||
end
|
||||
|
||||
def charge_state(name \\ @name, vehicle_id) do
|
||||
GenServer.call(name, {:charge_state, vehicle_id})
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
username = Keyword.fetch!(opts, :username)
|
||||
password = Keyword.fetch!(opts, :password)
|
||||
|
||||
case Auth.login(username, password) do
|
||||
%Error{error: error, message: reason, env: _env} ->
|
||||
Logger.error("Login failed: #{inspect({error, reason})}")
|
||||
{:stop, {error, reason}}
|
||||
|
||||
%Auth{} = auth ->
|
||||
Logger.info("Login successful")
|
||||
{:ok, %State{auth: auth}, {:continue, :schedule_refresh}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:charge_state, vehicle_id}, _from, state) do
|
||||
case Vehicle.State.charge_state(state.auth, vehicle_id) do
|
||||
%Vehicle.State.Charge{} = charge_state -> {:reply, charge_state, state}
|
||||
%Error{message: reason} -> {:reply, {:error, reason}, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_call(:list_vehicles, _from, state) do
|
||||
case Vehicle.list(state.auth) do
|
||||
vehicles when is_list(vehicles) -> {:reply, {:ok, vehicles}, state}
|
||||
%Error{message: reason} -> {:reply, {:error, reason}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:refresh_auth, %State{auth: auth} = state) do
|
||||
case Auth.refresh(auth) do
|
||||
%Auth{} = auth -> {:noreply, %State{state | auth: auth}, {:continue, :schedule_refresh}}
|
||||
%Error{error: error, message: reason, env: _} -> {:stop, {error, reason}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_continue(:schedule_refresh, state) do
|
||||
Process.send_after(self(), :refresh_auth, :timer.hours(24 * 30))
|
||||
{:noreply, state}
|
||||
end
|
||||
else
|
||||
# API
|
||||
|
||||
def start_link(opts) do
|
||||
GenServer.start_link(__MODULE__, opts, name: Keyword.get(opts, :name, @name))
|
||||
end
|
||||
|
||||
def charge_state(vehicle_id) do
|
||||
{:ok, %Vehicle.State.Charge{}}
|
||||
end
|
||||
|
||||
def list_vehicles do
|
||||
{:ok,
|
||||
[%Vehicle{vehicle_id: 0, display_name: "Tesla!M3", option_codes: ["MDL3", "BT37", "DV4W"]}]}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
{:ok, nil}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,20 +6,14 @@ defmodule TeslaMate.Application do
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
# List all child processes to be supervised
|
||||
children = [
|
||||
# Start the Ecto repository
|
||||
TeslaMate.Repo,
|
||||
# Start the endpoint when the application starts
|
||||
TeslaMateWeb.Endpoint
|
||||
# Starts a worker by calling: TeslaMate.Worker.start_link(arg)
|
||||
# {TeslaMate.Worker, arg},
|
||||
TeslaMateWeb.Endpoint,
|
||||
{TeslaMate.Api, auth()},
|
||||
TeslaMate.Vehicles
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: TeslaMate.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
Supervisor.start_link(children, strategy: :one_for_one, name: TeslaMate.Supervisor)
|
||||
end
|
||||
|
||||
# Tell Phoenix to update the endpoint configuration
|
||||
@@ -28,4 +22,8 @@ defmodule TeslaMate.Application do
|
||||
TeslaMateWeb.Endpoint.config_change(changed, removed)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp auth do
|
||||
Application.get_env(:tesla_mate, :tesla_auth)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
defmodule Core.Dependency do
|
||||
def call({m, a}, fun, args), do: apply(m, fun, [a] ++ args)
|
||||
def call(m, f, a), do: apply(m, f, a)
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
defmodule TeslaMate.Vehicles do
|
||||
use Supervisor
|
||||
|
||||
alias __MODULE__.Vehicle
|
||||
|
||||
@name __MODULE__
|
||||
|
||||
def start_link(opts) do
|
||||
Supervisor.start_link(__MODULE__, opts, name: @name)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
vehicles =
|
||||
Keyword.get_lazy(opts, :vehicles, fn ->
|
||||
{:ok, vehicles} = TeslaMate.Api.list_vehicles()
|
||||
vehicles
|
||||
end)
|
||||
|
||||
children =
|
||||
vehicles
|
||||
|> Enum.map(fn %TeslaApi.Vehicle{} = vehicle ->
|
||||
{Vehicle, vehicle}
|
||||
end)
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one, max_restarts: 5, max_seconds: 60)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
defmodule TeslaMate.Vehicles.Vehicle do
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
alias __MODULE__.Identification
|
||||
|
||||
# import Core.Dependency, only: [call: 3]
|
||||
|
||||
defstruct id: nil,
|
||||
properties: nil,
|
||||
deps: %{}
|
||||
|
||||
alias __MODULE__, as: State
|
||||
|
||||
def start_link(opts) do
|
||||
GenServer.start_link(__MODULE__, opts, [])
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(%TeslaApi.Vehicle{} = vehicle) do
|
||||
properties = Identification.properties(vehicle)
|
||||
|
||||
Logger.info("Found Vehicle '#{vehicle.display_name}' [#{inspect(properties)}]")
|
||||
|
||||
{:ok, %State{id: vehicle.vehicle_id, properties: properties}, {:continue, :init}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_continue(:init, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
# Private
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
defmodule TeslaMate.Vehicles.Vehicle.Identification do
|
||||
def properties(%TeslaApi.Vehicle{} = vehicle) do
|
||||
performance = vehicle.option_codes |> Enum.find(false, &is_performance/1)
|
||||
battery = vehicle.option_codes |> Enum.find_value(&which_battery/1)
|
||||
model = vehicle.option_codes |> Enum.find_value(&which_model/1)
|
||||
awd = vehicle.option_codes |> Enum.member?("DV4W")
|
||||
|
||||
{name, efficiency} = get_efficiency(model, battery, performance, awd)
|
||||
|
||||
%{
|
||||
name: name,
|
||||
model: model,
|
||||
battery: battery,
|
||||
awd: awd,
|
||||
performance: performance,
|
||||
efficiency: efficiency
|
||||
}
|
||||
end
|
||||
|
||||
defp which_model("MDLS"), do: "MS"
|
||||
defp which_model("MS01"), do: "MS"
|
||||
defp which_model("MS02"), do: "MS"
|
||||
defp which_model("MS03"), do: "MS"
|
||||
defp which_model("MDLX"), do: "MX"
|
||||
defp which_model("MDL3"), do: "M3"
|
||||
defp which_model(______), do: false
|
||||
|
||||
defp is_performance("PX01"), do: true
|
||||
defp is_performance("P85D"), do: true
|
||||
defp is_performance("PX6D"), do: true
|
||||
defp is_performance("X024"), do: true
|
||||
defp is_performance("PBT8"), do: true
|
||||
defp is_performance(______), do: nil
|
||||
|
||||
defp which_battery("BT" <> _ = battery), do: battery
|
||||
defp which_battery(_), do: nil
|
||||
|
||||
defp get_efficiency("MS", "BTX5", _, true), do: {"S 75D", 0.186}
|
||||
defp get_efficiency("MS", "BTX5", _, false), do: {"S 75", 0.185}
|
||||
defp get_efficiency("MS", "BTX4", true, _), do: {"S P90D", 0.200}
|
||||
defp get_efficiency("MS", "BTX4", false, _), do: {"S 90D", 0.189}
|
||||
defp get_efficiency("MS", "BTX6", true, _), do: {"S P100D", 0.200}
|
||||
defp get_efficiency("MS", "BTX6", false, _), do: {"S 100D", 0.189}
|
||||
defp get_efficiency("MS", "BTX8", _, true), do: {"S 75D (85kWh)", 0.186}
|
||||
defp get_efficiency("MS", "BTX8", _, false), do: {"S 75 (85kWh)", 0.185}
|
||||
defp get_efficiency("MS", "BT85", _, _), do: {"S 85 ?", 0.200}
|
||||
defp get_efficiency("MS", "BT70", _, _), do: {"S 70 ?", 0.200}
|
||||
defp get_efficiency("MS", "BT60", _, _), do: {"S 60 ?", 0.200}
|
||||
defp get_efficiency("MS", _, _, _), do: {"S ???", 0.200}
|
||||
|
||||
defp get_efficiency("MX", "BTX5", _, _), do: {"X 75D", 0.208}
|
||||
defp get_efficiency("MX", "BTX4", true, _), do: {"X 90D", 0.208}
|
||||
defp get_efficiency("MX", "BTX4", false, _), do: {"X P90D", 0.217}
|
||||
defp get_efficiency("MX", "BTX6", true, _), do: {"X P100D", 0.226}
|
||||
defp get_efficiency("MX", "BTX6", false, _), do: {"X 100D", 0.208}
|
||||
defp get_efficiency("MX", _, _, _), do: {"X ???", 0.208}
|
||||
|
||||
defp get_efficiency("M3", "BT37", _, _), do: {"M3 LR", 0.153}
|
||||
defp get_efficiency("M3", _, _, _), do: {"M3 ???", 0.153}
|
||||
|
||||
defp get_efficiency(_, _, _, _), do: {"???", nil}
|
||||
end
|
||||
@@ -33,6 +33,7 @@ defmodule TeslaMate.MixProject do
|
||||
# Type `mix help deps` for examples and options.
|
||||
defp deps do
|
||||
[
|
||||
# Default
|
||||
{:phoenix, "~> 1.4.3"},
|
||||
{:phoenix_pubsub, "~> 1.1"},
|
||||
{:phoenix_ecto, "~> 4.0"},
|
||||
@@ -43,6 +44,8 @@ defmodule TeslaMate.MixProject do
|
||||
{:gettext, "~> 0.11"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:plug_cowboy, "~> 2.0"},
|
||||
# Custom
|
||||
{:tesla_api, git: "https://github.com/mgwidmann/tesla_api"}
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
%{
|
||||
"certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
||||
"cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"},
|
||||
"db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"},
|
||||
"ecto": {:hex, :ecto, "3.0.8", "9eb6a1fcfc593e6619d45ef51afe607f1554c21ca188a1cd48eecc27223567f1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"},
|
||||
"gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm"},
|
||||
"hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
|
||||
"phoenix": {:hex, :phoenix, "1.4.3", "8eed4a64ff1e12372cd634724bddd69185938f52c18e1396ebac76375d85677d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "2.13.2", "f5d27c9b10ce881a60177d2b5227314fc60881e6b66b41dfe3349db6ed06cf57", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.0", "3bb31a9fbd40ffe8652e60c8660dffd72dd231efcdf49b744fb75b9ef7db5dd2", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
|
||||
"plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.0.2", "6055f16868cc4882b24b6e1d63d2bada94fb4978413377a3b32ac16c18dffba2", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
||||
"postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||
"telemetry": {:hex, :telemetry, "0.3.0", "099a7f3ce31e4780f971b4630a3c22ec66d22208bc090fe33a2a3a6a67754a73", [:rebar3], [], "hexpm"},
|
||||
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"tesla_api": {:git, "https://github.com/mgwidmann/tesla_api", "15001942824940b808b6d67672d2b47bf2297623", []},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
||||
}
|
||||
Reference in New Issue
Block a user