Superstack IoT Platform

Preliminary

This page is actively being updated. Information may change, so be sure to check back frequently.


Silicon Witchery Superstack platform

Superstack is a cloud IoT platform that lets you deploy, program, and manage connected devices from anywhere. You can update firmware, monitor data in real-time, and run AI-powered analytics, all through a secure web dashboard or API. Superstack handles connectivity, device onboarding, and fleet management, so you can focus on building your application. Its natural language query engine and easy business integration make it simple to turn raw device data into actionable insights at scale.


Contents

  1. Connecting your first module
  2. Lua programming & API
    1. Standard Lua libraries
    2. Digital IO
    3. Analog input
    4. PWM output (analog output)
    5. I2C communication
    6. SPI communication
    7. UART communication
    8. PDM microphone input
    9. Sleep, power & system info
    10. Networking (LTE)
    11. Location (GPS)
    12. File storage
    13. Timekeeping
  3. Working with data
    1. Data API
  4. Advanced AI usage
    1. Natural language API
  5. Managing devices & deployments
    1. Un-pairing devices
  6. Managing your subscription

Connecting your first module

Detailed setup and commissioning steps will be added soon.

In the meantime, the short video below describes the general setup, as well as overall features.

Lua programming & API

All Superstack-compatible devices, including the S2 Module, run a unified firmware that includes the Lua 5.4.7 runtime. A powerful yet simple scripting engine that can be picked up easily by new and seasoned programmers alike.

This engine allows devices to be programmed remotely to run scripts that gather data from sensors, process it, and then return data to Superstack. All the exposed Lua functions are hardware-accelerated under the hood, allowing for performance close to that of bare-metal in C.

Thanks to this scriptable nature, applications can be iterated and debugged incredibly quickly without having to maintain any local development tools or wait for compilations to complete. Best of all, this can all be done remotely, with devices that may be located across countries or continents.

Standard Lua libraries

Many of the standard libraries are included for the user to take advantage of.

Standard libraries which are not included are superseded by similar device specific libraries:


The following device-specific libraries allow access to all aspects of the device I/O and feature set such as LTE-based communication and GPS. Additionally, some other libraries provide convenient functions for running DSP operations and type conversions.

These functions may accept a combination of positional and named arguments. Named arguments are passed as tables which essentially expect key-value pairs. Most of these keys will take on a default value if not provided. These are marked as optional.

-- Calling a function with only positional arguments
device.digital.set_output("A0", true)

-- Calling a function with both positional and named arguments
device.i2c.write(0x12, 0x4F, "\x01", { scl_pin="B0", sda_pin="B1"})

-- Calling a function with only positional arguments. Note how the () can be omitted
network.send{ sensor_value=31.5 }

Digital IO

FunctionDetails
device.digital.set_output(pin, value) Sets or clears a digital output on a pin.

Parameters:
  • pin - string - The pin name. E.g. A0
  • value - boolean - The level to set on the pin. true for high, or false for low
Returns:
  • nil
device.digital.get_input(pin, { pull="PULL_DOWN" }) Gets the digital value on a pin.

Parameters:
  • pin - string - The pin name. E.g. A0
Optional parameters:
  • pull - string - Selects the pull mode on the pin. Can be "PULL_UP", "PULL_DOWN", or "NO_PULL"
Returns:
  • boolean - true if the pin is high, or false if it's low
device.digital.assign_input_event(pin, handler, { pull="PULL_DOWN" }) Assigns an event handler that triggers whenever the input value of a pin changes.

Parameters:
  • pin - string - The pin name. E.g. A0
  • handler - function - The function to call whenever the pin value changes. This function will be called with two arguments:
    • pin - string - The pin name that generated the event. E.g. A0
    • state - boolean - The value on the pin when the event occurred. true if the pin was high, or false if the pin was low
Optional parameters:
  • pull - string - Selects the pull mode on the pin. Can be "PULL_UP", "PULL_DOWN", or "NO_PULL"
Returns:
  • nil
device.digital.unassign_input_event(pin) Disables the event and detaches the pin from the handler.

Parameters:
  • pin - string - The pin name. E.g. A0
