neiam
/lambent_ex
Lambent Aether 5
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
tasmota finder + mix fmt.
664b307 · gmorell · 2023-09-22 02:29
Files changed
modified
Dockerfile
+1
−1
@@ -1,4 +1,4 @@
| 1 | −FROM elixir:alpine | |
| 1 | +FROM elixir:1.14.5-otp-25-alpine | |
| 2 | 2 | MAINTAINER Gabe <gmp@gmp.io> |
| 3 | 3 | ARG app_name=lambent_ex |
| 4 | 4 | ARG phoenix_subdir=. |
modified
lib/lambent_ex/meta.ex
+2
−2
@@ -194,9 +194,9 @@ defmodule LambentEx.Meta do
| 194 | 194 | def handle_info(:svc_pub, state) do |
| 195 | 195 | Process.send_after(self(), :svc_pub, 500) |
| 196 | 196 | |
| 197 | − Phoenix.PubSub.broadcast(@pubsub_name, @pubsub_svc, {:http, get_http}) | |
| 197 | + Phoenix.PubSub.broadcast(@pubsub_name, @pubsub_svc, {:http, get_http()}) | |
| 198 | 198 | # Phoenix.PubSub.broadcast(@pubsub_name, @pubsub_svc, {:mqtt, get_mqtt}) |
| 199 | − Phoenix.PubSub.broadcast(@pubsub_name, @pubsub_svc, {:cronos, get_cronos}) | |
| 199 | + Phoenix.PubSub.broadcast(@pubsub_name, @pubsub_svc, {:cronos, get_cronos()}) | |
| 200 | 200 | {:noreply, state} |
| 201 | 201 | end |
| 202 | 202 |
modified
lib/lambent_ex/mqtt.ex
+208
−4
@@ -1,10 +1,20 @@
| 1 | 1 | defmodule LambentEx.MQTT do |
| 2 | 2 | @moduledoc false |
| 3 | 3 | use GenServer |
| 4 | + require Logger | |
| 5 | + | |
| 6 | + alias LambentEx.Meta, as: M | |
| 7 | + | |
| 4 | 8 | @registry :lambent_mqtt |
| 5 | 9 | @pubsub_name LambentEx.PubSub |
| 6 | 10 | @pubsub_topic_idx "svc_idx" |
| 7 | 11 | |
| 12 | + @mqtt_topic_prefix_tasmota_tele "tele/" | |
| 13 | + @mqtt_topic_prefix_tasmota_cmnd "cmnd/" | |
| 14 | + @mqtt_topic_prefix_tasmota_stat "stat/" | |
| 15 | + | |
| 16 | + @lighting_keywords ["Color", "Fade", "White", "CT", "Dimmer"] | |
| 17 | + | |
| 8 | 18 | def start_link(_args, opts) do |
| 9 | 19 | GenServer.start_link(__MODULE__, opts, name: via_tuple("mqtt-#{opts[:name]}")) |
| 10 | 20 | end |
@@ -12,15 +22,27 @@ defmodule LambentEx.MQTT do
| 12 | 22 | def init(opts) do |
| 13 | 23 | Process.send_after(self(), :publish, 50) |
| 14 | 24 | |
| 15 | − {:ok, opts} = Keyword.validate(opts, [:name, :host, :port, tls: false, persist: false]) | |
| 25 | + {:ok, opts} = | |
| 26 | + Keyword.validate(opts, [:name, :host, :port, tls: false, persist: false, modes: [:tasmota]]) | |
| 16 | 27 | |
| 17 | 28 | {:ok, pid} = |
| 18 | 29 | Tortoise.Connection.start_link( |
| 19 | 30 | client_id: opts[:name], |
| 20 | 31 | server: {Tortoise.Transport.Tcp, host: opts[:host], port: opts[:port]}, |
| 21 | − handler: {Tortoise.Handler.Logger, []} | |
| 32 | + handler: {LambentEx.MQTT.Handler, [id: self()]} | |
| 22 | 33 | ) |
| 23 | 34 | |
| 35 | + cond do | |
| 36 | + Enum.member?(opts[:modes], :tasmota) -> | |
| 37 | + Tortoise.Connection.subscribe(opts[:name], @mqtt_topic_prefix_tasmota_tele <> "#", qos: 0) | |
| 38 | + Tortoise.Connection.subscribe(opts[:name], @mqtt_topic_prefix_tasmota_stat <> "#", qos: 0) | |
| 39 | + Logger.info("Subscribed to tasmota subscriptions") | |
| 40 | + :ok | |
| 41 | + | |
| 42 | + Enum.member?(opts[:modes], :apollos_crib) -> | |
| 43 | + :ok | |
| 44 | + end | |
| 45 | + | |
| 24 | 46 | Tortoise.Events.register(opts[:name], :status) |
| 25 | 47 | Tortoise.Events.register(opts[:name], :ping_response) |
| 26 | 48 |
@@ -32,19 +54,46 @@ defmodule LambentEx.MQTT do
| 32 | 54 | port: opts[:port], |
| 33 | 55 | persist: opts[:persist], |
| 34 | 56 | connected: :down, |
| 35 | − latency: nil | |
| 57 | + latency: nil, | |
| 58 | + modes: opts[:modes], | |
| 59 | + d_sensors: %{}, | |
| 60 | + d_lights: %{} | |
| 36 | 61 | }} |
| 37 | 62 | end |
| 38 | 63 | |
| 39 | 64 | defp via_tuple(name), do: {:via, Registry, {@registry, name}} |
| 40 | 65 | |
| 41 | 66 | # public apis |
| 67 | + def do_poke(name, id) do | |
| 68 | + via_tuple("mqtt-#{name}") | |
| 69 | + |> GenServer.cast({:poke, id}) | |
| 70 | + end | |
| 71 | + | |
| 72 | + def rename(name, id, new) do | |
| 73 | + via_tuple("mqtt-#{name}") | |
| 74 | + |> GenServer.cast({:rename_mac, id, new}) | |
| 75 | + end | |
| 76 | + | |
| 77 | + def replace(name, id, new) do | |
| 78 | + via_tuple("mqtt-#{name}") | |
| 79 | + |> GenServer.cast({:replace, id, new}) | |
| 80 | + end | |
| 42 | 81 | |
| 43 | 82 | # callbacks |
| 83 | + defp add_type(devices, name) do | |
| 84 | + devices |> Enum.map(fn {k, v} -> {k, v |> Map.put("type", "mqtt-tasmota") |> Map.put("dname", name)} end) |> Map.new() | |
| 85 | + end | |
| 86 | + | |
| 44 | 87 | @impl true |
| 45 | 88 | def handle_info(:publish, state) do |
| 46 | 89 | Process.send_after(self(), :publish, 400) |
| 47 | 90 | Phoenix.PubSub.broadcast(@pubsub_name, @pubsub_topic_idx, {:mqtt_pub, publish(state)}) |
| 91 | + | |
| 92 | + Phoenix.PubSub.broadcast( | |
| 93 | + @pubsub_name, | |
| 94 | + "scan-82667777", | |
| 95 | + {:devices_pub, state[:d_lights] |> add_type(state[:name])} | |
| 96 | + ) | |
| 48 | 97 | {:noreply, state} |
| 49 | 98 | end |
| 50 | 99 |
@@ -59,6 +108,44 @@ defmodule LambentEx.MQTT do
| 59 | 108 | end |
| 60 | 109 | |
| 61 | 110 | # impls |
| 111 | + def handle_info({{Tortoise, "hass"}, ref, result}, state) do | |
| 112 | + IO.inspect({"gottem", ref, result}) | |
| 113 | + {:noreply, state} | |
| 114 | + end | |
| 115 | + | |
| 116 | + def handle_info({:light, device, payload}, state) do | |
| 117 | + c = state[:d_lights] | |
| 118 | + d = c |> Map.get(device, %{}) | |
| 119 | + m = Map.merge(d, payload) | |
| 120 | + n = M.get_name(device) | |
| 121 | + p = M.get_place(device) | |
| 122 | + c2 = c |> Map.put(device, m |> Map.put("name", n) |> Map.put("place", p)) | |
| 123 | + {:noreply, state |> Map.put(:d_lights, c2)} | |
| 124 | + end | |
| 125 | + | |
| 126 | + @impl true | |
| 127 | + def handle_cast({:poke, id}, state) do | |
| 128 | + IO.inspect("got poke") | |
| 129 | + {:noreply, state} | |
| 130 | + end | |
| 131 | + | |
| 132 | + @impl true | |
| 133 | + def handle_cast({:rename_mac, device, name}, state) do | |
| 134 | + d = state[:d_lights] |> Map.get(device, %{}) | |
| 135 | + dev = state[:d_lights] |> Map.put(device, d |> Map.put("name", name)) | |
| 136 | + | |
| 137 | + M.put_name(device, name) | |
| 138 | + {:noreply, %{state | d_lights: dev}} | |
| 139 | + end | |
| 140 | + | |
| 141 | + @impl true | |
| 142 | + def handle_cast({:replace, device, place}, state) do | |
| 143 | + d = state[:d_lights] |> Map.get(device, %{}) | |
| 144 | + dev = state[:d_lights] |> Map.put(device, d |> Map.put("place", place)) | |
| 145 | + | |
| 146 | + M.put_place(device, place) | |
| 147 | + {:noreply, %{state | d_lights: dev}} | |
| 148 | + end | |
| 62 | 149 | |
| 63 | 150 | defp publish(state) do |
| 64 | 151 | %{ |
@@ -67,7 +154,124 @@ defmodule LambentEx.MQTT do
| 67 | 154 | port: state[:port], |
| 68 | 155 | persist: state[:persist], |
| 69 | 156 | connected: state[:connected], |
| 70 | − latency: state[:latency] | |
| 157 | + latency: state[:latency], | |
| 158 | + d_lights: state[:d_lights], | |
| 159 | + d_sensors: state[:d_sensors] | |
| 71 | 160 | } |
| 72 | 161 | end |
| 73 | 162 | end |
| 163 | + | |
| 164 | +defmodule LambentEx.MQTT.Handler do | |
| 165 | + @moduledoc false | |
| 166 | + | |
| 167 | + require Logger | |
| 168 | + | |
| 169 | + use Tortoise.Handler | |
| 170 | + | |
| 171 | + defstruct [] | |
| 172 | + alias __MODULE__, as: State | |
| 173 | + | |
| 174 | + def init(opts) do | |
| 175 | + Logger.info("Initializing handler") | |
| 176 | + {:ok, %{id: opts[:id]}} | |
| 177 | + end | |
| 178 | + | |
| 179 | + def connection(:up, state) do | |
| 180 | + Logger.info("Connection has been established") | |
| 181 | + {:ok, state} | |
| 182 | + end | |
| 183 | + | |
| 184 | + def connection(:down, state) do | |
| 185 | + Logger.warn("Connection has been dropped") | |
| 186 | + {:ok, state} | |
| 187 | + end | |
| 188 | + | |
| 189 | + def connection(:terminating, state) do | |
| 190 | + Logger.warn("Connection is terminating") | |
| 191 | + {:ok, state} | |
| 192 | + end | |
| 193 | + | |
| 194 | + def subscription(:up, topic, state) do | |
| 195 | + Logger.info("Subscribed to #{topic}") | |
| 196 | + {:ok, state} | |
| 197 | + end | |
| 198 | + | |
| 199 | + def subscription({:warn, [requested: req, accepted: qos]}, topic, state) do | |
| 200 | + Logger.warn("Subscribed to #{topic}; requested #{req} but got accepted with QoS #{qos}") | |
| 201 | + {:ok, state} | |
| 202 | + end | |
| 203 | + | |
| 204 | + def subscription({:error, reason}, topic, state) do | |
| 205 | + Logger.error("Error subscribing to #{topic}; #{inspect(reason)}") | |
| 206 | + {:ok, state} | |
| 207 | + end | |
| 208 | + | |
| 209 | + def subscription(:down, topic, state) do | |
| 210 | + Logger.info("Unsubscribed from #{topic}") | |
| 211 | + {:ok, state} | |
| 212 | + end | |
| 213 | + | |
| 214 | + def publish_entity(pid, cls, device, payload) do | |
| 215 | + Process.send(pid, {cls, device, payload}, []) | |
| 216 | + end | |
| 217 | + | |
| 218 | + def handle_message(topic, payload, state) do | |
| 219 | + case topic do | |
| 220 | + ["tele", device, "LWT"] -> | |
| 221 | + Logger.info("#{device}: got LWT, queueing discovery work, #{payload}") | |
| 222 | + | |
| 223 | + ["tele", device, "STATE"] -> | |
| 224 | + Logger.info("#{device}: got STATE, updating device states, #{payload}") | |
| 225 | + {:ok, p} = Poison.decode(payload) | |
| 226 | + | |
| 227 | + if is_lighting_entity?(:p, p) do | |
| 228 | + publish_entity(state[:id], :light, device, p) | |
| 229 | + end | |
| 230 | + | |
| 231 | + ["tele", device, "INFO1"] -> | |
| 232 | + Logger.info("#{device}: got INFO1, updating device states, #{payload}") | |
| 233 | + | |
| 234 | + ["tele", device, "INFO2"] -> | |
| 235 | + Logger.info("#{device}: got INFO2, updating device states, #{payload}") | |
| 236 | + | |
| 237 | + ["tele", device, "INFO3"] -> | |
| 238 | + Logger.info("#{device}: got INFO3, updating device states, #{payload}") | |
| 239 | + | |
| 240 | + ["stat", device, "POWER"] -> | |
| 241 | + Logger.info("#{device}: got POWER message, #{payload}") | |
| 242 | + | |
| 243 | + ["stat", device, "RESULT"] -> | |
| 244 | + Logger.info("#{device}: got RESULT message, #{payload}") | |
| 245 | + {:ok, p} = Poison.decode(payload) | |
| 246 | + | |
| 247 | + if is_lighting_entity?(:p, p) do | |
| 248 | + publish_entity(state[:id], :light, device, p) | |
| 249 | + end | |
| 250 | + _otherwise -> | |
| 251 | + Logger.debug("xxx---#{Enum.join(topic, "/")} #{inspect(payload)}") | |
| 252 | + end | |
| 253 | + | |
| 254 | + {:ok, state} | |
| 255 | + end | |
| 256 | + | |
| 257 | + def terminate(reason, _state) do | |
| 258 | + Logger.warn("Client has been terminated with reason: #{inspect(reason)}") | |
| 259 | + :ok | |
| 260 | + end | |
| 261 | + | |
| 262 | + def is_lighting_entity?(:t, topic) do | |
| 263 | + "tasmota" in topic and | |
| 264 | + Enum.any?(@lighting_keywords, fn keyword -> String.member?(topic, keyword) end) | |
| 265 | + end | |
| 266 | + | |
| 267 | + def is_lighting_entity?(:p, payload) do | |
| 268 | + keys = payload |> Map.keys() | |
| 269 | + | |
| 270 | + Enum.any?(["Color", "Fade", "White", "CT", "Dimmer"], fn keyword -> | |
| 271 | + Enum.member?(keys, keyword) | |
| 272 | + end) | |
| 273 | + end | |
| 274 | + | |
| 275 | + def is_sensor_entity(:p, payload) do | |
| 276 | + end | |
| 277 | +end |
modified
lib/lambent_ex/util/color.ex
+7
−7
@@ -122,13 +122,13 @@ defmodule LambentEx.Utils.Color do
| 122 | 122 | def cy, do: [192, 192, 0] |
| 123 | 123 | def ck, do: [0, 0, 0] |
| 124 | 124 | |
| 125 | − def cre, do: cr |> Stream.cycle() |> Enum.take(900) | |
| 126 | − def cge, do: cg |> Stream.cycle() |> Enum.take(900) | |
| 127 | − def cbe, do: cb |> Stream.cycle() |> Enum.take(900) | |
| 128 | − def cce, do: cc |> Stream.cycle() |> Enum.take(900) | |
| 129 | − def cme, do: cm |> Stream.cycle() |> Enum.take(900) | |
| 130 | − def cye, do: cy |> Stream.cycle() |> Enum.take(900) | |
| 131 | − def cke, do: ck |> Stream.cycle() |> Enum.take(900) | |
| 125 | + def cre, do: cr() |> Stream.cycle() |> Enum.take(900) | |
| 126 | + def cge, do: cg() |> Stream.cycle() |> Enum.take(900) | |
| 127 | + def cbe, do: cb() |> Stream.cycle() |> Enum.take(900) | |
| 128 | + def cce, do: cc() |> Stream.cycle() |> Enum.take(900) | |
| 129 | + def cme, do: cm() |> Stream.cycle() |> Enum.take(900) | |
| 130 | + def cye, do: cy() |> Stream.cycle() |> Enum.take(900) | |
| 131 | + def cke, do: ck() |> Stream.cycle() |> Enum.take(900) | |
| 132 | 132 | |
| 133 | 133 | # misc |
| 134 | 134 | def hex(val) when is_float(val), do: trunc(val) |> hex |
modified
lib/lambent_ex_web/live/devices/index.ex
+36
−7
@@ -27,16 +27,22 @@ defmodule LambentExWeb.DevicesLive.Index do
| 27 | 27 | |
| 28 | 28 | @impl true |
| 29 | 29 | def handle_info({:devices_pub, devices}, socket) do |
| 30 | − {:noreply, socket |> assign(:devices, devices)} | |
| 30 | + n = Map.merge(socket.assigns.devices, devices) | |
| 31 | + {:noreply, socket |> assign(:devices, n)} | |
| 31 | 32 | end |
| 32 | 33 | |
| 33 | 34 | @impl true |
| 34 | 35 | def handle_info({:rename, data}, socket) do |
| 35 | 36 | id = data["id"] |> String.trim_trailing("name") |> String.trim_trailing("namem") |
| 37 | + name = data["data"] |> re_nil | |
| 36 | 38 | |
| 37 | 39 | case socket.assigns[:devices] |> Map.get(id) |> Map.get("type") do |
| 38 | 40 | "8266-7777" -> |
| 39 | − LambentEx.Scan.ESP8266x7777.rename(:mac, id, data["data"] |> re_nil) | |
| 41 | + LambentEx.Scan.ESP8266x7777.rename(:mac, id, name) | |
| 42 | + | |
| 43 | + "mqtt-tasmota" -> | |
| 44 | + xname = socket.assigns[:devices] |> Map.get(id) |> Map.get("dname") | |
| 45 | + LambentEx.MQTT.rename(xname, id, name) | |
| 40 | 46 | |
| 41 | 47 | type -> |
| 42 | 48 | Logger.info("Need handler for this type: #{type}") |
@@ -66,11 +72,15 @@ defmodule LambentExWeb.DevicesLive.Index do
| 66 | 72 | @impl true |
| 67 | 73 | def handle_info({:replace, data}, socket) do |
| 68 | 74 | id = data["id"] |> String.trim_trailing("place") |> String.trim_trailing("placem") |
| 69 | − IO.inspect(id) | |
| 75 | + pl = data["data"] |> re_nil | |
| 70 | 76 | |
| 71 | 77 | case socket.assigns[:devices] |> Map.get(id) |> Map.get("type") do |
| 72 | 78 | "8266-7777" -> |
| 73 | − LambentEx.Scan.ESP8266x7777.replace(:mac, id, data["data"] |> re_nil) | |
| 79 | + LambentEx.Scan.ESP8266x7777.replace(:mac, id, pl) | |
| 80 | + | |
| 81 | + "mqtt-tasmota" -> | |
| 82 | + xname = socket.assigns[:devices] |> Map.get(id) |> Map.get("dname") | |
| 83 | + LambentEx.MQTT.replace(xname, id, pl) | |
| 74 | 84 | |
| 75 | 85 | type -> |
| 76 | 86 | Logger.info("Need handler for this type: #{type}") |
@@ -81,11 +91,26 @@ defmodule LambentExWeb.DevicesLive.Index do
| 81 | 91 | end |
| 82 | 92 | |
| 83 | 93 | @impl true |
| 84 | − def handle_event("poke", %{"id" => id}, socket) do | |
| 94 | + def handle_event("poke", %{"id" => id, "dname" => name}, socket) do | |
| 85 | 95 | case socket.assigns[:devices] |> Map.get(id) |> Map.get("type") do |
| 86 | 96 | "8266-7777" -> |
| 87 | 97 | LambentEx.Scan.ESP8266x7777.poke(id) |
| 98 | + "mqtt-tasmota" -> | |
| 99 | + LambentEx.MQTT.do_poke(name, id) | |
| 100 | + | |
| 101 | + type -> | |
| 102 | + Logger.info("Need handler for this type: #{type}") | |
| 103 | + :ok | |
| 104 | + end | |
| 105 | + | |
| 106 | + {:noreply, socket} | |
| 107 | + end | |
| 88 | 108 | |
| 109 | + @impl true | |
| 110 | + def handle_event("poke", %{"id" => id}, socket) do | |
| 111 | + case socket.assigns[:devices] |> Map.get(id) |> Map.get("type") do | |
| 112 | + "8266-7777" -> | |
| 113 | + LambentEx.Scan.ESP8266x7777.poke(id) | |
| 89 | 114 | type -> |
| 90 | 115 | Logger.info("Need handler for this type: #{type}") |
| 91 | 116 | :ok |
@@ -101,8 +126,12 @@ defmodule LambentExWeb.DevicesLive.Index do
| 101 | 126 | |> assign(:source, nil) |
| 102 | 127 | end |
| 103 | 128 | |
| 104 | − defp pip(ip) do | |
| 105 | − ip |> Tuple.to_list() |> Enum.join(".") | |
| 129 | + defp pip(map) do | |
| 130 | + cond do | |
| 131 | + Map.has_key?(map, "ip") -> Map.get(map, "ip") |> Tuple.to_list() |> Enum.join(".") | |
| 132 | + Map.has_key?(map, "POWER") -> "MQTT" | |
| 133 | + end | |
| 134 | + | |
| 106 | 135 | end |
| 107 | 136 | |
| 108 | 137 | defp options() do |
modified
lib/lambent_ex_web/live/devices/index.html.heex
+3
−3
@@ -23,7 +23,7 @@
| 23 | 23 | <%= for {k,v} <- @devices do %> |
| 24 | 24 | <tr> |
| 25 | 25 | <th><%= k %></th> |
| 26 | − <td><%= v["ip"] |> pip %></td> | |
| 26 | + <td><%= v |> pip %></td> | |
| 27 | 27 | <td> |
| 28 | 28 | <.live_editable |
| 29 | 29 | id={k <> "name"} |
@@ -52,7 +52,7 @@
| 52 | 52 | </td> |
| 53 | 53 | <td><%= v["type"] %></td> |
| 54 | 54 | <td> |
| 55 | − <button class="btn btn-circle" phx-click="poke" phx-value-id={k}> | |
| 55 | + <button class="btn btn-circle" phx-click="poke" phx-value-id={k} phx-value-dname={v["dname"]}> | |
| 56 | 56 | <i class="fa-solid fa-bolt-lightning"></i> |
| 57 | 57 | </button> |
| 58 | 58 | </td> |
@@ -91,7 +91,7 @@
| 91 | 91 | /> |
| 92 | 92 | </h2> |
| 93 | 93 | <p> |
| 94 | − <%= v["ip"] |> pip %> <i class="fa-solid fa-at"></i> <%= k %> | |
| 94 | + <%= v |> pip %> <i class="fa-solid fa-at"></i> <%= k %> | |
| 95 | 95 | |
| 96 | 96 | <div class="badge badge-lg badge-secondary"> |
| 97 | 97 | <i class="fa-solid fa-fw fa-traffic-light fa-spin"></i> |
modified
lib/lambent_ex_web/live/machines/form_component.html.heex
+1
−1
@@ -21,7 +21,7 @@
| 21 | 21 | field={{f, :type}} |
| 22 | 22 | type="select" |
| 23 | 23 | label="Machine Type" |
| 24 | − options={values} | |
| 24 | + options={values()} | |
| 25 | 25 | prompt="Select" |
| 26 | 26 | /> |
| 27 | 27 | </div> |
modified
lib/lambent_ex_web/live/services/index.html.heex
+2
−0
@@ -60,6 +60,8 @@
| 60 | 60 | <%= if v[:latency] do %> |
| 61 | 61 | <i class="fa-solid fa-table-tennis-paddle-ball"></i> <%= v[:latency] %> ms |
| 62 | 62 | <% end %> |
| 63 | + lights: | |
| 64 | + <%= v[:d_lights] |> Map.keys |> length %> | |
| 63 | 65 | <% else %> |
| 64 | 66 | down |
| 65 | 67 | <% end %> |
modified
mix.lock
+4
−4
@@ -4,8 +4,8 @@
| 4 | 4 | "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"}, |
| 5 | 5 | "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"}, |
| 6 | 6 | "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, |
| 7 | − "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, | |
| 8 | − "ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"}, | |
| 7 | + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, | |
| 8 | + "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, | |
| 9 | 9 | "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"}, |
| 10 | 10 | "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, |
| 11 | 11 | "floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"}, |
@@ -15,7 +15,7 @@
| 15 | 15 | "heroicons": {:hex, :heroicons, "0.5.1", "cca0dcca07af5f74d8a7d111e40418d3615d65e6773c0ea10e20cef070fd30aa", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.2", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "4b096d0a1d50e9054df9b12cc637c9f65c3972ff086791d3f2d1846f0653117e"}, |
| 16 | 16 | "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, |
| 17 | 17 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, |
| 18 | − "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, | |
| 18 | + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, | |
| 19 | 19 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, |
| 20 | 20 | "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, |
| 21 | 21 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, |
@@ -39,7 +39,7 @@
| 39 | 39 | "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, |
| 40 | 40 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, |
| 41 | 41 | "tailwind": {:hex, :tailwind, "0.1.9", "25ba09d42f7bfabe170eb67683a76d6ec2061952dc9bd263a52a99ba3d24bd4d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "9213f87709c458aaec313bb5f2df2b4d2cedc2b630e4ae821bf3c54c47a56d0b"}, |
| 42 | − "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, | |
| 42 | + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, | |
| 43 | 43 | "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, |
| 44 | 44 | "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, |
| 45 | 45 | "tortoise": {:hex, :tortoise, "0.10.0", "d8821b9a5052355ca61d9fa784652f435997fa98b210f88565c98f3494605efd", [:mix], [{:gen_state_machine, "~> 2.0 or ~> 3.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}], "hexpm", "926d97b03cd0d2f6e0e1ebf06c1efa50101de96d0317dea5604c1c9e43e66735"}, |
Parents: 6792fd8