# State

A driver should be built to represent the state of the device or service it is abstracting.

* User interfaces can bind to state to provide visual feedback
* Logic can query state and subscribe to state changes to trigger further actions

{% hint style="danger" %}
Driver state is should always be considered public and as such, should never contain any sensitive information (auth details, access tokens etc).
{% endhint %}

* Any authenticated user can read the state
* Do not store sensitive material in state

## Exposing State

A driver [quacks](https://en.wikipedia.org/wiki/Duck_typing) like a hash

{% hint style="info" %}
All state keys must be [Symbols](https://ruby-doc.org/core-2.4.2/Symbol.html), [Strings](https://ruby-doc.org/core-2.4.2/String.html) or respond to `#to_sym`
{% endhint %}

* Setting state `self[:state_key] = 'value'`
* Reading state `self[:stafe_key] # => 'value'`

Logic can access driver state in the same way

* Reading state `system[:Display][:power] # => true`
* Setting state `system[:Display][:not_recommended] = true`

Logic might trigger a useful action by changing the state of other drivers directly, however generally it is recommended to use a [mutator method](https://en.wikipedia.org/wiki/Mutator_method).

{% hint style="success" %}
All state values should be limited to objects that can be converted to JSON for transmitting over the API.
{% endhint %}

* JSON compatible classes: `nil, true, false, Hash, String, Integer, Array, Float, Symbol`
* Objects that respond to: `#to_json` or failing that `#to_s` will be called when sending over the API
* Objects that don’t meet these requirements can be used server side and are sent as `nil` over the API

## Subscribing to State

All drivers can subscribe to their own state.

```ruby
# Subscribe to internal state

ref = subscribe(:state_variable) do |notify|
    notification.value     # => value of the status variable that triggered this notification
    notification.old_value # => the value of the variable before this change

    # Also comes with the subscription information
    notify.sys_name # => The system name
    notify.sys_id   # => The system ID this value originated from
    notify.mod_name # => The generic module name 
    notify.mod_id   # => The module database ID
    notify.index    # => The device index
    notify.status   # => the name of the status variable

    # And a reference to the subscription should you want to unsubscribe
    unsubscribe(notification.subscription)
end

# Optionally unsubscribe
unsubscribe(ref)
```

Logic can additionally subscribe to state of other drivers.

## Change detection

When state is applied it is checked against the existing value and subscribers are only notified if the value has changed.

```ruby
self[:power] = On  # => subscribers are notified of the change
self[:power] = On  # => no change detected, no action taken
self[:power] = Off # => subscribers are notified of the change
```

Change detection doesn’t work if you mutate a variable.

```ruby
self[:my_array] = [1, 2, 3] # => subscribers are notified of the change
self[:my_array] << 4        # => no action taken (change detection isn't run)

@my_copy = self[:my_array]
@my_copy << 5
self[:my_array] = @my_copy  # => no change detected (change detection did run)

# Only if you are really sure you know what you are doing!
signal_status(:my_array)    # => forces a change notification to subscribers
```

{% hint style="warning" %}
Mutating complex status variables is **not recommended** as the variables might be being acted upon on another thread. This can lead to race conditions or worse.
{% endhint %}

The recommended method for updating complex state is:

1. **Duplicate** `my_array = ['my', 'array'].dup` or `{complex:['hash']}.deep_dup`
   * `.deep_dup` when in doubt
2. **Update** `my_array << 8`
3. **Apply** `self[:my_array] = my_array`

This can be achieved by using operations that create a new object

```ruby
self[:my_array] = [1, 2, 3] # => subscribers are notified of the change
self[:my_array] += [4]      # => subscribers are notified of the change
self[:my_array]             # => [1, 2, 3, 4]

# These will both trigger notifications
self[:my_hash] = { example: 1 }
self[:my_hash] = self[:my_hash].merge({ update: true })
self[:my_hash] # => { example: 1, update: true }
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://engine.place.technology/developer-guide/drivers/state.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
