Datastore

While drivers are long running processes that allow for simple storage of working information through the use of standard lua tables, there are situations where the driver will stop running. Either through an unexpected fatal error, or if the hub the driver is running on restarts (either through power loss, firmware update, or other situation). There are some classes of information that will be useful for a driver where persisting this through these restarts. The datastore module is the method provided for doing that. Each driver will have a datastore created to go with it, this datastore will be loaded at startup from whatever was persisted in the past, and will be periodically written into a persistent state. From the lua side, this datastore will act primarily as a standard lua table, but with some significant metatable restrictions on setting values. These restrictions are primarily to restrict values to only those that can be properly serialized for storage. It is recommended that you primarily interact with the data store through the driver, but below is a simple example of datastore usage

local ds = require "datastore"

local my_func = function() return 1 end
local my_table = {}
my_table.number = 1
my_table.string = "asdf"

local nested_func_table = {}
nested_func_table.sub_table = {}
nested_func_table.func = my_func

local multi_nested_table = {}
multi_nested_table.deeper = {}
multi_nested_table.deeper.number = 1
multi_nested_table.deeper.deepest = {}

my_ds = ds.init()

-- This will move these values into the datastore and do a check that things are
-- valid.  Since all values here are valid, this will be fine
my_ds.my_table = my_table

-- The above check happens recursively so nested values will be verified as well
my_ds.multi_nested_table = multi_nested_table

-- These should fail
local function set_func()
  my_ds.my_func = my_func
end

succ, val = pcall(set_func)
if succ then
  print("should have failed to set function")
else
  -- Data store keys and values must be JSON encodable: function: 0x55a76bbfc490 is of unsupported type function
  print(val)
end

local function set_nested_func()
  my_ds.nested_func = nested_func_table
end

succ, val = pcall(set_nested_func)
if succ then
  print("should have failed to set a nested function but didnt'")
else
-- Data store keys and values must be JSON encodable: function: 0x55a76bbfc490 is of unsupported type function
  print(val)
end

print(my_ds:is_dirty()) -- true
my_ds:save() -- sends table out for persistence.
print(my_ds:is_dirty()) -- false

my_ds.my_table = nil
print(my_ds:is_dirty()) -- true
my_ds:save()

The is_dirty and save functionality should not need to be used directly in normal driver operation, but saving will happen automatically as normal operation of the driver.

There are a few ways you can interact with a datastore from the driver. After using one of the driver_helper.init function (or one of the protocol specific driver inits) the driver context table will inlcude a datastore field that will be loaded from any data that had been previously written for this driver. You can access it through driver.datastore, and can directly set values as shown in the example above. Similarly the device objects expose sub tables of the datastore through the device.persistent_store table. These can be accessed directly through the datastore, but the keys are preceeded with the double underscore __ to avoid unintentional name conflict, and it’s recommended that you interact through those device objects for that data instead.