README.md

# Statux

Statux allows to track the status of entities based on configured constraints.
It abstracts all the state handling, you simply pass messages and get notified if a transition
happened.

## Usage Example

Imagine you are controlling your heating/cooling and ventilation with a
[nerves](https://hexdocs.pm/nerves/getting-started.html) powered device and some sensors.

A simple rule set could look like this:

```elixir
%{
  air_quality: %{
    status: %{
      good: %{value: %{min: 90}},
      ok: %{value: %{min: 70, lt: 90}},
      bad: %{value: %{lt: 70}},
  }}
  temperature: %{
    status: %{
      warm: %{value: %{gt: 21}},
      good: %{value: %{min: 18, max: 21}},
      cold: %{value: %{lt: 18}},
}}}
```

Now, whenever new sensor data is received, you may pass this data to Statux to compare it to your
constraints:

```elixir
Statux.put("living_room", :temperature, 17)
Statux.put("kitchen", :air_quality, 85)
```

Statux will respond to transitions by publishing messages to a pubsub, so you may receive
messages like

```elixir
{:exit, :temperature, :good, "living_room"}
{:enter, :temperature, :cold, "living_room"}
{:stay, :air_quality, :ok, "kitchen"}
```

and could control your heating and ventilation by simply implementing a GenServer handle_info or
a Process with a receive loop

```elixir
receive do
  {:enter, :temperature, :cold, room_id} -> turn_on_heater(room_id)
  {:exit, :temperature, :cold, room_id} -> turn_off_heater(room_id)
  {:enter, :temperature, :warm, room_id} -> turn_on_ac(room_id)
  {:exit, :temperature, :warm, room_id} -> turn_off_ac(room_id)
  {:enter, :air_quality, :bad, room_id} -> turn_on_ventilation(room_id)
  {:enter, :air_quality, :good, room_id} -> turn_off_ventilation(room_id)
end
```

So far, for the example above, there is no real reason to not just use a bunch of `if` statements
chained together. But when automating heating and ventilation, you may not want to activate or
stop those just because someone walked past your sensors and the values changed for a short time.

Additional constraints may be given, for example, the number of consecutive messages or a minimum
duration for which the :value constraints must be fulfilled. Also, we might want to ignore
specific values like `nil`. Let's change the configuration to require at least 5 consecutive
messages to trigger a state change and also a minimum of 1 minute valid state for air quality and
10 minutes for temperature:

```elixir
%{
  air_quality: %{
    ignore: %{is: nil}
    status: %{
      good: %{value: %{min: 90}, constraints: {count: %{min: 5}, duration: %{min: "PT1M"}}}
      ok: %{value: %{min: 70, lt: 90}, constraints: {count: %{min: 5}, duration: %{min: "PT1M"}}}
      bad: %{value: %{lt: 70}, constraints: {count: %{min: 5}, duration: %{min: "PT1M"}}}
    }
  }
  temperature: %{
    ignore: %{is: nil}
    status: %{
      warm: %{value: %{gt: 21}, constraints: {count: %{min: 5}, duration: %{min: "PT10M"}}}
      good: %{value: %{min: 18, max: 21}, constraints: {count: %{min: 5}, duration: %{min: "PT10M"}}}
      cold: %{value: %{lt: 18}, constraints: {count: %{min: 5}, duration: %{min: "PT10M"}}}
    }
  }
}
```


Your implementation remains the same and you can adjust the behaviour through the rules.


## Installation

See [hexdocs](https://hexdocs.pm/statux/index.html).