Returns:
  • nil

Example usage:

-- Set pin B1 to a high value
device.digital.set_output("B1")

-- Print the value on pin D0
local val = device.digital.get_input("D0")
print(val)

-- Assign a function that triggers whenever the input value of C3 changes
function my_pin_handler(pin, state)
    if state == true then
        print(pin.." went high")
    else
        print(pin.." went low")
    end
end

device.digital.assign_input_event("C3", my_pin_handler)

-- Disable the event and detach the pin from the handler if no longer needed
device.digital.unassign_input_event("C3")

Analog input

FunctionDetails
device.analog.get_input(pin, { acquisition_time=40, range=Vout }) Reads the analog value on an analog-capable pin.

Parameters:
  • pin - string - The pin name. E.g. A0
Optional parameters:
  • acquisition_time - integer - A time in microseconds across which to make the measurement. Can be either 3, 5, 10, 15, 20, 40, or multiples of 40 e.g. 80, 120, 160, etc. Higher values allow for accurate measurement of greater source resistances. Those maximum resistances being 10kΩ, 40kΩ, 100kΩ, 200kΩ, 400kΩ and 800kΩ respectively, with 800kΩ being the maximum source resistance for acquisition times greater than 40 microseconds
  • range - integer - The maximum expected voltage for the input signal. Defaults to the same value as VOUT
Returns:
  • table - A table of key-value pairs:
    • voltage - number - The voltage present on the pin
    • percentage - number - The voltage represented as a percentage with respect to the range of 0V and the range value
device.analog.get_differential_input(positive_pin, negative_pin, { acquisition_time=40, range=Vout }) Reads the analog value across two analog capable pins.

Parameters:
  • positive_pin - string - The pin name of the positive pin
  • negative_pin - string - The pin name of the negative pin
Optional parameters:
  • acquisition_time - integer - Same as above
  • range - integer - Same as above
Returns:
  • table - Same as above
device.analog.assign_input_high_event(pin, handler, { percentage, voltage, acquisition_time=40, range=Vout }) Assigns an event handler that triggers whenever the input pin crosses a high threshold.

Parameters:
  • pin - string - The pin name. E.g. D0
  • handler - function - The function to call whenever the threshold is crossed. This function will be called with two arguments:
    • pin - string - The pin name that generated the event. E.g. D0
    • exceeded - boolean - true if the voltage exceeded the threshold, or false otherwise
  • percentage - number - The level represented as a percentage at which to trigger the event. Either percentage or voltage must be provided, not both
  • voltage - number - The level represented as a voltage at which to trigger the event. Either percentage or voltage must be provided, not both
Optional parameters:
  • acquisition_time - integer - Same as above
  • range - integer - Same as above
Returns:
  • nil
device.analog.assign_input_low_event(pin, handler, { percentage, voltage, acquisition_time=40, range=Vout }) Assigns an event handler that triggers whenever the input pin crosses a low threshold.

Parameters:
  • pin - string - The pin name. E.g. D0
  • handler - function - The function to call whenever the threshold is crossed. This function will be called with two arguments:
    • pin - string - Same as above
    • exceeded - boolean - Same as above
  • percentage - number - Same as above
  • voltage - number - Same as above
Optional parameters:
  • acquisition_time - integer - Same as above
  • range - integer - Same as above
Returns:
  • nil
device.analog.unassign_input_event(pin) Disables the event and detaches the pin from the handler.

Parameters:
  • pin - string - The pin name. E.g. D0
Returns:
  • nil

Example usage:

-- Read the analog value on pin D1 and print both the percentage and voltage values
local d0_val = device.analog.get_input("D1")
print(d0_val.percentage)
print(d0_val.voltage)

-- Trigger a print whenever the voltage on D2 drops below 1.5V
function my_low_voltage_handler(pin, exceeded)
    if (exceeded) then
        print("Voltage fell below 1.5V")
    else
        print("Voltage has returned back above 1.5V")
    end
end

device.analog.assign_input_high_event("D1", my_low_voltage_handler, { voltage=1.5 })

-- Disable the event and detach the pin from the handler if no longer needed
device.analog.unassign_input_event("D1")

