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
Driver state is should always be considered public and as such, should never contain any sensitive information (auth details, access tokens etc).
  • Any authenticated user can read the state
  • Do not store sensitive material in state

Exposing State

A driver quacks like a hash
All state keys must be Symbols, Strings or respond to #to_sym
  • 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.
All state values should be limited to objects that can be converted to JSON for transmitting over the API.
  • 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.
1
# Subscribe to internal state
2
3
ref = subscribe(:state_variable) do |notify|
4
notification.value # => value of the status variable that triggered this notification
5
notification.old_value # => the value of the variable before this change
6
7
# Also comes with the subscription information
8
notify.sys_name # => The system name
9
notify.sys_id # => The system ID this value originated from
10
notify.mod_name # => The generic module name
11
notify.mod_id # => The module database ID
12
notify.index # => The device index
13
notify.status # => the name of the status variable
14
15
# And a reference to the subscription should you want to unsubscribe
16
unsubscribe(notification.subscription)
17
end
18
19
# Optionally unsubscribe
20
unsubscribe(ref)
Copied!
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.
1
self[:power] = On # => subscribers are notified of the change
2
self[:power] = On # => no change detected, no action taken
3
self[:power] = Off # => subscribers are notified of the change
Copied!
Change detection doesn’t work if you mutate a variable.
1
self[:my_array] = [1, 2, 3] # => subscribers are notified of the change
2
self[:my_array] << 4 # => no action taken (change detection isn't run)
3
4
@my_copy = self[:my_array]
5
@my_copy << 5
6
self[:my_array] = @my_copy # => no change detected (change detection did run)
7
8
# Only if you are really sure you know what you are doing!
9
signal_status(:my_array) # => forces a change notification to subscribers
Copied!
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.
The recommended method for updating complex state is:
  1. 1.
    Duplicate my_array = ['my', 'array'].dup or {complex:['hash']}.deep_dup
    • .deep_dup when in doubt
  2. 2.
    Update my_array << 8
  3. 3.
    Apply self[:my_array] = my_array
This can be achieved by using operations that create a new object
1
self[:my_array] = [1, 2, 3] # => subscribers are notified of the change
2
self[:my_array] += [4] # => subscribers are notified of the change
3
self[:my_array] # => [1, 2, 3, 4]
4
5
# These will both trigger notifications
6
self[:my_hash] = { example: 1 }
7
self[:my_hash] = self[:my_hash].merge({ update: true })
8
self[:my_hash] # => { example: 1, update: true }
Copied!
Last modified 2yr ago