Skip to content

Basic Notions

Calin Crisan edited this page Sep 1, 2022 · 7 revisions

Introduction

qToggle is a way of interconnecting sensors, actuators and other data sources. qToggle works by leveraging the power of a flexible API, which lays the groundwork of a simple, common communication scheme.

qToggle attempts to impose a standard that allows managing, provisioning and talking to devices. Rather than reinventing the wheel, qToggle makes use of existing, widely used, technologies, such as RESTful APIs on top of HTTP, passing over data encoded as JSON.

Terminology

Devices

Devices are usually sensors or actuators with an upstream network connection. In a qToggle setup, devices implement (parts of the) qToggle API. Most qToggle devices are based on ESP8266/ESP8285 chips or on Raspberry Pi boards, but this by no means a constraint.

Consumers

While devices act as API servers, being capable of responding to requests, consumers are API clients, issuing API requests and thus consuming the API. In the context of qToggle, any HTTP client that emits an API request to a device is considered a consumer.

Consumers can be represented by complex user interfaces that allow managing a qToggle server or can be simple scripts that do small automation jobs.

Hubs

In real-world setups, it's difficult to individually manage many devices. A hub allows centralized administration of many qToggle devices.

Hubs act as consumers when talking to other devices, but they also expose an API interface that allows other consumers to see them as devices. This allows creating complex hierarchies of devices and hubs that are in a master-slave relationship.

For more details, see the Master-Slave Operation API specifications.

Ports

To actually be useful, a device must expose ports. A port is a way of transmitting a meaningful value out of or into the device. Ports usually represent GPIOs or ADC peripherals that are physically attached to the device.

Ports can accept two types of values: boolean (logic) values and numeric values. The port type is normally dictated by what peripheral it represents.

Virtual ports are a special kind of ports, in that they don't represent a peripheral, but they are rather used for value storage, similarly to registers.

For more details, see the Ports section of the API specifications.

Attributes

Devices and ports need to be configured before being used. Attributes allow reading and/or supplying configuration for ports and devices. Each device and each port exposes their own attributes.

There is a set of standard attributes whose meaning is well defined by the API specifications. Among these standard attributes, there is a subset of common attributes that must be exposed by each device. The same applies to ports.

Non-standard, additional attributes are often exposed by ports and devices. Their meanings are described by definitions that are supplied by the device.

For more details, see the Attributes section of the API specifications.

The Power Of qToggle

The API specifications may seem quite complex, offering a large palette of functionalities and use cases. Nevertheless, most of them are optional, leaving just a small set of functions that are mandatory for a qToggle implementation.

Self-Descriptive Devices

A qToggle device will autodescribe itself, indicating its configuration, what optional functionalities it supports and what ports it exposes.

Each port, in its turn, will autodescribe itself, indicating its identifier, type, configuration and so on.

Hierarchical (Tree) Topologies

Combining master-slave relationships between simple devices and hubs in a network, one could obtain a complex tree topology that makes it easy to manage a large number of devices. Consumers could operate at any level in the hierarchy, thus limiting the access inside the network to any desired subtree.

For more details, see the Master-Slave Operation API specifications.

Access Levels

qToggle implements three roles that dictate the access level:

  • administrator role has absolute power over a device, being able to view and modify the configuration
  • normal role has no access to the configuration, but can read from or write to ports
  • view-only role can only read the values of ports

For more details, see the Authentication section of the API specifications.

Automation

You can add rules that decide port values based on various conditions. Basically, you can tell a port to use an expression (a formula) based on other ports and functions, in a way which resembles to spreadsheet formulas.

Expressions can be set at device level or at hub level. Expressions on a device are very fast but can only depend on the ports present on the device. When setting an expression at hub level, you can include ports of any device that is known by the hub, effectively implementing relations between different devices.

For more details, see the Expressions section of the API specifications.

Notifications

If consumers need to be notified about events that take place on the device (such as port value changes), qToggle offers three notification methods:

  • listening for events using long HTTP requests (also known as long polling)
  • webhooks
  • polling, which is the least efficient but easy to implement