PWM output (analog output)

FunctionDetails
device.analog.set_output(pin, percentage { frequency=1 }) Sets a PWM duty cycle on a pin.

Parameters:
  • pin - string - The pin name. E.g. A0
  • percentage - number - The duty cycle to output on the pin as a percentage
Optional parameters:
  • frequency - number - The PWM frequency in Hz
Returns:
  • nil

Example usage:

-- Set pin E1 to a 25% duty cycle at the default PWM frequency
device.analog.set_output("E1", 25)

I2C communication

FunctionDetails
device.i2c.read(address, register, length, { port="PORTA", scl_pin="A0", sda_pin="A1", frequency=400, register_address_size=8 }) Reads a number of bytes from a register on an I2C connected device.

Parameters:
  • address - integer - The 7-bit address of the I2C device
  • register - number - The address of the register to read from
  • length - integer - The number of bytes to read
Optional parameters:
  • port - string - The 4-pin port which the I2C device is connected to. I.e. "PORTA", "PORTB", "PORTE", or "PORTF". Using this parameter will assume the SCL and SDA pin order to match the Stemma QT and Qwiic pinout. If a different pin order is required, the scl_pin and sda_pin parameters should be provided instead
  • scl_pin - string - The pin name to use for the SCL signal. E.g "C3". Must be used in conjunction with sda_pin and cannot be used if the port parameter is already specified
  • sda_pin - string - The pin name to use for the SDA signal. E.g "C4". Must be used in conjunction with scl_pin and cannot be used if the port parameter is already specified
  • frequency - integer - The frequency to use for I2C communications in kHz. Can be either 100, 250 or 400
  • register_address_size - integer - The size of the register to read in bits. Can be either 8, 16 or 32
Returns:
  • table - A table of key-value pairs:
    • success - string - true if the transaction was a success, or false otherwise
    • data - string - The bytes read. Always of size length as specified in the function call
    • value - string - The first data value, useful if only one byte was requested
device.i2c.write(address, register, data, { port="PORTA", scl_pin="A0", sda_pin="A1", frequency=400, register_address_size=8 }) Writes a number of bytes to a register on an I2C connected device.

Parameters:
  • address - integer - Same as above
  • register - number - Same as above
  • data - string - The data to write to the device. Can be a hexadecimal string containing zeros. E.g. "\x1A\x50\x00\xF1"
Optional parameters:
  • port - string - Same as above
  • scl_pin - string - Same as above
  • sda_pin - string - Same as above
  • frequency - integer - Same as above
  • register_address_size - integer - Same as above
Returns:
  • boolean - true if the transaction was a success, or false otherwise
device.i2c.scan({ port="PORTA", scl_pin="A0", sda_pin="A1", frequency=400 }) Scans the given port for all connected I2C devices.

Optional parameters:
  • port - string - Same as above
  • scl_pin - string - Same as above
  • sda_pin - string - Same as above
  • frequency - integer - Same as above
Returns:
  • table - A table of integers listing the 7-bit addresses of all the devices found

Example usage:

-- Read a byte from register 0x1F on a device with address 0x23
local result = device.i2c.read(0x23, 0x1F, 1)

if result.success then
    print(result.value)
end

-- Read multiple bytes from the device and print the fourth byte
local result = device.i2c.read(0x23, 0x1F, 4)

if result.success then
    print(result.data[4])
end

-- Write 0x1234 to the register 0xF9
device.i2c.write(0x23, 0xF9, "\x12\x34")

-- Scan a port for devices
local d = device.i2c.scan({port="PORTF"})

