diff --git a/config/runtime.exs b/config/runtime.exs index 6b734a78..2df97cc1 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -5,6 +5,9 @@ defmodule Util do :crypto.strong_rand_bytes(length) |> Base.encode64() |> binary_part(0, length) end + def to_integer(nil), do: nil + def to_integer(str), do: String.to_integer(str) + def validate_namespace!(nil), do: nil def validate_namespace!(""), do: nil @@ -96,10 +99,11 @@ config :teslamate, TeslaMateWeb.Endpoint, if System.get_env("DISABLE_MQTT") != "true" or config_env() == :test do config :teslamate, :mqtt, host: Util.fetch_env!("MQTT_HOST", all: "localhost"), + port: System.get_env("MQTT_PORT") |> Util.to_integer(), 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"), + tls: System.get_env("MQTT_TLS") == "true", + accept_invalid_certs: System.get_env("MQTT_TLS_ACCEPT_INVALID_CERTS") == "true", namespace: System.get_env("MQTT_NAMESPACE") |> Util.validate_namespace!(), ipv6: System.get_env("MQTT_IPV6") == "true" end diff --git a/lib/teslamate/application.ex b/lib/teslamate/application.ex index 55f620b9..c3381027 100644 --- a/lib/teslamate/application.ex +++ b/lib/teslamate/application.ex @@ -14,7 +14,7 @@ defmodule TeslaMate.Application do end defp children do - mqtt_enabled? = !is_nil(Application.get_env(:teslamate, :mqtt)) + mqtt_config = Application.get_env(:teslamate, :mqtt) case Application.get_env(:teslamate, :import_directory) do nil -> @@ -27,7 +27,7 @@ defmodule TeslaMate.Application do TeslaMateWeb.Endpoint, TeslaMate.Terrain, TeslaMate.Vehicles, - if(mqtt_enabled?, do: TeslaMate.Mqtt), + if(mqtt_config != nil, do: {TeslaMate.Mqtt, mqtt_config}), TeslaMate.Repair ] |> Enum.reject(&is_nil/1) diff --git a/lib/teslamate/mqtt.ex b/lib/teslamate/mqtt.ex index ca5f206b..3f305bd9 100644 --- a/lib/teslamate/mqtt.ex +++ b/lib/teslamate/mqtt.ex @@ -10,13 +10,13 @@ defmodule TeslaMate.Mqtt do end @impl true - def init(_opts) do + def init(opts) do client_id = generate_client_id() children = [ - {Tortoise.Connection, connection_config() ++ [client_id: client_id]}, + {Tortoise.Connection, connection_config(opts) ++ [client_id: client_id]}, {Publisher, client_id: client_id}, - {PubSub, namespace: namespace()} + {PubSub, namespace: opts[:namespace]} ] Supervisor.init(children, strategy: :one_for_one) @@ -26,49 +26,38 @@ defmodule TeslaMate.Mqtt do alias Tortoise.Transport - defp connection_config do - opts = Application.get_env(:teslamate, :mqtt) - host = Keyword.get(opts, :host) - + defp connection_config(opts) do socket_opts = - if opts[:ipv6] do - [:inet6] - else - [] - end + if opts[:ipv6], + do: [:inet6], + else: [] server = - if Keyword.get(opts, :tls) == "true" do + if opts[:tls] do verify = - if Keyword.get(opts, :accept_invalid_certs) == "true" do - :verify_none - else - :verify_peer - end + if opts[:accept_invalid_certs], + do: :verify_none, + else: :verify_peer {Transport.SSL, - host: host, - port: 8883, + host: opts[:host], + port: opts[:port] || 8883, cacertfile: CAStore.file_path(), verify: verify, opts: socket_opts} else - {Transport.Tcp, host: host, port: 1883, opts: socket_opts} + {Transport.Tcp, host: opts[:host], port: opts[:port] || 1883, opts: socket_opts} end [ - user_name: Keyword.get(opts, :username), - password: Keyword.get(opts, :password), + user_name: opts[:username], + password: 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 diff --git a/lib/teslamate/mqtt/publisher.ex b/lib/teslamate/mqtt/publisher.ex index c1e32427..7cf0b111 100644 --- a/lib/teslamate/mqtt/publisher.ex +++ b/lib/teslamate/mqtt/publisher.ex @@ -30,16 +30,15 @@ defmodule TeslaMate.Mqtt.Publisher do @impl true def handle_call({:publish, topic, msg, opts}, from, %State{client_id: id, refs: refs} = state) do + opts = Keyword.put_new(opts, :timeout, round(@timeout * 0.95)) + case Keyword.get(opts, :qos, 0) do 0 -> - :ok = Tortoise.publish(id, topic, msg, Keyword.put_new(opts, :timeout, @timeout * 0.95)) - + :ok = Tortoise.publish(id, topic, msg, opts) {:reply, :ok, state} _ -> - {:ok, ref} = - Tortoise.publish(id, topic, msg, Keyword.put_new(opts, :timeout, @timeout * 0.95)) - + {:ok, ref} = Tortoise.publish(id, topic, msg, opts) {:noreply, %State{state | refs: Map.put(refs, ref, from)}} end end diff --git a/website/docs/configuration/environment_variables.md b/website/docs/configuration/environment_variables.md index bfec796a..ab0e4558 100644 --- a/website/docs/configuration/environment_variables.md +++ b/website/docs/configuration/environment_variables.md @@ -6,28 +6,29 @@ sidebar_label: Environment Variables TeslaMate accepts the following environment variables for runtime configuration: -| Variable Name | Description | Default Value | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| **DATABASE_USER** | Username (**required**) | | -| **DATABASE_PASS** | User password (**required**) | | -| **DATABASE_NAME** | The database to connect to (**required**) | | -| **DATABASE_HOST** | Hostname of the database server (**required**) | | -| **DATABASE_PORT** | Port of the database server | 5432 | -| **DATABASE_POOL_SIZE** | Size of the database connection pool | 10 | -| **DATABASE_TIMEOUT** | The time in milliseconds to wait for database query calls to finish | 60000 | -| **DATABASE_SSL** | Set to `true` if SSL should be used | false | -| **DATABASE_IPV6** | Set to `true` if IPv6 should be used | false | -| **VIRTUAL_HOST** | Host part used for generating URLs throughout the app | localhost | -| **CHECK_ORIGIN** | Configures whether to check the origin header or not. May be `true` (**recommended**), `false` (_default_) or a comma-separated list of hosts that are allowed (e.g. `https://example.com,//another.com:8080`). Hosts also support wildcards. If `true`, it will check against the host value in `VIRTUAL_HOST`. | false | -| **PORT** | Port where the web interface is exposed | 4000 | -| **HTTP_BINDING_ADDRESS** | IP address where the web interface is exposed, or blank (_default_) meaning all addresses. | | -| **DISABLE_MQTT** | Disables the MQTT feature if `true` | false | -| **MQTT_HOST** | Hostname of the broker (**required** unless DISABLE_MQTT is `true`) | | -| **MQTT_USERNAME** | Username | | -| **MQTT_PASSWORD** | Password | | -| **MQTT_TLS** | Enables TLS if `true` | false | -| **MQTT_TLS_ACCEPT_INVALID_CERTS** | Accepts invalid certificates if `true` | false | -| **MQTT_IPV6** | Set to `true` if IPv6 should be used | false | -| **MQTT_NAMESPACE** | Inserts a custom namespace into the MQTT topic . For example, with `MQTT_NAMESPACE=account_0`: `teslamate/account_0/cars/$car_id/state`. | | -| **IMPORT_DIR** | The path of the directory for the import of data (e.g. TeslaFi) | ./import | -| **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). | | +| Variable Name | Description | Default Value | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | +| **DATABASE_USER** | Username (**required**) | | +| **DATABASE_PASS** | User password (**required**) | | +| **DATABASE_NAME** | The database to connect to (**required**) | | +| **DATABASE_HOST** | Hostname of the database server (**required**) | | +| **DATABASE_PORT** | Port of the database server | 5432 | +| **DATABASE_POOL_SIZE** | Size of the database connection pool | 10 | +| **DATABASE_TIMEOUT** | The time in milliseconds to wait for database query calls to finish | 60000 | +| **DATABASE_SSL** | Set to `true` if SSL should be used | false | +| **DATABASE_IPV6** | Set to `true` if IPv6 should be used | false | +| **VIRTUAL_HOST** | Host part used for generating URLs throughout the app | localhost | +| **CHECK_ORIGIN** | Configures whether to check the origin header or not. May be `true` (**recommended**), `false` (_default_) or a comma-separated list of hosts that are allowed (e.g. `https://example.com,//another.com:8080`). Hosts also support wildcards. If `true`, it will check against the host value in `VIRTUAL_HOST`. | false | +| **PORT** | Port where the web interface is exposed | 4000 | +| **HTTP_BINDING_ADDRESS** | IP address where the web interface is exposed, or blank (_default_) meaning all addresses. | | +| **DISABLE_MQTT** | Disables the MQTT feature if `true` | false | +| **MQTT_HOST** | Hostname of the broker (**required** unless DISABLE_MQTT is `true`) | | +| **MQTT_PORT** | Port of the broker | 1883 (8883 for MQTT ovoer TLS) | +| **MQTT_USERNAME** | Username | | +| **MQTT_PASSWORD** | Password | | +| **MQTT_TLS** | Enables TLS if `true` | false | +| **MQTT_TLS_ACCEPT_INVALID_CERTS** | Accepts invalid certificates if `true` | false | +| **MQTT_IPV6** | Set to `true` if IPv6 should be used | false | +| **MQTT_NAMESPACE** | Inserts a custom namespace into the MQTT topic . For example, with `MQTT_NAMESPACE=account_0`: `teslamate/account_0/cars/$car_id/state`. | | +| **IMPORT_DIR** | The path of the directory for the import of data (e.g. TeslaFi) | ./import | +| **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). | |