For more details, see the Notifications section of the API specifications.

Firmware Updates

Keeping the device firmware up-to-date is probably one of the most important tasks that is often overlooked when dealing with large fleets of devices. qToggle makes this easy by allowing over-the-air update of the firmware in a common fashion for devices of different types and models.

Reverse API Calls

qToggle setups are usually deployed on top of private networks, preventing devices from being directly accessible from the Internet. Solutions often rely on port forwarding, where a public IP is available.

If port forwarding is not desired or impossible, qToggle devices can be set to open themselves a connection to an external, public server and to wait for API requests. This mechanism is generally known as reverse HTTP and allows making HTTP requests to a device inside a private network without forwarding any port.

For more details, see the Reverse API Calls Mechanism.

API Usage Examples

This section will show some common usages of the qToggle API. For a complete documentation on API calls, see API Functions

Playground Setup

For the purpose of exemplifying the way qToggle API works, we're going to set up a qToggleServer instance using Docker. Following instructions assume that you have a Linux machine (or similar) with a working Docker Engine installation.

Start a qToggleServer container from the qtoggle/qtoggleserver Docker image:

$ docker run -it --rm -p 8888:8888 qtoggle/qtoggleserver:stable

This will run qToggleServer in foreground, with a default configuration and ephemeral storage. You can think of it as a sandboxed server that you can play with. If the server started successfully, you can access it on your machine, on port 8888.

API Requests In Browser

If you point your browser to http://localhost:8888/api/device, you should see the following JSON content (pretty formatted by a browser extension):

Visiting http://localhost:8888/api/ports will show an empty list [], indicating there are no ports exposed by your qToggle device.

Typing in a browser's address bar will only let you do GET requests, so you can't do any modifications via API.

API Requests With cURL

We'll use curl to make API calls that use other methods than GET. The jq command will pretty format the JSON output received as response.

Let's add a virtual port:

$ curl -sS http://localhost:8888/api/ports -X POST -d '{"id": "test_port", "type": "boolean"}' | jq
{
  "id": "test_port",
  "display_name": "",
  "type": "boolean",
  "writable": true,
  "enabled": true,
  "tag": "",
  "expression": "",
  "transform_read": "",
  "transform_write": "",
  "persisted": false,
  "virtual": true,
  "value": false,
  "definitions": {}
}

The following command can be used to list all ports. We'll only see one port, the one that we've just added:

$ curl -sS http://localhost:8888/api/ports | jq
[
  {
    "id": "test_port",
    "display_name": "",
    "type": "boolean",
    "writable": true,
    "enabled": true,
    "tag": "",
    "expression": "",
    "transform_read": "",
    "transform_write": "",
    "persisted": false,
    "virtual": true,
    "value": false,
    "definitions": {}
  }
]

Let's set the port value to true:

$ curl -sS http://localhost:8888/api/ports/test_port/value -X PATCH -d true

Now let's verify the value is indeed true:

$ curl -sS http://localhost:8888/api/ports/test_port/value
true

Open another terminal and type a command that will listen for events (note the extra " quotes):

$ curl -sS "http://localhost:8888/api/listen?timeout=60&session_id=test" | jq

Head over to the previous terminal and change the value to false:

$ curl -sS http://localhost:8888/api/ports/test_port/value -X PATCH -d false

The listening request should now exit with the following content, indicating a port value change:

[
  {
    "type": "value-change",
    "params": {
      "id": "test_port",
      "value": false
    }
  }
]

The next command will configure the port to use an expression that is true when the current time's second is an even number:

$ curl -sS http://localhost:8888/api/ports/test_port -X PATCH -d '{"expression": "EQ(MOD(SECOND(), 2), 0)"}'

Use a loop to listen for value changes; you'll notice the value changes every second:

$ while true; do curl -sS "http://localhost:8888/api/listen?timeout=60&session_id=test" | jq; done