print("Found " .. tostring(#d) .. " devices")

SPI communication

FunctionDetails
device.spi.read(register, length, { mosi_pin="C0", miso_pin="C1", sck_pin="C2", cs_pin="C3", frequency=500, register_address_size=8 }) Reads a number of bytes from a register on an SPI connected device.

Parameters:
  • register - integer - The address of the register to read from
  • length - integer - The number of bytes to read
Optional parameters:
  • mosi_pin - string - The pin name to use for the MOSI signal. E.g "D0"
  • miso_pin - string - The pin name to use for the MISO signal. E.g "D1"
  • sck_pin - string - The pin name to use for the SCK signal. E.g "D2"
  • cs_pin - string - The pin name to use for the CS signal. E.g "D3"
  • frequency - integer - The frequency to use for SPI transactions in kHz
  • register_address_size - integer - The size of the register address in bits. Can be either 8, 16 or 32
Returns:
  • table - A table of key-value pairs:
    • data - string - The bytes read. Always of size length as specified in the function call
    • value - string - The first data value, useful if only one byte was requested
device.spi.write(register, data, { mosi_pin="C0", miso_pin="C1", sck_pin="C2", cs_pin="C3", frequency=500, register_address_size=8 }) Writes a number of bytes to a register on an SPI connected device.

Parameters:
  • register - integer - Same as above
  • data - string - The data to write to the device. Can be a hexadecimal string containing zeros. E.g. "\x1A\x50\x00\xF1"
Optional parameters:
  • mosi_pin - string - Same as above
  • miso_pin - string - Same as above
  • sck_pin - string - Same as above
  • cs_pin - string - Same as above
  • frequency - integer - Same as above
  • register_address_size - integer - Same as above
Returns:
  • nil
device.spi.transaction{ read_length, write_data, hold_cs=false, mosi_pin="C0", miso_pin="C1", sck_pin="C2", cs_pin="C3", frequency=500 } Reads and writes an arbitrary number of bytes at the same time. I.e while data is being clocked out on the MOSI pin, any data received on the MISO pin will be recorded in parallel. The total number of bytes transacted will therefore be the larger of read_length or write_data. If you wish to, for example, write 5 bytes, and then read 10 bytes, read_length must be set to 15. The first 5 bytes can be ignored, and the remaining 10 bytes will contain the read data.

Parameters:
  • read_length - integer - The number of bytes to read from
  • write_data - string - The data to write to the device. Can be a hexadecimal string containing zeros. E.g. "\x1A\x50\x00\xF1"
Optional parameters:
  • hold_cs - boolean - If set to true will continue to hold the CS pin low after the transaction is completed. This can be useful if the transaction needs to be broken up into multiple steps. Any subsequent call to device.spi.transaction with hold_cs set to false will then return the CS pin to a high value once completed
  • mosi_pin - string - Same as above
  • miso_pin - string - Same as above
  • sck_pin - string - Same as above
  • cs_pin - string - Same as above
  • frequency - integer - Same as above
Returns:
  • string - The bytes read. Always of size length as specified in the function call

Example usage:

-- Read and print 4 bytes from the 0x12 register
local result = device.spi.read(0x12, 4)

print(result[1])
print(result[2])
print(result[3])
print(result[4])

-- Write a 32-bit value (4 bytes) to the 0xA1 register at 1MHz
device.spi.write(0xA1, "\12\x34\x56\x78", { frequency=1000 })

-- Write 4 bytes to the device and then read back 10 bytes
device.spi.transaction{ write_data="\12\x34\x56\x78", hold_cs=true }
result = device.spi.transaction{ read_length=10 }

UART communication

FunctionDetails
device.uart.write(data, { baudrate=9600, tx_pin="B1", cts_pin=nil, parity=false, stop_bits=1 }) Writes UART data to a pin.

Parameters:
  • data - string - The data to send
Optional parameters:
  • baudrate - integer - The baudrate in bits-per-second at which to send the data. Can be 1200, 2400, 4800, 9600, 14400, 19200, 28800, 31250, 38400, 56000, 57600, 76800, 115200, 230400, 250000, 460800, 921600, or 1000000
  • tx_pin - string - The pin name to use for the transmit signal. E.g "C1"
  • cts_pin - string - The pin name to use for the clear-to-send signal. E.g "C3". If nil is given, the signal isn't used
  • parity - boolean - Enables the parity bit if set to true
  • stop_bits - integer - Sets the number of stop bits. Can be either 1 or 2
Returns:
  • nil
device.uart.assign_read_event(terminator, handler, { baudrate=9600, rx_pin="B0", tx_pin="B1", rts_pin=nil, cts_pin=nil, parity=false, stop_bits=1 }) Buffers UART data from a pin, and triggers an event whenever a specified terminating character is seen.

Parameters:
  • terminator - string - The character to wait for until triggering the event. If set to nil, the event is triggered for every new character received
  • handler - function - The function to call whenever data is received. This function will be called with one argument:
    • data - string - All the buffered bytes since the last event was triggered, or the event was enabled
Optional parameters:
  • baudrate - integer - Same as above
  • rx_pin - string - The pin name to use for the receive signal. E.g "C0"
  • rts_pin - string - The pin name to use for the ready-to-send signal. E.g "C2". If nil is given, the signal isn't used
  • parity - boolean - Same as above
Returns:
  • nil
device.uart.unassign_read_event(rx_pin) Disables the event and detaches the pin from the handler.

Parameters:
  • rx_pin - string - The pin name of the receive signal. E.g "C0"
Returns:
  • nil

Example usage:

-- Create a handler for receiving UART data
function my_receive_handler(data)
    print("Got a new line: "..data)
end

device.uart.assign_read_event("\n", my_receive_handler, { baudrate=19200 })

-- Send UART data
device.uart.write("Hello there. This is some data\n", { baudrate=19200 })

-- Disable the event and detach the pin from the handler if no longer needed
device.uart.unassign_read_event("B0")

PDM microphone input

FunctionDetails
device.audio.record(length, handler, { data_pin="E0", clock_pin="E1", sample_rate=8000, bit_depth=8 }) Begins recording microphone input and and outputs the data to an event handler. The handler is called every length seconds and repeats until the stop() function is called.

Parameters:
  • length - float - The length to record in seconds. The maximum allowable time will depend on the RAM currently used on the device. Using a lower sample_rate and bit_depth will allow for longer recordings but at reduced quality
  • handler - function - The function to call whenever data is ready to be processed. The function will be called with one argument:
    • data - string - Audio data as 1-byte-per-sample in the case of bit_depth=8, or 2-bytes-per-sample in the case of bit_depth=16. The samples will be signed values. This function should not spend longer than length time to process the data and exit, otherwise the internal buffer will overflow and cause glitches in the final audio
Optional parameters:
  • data_pin - string - The pin name to use for the data input. E.g "F0"
  • clock_pin - string - The pin name to use for the clock input. E.g "F1"
  • sample_rate - integer - The sample rate to record in samples per second. Can be either 8000 or 16000
  • but_depth - integer - he dynamic range of the samples recorded. Can be either 8 or 16
Returns:
  • nil
device.audio.stop(data_pin) Stops recording samples and calls the event handler one last time with any remaining data in the internal buffer.

Parameters:
  • data_pin - string - The pin name of the data input. E.g "F0"
Returns:
  • nil

Example usage:

-- Create a handler for receiving and processing audio samples
function my_microphone_handler(data)
    for i = 1, #data do
        local value = string.sub(data, i, i)
        
        if value > 128 or value < -128 then
            print("Loud noise detected")
        end
    end
end

-- Will capture 1s worth of audio at a time
device.audio.record(1, my_microphone_handler)

-- Recording can be stopped at any time
device.audio.stop("E0")

Sleep, power & system info

FunctionDetails
device.sleep(time) Puts the device into a low-power sleep for a certain amount of time.

Parameters:
  • time - number - The time to sleep in seconds. E.g. 1.5
Returns:
  • nil
device.power.battery.set_charger_cv_cc(voltage, current) Sets the termination voltage and constant current values for the charger.

Parameters:
  • voltage - number - The termination voltage of the cell. Can be either 3.50, 3.55, 3.60, 3.65, 4.00, 4.05, 4.10, 4.15, 4.20, 4.25, 4.30, 4.35, 4.40, or 4.45
  • current - number - The constant current to charge the cell at. Must be between 32 and 800 in steps of 2
Returns:
  • nil
device.power.battery.get_voltage() Gets the voltage of the cell.

Returns:
  • number - The voltage of the cell in volts
device.power.battery.get_charging_status() Gets the charging status of the cell.

Returns:
  • string - The current charging status. Either charging, charged or discharging if the battery is connected, or external_power if either no battery is installed, or the battery has a fault
device.power.battery.set_vout(voltage) Sets the voltage of VOUT.

Parameters:
  • voltage - number - The voltage to set. Can be between 1.8 and 3.3 in steps of 0.1
Returns:
  • nil
ConstantDetails
device.HARDWARE_VERSION The hardware version of the device.

Returns:
  • string - Always "s2-module"
device.FIRMWARE_VERSION The current firmware version of the Superstack firmware running on the device.

Returns:
  • string - A string representing the current firmware version. E.g. "0.1.0+0"

Example usage:

-- Print the hardware version, sleep for 1.5 seconds and then print firmware version
print(device.HARDWARE_VERSION)
device.sleep(1.5)
print(device.FIRMWARE_VERSION)

-- Configure the battery charger for a 4.2V 200mAh rated Li-Po cell
device.power.battery.set_charger_cv_cc(4.2, 200)

-- Get the current battery status
local voltage = device.power.battery.get_voltage()
local status = device.power.battery.get_charging_status()

if status ~= "external_power" then
    print("Battery is "..status)
    print("Battery voltage is "..tostring(voltage).."V")
else
    print("Battery not connected. On external power")
end

-- Set the voltage of Vout to 3.3V
device.power.set_vout(3.3)

Networking (LTE)

FunctionDetails
network.send{ data } Sends data to Superstack.

Parameters:
  • data - table - A table representing any data as key-value pairs. Will be converted to an equivalent JSON once it reaches Superstack. It's recommended to name keys in a full and clear way as that will be how the AI tools of Superstack will infer the meaning of the data. E.g. temperature_celsius = 43.5 will help the AI understand that 43.5 represents temperature in Celsius units
Returns:
  • nil

Example usage:

-- A simple sensor value
my_sensor_value = 23.5

network.send{ temperature=my_sensor_value }

-- Network send can contain any arbitrary data
network.send{ 
    some_int = -42, 
    some_float = 23.1
    some_string = "test"
    some_array = {1, 2, 3, 4},
    some_nested_thing = {
        another_int = 54,
        another_string = "test again"
    }
}

Location (GPS)

FunctionDetails
location.get_latest() Returns the latest GPS data.

Returns:
  • table - A table of key-value pairs:
    • valid - boolean - true if the GPS data is valid, or false otherwise
    • latitude - number - The current latitude
    • longitude - number - The current longitude
    • altitude - number - The current altitude
    • accuracy - number - The location and altitude accuracy
    • speed - number - The current speed
    • speed_accuracy - number - The speed accuracy
    • satellites - table - A table of key-value pairs:
      • tracked - integer - The number of satellites currently being tracked
      • in_fix - integer - The number of satellites currently being used for measurement
      • unhealthy - integer - The number of satellites that could not be used for measurement
location.set_options({ accuracy="HIGH", power_saving="MEDIUM", tracking_interval=1 }) Sets options related to the GPS module.

Optional parameters:
  • accuracy - string - The accuracy of the reading to acquire. Can either be "LOW" where only three satellites are required to attain a fix, or "HIGH" where four satellites are required to attain a fix
  • power_saving - string - The level of power saving that the GPS aim to achieve. Can either be "OFF", "MEDIUM", "MAX". A higher level of power saving will result in less accuracy and a slower time to attain a fix
  • tracking_interval - integer - The period to poll for new location updates. Can either be 1 for continuous 1-second updates, or a value in seconds between 10 and 65535 for slower updates which can save power
Returns:
  • nil

Example usage:

location.set_options({ accuracy = "LOW" })

while true do

    local l = location.get_latest()

    print("valid: " .. tostring(l["valid"]))
    print("latitude: " .. tostring(l["latitude"]))
    print("longitude: " .. tostring(l["longitude"]))
    print("altitude: " .. tostring(l["altitude"]))
    print("accuracy: " .. tostring(l["accuracy"]))
    print("speed: " .. tostring(l["speed"]))
    print("speed_accuracy: " .. tostring(l["speed_accuracy"]))

    print("satellites tracked: " .. tostring(l["satellites"]["tracked"]))
    print("satellites in fix: " .. tostring(l["satellites"]["in_fix"]))
    print("satellites unhealthy: " .. tostring(l["satellites"]["unhealthy"]))

    -- Note that LTE network activity (including logging) will interrupt the
    -- GPS and delay attaining a fix. Therefore the device should avoid
    -- network activity for a brief period of time while waiting for a fix
    device.sleep(30)
end

File storage

FunctionDetails
storage.write(filename, data) Creates a file and writes data to it. If the file already exists, it is overwritten.

Parameters:
  • filename - string - The name of the file
  • data - string - The data to save to the file
Returns:
  • nil
storage.append(filename, data) Creates a file and writes data to it. If the file already exists, the new data is appended to the current contents of the file.

Parameters:
  • filename - string - Same as above
  • data - string - Same as above
Returns:
  • nil
storage.read(filename, { line=-1, length=nil, offset=0 }) Reads out the contents of the file. Either returning an entire "\n" terminated line, or alternatively, a number of bytes with length length at an offset of offset.

Parameters:
  • filename - string - Same as above
Optional parameters:
  • line - integer - The index of the line to return. 1 being the first line, 2 being the second line, etc. Likewise, the lines can also be indexed from the end of the file. -1 returns the last line of the file, -2 the second to last, etc. length and offset are ignored when line is specified
  • length - integer - The number of bytes to read from the file. Cannot be used with the line option. If length is longer than the data present in the file, then a shorter result will be returned</li>
  • offset - integer - When length is specified, this option allows reading from some arbitrary offset from within the file</li>
Returns:
  • string - The contents read
storage.delete(filename) Deletes a file if it exists.

Parameters:
  • filename - string - Same as above
Returns:
  • boolean - true if the file was found and deleted, false otherwise
storage.list() Lists all the files stored on the device as a list of tables containing the filename and size.

Returns:
  • table - A table of tables:
    • table - A table of key-value pairs:
      • name - string - The name of the file
      • size - integer - The size of the file in bytes

Example usage:

-- Create a file and read it
storage.write("my_file.txt", "Hello world")

print(storage.read("my_file.txt"))

-- Append more data to the file
storage.append("my_file.txt", "\nThis is another line of text")
storage.append("my_file.txt", "\nAnd this is a final line of text")

-- Print the last line from the file
print(storage.read("my_file.txt", { line=-1 }))

-- Delete the file
storage.delete("my_file.txt")

Timekeeping

FunctionDetails
time.get_unix_time() Returns the current Unix timestamp (i.e the number of non-leap milliseconds that have elapsed since 00:00:00 UTC on 1 January 1970), or if the time is not yet known, returns the uptime of the device in milliseconds.

Returns:
  • integer - A number of milliseconds
time.get_time_date({unix_epoch, timezone}) Gets either the current time and date information, or the time and date for a specified Unix timestamp.

Optional parameters:
  • unix_epoch - number - A Unix timestamp that should be converted to a real time and date
  • timezone - string - The timezone to observe when returning the time and date. Can be given as a standard timezone offset such as "+02:00" or "-07:30"
Returns:
  • table - A table of key-value pairs:
    • year - integer - The current year
    • month - integer - The current month
    • day - integer - The current day
    • yearday - string - The current day of the year
    • weekday - string - The current weekday since Sunday
    • hour - integer - The current hour
    • minute - integer - The current minute
    • second - integer - The current second

Example usage:

-- Repeat something for 30 seconds
local t = time.get_unix_time()

while t + 30 > time.get_unix_time() do
    print("waiting")
    device.sleep(5)
end

-- Print the current time and date
local now = time.get_time_date()

print(string.format("The current time is: %02d:%02d", now.minute, now.hour))
print(string.format("The current date is: %d/%d/%d", now.year, now.month, now.day))

Working with data

Details coming soon

Data API

Details coming soon

Advanced AI usage

Details coming soon

Natural language API

Details coming soon

Managing devices & deployments

Details coming soon

Un-pairing devices

Details coming soon

Managing your subscription

Details coming soon