Compare commits
6 commits
rewrite/gl
...
trunk
Author | SHA1 | Date | |
---|---|---|---|
9484ed16a3 | |||
b4d7f653ca | |||
82bb4ff253 | |||
d7a05f3b1f | |||
bcba4a92b3 | |||
f469796260 |
31 changed files with 737 additions and 374 deletions
|
@ -1,3 +1,3 @@
|
|||
erlang 25.0.4
|
||||
elixir 1.13.4-otp-25
|
||||
gleam 0.25.3
|
||||
elixir 1.14.3-otp-25
|
||||
gleam 0.26.2
|
||||
|
|
|
@ -77,6 +77,13 @@ config :geo_therminator,
|
|||
api_device_temp_set_reg_index: 3,
|
||||
api_device_reg_set_client_id: get_env("API_DEVICE_REG_SET_CLIENT_ID"),
|
||||
api_refresh: 10_000,
|
||||
b2c_client_id: get_env("B2C_CLIENT_ID", "09ea4903-9e95-45fe-ae1f-e3b7d32fa385"),
|
||||
b2c_redirect_url: get_env("B2C_REDIRECT_URL", "https://online-genesis.thermia.se/login"),
|
||||
b2c_auth_url:
|
||||
get_env(
|
||||
"B2C_AUTH_URL",
|
||||
"https://thermialogin.b2clogin.com/thermialogin.onmicrosoft.com/b2c_1a_signuporsigninonline"
|
||||
),
|
||||
|
||||
# Database directory for settings
|
||||
db_dir:
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
name = "geo_therminator"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
target = "erlang"
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = "~> 0.25"
|
||||
gleam_http = "~> 3.1"
|
||||
gleam_hackney = "~> 0.2.1"
|
||||
gleam_erlang = "~> 0.17.1"
|
||||
gleam_json = "~> 0.5.0"
|
||||
finch_gleam = "~> 1.0"
|
||||
|
|
|
@ -75,7 +75,7 @@ defmodule GeoTherminator.PumpAPI.Auth.Server do
|
|||
schedule_token_check()
|
||||
|
||||
if diff < @token_check_diff do
|
||||
Logger.debug("Renewing auth token since #{diff} < #{@token_check_diff}")
|
||||
Logger.info("Renewing auth token since #{diff} < #{@token_check_diff}")
|
||||
|
||||
case init_state(state.username, state.password) do
|
||||
{:ok, new_state} -> {:noreply, new_state}
|
||||
|
@ -121,7 +121,9 @@ defmodule GeoTherminator.PumpAPI.Auth.Server do
|
|||
|
||||
{:ok, state}
|
||||
else
|
||||
_ -> :error
|
||||
err ->
|
||||
Logger.error("Could not auth or fetch installations! #{inspect(err)}")
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,11 +8,5 @@ defmodule GeoTherminator.PumpAPI.Auth.Tokens do
|
|||
:refresh_token_expiry
|
||||
])
|
||||
|
||||
@type t ::
|
||||
record(:record,
|
||||
access_token: String.t(),
|
||||
access_token_expiry: DateTime.t(),
|
||||
refresh_token: String.t(),
|
||||
refresh_token_expiry: DateTime.t()
|
||||
)
|
||||
@type t :: :pump_api@auth@tokens.tokens()
|
||||
end
|
||||
|
|
|
@ -3,5 +3,5 @@ defmodule GeoTherminator.PumpAPI.Auth.User do
|
|||
|
||||
Record.defrecord(:record, :user, [:tokens])
|
||||
|
||||
@type t :: record(:record, tokens: GeoTherminator.PumpAPI.Auth.Tokens.t())
|
||||
@type t :: :pump_api@auth@user.user()
|
||||
end
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
defmodule GeoTherminator.PumpAPI.Device.API do
|
||||
require GeoTherminator.PumpAPI.Auth.InstallationInfo
|
||||
|
||||
alias GeoTherminator.PumpAPI.HTTP
|
||||
alias GeoTherminator.PumpAPI.Device
|
||||
alias GeoTherminator.PumpAPI.Auth
|
||||
|
||||
@spec device_info(Auth.User.t(), Auth.InstallationInfo.t()) :: Device.t()
|
||||
def device_info(user, installation) do
|
||||
url =
|
||||
Application.get_env(:geo_therminator, :api_device_url)
|
||||
|> String.replace("{id}", to_string(Auth.InstallationInfo.record(installation, :id)))
|
||||
|
||||
req = HTTP.authed_req(user, :get, url)
|
||||
|
||||
{:ok, response} = Finch.request(req, HTTP)
|
||||
json = Jason.decode!(response.body)
|
||||
|
||||
last_online = NaiveDateTime.from_iso8601!(json["lastOnline"])
|
||||
created_when = NaiveDateTime.from_iso8601!(json["createdWhen"])
|
||||
|
||||
%Device{
|
||||
id: json["id"],
|
||||
device_id: json["deviceId"],
|
||||
is_online: json["isOnline"],
|
||||
last_online: last_online,
|
||||
created_when: created_when,
|
||||
mac_address: json["macAddress"],
|
||||
name: json["name"],
|
||||
model: json["model"],
|
||||
retailer_access: json["retailerAccess"]
|
||||
}
|
||||
end
|
||||
|
||||
@spec status(Auth.User.t(), Device.t()) :: Device.Status.t()
|
||||
def status(user, device) do
|
||||
url =
|
||||
Application.get_env(:geo_therminator, :api_device_status_url)
|
||||
|> String.replace("{id}", to_string(device.id))
|
||||
|
||||
req = HTTP.authed_req(user, :get, url)
|
||||
|
||||
{:ok, response} = Finch.request(req, HTTP)
|
||||
json = Jason.decode!(response.body)
|
||||
|
||||
%Device.Status{
|
||||
heating_effect: json["heatingEffect"],
|
||||
is_heating_effect_set_by_user: json["isHeatingEffectSetByUser"]
|
||||
}
|
||||
end
|
||||
|
||||
@spec register_info(Auth.User.t(), Device.t()) :: Device.RegisterCollection.t()
|
||||
def register_info(user, device) do
|
||||
url =
|
||||
Application.get_env(:geo_therminator, :api_device_register_url)
|
||||
|> String.replace("{id}", to_string(device.id))
|
||||
|
||||
req = HTTP.authed_req(user, :get, url)
|
||||
|
||||
{:ok, response} = Finch.request(req, HTTP)
|
||||
json = Jason.decode!(response.body)
|
||||
|
||||
registers =
|
||||
Enum.map(json, fn item ->
|
||||
{:ok, timestamp, _} = DateTime.from_iso8601(item["timeStamp"])
|
||||
|
||||
%Device.Register{
|
||||
register_name: item["registerName"],
|
||||
register_value: item["registerValue"],
|
||||
timestamp: timestamp
|
||||
}
|
||||
end)
|
||||
|
||||
%Device.RegisterCollection{
|
||||
outdoor_temp: find_register(registers, "REG_OUTDOOR_TEMPERATURE"),
|
||||
supply_out: find_register(registers, "REG_SUPPLY_LINE"),
|
||||
supply_in: find_register(registers, "REG_OPER_DATA_RETURN"),
|
||||
desired_supply: find_register(registers, "REG_DESIRED_SYS_SUPPLY_LINE_TEMP"),
|
||||
brine_out: find_register(registers, "REG_BRINE_OUT"),
|
||||
brine_in: find_register(registers, "REG_BRINE_IN"),
|
||||
hot_water_temp: find_register(registers, "REG_HOT_WATER_TEMPERATURE")
|
||||
}
|
||||
end
|
||||
|
||||
@spec opstat(Auth.User.t(), Device.t()) :: Device.OpStat.t()
|
||||
def opstat(user, device) do
|
||||
url =
|
||||
Application.get_env(:geo_therminator, :api_device_opstat_url)
|
||||
|> String.replace("{id}", to_string(device.id))
|
||||
|
||||
req = HTTP.authed_req(user, :get, url)
|
||||
|
||||
{:ok, response} = Finch.request(req, HTTP)
|
||||
json = Jason.decode!(response.body)
|
||||
|
||||
registers =
|
||||
Enum.map(json, fn item ->
|
||||
{:ok, timestamp, _} = DateTime.from_iso8601(item["timeStamp"])
|
||||
|
||||
%Device.Register{
|
||||
register_name: item["registerName"],
|
||||
register_value: item["registerValue"],
|
||||
timestamp: timestamp
|
||||
}
|
||||
end)
|
||||
|
||||
priority_register = find_register(registers, "REG_OPERATIONAL_STATUS_PRIO1")
|
||||
|
||||
priority_register_fallback =
|
||||
find_register(registers, "REG_OPERATIONAL_STATUS_PRIORITY_BITMASK")
|
||||
|
||||
{mapping, register} =
|
||||
if not is_nil(priority_register) do
|
||||
{Application.get_env(:geo_therminator, :api_opstat_mapping), priority_register}
|
||||
else
|
||||
{Application.get_env(:geo_therminator, :api_opstat_bitmask_mapping),
|
||||
priority_register_fallback}
|
||||
end
|
||||
|
||||
%Device.OpStat{
|
||||
priority: Map.get(mapping, register.register_value, :unknown)
|
||||
}
|
||||
end
|
||||
|
||||
@spec set_temp(Auth.User.t(), Device.t(), integer()) :: :ok | {:error, String.t()}
|
||||
def set_temp(user, device, temp) do
|
||||
url =
|
||||
Application.get_env(:geo_therminator, :api_device_reg_set_url)
|
||||
|> String.replace("{id}", to_string(device.id))
|
||||
|
||||
register_index = Application.get_env(:geo_therminator, :api_device_temp_set_reg_index)
|
||||
client_id = Application.get_env(:geo_therminator, :api_device_reg_set_client_id)
|
||||
|
||||
req =
|
||||
HTTP.authed_req(user, :post, url, [], %{
|
||||
registerIndex: register_index,
|
||||
registerValue: temp,
|
||||
clientUuid: client_id
|
||||
})
|
||||
|
||||
IO.inspect(req)
|
||||
{:ok, response} = Finch.request(req, HTTP)
|
||||
|
||||
if response.status == 200 do
|
||||
:ok
|
||||
else
|
||||
{:error, "Error #{response.status}: " <> response.body}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_register(registers, name) do
|
||||
Enum.find(registers, &(&1.register_name == name))
|
||||
end
|
||||
end
|
|
@ -1,61 +1,50 @@
|
|||
defmodule GeoTherminator.PumpAPI.Device do
|
||||
import GeoTherminator.TypedStruct
|
||||
require Record
|
||||
|
||||
deftypedstruct(%{
|
||||
id: integer(),
|
||||
device_id: integer(),
|
||||
is_online: boolean(),
|
||||
last_online: NaiveDateTime.t(),
|
||||
created_when: NaiveDateTime.t(),
|
||||
mac_address: String.t(),
|
||||
name: String.t(),
|
||||
model: String.t(),
|
||||
retailer_access: integer()
|
||||
})
|
||||
Record.defrecord(:record, :device, [
|
||||
:id,
|
||||
:device_id,
|
||||
:is_online,
|
||||
:last_online,
|
||||
:created_when,
|
||||
:mac_address,
|
||||
:name,
|
||||
:model,
|
||||
:retailer_access
|
||||
])
|
||||
|
||||
@type t :: :pump_api@device.device()
|
||||
|
||||
defmodule Status do
|
||||
deftypedstruct(%{
|
||||
heating_effect: integer(),
|
||||
is_heating_effect_set_by_user: boolean()
|
||||
})
|
||||
Record.defrecord(:record, :status, [:heating_effect, :is_heating_effect_set_by_user])
|
||||
|
||||
@type t :: :pump_api@device.status()
|
||||
end
|
||||
|
||||
defmodule Register do
|
||||
deftypedstruct(%{
|
||||
register_name: String.t(),
|
||||
register_value: integer(),
|
||||
timestamp: DateTime.t()
|
||||
})
|
||||
Record.defrecord(:record, :register, [:name, :value, :timestamp])
|
||||
|
||||
@type t :: :pump_api@device.register()
|
||||
end
|
||||
|
||||
defmodule RegisterCollection do
|
||||
deftypedstruct(%{
|
||||
outdoor_temp: Register.t(),
|
||||
supply_out: Register.t(),
|
||||
supply_in: Register.t(),
|
||||
desired_supply: Register.t(),
|
||||
brine_out: Register.t(),
|
||||
brine_in: Register.t(),
|
||||
hot_water_temp: Register.t()
|
||||
})
|
||||
Record.defrecord(:record, :register_collection, [
|
||||
:outdoor_temp,
|
||||
:supply_out,
|
||||
:supply_in,
|
||||
:desired_supply,
|
||||
:brine_out,
|
||||
:brine_in,
|
||||
:hot_water_temp
|
||||
])
|
||||
|
||||
@type t :: :pump_api@device.register_collection()
|
||||
end
|
||||
|
||||
defmodule OpStat do
|
||||
deftypedstruct(%{
|
||||
priority:
|
||||
:hand_operated
|
||||
| :hot_water
|
||||
| :heating
|
||||
| :active_cooling
|
||||
| :pool
|
||||
| :anti_legionella
|
||||
| :passive_cooling
|
||||
| :standby
|
||||
| :idle
|
||||
| :off
|
||||
| :defrost
|
||||
| :unknown
|
||||
})
|
||||
Record.defrecord(:record, :op_stat, [:priority])
|
||||
|
||||
@type t :: :pump_api@device.op_stat()
|
||||
end
|
||||
|
||||
@spec get_device_process(GenServer.name(), GeoTherminator.PumpAPI.Auth.InstallationInfo.t()) ::
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
defmodule GeoTherminator.PumpAPI.Device.PubSub do
|
||||
require GeoTherminator.PumpAPI.Auth.InstallationInfo
|
||||
require GeoTherminator.PumpAPI.Device
|
||||
|
||||
alias Phoenix.PubSub
|
||||
alias GeoTherminator.PumpAPI.Auth.InstallationInfo
|
||||
alias GeoTherminator.PumpAPI.Device
|
||||
|
||||
@installation_topic "installation:"
|
||||
|
||||
|
@ -20,7 +22,7 @@ defmodule GeoTherminator.PumpAPI.Device.PubSub do
|
|||
:ok =
|
||||
PubSub.broadcast!(
|
||||
__MODULE__,
|
||||
@installation_topic <> to_string(device.id),
|
||||
@installation_topic <> to_string(Device.record(device, :id)),
|
||||
{:device, device}
|
||||
)
|
||||
end
|
||||
|
@ -33,7 +35,7 @@ defmodule GeoTherminator.PumpAPI.Device.PubSub do
|
|||
:ok =
|
||||
PubSub.broadcast!(
|
||||
__MODULE__,
|
||||
@installation_topic <> to_string(device.id),
|
||||
@installation_topic <> to_string(Device.record(device, :id)),
|
||||
{:status, status}
|
||||
)
|
||||
end
|
||||
|
@ -46,7 +48,7 @@ defmodule GeoTherminator.PumpAPI.Device.PubSub do
|
|||
:ok =
|
||||
PubSub.broadcast!(
|
||||
__MODULE__,
|
||||
@installation_topic <> to_string(device.id),
|
||||
@installation_topic <> to_string(Device.record(device, :id)),
|
||||
{:registers, registers}
|
||||
)
|
||||
end
|
||||
|
@ -59,7 +61,7 @@ defmodule GeoTherminator.PumpAPI.Device.PubSub do
|
|||
:ok =
|
||||
PubSub.broadcast!(
|
||||
__MODULE__,
|
||||
@installation_topic <> to_string(device.id),
|
||||
@installation_topic <> to_string(Device.record(device, :id)),
|
||||
{:opstat, opstat}
|
||||
)
|
||||
end
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
defmodule GeoTherminator.PumpAPI.Device.Server do
|
||||
require Logger
|
||||
require GeoTherminator.PumpAPI.Auth.InstallationInfo
|
||||
|
||||
use GenServer
|
||||
import GeoTherminator.TypedStruct
|
||||
alias GeoTherminator.PumpAPI.Device
|
||||
alias GeoTherminator.PumpAPI.Auth.InstallationInfo
|
||||
alias GeoTherminator.PumpAPI.Auth.Server, as: AuthServer
|
||||
require Logger
|
||||
|
||||
defmodule Options do
|
||||
deftypedstruct(%{
|
||||
|
@ -26,7 +28,7 @@ defmodule GeoTherminator.PumpAPI.Device.Server do
|
|||
@spec start_link(Options.t()) :: GenServer.on_start()
|
||||
def start_link(opts) do
|
||||
GenServer.start_link(__MODULE__, opts,
|
||||
name: {:via, Registry, {Device.Registry, opts.installation.id}}
|
||||
name: {:via, Registry, {Device.Registry, InstallationInfo.record(opts.installation, :id)}}
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -38,8 +40,7 @@ defmodule GeoTherminator.PumpAPI.Device.Server do
|
|||
@impl true
|
||||
def handle_continue({:init, installation}, state) do
|
||||
user = AuthServer.get_auth(state.auth_server)
|
||||
device = Device.API.device_info(user, installation)
|
||||
|
||||
{:ok, device} = :pump_api@device@api.device_info(user, installation)
|
||||
:ok = Device.PubSub.broadcast_device(device)
|
||||
|
||||
state =
|
||||
|
@ -73,7 +74,7 @@ defmodule GeoTherminator.PumpAPI.Device.Server do
|
|||
user = AuthServer.get_auth(state.auth_server)
|
||||
|
||||
Logger.debug("Begin set temp to #{temp}")
|
||||
resp = Device.API.set_temp(user, state.device, temp)
|
||||
resp = :pump_api@device@api.set_temp(user, state.device, temp)
|
||||
Logger.debug("Set temp result: #{inspect(resp)}")
|
||||
{:reply, resp, state}
|
||||
end
|
||||
|
@ -117,11 +118,15 @@ defmodule GeoTherminator.PumpAPI.Device.Server do
|
|||
|
||||
[status, registers, opstat] =
|
||||
Task.async_stream(
|
||||
[&Device.API.status/2, &Device.API.register_info/2, &Device.API.opstat/2],
|
||||
[
|
||||
&:pump_api@device@api.status/2,
|
||||
&:pump_api@device@api.register_info/2,
|
||||
&:pump_api@device@api.opstat/2
|
||||
],
|
||||
& &1.(user, state.device),
|
||||
timeout: Application.get_env(:geo_therminator, :api_timeout)
|
||||
timeout: Application.fetch_env!(:geo_therminator, :api_timeout)
|
||||
)
|
||||
|> Enum.map(fn {:ok, val} -> val end)
|
||||
|> Enum.map(fn {:ok, {:ok, val}} -> val end)
|
||||
|
||||
Device.PubSub.broadcast_status(state.device, status)
|
||||
Device.PubSub.broadcast_registers(state.device, registers)
|
||||
|
@ -133,7 +138,7 @@ defmodule GeoTherminator.PumpAPI.Device.Server do
|
|||
Process.send_after(
|
||||
self(),
|
||||
:refresh_status,
|
||||
Application.get_env(:geo_therminator, :api_refresh)
|
||||
Application.fetch_env!(:geo_therminator, :api_refresh)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,19 +1,54 @@
|
|||
defmodule GeoTherminatorWeb.Components.MainView do
|
||||
require GeoTherminator.PumpAPI.Device
|
||||
require GeoTherminator.PumpAPI.Device.Status
|
||||
require GeoTherminator.PumpAPI.Device.RegisterCollection
|
||||
require GeoTherminator.PumpAPI.Device.OpStat
|
||||
require GeoTherminator.PumpAPI.Device.Register
|
||||
|
||||
use GeoTherminatorWeb, :live_component
|
||||
|
||||
alias GeoTherminator.PumpAPI.Device
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
priority_set =
|
||||
Device.OpStat.record(assigns.opstat, :priority) |> elem(1) |> Map.keys() |> MapSet.new()
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
set_temp: assigns.status.heating_effect,
|
||||
set_temp_active: assigns.status.is_heating_effect_set_by_user,
|
||||
hot_water_temp: assigns.registers.hot_water_temp.register_value,
|
||||
brine_out: assigns.registers.brine_out.register_value,
|
||||
brine_in: assigns.registers.brine_in.register_value,
|
||||
supply_out: assigns.registers.supply_out.register_value,
|
||||
supply_in: assigns.registers.supply_in.register_value,
|
||||
outdoor_temp: assigns.registers.outdoor_temp.register_value,
|
||||
priority: assigns.opstat.priority
|
||||
set_temp: Device.Status.record(assigns.status, :heating_effect),
|
||||
set_temp_active: Device.Status.record(assigns.status, :is_heating_effect_set_by_user),
|
||||
hot_water_temp:
|
||||
Device.Register.record(
|
||||
Device.RegisterCollection.record(assigns.registers, :hot_water_temp),
|
||||
:value
|
||||
),
|
||||
brine_out:
|
||||
Device.Register.record(
|
||||
Device.RegisterCollection.record(assigns.registers, :brine_out),
|
||||
:value
|
||||
),
|
||||
brine_in:
|
||||
Device.Register.record(
|
||||
Device.RegisterCollection.record(assigns.registers, :brine_in),
|
||||
:value
|
||||
),
|
||||
supply_out:
|
||||
Device.Register.record(
|
||||
Device.RegisterCollection.record(assigns.registers, :supply_out),
|
||||
:value
|
||||
),
|
||||
supply_in:
|
||||
Device.Register.record(
|
||||
Device.RegisterCollection.record(assigns.registers, :supply_in),
|
||||
:value
|
||||
),
|
||||
outdoor_temp:
|
||||
Device.Register.record(
|
||||
Device.RegisterCollection.record(assigns.registers, :outdoor_temp),
|
||||
:value
|
||||
),
|
||||
priority_set: priority_set
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
pointer-events="none"
|
||||
/>
|
||||
|
||||
<%= if @priority == :heating do %>
|
||||
<%= if MapSet.member?(@priority_set, :heating) do %>
|
||||
<ellipse
|
||||
cx="172"
|
||||
cy="289"
|
||||
|
@ -98,7 +98,7 @@
|
|||
/>
|
||||
<% end %>
|
||||
|
||||
<%= if @priority == :hot_water do %>
|
||||
<%= if MapSet.member?(@priority_set, :hot_water) do %>
|
||||
<ellipse
|
||||
cx="100"
|
||||
cy="169"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
defmodule GeoTherminatorWeb.MainLive.DeviceList do
|
||||
require GeoTherminator.PumpAPI.Auth.InstallationInfo
|
||||
use GeoTherminatorWeb, :live_view
|
||||
alias GeoTherminator.PumpAPI.Auth.InstallationInfo
|
||||
|
||||
@impl Phoenix.LiveView
|
||||
def mount(_params, _session, socket) do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<section id="main-section" class="page-container">
|
||||
<header>
|
||||
<h1>Welcome, <%= @user.first_name %>!</h1>
|
||||
<h1>Welcome!</h1>
|
||||
</header>
|
||||
|
||||
<section class="pumps">
|
||||
|
@ -8,8 +8,14 @@
|
|||
<ul>
|
||||
<%= for installation <- @installations do %>
|
||||
<li>
|
||||
<%= live_redirect(installation.id,
|
||||
to: Routes.live_path(@socket, GeoTherminatorWeb.MainLive.Pump, installation.id)
|
||||
<%= live_redirect(
|
||||
InstallationInfo.record(installation, :id),
|
||||
to:
|
||||
Routes.live_path(
|
||||
@socket,
|
||||
GeoTherminatorWeb.MainLive.Pump,
|
||||
InstallationInfo.record(installation, :id)
|
||||
)
|
||||
) %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule GeoTherminatorWeb.MainLive.Index do
|
||||
require Logger
|
||||
use GeoTherminatorWeb, :live_view
|
||||
|
||||
@impl Phoenix.LiveView
|
||||
|
@ -55,8 +56,9 @@ defmodule GeoTherminatorWeb.MainLive.Index do
|
|||
)
|
||||
end
|
||||
|
||||
_ ->
|
||||
assign(socket, error: true)
|
||||
err ->
|
||||
Logger.debug("Error starting auth server: #{inspect(err)}")
|
||||
assign(socket, error: err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
<label>Password <input type="password" name="password" /></label>
|
||||
<button>Log in</button>
|
||||
|
||||
<%= if @error do %>
|
||||
<div class="error">Error occurred, please try again.</div>
|
||||
<%= if @error != false do %>
|
||||
<div class="error">Error occurred, please try again: <%= inspect(@error) %></div>
|
||||
<% end %>
|
||||
</form>
|
||||
<% else %>
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
defmodule GeoTherminatorWeb.MainLive.Pump do
|
||||
require GeoTherminator.PumpAPI.Device
|
||||
require GeoTherminator.PumpAPI.Device.Status
|
||||
require GeoTherminator.PumpAPI.Device.RegisterCollection
|
||||
require GeoTherminator.PumpAPI.Device.OpStat
|
||||
require GeoTherminator.PumpAPI.Device.Register
|
||||
use GeoTherminatorWeb, :live_view
|
||||
alias GeoTherminator.PumpAPI.Auth
|
||||
alias GeoTherminator.PumpAPI.Device
|
||||
|
@ -11,10 +16,10 @@ defmodule GeoTherminatorWeb.MainLive.Pump do
|
|||
info when info != nil <- Auth.Server.get_installation(Auth.Server, id),
|
||||
:ok <- Device.PubSub.subscribe_installation(info),
|
||||
{:ok, pid} <- Device.get_device_process(Auth.Server, info),
|
||||
%Device{} = device <- Device.Server.get_device(pid),
|
||||
%Device.Status{} = status <- Device.Server.get_status(pid),
|
||||
%Device.RegisterCollection{} = registers <- Device.Server.get_registers(pid),
|
||||
%Device.OpStat{} = opstat <- Device.Server.get_opstat(pid) do
|
||||
device <- Device.Server.get_device(pid),
|
||||
status <- Device.Server.get_status(pid),
|
||||
registers <- Device.Server.get_registers(pid),
|
||||
opstat <- Device.Server.get_opstat(pid) do
|
||||
CubDB.put(GeoTherminator.DB, :viewing_pump, str_id)
|
||||
|
||||
assign(socket,
|
||||
|
@ -37,14 +42,15 @@ defmodule GeoTherminatorWeb.MainLive.Pump do
|
|||
def handle_event(event, unsigned_params, socket)
|
||||
|
||||
def handle_event("inc_temp", _params, socket) do
|
||||
if not socket.assigns.status.is_heating_effect_set_by_user do
|
||||
current = socket.assigns.status.heating_effect
|
||||
if not Device.Status.record(socket.assigns.status, :is_heating_effect_set_by_user) do
|
||||
current = Device.Status.record(socket.assigns.status, :heating_effect)
|
||||
_ = Device.Server.set_temp(socket.assigns.pid, current + 1)
|
||||
|
||||
optimistic_status = %Device.Status{
|
||||
socket.assigns.status
|
||||
| is_heating_effect_set_by_user: true
|
||||
}
|
||||
optimistic_status =
|
||||
Device.Status.record(
|
||||
socket.assigns.status,
|
||||
is_heating_effect_set_by_user: true
|
||||
)
|
||||
|
||||
{:noreply, assign(socket, status: optimistic_status)}
|
||||
else
|
||||
|
@ -53,14 +59,15 @@ defmodule GeoTherminatorWeb.MainLive.Pump do
|
|||
end
|
||||
|
||||
def handle_event("dec_temp", _params, socket) do
|
||||
if not socket.assigns.status.is_heating_effect_set_by_user do
|
||||
current = socket.assigns.status.heating_effect
|
||||
if not Device.Status.record(socket.assigns.status, :is_heating_effect_set_by_user) do
|
||||
current = Device.Status.record(socket.assigns.status, :heating_effect)
|
||||
_ = Device.Server.set_temp(socket.assigns.pid, current - 1)
|
||||
|
||||
optimistic_status = %Device.Status{
|
||||
socket.assigns.status
|
||||
| is_heating_effect_set_by_user: true
|
||||
}
|
||||
optimistic_status =
|
||||
Device.Status.record(
|
||||
socket.assigns.status,
|
||||
is_heating_effect_set_by_user: true
|
||||
)
|
||||
|
||||
{:noreply, assign(socket, status: optimistic_status)}
|
||||
else
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<% else %>
|
||||
<.live_component
|
||||
module={GeoTherminatorWeb.Components.MainView}
|
||||
id={"pump-#{@device.id}"}
|
||||
id={"pump-#{Device.record(@device, :id)}"}
|
||||
device={@device}
|
||||
status={@status}
|
||||
registers={@registers}
|
||||
|
|
|
@ -2,25 +2,25 @@
|
|||
# You typically do not need to edit this file
|
||||
|
||||
packages = [
|
||||
{ name = "certifi", version = "2.9.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "266DA46BDB06D6C6D35FDE799BCB28D36D985D424AD7C08B5BB48F5B5CDD4641" },
|
||||
{ name = "castore", version = "0.1.22", build_tools = ["mix"], requirements = [], otp_app = "castore", source = "hex", outer_checksum = "C17576DF47EB5AA1EE40CC4134316A99F5CAD3E215D5C77B8DD3CFEF12A22CAC" },
|
||||
{ name = "finch", version = "0.14.0", build_tools = ["mix"], requirements = ["nimble_options", "mint", "mime", "nimble_pool", "castore", "telemetry"], otp_app = "finch", source = "hex", outer_checksum = "5459ACAF18C4FDB47A8C22FB3BAFF5D8173106217C8E56C5BA0B93E66501A8DD" },
|
||||
{ name = "finch_gleam", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "finch", "gleam_stdlib", "gleam_erlang"], otp_app = "finch_gleam", source = "hex", outer_checksum = "42E0C02FC48184E7B50A6D81DDE955378460142B27542BE50965C15B2AFF6CA1" },
|
||||
{ name = "gleam_erlang", version = "0.17.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "BAAA84F5BCC4477E809BA3E03BB3009A3894A6544C1511626C44408E39DB2AE6" },
|
||||
{ name = "gleam_hackney", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "hackney", "gleam_http"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "CCACA00027C827436D8EB945651392B6E5798CFC9E69907A28BE61832B0C02A4" },
|
||||
{ name = "gleam_http", version = "3.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "B66B7A1539CCB577119E4DC80DD3484C1A652CB032967954498EEDBAE3355763" },
|
||||
{ name = "gleam_json", version = "0.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "E42443C98AA66E30143C24818F2CEA801491C10CE6B1A5EDDF3FC4ABDC7601CB" },
|
||||
{ name = "gleam_stdlib", version = "0.25.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "AD0F89928E0B919C8F8EDF640484633B28DBF88630A9E6AE504617A3E3E5B9A2" },
|
||||
{ name = "hackney", version = "1.18.1", build_tools = ["rebar3"], requirements = ["certifi", "mimerl", "parse_trans", "ssl_verify_fun", "unicode_util_compat", "metrics", "idna"], otp_app = "hackney", source = "hex", outer_checksum = "A4ECDAFF44297E9B5894AE499E9A070EA1888C84AFDD1FD9B7B2BC384950128E" },
|
||||
{ name = "idna", version = "6.1.1", build_tools = ["rebar3"], requirements = ["unicode_util_compat"], otp_app = "idna", source = "hex", outer_checksum = "92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA" },
|
||||
{ name = "metrics", version = "1.0.1", build_tools = ["rebar3"], requirements = [], otp_app = "metrics", source = "hex", outer_checksum = "69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16" },
|
||||
{ name = "mimerl", version = "1.2.0", build_tools = ["rebar3"], requirements = [], otp_app = "mimerl", source = "hex", outer_checksum = "F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323" },
|
||||
{ name = "parse_trans", version = "3.3.1", build_tools = ["rebar3"], requirements = [], otp_app = "parse_trans", source = "hex", outer_checksum = "07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B" },
|
||||
{ name = "ssl_verify_fun", version = "1.1.6", build_tools = ["mix", "rebar3", "make"], requirements = [], otp_app = "ssl_verify_fun", source = "hex", outer_checksum = "BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680" },
|
||||
{ name = "thoas", version = "0.4.0", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "442296847ACA11DB8D25180693D7CA3073D6D7179F66952F07B16415306513B6" },
|
||||
{ name = "unicode_util_compat", version = "0.7.0", build_tools = ["rebar3"], requirements = [], otp_app = "unicode_util_compat", source = "hex", outer_checksum = "25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521" },
|
||||
{ name = "gleam_stdlib", version = "0.26.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "B17BBE8A78F3909D93BCC6C24F531673A7E328A61F24222EB1E58D0A7552B1FE" },
|
||||
{ name = "hpax", version = "0.1.2", build_tools = ["mix"], requirements = [], otp_app = "hpax", source = "hex", outer_checksum = "2C87843D5A23F5F16748EBE77969880E29809580EFDACCD615CD3BED628A8C13" },
|
||||
{ name = "mime", version = "2.0.3", build_tools = ["mix"], requirements = [], otp_app = "mime", source = "hex", outer_checksum = "27A30BF0DB44D25EECBA73755ACF4068CBFE26A4372F9EB3E4EA3A45956BFF6B" },
|
||||
{ name = "mint", version = "1.4.2", build_tools = ["mix"], requirements = ["hpax", "castore"], otp_app = "mint", source = "hex", outer_checksum = "CE75A5BBCC59B4D7D8D70F8B2FC284B1751FFB35C7B6A6302B5192F8AB4DDD80" },
|
||||
{ name = "nimble_options", version = "0.5.2", build_tools = ["mix"], requirements = [], otp_app = "nimble_options", source = "hex", outer_checksum = "4DA7F904B915FD71DB549BCDC25F8D56F378EF7AE07DC1D372CBE72BA950DCE0" },
|
||||
{ name = "nimble_pool", version = "0.2.6", build_tools = ["mix"], requirements = [], otp_app = "nimble_pool", source = "hex", outer_checksum = "1C715055095D3F2705C4E236C18B618420A35490DA94149FF8B580A2144F653F" },
|
||||
{ name = "telemetry", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "DAD9CE9D8EFFC621708F99EAC538EF1CBE05D6A874DD741DE2E689C47FEAFED5" },
|
||||
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
finch_gleam = "~> 1.0"
|
||||
gleam_erlang = "~> 0.17.1"
|
||||
gleam_hackney = "~> 0.2.1"
|
||||
gleam_http = "~> 3.1"
|
||||
gleam_json = "~> 0.5.0"
|
||||
gleam_stdlib = "~> 0.25"
|
||||
|
|
25
mix.exs
25
mix.exs
|
@ -6,8 +6,8 @@ defmodule GeoTherminator.MixProject do
|
|||
def project do
|
||||
[
|
||||
app: @app,
|
||||
version: "0.3.0",
|
||||
elixir: "~> 1.13",
|
||||
version: "0.4.0",
|
||||
elixir: "~> 1.14",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
erlc_paths: [
|
||||
"build/dev/erlang/#{@app}/_gleam_artefacts"
|
||||
|
@ -38,7 +38,16 @@ defmodule GeoTherminator.MixProject do
|
|||
def application do
|
||||
[
|
||||
mod: {GeoTherminator.Application, []},
|
||||
extra_applications: [:logger, :runtime_tools, :logger, :ssl, :crypto, :sasl, :tools, :inets]
|
||||
extra_applications: [
|
||||
:logger,
|
||||
:runtime_tools,
|
||||
:logger,
|
||||
:ssl,
|
||||
:crypto,
|
||||
:sasl,
|
||||
:tools,
|
||||
:inets
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -63,14 +72,14 @@ defmodule GeoTherminator.MixProject do
|
|||
{:gettext, "~> 0.18"},
|
||||
{:jason, "~> 1.2"},
|
||||
{:plug_cowboy, "~> 2.5"},
|
||||
{:dotenv_parser, "~> 1.2"},
|
||||
{:finch, "~> 0.9.0"},
|
||||
{:dotenv_parser, "~> 2.0"},
|
||||
{:finch, "~> 0.14.0"},
|
||||
{:finch_gleam, "~> 1.0.0"},
|
||||
{:desktop, "~> 1.4"},
|
||||
{:cubdb, "~> 2.0"},
|
||||
{:gleam_stdlib, "~> 0.25"},
|
||||
{:gleam_stdlib, "~> 0.26"},
|
||||
{:gleam_http, "~> 3.1"},
|
||||
{:gleam_hackney, "~> 0.2.1"},
|
||||
{:gleam_erlang, "~> 0.17.1"},
|
||||
{:gleam_erlang, "~> 0.18.0"},
|
||||
{:gleam_json, "~> 0.5.0"}
|
||||
]
|
||||
|
||||
|
|
37
mix.lock
37
mix.lock
|
@ -1,5 +1,5 @@
|
|||
%{
|
||||
"castore": {:hex, :castore, "0.1.20", "62a0126cbb7cb3e259257827b9190f88316eb7aa3fdac01fd6f2dfd64e7f46e9", [:mix], [], "hexpm", "a020b7650529c986c454a4035b6b13a328e288466986307bea3aadb4c95ac98a"},
|
||||
"castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"},
|
||||
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
|
||||
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||
|
@ -7,21 +7,21 @@
|
|||
"cubdb": {:hex, :cubdb, "2.0.2", "d4253885084dae37a8ff73887d232864eb38ecac962aa08543e686b0183a1d62", [:mix], [], "hexpm", "c99cc8f9e6c4deb98d16cca5ded1928edd22e48b4736b76e8a1a85367d7fe921"},
|
||||
"dbus": {:hex, :dbus, "0.8.0", "7c800681f35d909c199265e55a8ee4aea9ebe4acccce77a0740f89f29cc57648", [:make], [], "hexpm", "a9784f2d9717ffa1f74169144a226c39633ac0d9c7fe8cb3594aeb89c827cca5"},
|
||||
"debouncer": {:hex, :debouncer, "0.1.7", "a7f59fb55cdb54072aff8ece461f4d041d2a709da84e07ed0ab302d348724640", [:mix], [], "hexpm", "b7fd0623df8ab16933bb164d19769884b18c98cab8677cd53eed59587f290603"},
|
||||
"desktop": {:hex, :desktop, "1.4.2", "48cb5f02aa77522bd9996bfe02c4b23f8dc40d30076ada46b660f4a20bd7a3a1", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_sni, "~> 0.2", [hex: :ex_sni, repo: "hexpm", optional: false]}, {:gettext, "> 0.10.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:oncrash, "~> 0.1", [hex: :oncrash, repo: "hexpm", optional: false]}, {:phoenix, "> 1.0.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:plug, "> 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bcaee5daf0c547ed988d26d4bec04388f104ce169a4e255a786cd64598ce3362"},
|
||||
"dotenv_parser": {:hex, :dotenv_parser, "1.2.0", "f062900aeb57727b619aeb182fa4a8b1cbb7b4260ebec2b70b3d5c064885aff3", [:mix], [], "hexpm", "eddd69e7fde28618adb2e4153fa380db5c56161b32341e7a4e0530d86987c47f"},
|
||||
"esbuild": {:hex, :esbuild, "0.6.0", "9ba6ead054abd43cb3d7b14946a0cdd1493698ccd8e054e0e5d6286d7f0f509c", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "30f9a05d4a5bab0d3e37398f312f80864e1ee1a081ca09149d06d474318fd040"},
|
||||
"desktop": {:hex, :desktop, "1.4.3", "b2e7d35d6dcfa8da8cb7db810477b6356388fd3c93c24821b8187517691232d7", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_sni, "~> 0.2", [hex: :ex_sni, repo: "hexpm", optional: false]}, {:gettext, "> 0.10.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:oncrash, "~> 0.1", [hex: :oncrash, repo: "hexpm", optional: false]}, {:phoenix, "> 1.0.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:plug, "> 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "215311885601b39c962c0e06ccb1edea94a0e53240ad118e6899b957845a6f1f"},
|
||||
"dotenv_parser": {:hex, :dotenv_parser, "2.0.0", "0f999196857e4ee18cbba1413018d5e4980ab16b397e3a2f8d0cf541fe683181", [:mix], [], "hexpm", "e769bde2dbff5b0cd0d9d877a9ccfd2c6dd84772dfb405d5a43cceb4f93616c5"},
|
||||
"esbuild": {:hex, :esbuild, "0.6.1", "a774bfa7b4512a1211bf15880b462be12a4c48ed753a170c68c63b2c95888150", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "569f7409fb5a932211573fc20e2a930a0d5cf3377c5b4f6506c651b1783a1678"},
|
||||
"ex_dbus": {:hex, :ex_dbus, "0.1.4", "053df83d45b27ba0b9b6ef55a47253922069a3ace12a2a7dd30d3aff58301e17", [:mix], [{:dbus, "~> 0.8.0", [hex: :dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "d8baeaf465eab57b70a47b70e29fdfef6eb09ba110fc37176eebe6ac7874d6d5"},
|
||||
"ex_sni": {:hex, :ex_sni, "0.2.9", "81f9421035dd3edb6d69f1a4dd5f53c7071b41628130d32ba5ab7bb4bfdc2da0", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_dbus, "~> 0.1", [hex: :ex_dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "921d67d913765ed20ea8354fd1798dabc957bf66990a6842d6aaa7cd5ee5bc06"},
|
||||
"expo": {:hex, :expo, "0.1.0", "d4e932bdad052c374118e312e35280f1919ac13881cb3ac07a209a54d0c81dd8", [:mix], [], "hexpm", "c22c536021c56de058aaeedeabb4744eb5d48137bacf8c29f04d25b6c6bbbf45"},
|
||||
"expo": {:hex, :expo, "0.4.0", "bbe4bf455e2eb2ebd2f1e7d83530ce50fb9990eb88fc47855c515bfdf1c6626f", [:mix], [], "hexpm", "a8ed1683ec8b7c7fa53fd7a41b2c6935f539168a6bb0616d7fd6b58a36f3abf2"},
|
||||
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
||||
"finch": {:hex, :finch, "0.9.1", "ab2b0151ba88543e221cb50bf0734860db55e8748816ee16e4997fe205f7b315", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6d6b898a59d19f84958eaffec40580f5a9ff88a31e93156707fa8b1d552aa425"},
|
||||
"floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"},
|
||||
"gettext": {:hex, :gettext, "0.21.0", "15bbceb20b317b706a8041061a08e858b5a189654128618b53746bf36c84352b", [:mix], [{:expo, "~> 0.1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "04a66db4103b6d1d18f92240bb2c73167b517229316b7bef84e4eebbfb2f14f6"},
|
||||
"gleam_erlang": {:hex, :gleam_erlang, "0.17.1", "40fff501e8ca39fa166f4c12ed13bb57e94fc5bb59a93b4446687d82d4a12ff9", [:gleam], [{:gleam_stdlib, "~> 0.22", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}], "hexpm", "baaa84f5bcc4477e809ba3e03bb3009a3894a6544c1511626c44408e39db2ae6"},
|
||||
"gleam_hackney": {:hex, :gleam_hackney, "0.2.1", "ca3c5677b85f31885a4366c73a110803515d6d23a2e233e459dc164260315404", [:gleam], [{:gleam_http, "~> 3.0", [hex: :gleam_http, repo: "hexpm", optional: false]}, {:gleam_stdlib, "~> 0.18", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}, {:hackney, "~> 1.18", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "ccaca00027c827436d8eb945651392b6e5798cfc9e69907a28be61832b0c02a4"},
|
||||
"finch": {:hex, :finch, "0.14.0", "619bfdee18fc135190bf590356c4bf5d5f71f916adb12aec94caa3fa9267a4bc", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5459acaf18c4fdb47a8c22fb3baff5d8173106217c8e56c5ba0b93e66501a8dd"},
|
||||
"finch_gleam": {:hex, :finch_gleam, "1.0.0", "40a21698f005d03cb45a122322407422de383ef34cf64c43e443efbedb2897b7", [:gleam], [{:finch, "~> 0.14", [hex: :finch, repo: "hexpm", optional: false]}, {:gleam_erlang, "~> 0.17", [hex: :gleam_erlang, repo: "hexpm", optional: false]}, {:gleam_http, "~> 3.1", [hex: :gleam_http, repo: "hexpm", optional: false]}, {:gleam_stdlib, "~> 0.26", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}], "hexpm", "42e0c02fc48184e7b50a6d81dde955378460142b27542be50965c15b2aff6ca1"},
|
||||
"floki": {:hex, :floki, "0.34.1", "b1f9c413d91140230788b173906065f6f8906bbbf5b3f0d3c626301aeeef44c5", [:mix], [], "hexpm", "cc9b62312a45c1239ca8f65e05377ef8c646f3d7712e5727a9b47c43c946e885"},
|
||||
"gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"},
|
||||
"gleam_erlang": {:hex, :gleam_erlang, "0.18.0", "9810c548c9a9dbcbe0e170012a9255ec18add3efbf758d2c1b2108475182100a", [:gleam], [{:gleam_stdlib, "~> 0.22", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}], "hexpm", "14abc93a7975369ccddc0c4d9a34c0e0fe99ca3ad336be6a17299ec0285b7107"},
|
||||
"gleam_http": {:hex, :gleam_http, "3.1.1", "609158240630e21fc70c69b21384e5ebbcd86f71bd378a6f7c2b87f910ab3561", [:gleam], [{:gleam_stdlib, "~> 0.18", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}], "hexpm", "b66b7a1539ccb577119e4dc80dd3484c1a652cb032967954498eedbae3355763"},
|
||||
"gleam_json": {:hex, :gleam_json, "0.5.0", "aff4507ad7700ad794ada6671c6dfd0174696713659bd8782858135b19f41b58", [:gleam], [{:gleam_stdlib, "~> 0.19", [hex: :gleam_stdlib, repo: "hexpm", optional: false]}, {:thoas, "~> 0.2", [hex: :thoas, repo: "hexpm", optional: false]}], "hexpm", "e42443c98aa66e30143c24818f2cea801491c10ce6b1a5eddf3fc4abdc7601cb"},
|
||||
"gleam_stdlib": {:hex, :gleam_stdlib, "0.25.0", "656f39258dcc8772719e463bbe7d1d1c7800238a520b41558fad53ea206ee3ab", [:gleam], [], "hexpm", "ad0f89928e0b919c8f8edf640484633b28dbf88630a9e6ae504617a3e3e5b9a2"},
|
||||
"gleam_stdlib": {:hex, :gleam_stdlib, "0.26.1", "adba364f7ce31dfa456d3d542392eb9173e6f80f925beaf0009070b75b86ce71", [:gleam], [], "hexpm", "b17bbe8a78f3909d93bcc6c24f531673a7e328a61f24222eb1e58d0a7552b1fe"},
|
||||
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
|
||||
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
|
||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||
|
@ -30,17 +30,17 @@
|
|||
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
|
||||
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
|
||||
"nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
|
||||
"oncrash": {:hex, :oncrash, "0.1.0", "9cf4ae8eba4ea250b579470172c5e9b8c75418b2264de7dbcf42e408d62e30fb", [:mix], [], "hexpm", "6968e775491cd857f9b6ff940bf2574fd1c2fab84fa7e14d5f56c39174c00018"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
|
||||
"phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
|
||||
"phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.3.0", "bf451c71ebdaac8d2f40d3b703435e819ccfbb9ff243140ca3bd10c155f134cc", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "272c5c1533499f0132309936c619186480bafcc2246588f99a69ce85095556ef"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [: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", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.17.12", "74f4c0ad02d7deac2d04f50b52827a5efdc5c6e7fac5cede145f5f0e4183aedc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "af6dd5e0aac16ff43571f527a8e0616d62cb80b10eb87aac82170243e50d99c8"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.17.14", "5ec615d4d61bf9d4755f158bd6c80372b715533fe6d6219e12d74fb5eedbeac1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "afeb6ba43ce329a6f7fc1c9acdfc6d3039995345f025febb7f409a92f6faebd3"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
|
||||
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
|
||||
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
|
||||
|
@ -48,9 +48,10 @@
|
|||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
"saxy": {:hex, :saxy, "1.4.0", "c7203ad20001f72eaaad07d08f82be063fa94a40924e6bb39d93d55f979abcba", [:mix], [], "hexpm", "3fe790354d3f2234ad0b5be2d99822a23fa2d4e8ccd6657c672901dac172e9a9"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.0", "a8ce551485a9a3dac8d523542de130eafd12e40bbf76cf0ecd2528f24e812a44", [:rebar3], [], "hexpm", "1427e73667b9a2002cf1f26694c422d5c905df889023903c4518921d53e3e883"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
||||
"thoas": {:hex, :thoas, "0.4.0", "86a72ccdc5ec388a13f9f843bcd6c1076640233b95440e47ffb8e3c0dbdb5a17", [:rebar3], [], "hexpm", "442296847aca11db8d25180693d7ca3073d6d7179f66952f07b16415306513b6"},
|
||||
"thoas": {:hex, :thoas, "0.4.1", "5471b049afd1c63b67b772a181a127b2c9aca398b4d1ae877ad49959109e50c4", [:rebar3], [], "hexpm", "4918d50026c073c4ab1388437132c77a6f6f7c8ac43c60c13758cc0adce2134e"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
"wx": {:hex, :bridge, "1.0.10", "5051dfe881e498a0bc056603df97b734bfe5fdc084f5e56dc42fab8049247144", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b30941e57a194e557a123c5246f123f8b13acdd5c580f75590a52e72bda15632"},
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
//// Mostly translated from:
|
||||
//// https://github.com/klejejs/python-thermia-online-api/blob/2f0ec4e45bfecbd90932a10247283cbcd6a6c48c/ThermiaOnlineAPI/api/ThermiaAPI.py
|
||||
//// Used under the Gnu General Public License 3.0
|
||||
////
|
||||
//// Refreshing using the refresh token is not implemented, we just get a new
|
||||
//// access token every time, YOLO
|
||||
|
||||
import gleam/json
|
||||
import gleam/base
|
||||
import gleam/hackney
|
||||
import gleam/uri
|
||||
import gleam/http
|
||||
import gleam/http/request
|
||||
|
@ -16,20 +18,14 @@ import gleam/list
|
|||
import gleam/result
|
||||
import gleam/int
|
||||
import azure/utils
|
||||
import helpers/config
|
||||
import helpers/crypto
|
||||
import helpers/uri as uri_helpers
|
||||
import helpers/parsing
|
||||
import finch
|
||||
|
||||
const challenge_length = 43
|
||||
|
||||
const b2c_client_id = "09ea4903-9e95-45fe-ae1f-e3b7d32fa385"
|
||||
|
||||
const b2c_scope = b2c_client_id
|
||||
|
||||
const b2c_redirect_uri = "https://online-genesis.thermia.se/login"
|
||||
|
||||
const b2c_auth_url = "https://thermialogin.b2clogin.com/thermialogin.onmicrosoft.com/b2c_1a_signuporsigninonline"
|
||||
|
||||
const b2c_authorize_prefix = "var SETTINGS = "
|
||||
|
||||
pub type Tokens {
|
||||
|
@ -73,10 +69,6 @@ pub fn authenticate(
|
|||
get_tokens(confirmed, code_challenge)
|
||||
}
|
||||
|
||||
pub fn refresh(tokens: Tokens) -> Result(Tokens, B2CError) {
|
||||
todo
|
||||
}
|
||||
|
||||
fn authorize(code_challenge: String) -> Result(AuthInfo, B2CError) {
|
||||
let auth_data = [
|
||||
#("response_type", "code"),
|
||||
|
@ -186,7 +178,8 @@ fn confirm(
|
|||
|
||||
try resp =
|
||||
req
|
||||
|> hackney.send()
|
||||
|> finch.build([])
|
||||
|> finch.request(config.finch_server())
|
||||
|> b2c_error("Confirm HTTP request failed.")
|
||||
|
||||
try resp = case resp.status {
|
||||
|
@ -248,25 +241,28 @@ fn hash_challenge(challenge: String) -> String {
|
|||
base.url_encode64(hashed, False)
|
||||
}
|
||||
|
||||
fn authorize_url() -> String {
|
||||
b2c_auth_url <> "/oauth2/v2.0/authorize"
|
||||
fn authorize_url() -> uri.Uri {
|
||||
let url = config.api_url("b2c_auth_url")
|
||||
uri.Uri(..url, path: url.path <> "/oauth2/v2.0/authorize")
|
||||
}
|
||||
|
||||
fn self_asserted_url() -> String {
|
||||
b2c_auth_url <> "/SelfAsserted"
|
||||
fn self_asserted_url() -> uri.Uri {
|
||||
let url = config.api_url("b2c_auth_url")
|
||||
uri.Uri(..url, path: url.path <> "/SelfAsserted")
|
||||
}
|
||||
|
||||
fn confirm_url() -> String {
|
||||
b2c_auth_url <> "/api/CombinedSigninAndSignup/confirmed"
|
||||
fn confirm_url() -> uri.Uri {
|
||||
let url = config.api_url("b2c_auth_url")
|
||||
uri.Uri(..url, path: url.path <> "/api/CombinedSigninAndSignup/confirmed")
|
||||
}
|
||||
|
||||
fn get_token_url() -> String {
|
||||
b2c_auth_url <> "/oauth2/v2.0/token"
|
||||
fn get_token_url() -> uri.Uri {
|
||||
let url = config.api_url("b2c_auth_url")
|
||||
uri.Uri(..url, path: url.path <> "/oauth2/v2.0/token")
|
||||
}
|
||||
|
||||
fn build_req(url: String, method: http.Method) -> request.Request(String) {
|
||||
assert Ok(req_url) = uri.parse(url)
|
||||
assert Ok(req) = request.from_uri(req_url)
|
||||
fn build_req(url: uri.Uri, method: http.Method) -> request.Request(String) {
|
||||
assert Ok(req) = request.from_uri(url)
|
||||
|
||||
let req = request.set_method(req, method)
|
||||
|
||||
|
@ -286,7 +282,8 @@ fn run_req(
|
|||
) -> Result(response.Response(String), B2CError) {
|
||||
try resp =
|
||||
req
|
||||
|> hackney.send()
|
||||
|> finch.build([])
|
||||
|> finch.request(config.finch_server())
|
||||
|> b2c_error("HTTP request failed.")
|
||||
|
||||
case resp.status {
|
||||
|
@ -301,10 +298,12 @@ fn run_req(
|
|||
}
|
||||
|
||||
fn base_request_data() -> List(#(String, String)) {
|
||||
let b2c_client_id = config.str("b2c_client_id")
|
||||
|
||||
[
|
||||
#("client_id", b2c_client_id),
|
||||
#("scope", b2c_scope),
|
||||
#("redirect_uri", b2c_redirect_uri),
|
||||
#("scope", b2c_client_id),
|
||||
#("redirect_uri", config.str("b2c_redirect_url")),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,8 @@ import gleam/dynamic
|
|||
import gleam/uri
|
||||
import helpers/application
|
||||
|
||||
fn app_name() -> atom.Atom {
|
||||
assert Ok(name) = atom.from_string("geo_therminator")
|
||||
name
|
||||
pub fn finch_server() -> atom.Atom {
|
||||
atom.create_from_string("Elixir.GeoTherminator.PumpAPI.HTTP")
|
||||
}
|
||||
|
||||
pub fn api_timeout() -> Int {
|
||||
|
@ -19,19 +18,35 @@ pub fn api_timeout() -> Int {
|
|||
timeout_int
|
||||
}
|
||||
|
||||
pub fn api_auth_url() -> uri.Uri {
|
||||
config_url("api_auth_url")
|
||||
pub fn api_url(config_key: String) -> uri.Uri {
|
||||
map_api_url(config_key, fn(s) { s })
|
||||
}
|
||||
|
||||
pub fn api_installations_url() -> uri.Uri {
|
||||
config_url("api_installations_url")
|
||||
}
|
||||
|
||||
fn config_url(config_key: String) -> uri.Uri {
|
||||
pub fn map_api_url(config_key: String, mapper: fn(String) -> String) -> uri.Uri {
|
||||
let url =
|
||||
application.fetch_env_angry(app_name(), atom.create_from_string(config_key))
|
||||
|
||||
assert Ok(url_str) = dynamic.string(url)
|
||||
let url_str = mapper(url_str)
|
||||
assert Ok(parsed_url) = uri.parse(url_str)
|
||||
parsed_url
|
||||
}
|
||||
|
||||
pub fn str(config_key: String) -> String {
|
||||
let val =
|
||||
application.fetch_env_angry(app_name(), atom.create_from_string(config_key))
|
||||
assert Ok(val_str) = dynamic.string(val)
|
||||
val_str
|
||||
}
|
||||
|
||||
pub fn int(config_key: String) -> Int {
|
||||
let val =
|
||||
application.fetch_env_angry(app_name(), atom.create_from_string(config_key))
|
||||
assert Ok(val_int) = dynamic.int(val)
|
||||
val_int
|
||||
}
|
||||
|
||||
fn app_name() -> atom.Atom {
|
||||
assert Ok(name) = atom.from_string("geo_therminator")
|
||||
name
|
||||
}
|
||||
|
|
|
@ -1,9 +1,45 @@
|
|||
import gleam
|
||||
import gleam/erlang/atom.{Atom}
|
||||
import gleam/dynamic
|
||||
|
||||
pub type FromDynamicError {
|
||||
NotAString
|
||||
ParseError
|
||||
}
|
||||
|
||||
pub external type DateTime
|
||||
|
||||
pub external fn from_iso8601(String) -> Result(#(DateTime, Int), Atom) =
|
||||
pub type DateTimeFromISO8601Result {
|
||||
Ok(datetime: DateTime, offset: Int)
|
||||
Error(reason: Atom)
|
||||
}
|
||||
|
||||
pub fn from_dynamic_iso8601(
|
||||
data: dynamic.Dynamic,
|
||||
) -> Result(#(DateTime, Int), List(dynamic.DecodeError)) {
|
||||
try data_str = dynamic.string(data)
|
||||
|
||||
case from_iso8601(data_str) {
|
||||
Ok(datetime, offset) -> gleam.Ok(#(datetime, offset))
|
||||
Error(_reason) ->
|
||||
gleam.Error([
|
||||
dynamic.DecodeError(
|
||||
expected: "ISO-8601 formatted datetime with timezone",
|
||||
found: data_str,
|
||||
path: [],
|
||||
),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
pub external fn from_iso8601(String) -> DateTimeFromISO8601Result =
|
||||
"Elixir.DateTime" "from_iso8601"
|
||||
|
||||
pub external fn from_unix(Int) -> Result(DateTime, #(Atom, Atom)) =
|
||||
"Elixir.DateTime" "from_unix"
|
||||
|
||||
pub external fn utc_now() -> DateTime =
|
||||
"Elixir.DateTime" "utc_now"
|
||||
|
||||
pub external fn to_unix(DateTime) -> Int =
|
||||
"Elixir.DateTime" "to_unix"
|
||||
|
|
12
src/helpers/keyword.gleam
Normal file
12
src/helpers/keyword.gleam
Normal file
|
@ -0,0 +1,12 @@
|
|||
import gleam/erlang/atom.{Atom}
|
||||
|
||||
pub external type Keyword
|
||||
|
||||
pub external fn init() -> Keyword =
|
||||
"Elixir.Keyword" "new"
|
||||
|
||||
pub external fn put_int(data: Keyword, key: Atom, value: Int) -> Keyword =
|
||||
"Elixir.Keyword" "put"
|
||||
|
||||
pub external fn put_string(data: Keyword, key: Atom, value: String) -> Keyword =
|
||||
"Elixir.Keyword" "put"
|
22
src/helpers/naive_date_time.gleam
Normal file
22
src/helpers/naive_date_time.gleam
Normal file
|
@ -0,0 +1,22 @@
|
|||
import gleam/dynamic
|
||||
import gleam/erlang/atom.{Atom}
|
||||
import gleam/result
|
||||
|
||||
pub external type NaiveDateTime
|
||||
|
||||
pub fn from_dynamic_iso8601(
|
||||
data: dynamic.Dynamic,
|
||||
) -> Result(NaiveDateTime, List(dynamic.DecodeError)) {
|
||||
try data_str = dynamic.string(data)
|
||||
from_iso8601(data_str)
|
||||
|> result.replace_error([
|
||||
dynamic.DecodeError(
|
||||
expected: "ISO-8601 formatted naive datetime",
|
||||
found: data_str,
|
||||
path: [],
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
pub external fn from_iso8601(String) -> Result(NaiveDateTime, Atom) =
|
||||
"Elixir.NaiveDateTime" "from_iso8601"
|
17
src/pump_api/api.gleam
Normal file
17
src/pump_api/api.gleam
Normal file
|
@ -0,0 +1,17 @@
|
|||
import azure/b2c.{B2CError}
|
||||
import pump_api/http
|
||||
|
||||
pub type ApiError {
|
||||
RequestFailed
|
||||
NotOkResponse
|
||||
InvalidData(msg: String)
|
||||
AuthError(inner: B2CError)
|
||||
}
|
||||
|
||||
pub fn from_http(err: http.ApiError) -> ApiError {
|
||||
case err {
|
||||
http.RequestFailed -> RequestFailed
|
||||
http.NotOkResponse -> NotOkResponse
|
||||
http.InvalidData -> InvalidData("Invalid data in HTTP response.")
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
import gleam/http/request
|
||||
import gleam/json
|
||||
import gleam/hackney
|
||||
import gleam/result
|
||||
import gleam/dynamic
|
||||
import gleam/list
|
||||
|
@ -9,31 +7,29 @@ import pump_api/auth/user.{User}
|
|||
import pump_api/auth/tokens.{Tokens}
|
||||
import pump_api/auth/installation_info.{InstallationInfo}
|
||||
import pump_api/http
|
||||
import pump_api/api.{ApiError, AuthError, InvalidData}
|
||||
import helpers/config
|
||||
import helpers/date_time
|
||||
import helpers/parsing
|
||||
import azure/b2c.{B2CError}
|
||||
|
||||
pub type ApiError {
|
||||
ApiRequestFailed
|
||||
NotOkResponse
|
||||
InvalidData(msg: String)
|
||||
AuthError(inner: B2CError)
|
||||
}
|
||||
import azure/b2c
|
||||
|
||||
pub fn auth(username: String, password: String) -> Result(User, ApiError) {
|
||||
try tokens =
|
||||
b2c.authenticate(username, password)
|
||||
|> result.map_error(fn(err) { AuthError(inner: err) })
|
||||
try access_token_expires_in =
|
||||
date_time.from_unix(tokens.access_token_expires_in)
|
||||
try access_token_expiry =
|
||||
date_time.from_unix(
|
||||
date_time.to_unix(date_time.utc_now()) + tokens.access_token_expires_in,
|
||||
)
|
||||
|> result.replace_error(InvalidData(
|
||||
msg: "Access token expiry could not be converted into DateTime: " <> string.inspect(
|
||||
tokens.access_token_expires_in,
|
||||
),
|
||||
))
|
||||
try refresh_token_expires_in =
|
||||
date_time.from_unix(tokens.refresh_token_expires_in)
|
||||
try refresh_token_expiry =
|
||||
date_time.from_unix(
|
||||
date_time.to_unix(date_time.utc_now()) + tokens.refresh_token_expires_in,
|
||||
)
|
||||
|> result.replace_error(InvalidData(
|
||||
msg: "Refresh token expiry could not be converted into DateTime: " <> string.inspect(
|
||||
tokens.refresh_token_expires_in,
|
||||
|
@ -42,18 +38,22 @@ pub fn auth(username: String, password: String) -> Result(User, ApiError) {
|
|||
|
||||
Ok(User(tokens: Tokens(
|
||||
access_token: tokens.access_token,
|
||||
access_token_expiry: access_token_expires_in,
|
||||
access_token_expiry: access_token_expiry,
|
||||
refresh_token: tokens.refresh_token,
|
||||
refresh_token_expiry: refresh_token_expires_in,
|
||||
refresh_token_expiry: refresh_token_expiry,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn installation_info(user: User) -> Result(List(InstallationInfo), ApiError) {
|
||||
let url = config.api_installations_url()
|
||||
let url = config.api_url("api_installations_url")
|
||||
assert Ok(raw_req) = request.from_uri(url)
|
||||
|
||||
let empty_req = request.set_body(raw_req, http.Empty)
|
||||
try data = run_req(http.authed_req(user, empty_req))
|
||||
try data =
|
||||
http.run_json_req(http.authed_req(user, empty_req))
|
||||
|> result.replace_error(InvalidData(
|
||||
msg: "Could not parse InstallationInfo JSON.",
|
||||
))
|
||||
|
||||
try items =
|
||||
parsing.data_get(
|
||||
|
@ -65,21 +65,3 @@ pub fn installation_info(user: User) -> Result(List(InstallationInfo), ApiError)
|
|||
|
||||
Ok(list.map(items, fn(id) { InstallationInfo(id: id) }))
|
||||
}
|
||||
|
||||
fn run_req(req: request.Request(String)) {
|
||||
try resp =
|
||||
req
|
||||
|> hackney.send()
|
||||
|> result.replace_error(ApiRequestFailed)
|
||||
|
||||
try body = case resp.status {
|
||||
200 -> Ok(resp.body)
|
||||
_ -> Error(NotOkResponse)
|
||||
}
|
||||
|
||||
body
|
||||
|> json.decode(using: dynamic.dynamic)
|
||||
|> result.replace_error(InvalidData(
|
||||
msg: "Could not parse InstallationInfo JSON.",
|
||||
))
|
||||
}
|
||||
|
|
56
src/pump_api/device.gleam
Normal file
56
src/pump_api/device.gleam
Normal file
|
@ -0,0 +1,56 @@
|
|||
import gleam/set
|
||||
import helpers/naive_date_time.{NaiveDateTime}
|
||||
import helpers/date_time.{DateTime}
|
||||
|
||||
pub type Device {
|
||||
Device(
|
||||
id: Int,
|
||||
device_id: Int,
|
||||
is_online: Bool,
|
||||
last_online: NaiveDateTime,
|
||||
created_when: NaiveDateTime,
|
||||
mac_address: String,
|
||||
name: String,
|
||||
model: String,
|
||||
retailer_access: Int,
|
||||
)
|
||||
}
|
||||
|
||||
pub type Status {
|
||||
Status(heating_effect: Int, is_heating_effect_set_by_user: Bool)
|
||||
}
|
||||
|
||||
pub type Register {
|
||||
Register(name: String, value: Int, timestamp: DateTime)
|
||||
}
|
||||
|
||||
pub type RegisterCollection {
|
||||
RegisterCollection(
|
||||
outdoor_temp: Register,
|
||||
supply_out: Register,
|
||||
supply_in: Register,
|
||||
desired_supply: Register,
|
||||
brine_out: Register,
|
||||
brine_in: Register,
|
||||
hot_water_temp: Register,
|
||||
)
|
||||
}
|
||||
|
||||
pub type Priority {
|
||||
HandOperated
|
||||
HotWater
|
||||
Heating
|
||||
ActiveCooling
|
||||
Pool
|
||||
AntiLegionella
|
||||
PassiveCooling
|
||||
Standby
|
||||
Idle
|
||||
Off
|
||||
Defrost
|
||||
Unknown
|
||||
}
|
||||
|
||||
pub type OpStat {
|
||||
OpStat(priority: set.Set(Priority))
|
||||
}
|
286
src/pump_api/device/api.gleam
Normal file
286
src/pump_api/device/api.gleam
Normal file
|
@ -0,0 +1,286 @@
|
|||
import gleam/http/request
|
||||
import gleam/http as gleam_http
|
||||
import gleam/string
|
||||
import gleam/int
|
||||
import gleam/dynamic
|
||||
import gleam/result
|
||||
import gleam/list
|
||||
import gleam/map
|
||||
import gleam/set
|
||||
import gleam/json
|
||||
import pump_api/auth/user.{User}
|
||||
import pump_api/auth/installation_info.{InstallationInfo}
|
||||
import pump_api/device.{
|
||||
Device, OpStat, Priority, Register, RegisterCollection, Status,
|
||||
}
|
||||
import pump_api/http
|
||||
import pump_api/api.{ApiError, InvalidData}
|
||||
import helpers/config
|
||||
import helpers/parsing
|
||||
import helpers/naive_date_time
|
||||
import helpers/date_time
|
||||
|
||||
const opstat_bitmask_mapping = [
|
||||
#(1, device.HandOperated),
|
||||
#(2, device.Defrost),
|
||||
#(4, device.HotWater),
|
||||
#(8, device.Heating),
|
||||
#(16, device.ActiveCooling),
|
||||
#(32, device.Pool),
|
||||
#(64, device.AntiLegionella),
|
||||
#(128, device.PassiveCooling),
|
||||
#(512, device.Standby),
|
||||
#(1024, device.Idle),
|
||||
#(2048, device.Off),
|
||||
]
|
||||
|
||||
pub fn device_info(
|
||||
user: User,
|
||||
installation: InstallationInfo,
|
||||
) -> Result(Device, ApiError) {
|
||||
let url =
|
||||
config.map_api_url(
|
||||
"api_device_url",
|
||||
fn(url) { string.replace(url, "{id}", int.to_string(installation.id)) },
|
||||
)
|
||||
assert Ok(raw_req) = request.from_uri(url)
|
||||
|
||||
let empty_req = request.set_body(raw_req, http.Empty)
|
||||
try data =
|
||||
http.run_json_req(http.authed_req(user, empty_req))
|
||||
|> result.map_error(api.from_http)
|
||||
|
||||
try last_online =
|
||||
parsing.data_get(
|
||||
data,
|
||||
"lastOnline",
|
||||
naive_date_time.from_dynamic_iso8601,
|
||||
InvalidData,
|
||||
)
|
||||
try created_when =
|
||||
parsing.data_get(
|
||||
data,
|
||||
"createdWhen",
|
||||
naive_date_time.from_dynamic_iso8601,
|
||||
InvalidData,
|
||||
)
|
||||
try id = parsing.data_get(data, "id", dynamic.int, InvalidData)
|
||||
try device_id = parsing.data_get(data, "deviceId", dynamic.int, InvalidData)
|
||||
try is_online = parsing.data_get(data, "isOnline", dynamic.bool, InvalidData)
|
||||
try mac_address =
|
||||
parsing.data_get(data, "macAddress", dynamic.string, InvalidData)
|
||||
try name = parsing.data_get(data, "name", dynamic.string, InvalidData)
|
||||
try model = parsing.data_get(data, "model", dynamic.string, InvalidData)
|
||||
try retailer_access =
|
||||
parsing.data_get(data, "retailerAccess", dynamic.int, InvalidData)
|
||||
|
||||
Ok(Device(
|
||||
id: id,
|
||||
device_id: device_id,
|
||||
is_online: is_online,
|
||||
last_online: last_online,
|
||||
created_when: created_when,
|
||||
mac_address: mac_address,
|
||||
name: name,
|
||||
model: model,
|
||||
retailer_access: retailer_access,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn status(user: User, device: Device) {
|
||||
let url =
|
||||
config.map_api_url(
|
||||
"api_device_status_url",
|
||||
fn(url) { string.replace(url, "{id}", int.to_string(device.id)) },
|
||||
)
|
||||
assert Ok(raw_req) = request.from_uri(url)
|
||||
|
||||
let empty_req = request.set_body(raw_req, http.Empty)
|
||||
try data =
|
||||
http.run_json_req(http.authed_req(user, empty_req))
|
||||
|> result.map_error(api.from_http)
|
||||
|
||||
try heating_effect =
|
||||
parsing.data_get(data, "heatingEffect", dynamic.int, InvalidData)
|
||||
try is_heating_effect_set_by_user =
|
||||
parsing.data_get(
|
||||
data,
|
||||
"isHeatingEffectSetByUser",
|
||||
dynamic.bool,
|
||||
InvalidData,
|
||||
)
|
||||
|
||||
Ok(Status(
|
||||
heating_effect: heating_effect,
|
||||
is_heating_effect_set_by_user: is_heating_effect_set_by_user,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn register_info(user: User, device: Device) {
|
||||
let url =
|
||||
config.map_api_url(
|
||||
"api_device_register_url",
|
||||
fn(url) { string.replace(url, "{id}", int.to_string(device.id)) },
|
||||
)
|
||||
assert Ok(raw_req) = request.from_uri(url)
|
||||
|
||||
let empty_req = request.set_body(raw_req, http.Empty)
|
||||
try data =
|
||||
http.run_json_req(http.authed_req(user, empty_req))
|
||||
|> result.map_error(api.from_http)
|
||||
|
||||
try registers =
|
||||
dynamic.list(parse_register)(data)
|
||||
|> result.map_error(fn(err) {
|
||||
InvalidData("Unable to parse registers: " <> string.inspect(err))
|
||||
})
|
||||
let registers_map =
|
||||
registers
|
||||
|> list.map(fn(r) { #(r.name, r) })
|
||||
|> map.from_list()
|
||||
|
||||
try outdoor_temp = get_register(registers_map, "REG_OUTDOOR_TEMPERATURE")
|
||||
try supply_out = get_register(registers_map, "REG_SUPPLY_LINE")
|
||||
try supply_in = get_register(registers_map, "REG_OPER_DATA_RETURN")
|
||||
try desired_supply =
|
||||
get_register(registers_map, "REG_DESIRED_SYS_SUPPLY_LINE_TEMP")
|
||||
try brine_out = get_register(registers_map, "REG_BRINE_OUT")
|
||||
try brine_in = get_register(registers_map, "REG_BRINE_IN")
|
||||
try hot_water_temp = get_register(registers_map, "REG_HOT_WATER_TEMPERATURE")
|
||||
|
||||
Ok(RegisterCollection(
|
||||
outdoor_temp: outdoor_temp,
|
||||
supply_out: supply_out,
|
||||
supply_in: supply_in,
|
||||
desired_supply: desired_supply,
|
||||
brine_out: brine_out,
|
||||
brine_in: brine_in,
|
||||
hot_water_temp: hot_water_temp,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn opstat(user: User, device: Device) {
|
||||
let url =
|
||||
config.map_api_url(
|
||||
"api_device_opstat_url",
|
||||
fn(url) { string.replace(url, "{id}", int.to_string(device.id)) },
|
||||
)
|
||||
assert Ok(raw_req) = request.from_uri(url)
|
||||
|
||||
let empty_req = request.set_body(raw_req, http.Empty)
|
||||
try data =
|
||||
http.run_json_req(http.authed_req(user, empty_req))
|
||||
|> result.map_error(api.from_http)
|
||||
|
||||
try registers =
|
||||
dynamic.list(parse_register)(data)
|
||||
|> result.map_error(fn(err) {
|
||||
InvalidData("Unable to parse registers: " <> string.inspect(err))
|
||||
})
|
||||
let registers_map =
|
||||
registers
|
||||
|> list.map(fn(r) { #(r.name, r) })
|
||||
|> map.from_list()
|
||||
|
||||
let priority_register =
|
||||
get_register(registers_map, "REG_OPERATIONAL_STATUS_PRIO1")
|
||||
let priority_register_fallback =
|
||||
get_register(registers_map, "REG_OPERATIONAL_STATUS_PRIORITY_BITMASK")
|
||||
|
||||
try priority = case #(priority_register, priority_register_fallback) {
|
||||
#(Ok(data), _) -> Ok(opstat_map(data))
|
||||
#(_, Ok(data)) -> Ok(opstat_bitmask_map(data))
|
||||
_ ->
|
||||
Error(InvalidData(
|
||||
"Unable to parse opstat: " <> string.inspect(#(
|
||||
priority_register,
|
||||
priority_register_fallback,
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
Ok(OpStat(priority: priority))
|
||||
}
|
||||
|
||||
pub fn set_temp(user: User, device: Device, temp: Int) {
|
||||
let url =
|
||||
config.map_api_url(
|
||||
"api_device_opstat_url",
|
||||
fn(url) { string.replace(url, "{id}", int.to_string(device.id)) },
|
||||
)
|
||||
assert Ok(raw_req) = request.from_uri(url)
|
||||
|
||||
let register_index = config.int("api_device_temp_set_reg_index")
|
||||
let client_id = config.str("api_device_reg_set_client_id")
|
||||
|
||||
let req =
|
||||
raw_req
|
||||
|> request.set_method(gleam_http.Post)
|
||||
|> request.set_body(http.Json(data: json.object([
|
||||
#("registerIndex", json.int(register_index)),
|
||||
#("clientUuid", json.string(client_id)),
|
||||
#("registerValue", json.int(temp)),
|
||||
])))
|
||||
|
||||
http.run_req(http.authed_req(user, req))
|
||||
}
|
||||
|
||||
fn parse_register(
|
||||
item: dynamic.Dynamic,
|
||||
) -> Result(Register, List(dynamic.DecodeError)) {
|
||||
try #(timestamp, _) =
|
||||
dynamic.field("timeStamp", date_time.from_dynamic_iso8601)(item)
|
||||
try name = dynamic.field("registerName", dynamic.string)(item)
|
||||
try value = dynamic.field("registerValue", dynamic.int)(item)
|
||||
|
||||
Ok(Register(timestamp: timestamp, name: name, value: value))
|
||||
}
|
||||
|
||||
fn get_register(
|
||||
data: map.Map(String, Register),
|
||||
key: String,
|
||||
) -> Result(Register, ApiError) {
|
||||
map.get(data, key)
|
||||
|> result.replace_error(InvalidData(
|
||||
"Could not find " <> key <> " in data: " <> string.inspect(data),
|
||||
))
|
||||
}
|
||||
|
||||
fn opstat_map(register: Register) {
|
||||
let val = case register.value {
|
||||
1 -> device.HandOperated
|
||||
3 -> device.HotWater
|
||||
4 -> device.Heating
|
||||
5 -> device.ActiveCooling
|
||||
6 -> device.Pool
|
||||
7 -> device.AntiLegionella
|
||||
8 -> device.PassiveCooling
|
||||
98 -> device.Standby
|
||||
99 -> device.Idle
|
||||
100 -> device.Off
|
||||
_ -> device.Unknown
|
||||
}
|
||||
|
||||
set.new()
|
||||
|> set.insert(val)
|
||||
}
|
||||
|
||||
fn opstat_bitmask_map(register: Register) {
|
||||
let priority_set: set.Set(Priority) = set.new()
|
||||
|
||||
list.fold(
|
||||
opstat_bitmask_mapping,
|
||||
priority_set,
|
||||
fn(output, mapping) {
|
||||
let #(int, priority) = mapping
|
||||
|
||||
case band(register.value, int) {
|
||||
0 -> output
|
||||
_ -> set.insert(output, priority)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
external fn band(i1: Int, i2: Int) -> Int =
|
||||
"erlang" "band"
|
|
@ -1,6 +1,10 @@
|
|||
import gleam/http/request
|
||||
import gleam/json.{Json}
|
||||
import gleam/result
|
||||
import gleam/dynamic
|
||||
import pump_api/auth/user.{User}
|
||||
import helpers/config
|
||||
import finch
|
||||
|
||||
pub type Body {
|
||||
Empty
|
||||
|
@ -11,6 +15,12 @@ pub type Body {
|
|||
pub type ApiRequest =
|
||||
request.Request(Body)
|
||||
|
||||
pub type ApiError {
|
||||
RequestFailed
|
||||
NotOkResponse
|
||||
InvalidData
|
||||
}
|
||||
|
||||
pub fn authed_req(user: User, r: ApiRequest) {
|
||||
r
|
||||
|> request.set_header("authorization", "Bearer " <> user.tokens.access_token)
|
||||
|
@ -29,3 +39,24 @@ pub fn req(r: ApiRequest) -> request.Request(String) {
|
|||
|> request.set_body(json.to_string(data))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_req(req: request.Request(String)) {
|
||||
try resp =
|
||||
req
|
||||
|> finch.build([])
|
||||
|> finch.request(config.finch_server())
|
||||
|> result.replace_error(RequestFailed)
|
||||
|
||||
case resp.status {
|
||||
200 -> Ok(resp.body)
|
||||
_ -> Error(NotOkResponse)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_json_req(req: request.Request(String)) {
|
||||
try body = run_req(req)
|
||||
|
||||
body
|
||||
|> json.decode(using: dynamic.dynamic)
|
||||
|> result.replace_error(InvalidData)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue