Current section

Files

Jump to
helen lib thermostat control.ex
Raw

lib/thermostat/control.ex

defmodule Thermostat.Control do
@moduledoc false
require Logger
use Switch
alias Thermostat.Profile
def confirm_switch_position(%Thermostat{name: name, switch: switch} = t) do
state_pos = state_to_position(t)
{sw_rc, _pos_rc} =
pos_rc = sw_position(switch, position: state_pos, ensure: true)
if sw_rc in [:ok, :pending] do
# switch position was good or is now set
true
else
# some kind of issue occurred, log it
error = %{
error: :unhandled,
device: switch,
state_pos: state_pos,
pos_rc: pos_rc
}
Logger.warn([
inspect(name, prety: true),
" confirm switch position error\n",
inspect(error, pretty: true)
])
end
pos_rc
end
def current_val(%Thermostat{sensor: sensor}) do
Sensor.fahrenheit(name: sensor, since_secs: 30)
end
# handle the case when a thermostat is disabled
def next_state(%{}, "disabled", _set_pt, _val), do: "disabled"
# handle the case where the sensor doesn't have a value
def next_state(%{}, state, set_pt, val)
when is_nil(val) or is_nil(set_pt) or state === "stopped",
do: "off"
# handle the case when a thermostat is in standby
def next_state(%{name: "standby"}, _state, _set_pt, _val), do: "off"
# handle typical operational case of thermostat controlling a device
def next_state(
%{low_offset: low_offset, high_offset: high_offset},
state,
set_pt,
val
) do
cond do
val > set_pt + high_offset and state in ["on", "started", "disabled"] ->
"off"
val < set_pt + low_offset and state in ["off", "started", "disabled"] ->
"on"
true ->
# if none of the above then keep the same state
# this handles the case when the temperature is between the low / high offsets
state
end
end
# handle invocation with an empty map
def next_state(%{}, state, set_pt, val) do
next_state(%{low_offset: 0.0, high_offset: 0.0}, state, set_pt, val)
end
def temperature(%Thermostat{name: name, active_profile: profile} = t)
when is_nil(profile) do
Thermostat.log?(t) &&
Logger.warn([
inspect(name, pretty: true),
" active profile is ",
inspect(profile, pretty: true)
])
{:nil_active_profile, t}
end
def temperature(%Thermostat{name: name, active_profile: profile} = t)
when profile == "none" do
Thermostat.log?(t) &&
Logger.warn([
inspect(name, pretty: true),
" active profile is ",
inspect(profile, pretty: true)
])
{:no_active_profile, t}
end
def temperature(%Thermostat{} = t) do
profile = Profile.active(t)
curr_val = current_val(t)
set_pt = Profile.set_point(profile)
next_state = next_state(profile, Thermostat.state(t), set_pt, curr_val)
if next_state === Thermostat.state(t) do
# handle no change in state
{:ok, t}
else
sw_position(Thermostat.switch(t),
position: state_to_position(next_state),
lazy: true
)
Thermostat.state(t, next_state)
end
end
def stop(%Thermostat{} = t) do
sw_position(Thermostat.switch(t),
position: false,
lazy: true,
ack: false
)
Thermostat.state(t, "off")
end
defp state_to_position(%Thermostat{state: state}),
do: state_to_position(state)
defp state_to_position("on"), do: true
defp state_to_position(_other), do: false
end