Hyperspace Basics

Hyperspace is a technology which allows Hemera applications to expose control interfaces to outer networks.

The peculiarity of Hyperspace is its abstract nature: Hyperspace is not tied to any specific protocol, but instead it is capable of exposing resources on any protocol for which a transport exists. Possible protocols include HTTP via TCP/IP, MQTT, Bluetooth and more.

Applications can register their interfaces in Hyperspace in a very similar fashion to how Web resources are exposed. Applications, in fact, are given the choice to expose their interfaces via REST or SOAP paradigms. Applications do not care about which transport is being used.

Refer to Hyperspace Qt5 API documentation to learn more about Hyperspace and its internals.

Terminology and Concepts

Hyperspace has its own terminology for what concerns its internals:

  • Wave: A wave is an incoming request. It is the equivalent of an HTTP call, and it features a method (just like HTTP), a set of attributes (headers), and a payload (not compulsory). Internally, it is also given an unique UUID.
  • Rebound: A Rebound is a counterpart to a Wave. Each Wave has its own Rebound, which is the reply from the target. A rebound, in an HTTP-like fashion, has a status code, attributes and a payload.
  • Gate: A Gate is a way an Application connects to Hyperspace. Any application can register its own Gates, which can then host any number of interfaces/resources. A Gate is identified by a set of capabilities (usually only one) and a path. Capabilities are in reverse domain name notation, and are used for namespacing resources and for discovering targets. More than one application can register the same capabilities, if it does not attempt to overwrite an already taken path.

To identify a resource over Hyperspace, the resulting URL would be something like:

hyperspace://my-device-id-over-hyperspace/com.my.capability/api/REST/collection/resource

It's then important to keep in mind that:

  • Hyperspace does not care both on the caller and on the receiver end about the protocol being used.
  • Hyperspace does not call any method explicitly on any application: instead, it accesses a capability, which then redirects to the correct Gate of the correct Application.

Hyperdrive

Hyperdrive is Hyperspace's engine, running on a target Hemera device. It acts like a router for messages, managing transports and Gates. Each Hyperdrive features a set of Transports, each one implementing a specific protocol/mechanism. Every Wave sent to a device is sent to Hyperdrive, which takes care of redirecting it to the correct application. In the very same fashion, Rebounds are sent to Hyperdrive and re-routed to the original caller via the calling transport.

Hyperdrive is always an hidden detail on each end, but you will have to choose which Hyperdrive transports should be enabled in your device.

Hyperdiscovery

Hyperdiscovery is an Hyperspace feature which enable devices to be dynamically discovered, if on the same Hyperspace network, based on their available capabilities. Hyperdiscovery, in the very same fashion, has a number of transports associated to Hyperspace transports which implement discovery for a specific protocol. Using Hyperdiscovery is then possible not only to discover devices on Hyperspace, but also to find out about which transports are available for every device.

Hyperspace and QObject

When using Qt5 bindings to Hyperspace, you benefit from an additional integration feature between Hyperspace REST and Qt's QObject. Hyperspace, by itself, strives to be as lightweight as possible, and as such QObject is rarely used among the APIs, also because Hyperspace itself is stateless.

On the other hand, QObject's property framework implements complete RESTful semantics. Hyperspace is then capable of exporting any QObject as a REST resource, using its QObject properties as the REST properties. This is achieved through two main subclasses, Hyperspace::REST::PropertyCollection and Hyperspace::REST::PropertyResource.

There are several property parameters which influence the behavior of Hyperspace when exporting objects:

  • READ: When a READ attribute is present, the property's current value is returned when a GET operation is performed on the resource.
  • WRITE: When a WRITE attribute is present, the property's current value will be altered when a PUT or POST operation is performed on the resource. More on this later.
  • NOTIFY: NOTIFY is compulsory if the property will change its value overtime. Given an aggressive caching is performed on Hyperspace's end, the property's value is read only once, and whenever it is changed. It is advised to always declare a NOTIFY attribute on stateful properties.
  • MEMBER: When MEMBER is used, it is implied the property is both readable and writable.
  • STORED: STORED defines the behavior of the resource when writing. As per RESTful semantics, PUT acts on idempotent resources, whereas POST acts on non-idempotent resources. When STORED is true, the property is defined as idempotent, and a PUT operation will succeed, when false a POST operation will succeed.

Let's have a look at a small example.

class MyRESTResource : public Hyperspace::REST::PropertyResource
{
Q_OBJECT
Q_PROPERTY(QString valueOne READ valueOne WRITE setValueOne NOTIFY valueOneChanged STORED true)
Q_PROPERTY(int valueTwo READ valueTwo NOTIFY valueTwoChanged STORED true)
Q_PROPERTY(QString doThings READ lastThingDone WRITE startDoingThings STORED false)
public:
[...]
};

Under the assumption our Gate is working with a JSON serializer, a GET operation will return

{ "valueOne": "value", "valueTwo": 2, "doThings": "reboot" }

We can write to valueOne and doThings. On the other hand, we can use a PUT operation to write to valueOne and a POST operation to write to doThings. So sending a payload like:

{ "valueTwo": 3, "doThings": "shutdown" }

Would fail both with PUT and POST. We should instead send:

{ "valueTwo": 3 }

Through a PUT operation, and:

{ "doThings": "shutdown" }

Through a POST operation.