mirror of
https://github.com/netfun2000/hipudding-teslamate.git
synced 2026-02-27 09:44:28 +08:00
Add option use custom namespace for MQTT topics
This commit is contained in:
@@ -9,6 +9,16 @@ defmodule Util do
|
||||
def validate_locale!("de"), do: "de"
|
||||
def validate_locale!(lang), do: raise("Unsupported locale: #{inspect(lang)}")
|
||||
|
||||
def validate_namespace!(nil), do: nil
|
||||
def validate_namespace!(""), do: nil
|
||||
|
||||
def validate_namespace!(ns) when is_binary(ns) do
|
||||
case String.contains?(ns, "/") do
|
||||
true -> raise "MQTT_NAMESPACE must not contain '/'"
|
||||
false -> ns
|
||||
end
|
||||
end
|
||||
|
||||
def parse_check_origin!("true"), do: true
|
||||
def parse_check_origin!("false"), do: false
|
||||
def parse_check_origin!(hosts) when is_binary(hosts), do: String.split(hosts, ",")
|
||||
@@ -40,7 +50,8 @@ if System.get_env("DISABLE_MQTT") != "true" do
|
||||
username: System.get_env("MQTT_USERNAME"),
|
||||
password: System.get_env("MQTT_PASSWORD"),
|
||||
tls: System.get_env("MQTT_TLS"),
|
||||
accept_invalid_certs: System.get_env("MQTT_TLS_ACCEPT_INVALID_CERTS")
|
||||
accept_invalid_certs: System.get_env("MQTT_TLS_ACCEPT_INVALID_CERTS"),
|
||||
namespace: System.get_env("MQTT_NAMESPACE") |> Util.validate_namespace!()
|
||||
end
|
||||
|
||||
config :logger,
|
||||
|
||||
@@ -19,5 +19,6 @@ TeslaMate accepts the following environment variables for runtime configuration:
|
||||
| **MQTT_PASSWORD** | Password _(optional)_ | |
|
||||
| **MQTT_TLS** | Enables TLS if `true` _(optional)_ | false |
|
||||
| **MQTT_TLS_ACCEPT_INVALID_CERTS** | Accepts invalid certificates if `true` _(optional)_ | false |
|
||||
| **MQTT_NAMESPACE** | Inserts a custom namespace into the MQTT topic _(optional)_. For example, with `MQTT_NAMESPACE=account_0`: `teslamate/account_0/cars/$car_id/state`. | |
|
||||
| **LOCALE** | The default locale for the web interface and addresses. Currently available: `en` (default) and `de` | en |
|
||||
| **TZ** | Used to establish the local time zone, e.g. to use the local time in logs. See [List of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | |
|
||||
|
||||
@@ -14,9 +14,9 @@ defmodule TeslaMate.Mqtt do
|
||||
client_id = generate_client_id()
|
||||
|
||||
children = [
|
||||
{Tortoise.Connection, config() ++ [client_id: client_id]},
|
||||
{Tortoise.Connection, connection_config() ++ [client_id: client_id]},
|
||||
{Publisher, client_id: client_id},
|
||||
PubSub
|
||||
{PubSub, namespace: namespace()}
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
@@ -26,14 +26,14 @@ defmodule TeslaMate.Mqtt do
|
||||
|
||||
alias Tortoise.Transport
|
||||
|
||||
defp config do
|
||||
auth = Application.get_env(:teslamate, :mqtt)
|
||||
host = Keyword.get(auth, :host)
|
||||
defp connection_config do
|
||||
opts = Application.get_env(:teslamate, :mqtt)
|
||||
host = Keyword.get(opts, :host)
|
||||
|
||||
server =
|
||||
if Keyword.get(auth, :tls) == "true" do
|
||||
if Keyword.get(opts, :tls) == "true" do
|
||||
verify =
|
||||
if Keyword.get(auth, :accept_invalid_certs) == "true" do
|
||||
if Keyword.get(opts, :accept_invalid_certs) == "true" do
|
||||
:verify_none
|
||||
else
|
||||
:verify_peer
|
||||
@@ -45,14 +45,18 @@ defmodule TeslaMate.Mqtt do
|
||||
end
|
||||
|
||||
[
|
||||
user_name: Keyword.get(auth, :username),
|
||||
password: Keyword.get(auth, :password),
|
||||
user_name: Keyword.get(opts, :username),
|
||||
password: Keyword.get(opts, :password),
|
||||
server: server,
|
||||
handler: {Handler, []},
|
||||
subscriptions: []
|
||||
]
|
||||
end
|
||||
|
||||
defp namespace do
|
||||
Application.get_env(:teslamate, :mqtt) |> Keyword.get(:namespace)
|
||||
end
|
||||
|
||||
defp generate_client_id do
|
||||
"TESLAMATE_" <> (:rand.uniform() |> to_string() |> Base.encode16() |> String.slice(0..10))
|
||||
end
|
||||
|
||||
@@ -11,10 +11,10 @@ defmodule TeslaMate.Mqtt.PubSub do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
def init(opts) do
|
||||
children =
|
||||
Vehicles.list()
|
||||
|> Enum.map(&{VehicleSubscriber, car_id: &1.car.id})
|
||||
|> Enum.map(&{VehicleSubscriber, Keyword.merge(opts, car_id: &1.car.id)})
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
|
||||
alias TeslaMate.Vehicles.Vehicle.Summary
|
||||
alias TeslaMate.Vehicles
|
||||
|
||||
defstruct [:car_id, :last_summary, :deps]
|
||||
defstruct [:car_id, :last_summary, :deps, :namespace]
|
||||
alias __MODULE__, as: State
|
||||
|
||||
def child_spec(arg) do
|
||||
@@ -25,6 +25,7 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
|
||||
@impl true
|
||||
def init(opts) do
|
||||
car_id = Keyword.fetch!(opts, :car_id)
|
||||
namespace = Keyword.fetch!(opts, :namespace)
|
||||
|
||||
deps = %{
|
||||
vehicles: Keyword.get(opts, :deps_vehicles, Vehicles),
|
||||
@@ -33,7 +34,7 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
|
||||
|
||||
:ok = call(deps.vehicles, :subscribe_to_summary, [car_id])
|
||||
|
||||
{:ok, %State{car_id: car_id, deps: deps}}
|
||||
{:ok, %State{car_id: car_id, namespace: namespace, deps: deps}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -65,12 +66,13 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
|
||||
{:noreply, %State{state | last_summary: summary}}
|
||||
end
|
||||
|
||||
defp publish({key, value}, %State{car_id: car_id, deps: deps}) do
|
||||
call(deps.publisher, :publish, [
|
||||
"teslamate/cars/#{car_id}/#{key}",
|
||||
to_str(value),
|
||||
[retain: true, qos: 1]
|
||||
])
|
||||
defp publish({key, value}, %State{car_id: car_id, namespace: namespace, deps: deps}) do
|
||||
topic =
|
||||
["teslamate", namespace, "cars", car_id, key]
|
||||
|> Enum.reject(&is_nil(&1))
|
||||
|> Enum.join("/")
|
||||
|
||||
call(deps.publisher, :publish, [topic, to_str(value), [retain: true, qos: 1]])
|
||||
end
|
||||
|
||||
defp to_str(%DateTime{} = datetime), do: DateTime.to_iso8601(datetime)
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriberTest do
|
||||
alias TeslaMate.Mqtt.PubSub.VehicleSubscriber
|
||||
alias TeslaMate.Vehicles.Vehicle.Summary
|
||||
|
||||
defp start_subscriber(name, car_id) do
|
||||
defp start_subscriber(name, car_id, namespace \\ nil) do
|
||||
publisher_name = :"mqtt_publisher_#{name}"
|
||||
vehicles_name = :"vehicles_#{name}"
|
||||
|
||||
@@ -16,6 +16,7 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriberTest do
|
||||
[
|
||||
name: name,
|
||||
car_id: car_id,
|
||||
namespace: namespace,
|
||||
deps_publisher: {MqttPublisherMock, publisher_name},
|
||||
deps_vehicles: {VehiclesMock, vehicles_name}
|
||||
]}
|
||||
@@ -88,6 +89,7 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriberTest do
|
||||
summary = %Summary{
|
||||
plugged_in: false,
|
||||
battery_level: 60.0,
|
||||
usable_battery_level: 59,
|
||||
charge_energy_added: 25,
|
||||
charge_limit_soc: 90,
|
||||
charge_port_door_open: false,
|
||||
@@ -124,4 +126,42 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriberTest do
|
||||
|
||||
refute_receive _
|
||||
end
|
||||
|
||||
test "allows namespaces", %{test: name} do
|
||||
{:ok, pid} = start_subscriber(name, 0, "account_0")
|
||||
|
||||
assert_receive {VehiclesMock, {:subscribe_to_summary, 0}}
|
||||
|
||||
summary = %Summary{
|
||||
display_name: "Foo",
|
||||
state: :online
|
||||
}
|
||||
|
||||
send(pid, summary)
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/account_0/cars/0/display_name", "Foo",
|
||||
[retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/account_0/cars/0/state", "online",
|
||||
[retain: true, qos: 1]}}
|
||||
|
||||
# Always published
|
||||
for key <- [
|
||||
:charge_energy_added,
|
||||
:charger_actual_current,
|
||||
:charger_phases,
|
||||
:charger_power,
|
||||
:charger_voltage,
|
||||
:scheduled_charging_start_time,
|
||||
:time_to_full_charge,
|
||||
:shift_state
|
||||
] do
|
||||
topic = "teslamate/account_0/cars/0/#{key}"
|
||||
assert_receive {MqttPublisherMock, {:publish, ^topic, "", [retain: true, qos: 1]}}
|
||||
end
|
||||
|
||||
refute_receive _
|
||||
end
|
||||
end
|
||||
|
||||
@@ -198,16 +198,18 @@ defmodule TeslaMateWeb.CarLive.SummaryTest do
|
||||
[view] = children(parent_view)
|
||||
render_click(view, :suspend_logging)
|
||||
|
||||
assert html = render(view)
|
||||
assert html =~ table_row("Status", "falling asleep")
|
||||
TestHelper.eventually(fn ->
|
||||
assert html = render(view)
|
||||
assert html =~ table_row("Status", "falling asleep")
|
||||
|
||||
assert "cancel sleep attempt" ==
|
||||
html |> Floki.find("a[phx-click=resume_logging]") |> Floki.text()
|
||||
assert "cancel sleep attempt" ==
|
||||
html |> Floki.find("a[phx-click=resume_logging]") |> Floki.text()
|
||||
|
||||
render_click(view, :resume_logging)
|
||||
render_click(view, :resume_logging)
|
||||
|
||||
assert html = render(view)
|
||||
assert html =~ table_row("Status", "online")
|
||||
assert html = render(view)
|
||||
assert html =~ table_row("Status", "online")
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user