neiam
/lambent_ex
Lambent Aether 5
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions success · Badges · Packages
Log in to mark this repository.
Clamp machine output globally and per place
81f5281 · Gabriel Morell · 2026-09-16 21:19
Message
{commit_body(@commit)}
Files changed
added
lib/lambent_ex/brightness.ex
+255
−0
| 1 | +defmodule LambentEx.Brightness do | |
| 2 | + @moduledoc """ | |
| 3 | + The global and per-place brightness clamps. | |
| 4 | + | |
| 5 | + Both are ceilings rather than levels: a machine's own brightness walk still | |
| 6 | + runs, and what it produces is then scaled by the global clamp on its way out | |
| 7 | + of the machine, and by the target device's place clamp on its way through the | |
| 8 | + link. Everything sits at 255 (unclamped) unless something moves it. | |
| 9 | + | |
| 10 | + Targets walk down to their current value the way a machine's brightness does, | |
| 11 | + one step at a time on this server's own clock, so a clamp change fades rather | |
| 12 | + than snapping. | |
| 13 | + | |
| 14 | + Current values live in a public ETS table so the hot paths — every machine | |
| 15 | + step, every link publish — read them without a call into this process. | |
| 16 | + """ | |
| 17 | + use GenServer | |
| 18 | + require Logger | |
| 19 | + | |
| 20 | + alias LambentEx.Utils.Brightness, as: B | |
| 21 | + alias LambentEx.Meta, as: M | |
| 22 | + | |
| 23 | + @table :lambent_brightness | |
| 24 | + @pubsub_name LambentEx.PubSub | |
| 25 | + @pubsub_topic "brightness" | |
| 26 | + | |
| 27 | + # how often a walk moves. how far it moves per tick comes from that clamp's | |
| 28 | + # own fade rate, so this is just the resolution of the fade | |
| 29 | + @tick 20 | |
| 30 | + @publish 400 | |
| 31 | + | |
| 32 | + @s "🔆" | |
| 33 | + | |
| 34 | + def start_link(arg) do | |
| 35 | + GenServer.start_link(__MODULE__, arg, name: __MODULE__) | |
| 36 | + end | |
| 37 | + | |
| 38 | + @impl true | |
| 39 | + def init(_arg) do | |
| 40 | + table = :ets.new(@table, [:set, :named_table, :protected, read_concurrency: true]) | |
| 41 | + | |
| 42 | + saved = M.get_brightness() | |
| 43 | + | |
| 44 | + global = saved |> Map.get(:global) |> restore() | |
| 45 | + | |
| 46 | + places = | |
| 47 | + saved | |
| 48 | + |> Map.get(:places, %{}) | |
| 49 | + |> Enum.map(fn {place, val} -> {place, restore(val)} end) | |
| 50 | + |> Map.new() | |
| 51 | + | |
| 52 | + # restored clamps start where they left off rather than fading in from full | |
| 53 | + :ets.insert(table, {:global, global.curr}) | |
| 54 | + places |> Enum.each(fn {place, w} -> :ets.insert(table, {{:place, place}, w.curr}) end) | |
| 55 | + | |
| 56 | + if global.curr != B.full() or places != %{} do | |
| 57 | + Logger.info( | |
| 58 | + "#{@s} Restored a global clamp of #{global.curr} and #{map_size(places)} place(s)" | |
| 59 | + ) | |
| 60 | + end | |
| 61 | + | |
| 62 | + Process.send_after(self(), :tick, @tick) | |
| 63 | + Process.send_after(self(), :publish, @publish) | |
| 64 | + | |
| 65 | + {:ok, %{table: table, global: global, places: places}} | |
| 66 | + end | |
| 67 | + | |
| 68 | + # acc carries the fraction of a step a slow fade did not get to move this | |
| 69 | + # tick, so a five minute walk still moves rather than rounding to nothing | |
| 70 | + defp walk(val, rate \\ B.default_fade()), | |
| 71 | + do: %{tgt: val, curr: val, rate: B.fade_clamp(rate), acc: 0.0} | |
| 72 | + | |
| 73 | + # a saved clamp was a bare target before fade rates existed | |
| 74 | + defp restore(nil), do: walk(B.full()) | |
| 75 | + defp restore(val) when is_integer(val), do: walk(B.clamp(val)) | |
| 76 | + | |
| 77 | + defp restore(%{} = saved), | |
| 78 | + do: walk(B.clamp(Map.get(saved, :tgt)), Map.get(saved, :rate, B.default_fade())) | |
| 79 | + | |
| 80 | + # reads -- straight off the table, no call into the server | |
| 81 | + | |
| 82 | + @doc "The global clamp, 0..255. Full brightness before the server is up." | |
| 83 | + def global do | |
| 84 | + read(:global) | |
| 85 | + end | |
| 86 | + | |
| 87 | + @doc "The clamp for a place. Devices without a place are never clamped." | |
| 88 | + def place(nil), do: B.full() | |
| 89 | + def place(place), do: read({:place, place}) | |
| 90 | + | |
| 91 | + defp read(key) do | |
| 92 | + case :ets.whereis(@table) do | |
| 93 | + :undefined -> | |
| 94 | + B.full() | |
| 95 | + | |
| 96 | + tid -> | |
| 97 | + case :ets.lookup(tid, key) do | |
| 98 | + [{^key, val}] -> val | |
| 99 | + [] -> B.full() | |
| 100 | + end | |
| 101 | + end | |
| 102 | + end | |
| 103 | + | |
| 104 | + # writes | |
| 105 | + | |
| 106 | + def set_global(val), do: GenServer.cast(__MODULE__, {:set_global, val}) | |
| 107 | + def brighter, do: GenServer.cast(__MODULE__, {:step_global, :up}) | |
| 108 | + def dimmer, do: GenServer.cast(__MODULE__, {:step_global, :down}) | |
| 109 | + | |
| 110 | + def set_place(place, val), do: GenServer.cast(__MODULE__, {:set_place, place, val}) | |
| 111 | + | |
| 112 | + @doc "How long a full 0..255 walk takes for this clamp, in ms. 0 snaps." | |
| 113 | + def set_global_rate(rate), do: GenServer.cast(__MODULE__, {:set_global_rate, rate}) | |
| 114 | + | |
| 115 | + def set_place_rate(place, rate), | |
| 116 | + do: GenServer.cast(__MODULE__, {:set_place_rate, place, rate}) | |
| 117 | + | |
| 118 | + def brighter(place), do: GenServer.cast(__MODULE__, {:step_place, place, :up}) | |
| 119 | + def dimmer(place), do: GenServer.cast(__MODULE__, {:step_place, place, :down}) | |
| 120 | + | |
| 121 | + @doc "Drop a place's clamp entirely, which is the same as leaving it at full." | |
| 122 | + def clear_place(place), do: GenServer.cast(__MODULE__, {:clear_place, place}) | |
| 123 | + | |
| 124 | + @doc "Everything at once, for a view that has just mounted." | |
| 125 | + def snapshot, do: GenServer.call(__MODULE__, :snapshot) | |
| 126 | + | |
| 127 | + # impls | |
| 128 | + | |
| 129 | + @impl true | |
| 130 | + def handle_cast({:set_global, val}, state) do | |
| 131 | + {:noreply, state |> put_in([:global, :tgt], B.clamp(val)) |> flush()} | |
| 132 | + end | |
| 133 | + | |
| 134 | + @impl true | |
| 135 | + def handle_cast({:step_global, dir}, state) do | |
| 136 | + {:noreply, state |> put_in([:global, :tgt], rung(dir, state.global.tgt)) |> flush()} | |
| 137 | + end | |
| 138 | + | |
| 139 | + @impl true | |
| 140 | + def handle_cast({:set_global_rate, rate}, state) do | |
| 141 | + {:noreply, state |> put_in([:global, :rate], B.fade_clamp(rate)) |> flush()} | |
| 142 | + end | |
| 143 | + | |
| 144 | + @impl true | |
| 145 | + def handle_cast({:set_place_rate, place, rate}, state) do | |
| 146 | + w = state.places |> Map.get(place, walk(B.full())) |> Map.put(:rate, B.fade_clamp(rate)) | |
| 147 | + {:noreply, %{state | places: Map.put(state.places, place, w)} |> flush()} | |
| 148 | + end | |
| 149 | + | |
| 150 | + @impl true | |
| 151 | + def handle_cast({:set_place, place, val}, state) do | |
| 152 | + {:noreply, state |> put_place(place, B.clamp(val)) |> flush()} | |
| 153 | + end | |
| 154 | + | |
| 155 | + @impl true | |
| 156 | + def handle_cast({:step_place, place, dir}, state) do | |
| 157 | + curr = state.places |> Map.get(place, walk(B.full())) | |
| 158 | + {:noreply, state |> put_place(place, rung(dir, curr.tgt)) |> flush()} | |
| 159 | + end | |
| 160 | + | |
| 161 | + @impl true | |
| 162 | + def handle_cast({:clear_place, place}, state) do | |
| 163 | + :ets.delete(state.table, {:place, place}) | |
| 164 | + {:noreply, %{state | places: Map.delete(state.places, place)} |> flush()} | |
| 165 | + end | |
| 166 | + | |
| 167 | + @impl true | |
| 168 | + def handle_cast(_msg, state), do: {:noreply, state} | |
| 169 | + | |
| 170 | + @impl true | |
| 171 | + def handle_call(:snapshot, _from, state) do | |
| 172 | + {:reply, publish(state), state} | |
| 173 | + end | |
| 174 | + | |
| 175 | + @impl true | |
| 176 | + def handle_call(_msg, _from, state), do: {:reply, :ok, state} | |
| 177 | + | |
| 178 | + defp rung(:up, val), do: B.up(val) | |
| 179 | + defp rung(:down, val), do: B.down(val) | |
| 180 | + | |
| 181 | + # a place picks up a walk the first time it is clamped, starting from full | |
| 182 | + defp put_place(state, place, tgt) do | |
| 183 | + w = state.places |> Map.get(place, walk(B.full())) |> Map.put(:tgt, tgt) | |
| 184 | + %{state | places: Map.put(state.places, place, w)} | |
| 185 | + end | |
| 186 | + | |
| 187 | + @impl true | |
| 188 | + def handle_info(:tick, state) do | |
| 189 | + Process.send_after(self(), :tick, @tick) | |
| 190 | + | |
| 191 | + global = step(state.global) | |
| 192 | + if global.curr != state.global.curr, do: :ets.insert(state.table, {:global, global.curr}) | |
| 193 | + | |
| 194 | + places = | |
| 195 | + state.places | |
| 196 | + |> Enum.map(fn {place, w} -> | |
| 197 | + stepped = step(w) | |
| 198 | + | |
| 199 | + if stepped.curr != w.curr, | |
| 200 | + do: :ets.insert(state.table, {{:place, place}, stepped.curr}) | |
| 201 | + | |
| 202 | + {place, stepped} | |
| 203 | + end) | |
| 204 | + |> Map.new() | |
| 205 | + | |
| 206 | + {:noreply, %{state | global: global, places: places}} | |
| 207 | + end | |
| 208 | + | |
| 209 | + @impl true | |
| 210 | + def handle_info(:publish, state) do | |
| 211 | + Process.send_after(self(), :publish, @publish) | |
| 212 | + Phoenix.PubSub.broadcast(@pubsub_name, @pubsub_topic, {:brightness_pub, publish(state)}) | |
| 213 | + {:noreply, state} | |
| 214 | + end | |
| 215 | + | |
| 216 | + @impl true | |
| 217 | + def handle_info(_msg, state), do: {:noreply, state} | |
| 218 | + | |
| 219 | + defp step(%{tgt: tgt, curr: curr} = w) when tgt == curr, do: %{w | acc: 0.0} | |
| 220 | + | |
| 221 | + # a zero fade is a snap, and skips the accumulator entirely | |
| 222 | + defp step(%{rate: 0} = w), do: %{w | curr: w.tgt, acc: 0.0} | |
| 223 | + | |
| 224 | + defp step(w) do | |
| 225 | + acc = w.acc + @tick * B.full() / w.rate | |
| 226 | + units = trunc(acc) | |
| 227 | + %{w | curr: toward(w.curr, w.tgt, units), acc: acc - units} | |
| 228 | + end | |
| 229 | + | |
| 230 | + defp toward(curr, tgt, units) when curr < tgt, do: min(curr + units, tgt) | |
| 231 | + defp toward(curr, tgt, units), do: max(curr - units, tgt) | |
| 232 | + | |
| 233 | + defp publish(state) do | |
| 234 | + %{ | |
| 235 | + global: state.global |> shown(), | |
| 236 | + places: state.places |> Enum.map(fn {k, w} -> {k, shown(w)} end) |> Map.new() | |
| 237 | + } | |
| 238 | + end | |
| 239 | + | |
| 240 | + defp shown(w), do: %{tgt: w.tgt, curr: w.curr, rate: w.rate} | |
| 241 | + | |
| 242 | + # only the targets are worth keeping -- a restart that lands mid-walk should | |
| 243 | + # come up where the clamp was headed, not where it happened to be | |
| 244 | + defp flush(state) do | |
| 245 | + M.put_brightness(%{ | |
| 246 | + global: %{tgt: state.global.tgt, rate: state.global.rate}, | |
| 247 | + places: | |
| 248 | + state.places | |
| 249 | + |> Enum.map(fn {k, w} -> {k, %{tgt: w.tgt, rate: w.rate}} end) | |
| 250 | + |> Map.new() | |
| 251 | + }) | |
| 252 | + | |
| 253 | + state | |
| 254 | + end | |
| 255 | +end |
modified
lib/lambent_ex/cronos.ex
+33
−6
| 11 | 11 | |
| 12 | 12 | Process.send_after(self(), :work, :timer.seconds(60 - DateTime.utc_now().second + 1)) |
| 13 | 13 | {zone, result} = System.cmd("date", ["+%Z"]) |
| 14 | − zone = zone |> String.trim | |
| 15 | − zone = case zone do | |
| 16 | − "EDT" -> "America/New_York" | |
| 17 | − _otherwise -> zone | |
| 18 | − end | |
| 14 | + zone = zone |> String.trim() | |
| 15 | + | |
| 16 | + zone = | |
| 17 | + case zone do | |
| 18 | + "EDT" -> "America/New_York" | |
| 19 | + _otherwise -> zone | |
| 20 | + end | |
| 19 | 21 | |
| 20 | 22 | Logger.info( |
| 21 | − "#{@s} Cronos uses the system timezone to determine when to tick and when to sleep, we've found this to be '#{zone}'" | |
| 23 | + "#{@s} Cronos uses the system timezone to determine when to tick and when to sleep, we've found this to be '#{zone}'" | |
| 22 | 24 | ) |
| 25 | + | |
| 23 | 26 | if result == 0 do |
| 24 | 27 | {:ok, %{tz: zone |> String.trim()}} |
| 25 | 28 | else |
| 69 | 72 | i[:targets] |> Enum.map(fn t -> LambentEx.Machine.set_brightness(t, otherwise) end) |
| 70 | 73 | end |
| 71 | 74 | |
| 75 | + # the global clamp has no targets, it is the one ceiling over everything | |
| 76 | + :global_bright -> | |
| 77 | + case i[:arg] do | |
| 78 | + :dimmer -> LambentEx.Brightness.dimmer() | |
| 79 | + :brighter -> LambentEx.Brightness.brighter() | |
| 80 | + otherwise -> LambentEx.Brightness.set_global(otherwise) | |
| 81 | + end | |
| 82 | + | |
| 83 | + # targets here are places, not machines | |
| 84 | + :place_bright -> | |
| 85 | + case i[:arg] do | |
| 86 | + :dimmer -> | |
| 87 | + i[:targets] |> Enum.map(&LambentEx.Brightness.dimmer/1) | |
| 88 | + | |
| 89 | + :brighter -> | |
| 90 | + i[:targets] |> Enum.map(&LambentEx.Brightness.brighter/1) | |
| 91 | + | |
| 92 | + otherwise -> | |
| 93 | + i[:targets] |> Enum.map(fn t -> LambentEx.Brightness.set_place(t, otherwise) end) | |
| 94 | + end | |
| 95 | + | |
| 72 | 96 | :link_toggle -> |
| 73 | 97 | i[:targets] |> Enum.map(&LambentEx.Link.toggle/1) |
| 74 | 98 | end |
| 83 | 107 | # LambentEx.Meta.put_cronos("testb", %{time: ~T[23:11:00], action: :machine_speed, arg: :faster, targets: ["red"]}) |
| 84 | 108 | # LambentEx.Meta.put_cronos("testc", %{time: ~T[23:10:00], action: :machine_bright, arg: :dimmer, targets: ["red"]}) |
| 85 | 109 | # LambentEx.Meta.put_cronos("testd", %{time: ~T[23:12:00], action: :machine_bright, arg: :brighter, targets: ["red"]}) |
| 110 | +# LambentEx.Meta.put_cronos("teste", %{time: ~T[23:00:00], action: :global_bright, arg: 63, targets: []}) | |
| 111 | +# LambentEx.Meta.put_cronos("testf", %{time: ~T[07:00:00], action: :global_bright, arg: 255, targets: []}) | |
| 112 | +# LambentEx.Meta.put_cronos("testg", %{time: ~T[22:00:00], action: :place_bright, arg: :dimmer, targets: ["bedroom"]}) |
modified
lib/lambent_ex/link.ex
+11
−2
| 9 | 9 | |
| 10 | 10 | @registry :lambent_links |
| 11 | 11 | |
| 12 | + alias LambentEx.Brightness | |
| 13 | + alias LambentEx.Meta, as: M | |
| 14 | + alias LambentEx.Utils.Brightness, as: B | |
| 15 | + | |
| 12 | 16 | def start_link(_args, opts) do |
| 13 | 17 | GenServer.start_link(__MODULE__, opts, name: via_tuple("link-#{opts[:name]}")) |
| 14 | 18 | end |
| 26 | 30 | name: opts[:name], |
| 27 | 31 | chan: opts[:chan], |
| 28 | 32 | enabled: true, |
| 33 | + # the target device's place, for the place clamp. refreshed on the | |
| 34 | + # publish tick so a device moving rooms does not need a link restart | |
| 35 | + place: M.get_place(opts[:target]), | |
| 29 | 36 | started: DateTime.utc_now(), |
| 30 | 37 | persist: opts[:persist] || false, |
| 31 | 38 | full_opts: opts |
| 90 | 97 | def handle_info({:publish, data}, state) do |
| 91 | 98 | case state[:enabled] do |
| 92 | 99 | true -> |
| 100 | + # the machine has already applied its own brightness and the global | |
| 101 | + # clamp; the place clamp is the last thing between here and the wire | |
| 93 | 102 | LambentEx.Scan.ESP8266x7777.submit( |
| 94 | 103 | state[:target], |
| 95 | − data |> List.flatten(), | |
| 104 | + data |> List.flatten() |> B.scale(Brightness.place(state[:place])), | |
| 96 | 105 | state[:chan] |
| 97 | 106 | ) |
| 98 | 107 |
| 107 | 116 | def handle_info(:publish, state) do |
| 108 | 117 | Process.send_after(self(), :publish, 400) |
| 109 | 118 | Phoenix.PubSub.broadcast(@pubsub_name, @pubsub_topic_idx, {:links_pub, publish(state)}) |
| 110 | − {:noreply, state} | |
| 119 | + {:noreply, %{state | place: M.get_place(state[:target])}} | |
| 111 | 120 | end |
| 112 | 121 | |
| 113 | 122 | @impl true |
modified
lib/lambent_ex/machine.ex
+15
−38
| 9 | 9 | @pubsub_topic_fh "machine:" |
| 10 | 10 | @pubsub_topic_idx "machines_idx" |
| 11 | 11 | |
| 12 | + alias LambentEx.Utils.Brightness, as: B | |
| 13 | + | |
| 12 | 14 | @speeds %{ |
| 13 | 15 | # 1 => :THOU, |
| 14 | 16 | # 5 => :FTHOU, |
| 25 | 27 | 60000 => :MINS |
| 26 | 28 | } |
| 27 | 29 | |
| 28 | − @brightness %{ | |
| 29 | − 0 => :OFF, | |
| 30 | − # 1 => :XDIM, | |
| 31 | − 3 => :SIXTYFOURTH, | |
| 32 | − 7 => :THIRTY2ND, | |
| 33 | − 15 => :SIXTEENTH, | |
| 34 | − 31 => :EIGHTH, | |
| 35 | − 63 => :QUARTER, | |
| 36 | − 127 => :HALF, | |
| 37 | − # 191 => :THREEFOURTHS | |
| 38 | − 255 => :FULL | |
| 39 | − } | |
| 40 | − | |
| 41 | 30 | # check to see if machine-opts[:step]-name is better in machine-name |
| 42 | 31 | def start_link(_foo, opts) do |
| 43 | 32 | Parent.GenServer.start_link(__MODULE__, opts, name: via_tuple("machine-#{opts[:name]}")) |
| 110 | 99 | speed: opts[:speed], |
| 111 | 100 | bright_tgt: 255, |
| 112 | 101 | bright_curr: 255, |
| 102 | + # the global clamp, re-read once per step rather than per pixel | |
| 103 | + bright_global: 255, | |
| 113 | 104 | bright_delay: 0, |
| 114 | 105 | bright_mvmul: 2, |
| 115 | 106 | cnt: cnt, |
| 176 | 167 | end |
| 177 | 168 | |
| 178 | 169 | defp bmath(i, state) do |
| 179 | − (i * state.bright_curr / 255.0) |> round | |
| 170 | + (i * state.bright_curr * state.bright_global / 65_025.0) |> round | |
| 180 | 171 | end |
| 181 | 172 | |
| 182 | 173 | def stripstep(step) do |
| 239 | 230 | @impl true |
| 240 | 231 | def handle_info(:step, state) do |
| 241 | 232 | Process.send_after(self(), :step, state.speed) |
| 233 | + state = state |> Map.put(:bright_global, LambentEx.Brightness.global()) | |
| 242 | 234 | |
| 243 | 235 | state[:steps] |
| 244 | 236 | |> Enum.map(&GenServer.cast(&1, :step)) |
| 403 | 395 | end |
| 404 | 396 | end |
| 405 | 397 | |
| 406 | − defp do_brighter(current_val) do | |
| 407 | − sl = (@brightness |> Map.keys() |> length) - 1 | |
| 408 | − | |
| 409 | − case @brightness |> Map.keys() |> Enum.find_index(fn x -> x == current_val end) do | |
| 410 | − ^sl -> | |
| 411 | − current_val | |
| 412 | − | |
| 413 | − otherwise -> | |
| 414 | − @brightness |> Map.keys() |> Enum.fetch!(otherwise + 1) | |
| 415 | − end | |
| 416 | − end | |
| 417 | − | |
| 418 | − defp do_dimmer(current_val) do | |
| 419 | − case @brightness |> Map.keys() |> Enum.find_index(fn x -> x == current_val end) do | |
| 420 | − 0 -> | |
| 421 | − current_val | |
| 422 | − | |
| 423 | − otherwise -> | |
| 424 | − @brightness |> Map.keys() |> Enum.fetch!(otherwise - 1) | |
| 425 | − end | |
| 426 | − end | |
| 427 | − | |
| 428 | 398 | defp do_bright_step(tgt, curr) do |
| 429 | 399 | if curr < tgt do |
| 430 | 400 | curr + 1 |
| 483 | 453 | |
| 484 | 454 | @impl true |
| 485 | 455 | def handle_cast(:brighter, state) do |
| 486 | − {:noreply, state |> Map.put(:bright_tgt, do_brighter(state.bright_tgt))} | |
| 456 | + {:noreply, state |> Map.put(:bright_tgt, B.up(state.bright_tgt))} | |
| 487 | 457 | end |
| 488 | 458 | |
| 489 | 459 | @impl true |
| 490 | 460 | def handle_cast(:dimmer, state) do |
| 491 | − {:noreply, state |> Map.put(:bright_tgt, do_dimmer(state.bright_tgt))} | |
| 461 | + {:noreply, state |> Map.put(:bright_tgt, B.down(state.bright_tgt))} | |
| 462 | + end | |
| 463 | + | |
| 464 | + # cronos has been able to ask for this since it was written, but the cast | |
| 465 | + # had no clause and fell through to the catch-all below | |
| 466 | + @impl true | |
| 467 | + def handle_cast({:set_brightness, val}, state) do | |
| 468 | + {:noreply, state |> Map.put(:bright_tgt, B.clamp(val))} | |
| 492 | 469 | end |
| 493 | 470 | |
| 494 | 471 | @impl true |
modified
lib/lambent_ex/meta.ex
+16
−0
| 87 | 87 | :dets.insert(@table, {"place-#{key}", value}) |
| 88 | 88 | end |
| 89 | 89 | |
| 90 | + # brightness clamps. one row, since it is a single small map and the walk | |
| 91 | + # targets change together often enough that separate keys buy nothing | |
| 92 | + def get_brightness do | |
| 93 | + case :dets.lookup(@table, "brightness") do | |
| 94 | + [] -> | |
| 95 | + %{} | |
| 96 | + | |
| 97 | + [{_key, value}] -> | |
| 98 | + value | |
| 99 | + end | |
| 100 | + end | |
| 101 | + | |
| 102 | + def put_brightness(value) do | |
| 103 | + :dets.insert(@table, {"brightness", value}) | |
| 104 | + end | |
| 105 | + | |
| 90 | 106 | # svcs |
| 91 | 107 | defp put_gen(type, name, entry) do |
| 92 | 108 | curr = get_gen(type) |
modified
lib/lambent_ex/supervision/component.ex
+1
−0
Click to load diff…
added
lib/lambent_ex/util/brightness.ex
+130
−0
Click to load diff…
modified
lib/lambent_ex_web/components/layouts/app.html.heex
+10
−0
Click to load diff…
added
lib/lambent_ex_web/live/brightness/index.ex
+185
−0
Click to load diff…
added
lib/lambent_ex_web/live/brightness/index.html.heex
+102
−0
Click to load diff…
modified
lib/lambent_ex_web/router.ex
+1
−0
Click to load diff…
Parents: 46b785f