# What is Engine?

Engine is a platform for creating digital experiences for physical environments. It connects devices, hardware systems and third-party software to provide an API for the built environment.

{% hint style="info" %}
Engine was the pre-cursor to PlaceOS. These guides are provided as a reference for existing deployments. For up-to-date information on the current platform, head on over to [placeos.com](https://placeos.com).
{% endhint %}

* **Software only** built on the modern web.
* **Open source** continuously improved and backed by a full-time team of developers.
* **Extensible** able to run stand-alone, or integrated into staff, building or third party apps.
* **Vendor agnostic** designed to bridge multiple disparate systems.
* **Scale-able** from rooms, to buildings, to precincts.
* **Distributed** multi-node replication for live backup and fail-over.
* **Secure** audit-able interaction with the physical environment and encrypted device communication.

The platform handles the low level device communication, system management and connectivity with user interfaces so that you can focus on how these combine to create intuitive, modern, connected environments.

Designed to run within your on-prem environment or private cloud the entire stack is open to ensure the privacy of your organisation and users is respected.

Deployments running today integrate tens of thousands of devices and services into unique experiences designed to improve health and happiness for staff and tenants.

There are no restrictions on what can be built, but here are some examples of [what's possible](https://acaprojects.com/#spotlight).

## Who builds it?

[Place Technology](https://place.technology) is a team of developers, designers and technologists based across Australia, Singapore, Hong Kong, Shanghai and London. We believe in modern tech and despise black boxes.


# Key Concepts

Core concepts to be familiar with when working with Engine.

Regardless of if you are designing, building or managing deployments, it is essential to understand a few foundational components and how they relate.


# Drivers

*Drivers* are an open plugin system. They provide the ability to either:

* **communicate** with external systems, hardware, or other software platforms (*device* and *service* drivers), or
* **coordinate** actions across modules and build complex behaviours (*logic* drivers).

![Drivers either communicate or coordinate.](/files/-LeK5oo5NehpRAF1MLPe)

All drivers, regardless of type, must be instantiated as [modules](/key-concepts/modules) before they can be used.


# Modules

*Modules* are instances of [drivers](/key-concepts/drivers). These represent a device to be controlled, service integration or piece of logic that controls how a system should behave.

![Modules are instances of drivers.](/files/-LeKFkzdUDnEJtqf2vTO)

All modules expose two things:

1. **State** - status information about the device, service, or higher level logic they control (e.g. power status, upcoming booking info, recent chat messages)
2. **Behavior** - actions which they can execute (e.g. power on/off, create/edit booking, post a chat message)

Each module can be individual started or stopped at any time. When started, ngine will attempt to connect to the associated physical device or service and keep track of its status. When Stopped, Engine will disconnect from that device and not send any commands to it. Or in the case of logic modules, this can be used to enable / disable its functionality.

Modules must be associated with at least one [system](/key-concepts/systems). Where a device or service is used across multiple systems (such as a lighting gateway, centrally racked matrix switcher, or common service such as a chatbot integration) the same instance can be shared across everywhere it is needed.


# Systems

A *system* is a collection of [modules](/key-concepts/modules), [settings](/key-concepts/settings) and basic metadata (name, description etc). They provide the main logical building blocks within Engine deployments.

![Systems have settings and a collection of modules.](/files/-LeJgNwvu3yRHgUe6sss)

Systems often represent physical spaces (such as a meeting room) but can also represent standalone items such as a digital signage endpoint or a non-physical system with information based inputs and outputs.


# Zones

*Zones* are collections of [systems](/key-concepts/systems). A system can be tagged as a member of any number of zones.

![Zones have settings and a collection of systems.](/files/-LZZ3h3uyjW3MAURDd1v)

They serve two main purposes:

1. Logical groupings of systems with common traits (such as those in the same building, or of the same basic type).
2. A point to define settings that compose across systems.


# Settings

*Settings* are the configuration information that define how an Engine deployment should behave. They are ultimately ingested and used by [modules](/key-concepts/modules), but can be defined against [zones](/key-concepts/zones), [systems](/key-concepts/systems), [drivers](/key-concepts/drivers) or [modules](/key-concepts/modules) and compose to create an overall system configuration that can be managed at scale.

Settings are expressed as [JSON data](https://en.wikipedia.org/wiki/JSON#JSON_sample), or simply–key/value pairs:

{% code title="" %}

```javascript
{
  "key": "value",
  "foo": [1, 2, 3],
  "bar": true 
  "baz": { 
    "qux": 1.234
  }
}
```

{% endcode %}

{% hint style="info" %}
JSON is a common, simple data-interchange format that is designed to be easy for humans to read and write, and for machines to parse and generate. If it is a new concept, you can [learn more here](https://learnxinyminutes.com/docs/json/).
{% endhint %}

The naming and expected values are defined within drivers and will vary based on the integrations in use within each deployment, however the overall structure will always be similar.

Examples of some common uses for settings are: available video inputs/outputs, source names, DSP block ID's, lighting control ID's, device auth information, desk / room auto-release timeouts etc.

### Settings lookup

To simplify large deployments, standardise systems and reduce management overhead, settings are designed to be defined at different layers which then combine to produce the final configuration.

Settings defined on a zone are inherited by all systems in that zone.

Similarly, settings defined on a driver are inherited by all modules created from it.

![Settings inheritance.](/files/-LeKr_GiDA6etXWbQjgn)

When settings are inherited from a zone or driver they will be aggregated with any settings defined directly on a system or module. If an inherited setting has the same key as one that is defined specifically for that system/module, the latter will override the inherited attribute. This allows a general config to be applied at the highest 'shared' point of a system, with more specific configuration applied on a system or individual module basis.


# Interfaces

*Interfaces* are web pages or native apps that provide users with a direct method of interaction with an Engine instance. Common examples of these are a staff app, control UI, booking system or other experiences that lend themselves to screen-based interaction.


# Triggers

*Triggers* provide the abiity to dynamically link state and behaviour across different modules. They are used to define actions that fire based on a certain system state, time, or external input from a webhook.

Using backoffice it's possible to create, assign and manage triggers to build event driven behaviour that accompanies core system logic.


# Security

A core challenge that Engine solves is providing a secure way to connect and interact with physical spaces. Deployments form an interface that isolates individual hardware components and subsystems of a building from direct communications. Connectivity to these is then provided by a modern API service that supports regular patching and updates to safely support integration.

## Data in Transit

Engine API's and static resources are served over HTTPS only.

| Supported Protocols |
| ------------------- |
| TLS v1.2            |
| TLS v1.3            |

| Supported Ciphers |
| ----------------- |
| EECDH+AESGCM      |
| EDH+AESGCM        |
| AES256+EECDH      |
| AES256+EDH        |

Unique Diffie-Hellman parameters are generated for each new server.

SSL certificates can be provided and signed by your internal CA or generated by Place Technology and signed by [Let’s Encrypt](https://letsencrypt.org/).

## Data at Rest

Minimal configuration information is stored on disk as part of the on-premise infrastructure. All system settings support encryption via AES256-GCM. This is stored by the [data service](https://docs.acaengine.com/deployment/architecture#data) and includes:

| Information Type              | Description                                                                                                                                                                                    |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| System and Zone configuration | <p></p><ul><li>System (room) / Zone names</li><li>System / Zone descriptions</li><li>Room resource mailbox address (if using a calendar integration)</li><li>System / Zone settings</li></ul>  |
| Device settings               | <p></p><ul><li>Device name</li><li>Device description</li><li>Device configuration</li><li>Device role based account username</li><li>(encrypted) Device role based account password</li></ul> |
| Device metadata               | <p></p><ul><li>1 month history of online/offline status of each device</li></ul>                                                                                                               |
| User data                     | <p></p><ul><li>Email address</li><li>First and last name</li><li>Username</li><li>User’s permissions within Engine application</li><li>Federated authentication source</li></ul>               |

The [search service](https://docs.acaengine.com/deployment/architecture#search) stores an optimised index of system, zone and device names and descriptions.

## Authentication

All API requests use short-lived auth tokens obtained via OAuth2.

Authentication for token creation takes place via an external identify service (SSO). Options include SAML2 and OAuth2.

No Engine components store or have access to SSO user credentials at any point during authentication.

In cases where an external identify provider is not available (dev / staging environments), local role-based accounts may be created. Credentials for these are encrypted using scrypt (256 bit AES using GCM ciphers) prior to storage. No “default” passwords exist for these.

## Privacy

No information is ever transmitted externally by the platform. Default deployment configurations do not include any remote telemetry, data collection or remote components.

Information collected by device and service integrations is dependent on driver functionality and versions used. All drivers are open source and individually auditable within each deployment.


# System Architecture

Engine is packaged and distributed as a set of Docker containers for deployment within private cloud, or on-premise infrastructure. All application components run on hosts owned and managed by your organistation.

## Application Services

Each of these components provide a core service to the application and require at least one instance. These may be replicated any number of times to provide redundancy, additional load capacity, or geographic distribution to improve latency in multi-site deployments.

### <img src="/files/-Lh-I9Cdspc0SRIKYHYT" alt="" data-size="line"> Engine

This is the core application. Instances of this service provide the API endpoints and run drivers. All communication with external service integrations, devices and third-party platforms takes place from these nodes.

### <img src="/files/-Lh-JCyHbpT1okp05xlx" alt="" data-size="line"> Web

Instances of the web service are responsible for the routing of incoming traffic from API users and serving static assets used by user interfaces. These are a Dockerized instance of the [Nginx](https://www.nginx.com/) web server. Web nodes provide the gateway between external users and instances of the Engine service.

### <img src="/files/-Lh-JI-Q7aFYFzyqD9WA" alt="" data-size="line"> Data

Persistent storage–system/zone configuration, settings, device metadata, user data and access logs–is managed as a distributed document store, backed by [Couchbase](https://www.couchbase.com/). Instances of this service are only interacted with by Engine nodes.

### <img src="/files/-Lh-JLIlFXWSMyaSPDO5" alt="" data-size="line"> Search

A dedicated search service is used to provide fast, efficient access to information managed by Data nodes. This is supported via [ElasticSearch](https://www.elastic.co/products/elasticsearch). As with the Data service, all interaction with Search nodes takes place directly from the Engine service.

## External Components

In addition to the core application services, a number of external components contribute to the overall deployment environment. These are not created as part of Engine, but may require configuration as part of the deployment.

### <img src="/files/-Lh0QJUJV2GzaYfydQpS" alt="" data-size="line"> Clients

Clients are devices running interfaces distributed by Web nodes, or native apps and other services which use the Engine API. Common examples may be a phone running a staff app, room booking panels or wayfinding displays.

### <img src="/files/-Lh0QN0Hi0D0hm1tCu3X" alt="" data-size="line"> Identity Provider

Interactions with Engine must be associated with a valid user. To simplify UX and improve security, authentication integrates tightly with existing identity providers operating in an environment. This may be services such as Azure AD, Google Sign-In or other services providing SAML or OAuth2 endpoints.

### <img src="/files/-Lh0QPhJccw747QA-qNH" alt="" data-size="line"> Load Balancer

In distributed and active-passive deployments a load balancer is used upstream of Web nodes to direct traffic to the appropriate infrastructure.

### <img src="/files/-Lh0QSzH5RauyEUJJeCT" alt="" data-size="line"> Integrations

The specifics of external service integrations will vary based on your system requirements and functionality, however this commonly includes calendaring services, network infrastructure, audio/video devices, lighting gateways and other systems that form part of your physical environment.

## Common Deployment Scenarios

{% tabs %}
{% tab title="Distributed" %}

<div align="center"><img src="/files/-Lh0PEDEpTr7SQHAAJCp" alt="Example distributed deployment architecture."></div>

Distributed deployments split application services across hosts using a three-tier architecture–web, app and db. This provides isolation of different application components and provisioning of resources appropriate for each.

Each of these stacks may be replicated as required to meet availability requirements. To provide redundancy in the event of network or infrastructure failure it is recommended that these replications take place across availability zones.

{% hint style="warning" %}
Due to limitations of hardware being interacted with, integrations will only be active from a single Engine node at any point in time. In the case of primary node failure, the secondary node will establish device connectivity and begin accepting control traffic.
{% endhint %}
{% endtab %}

{% tab title="Active-Passive" %}
![Active-passive infrastructure deployment.](/files/-Lh3Y3nDx-29_nEiusEO)

In an active-passive deployment each stack runs on a self-contained host. The load balancer should direct all traffic to the primary host. If this becomes unavailable, traffic should be re-route to the secondary host to provide continued operation.
{% endtab %}

{% tab title="Standalone" %}
{% hint style="danger" %}
Standalone deployments are not recommended for production use. They provide a good option for UAT environments, proof-of-concept systems, or test environments where availability is not a critical concern.
{% endhint %}

![Standalone deployment overview.](/files/-Lh4Bg7guuS52XWdMpgP)

In a standalone deployment all application components are provisioned on a single host. This provides simplicity of deployment and minimises infrastructure requirements, but offers no redundancy in the event of network or infrastructure failure.
{% endtab %}
{% endtabs %}


# Single Sign-On


# Configuring Engine for SAML2

Steps required for enabling SAML2 sign on for users logging in to all Engine web apps

By default, Engine uses local authentication. An admin account is generated upon initial deployment and the administrator can manually create additional user accounts in the Backoffice (on the Users tab).

Switching to federated authentication is recommended. There are 3 steps required:

1. In Backoffice, create a new SAML2 Identity provider entry
2. In your organisation's SAML2 Identity provider dashboard (e.g. Azure AD, ADFS, Auth0), create the SAML2 Service provider for entry for Engine
3. Back in Backoffice, update the SAML2 Identity provider entry with the new details retrieved from step 2

## Prerequisites

1. The domain where users will visit to login must exist as a valid **DNS** entry&#x20;
2. Browsers should consider the domain secure: Valid **SSL certificates** should be in place and served by either your load balancer or the web server in front of Engine.

## Step 1: Add a new SAML2 authentication source

1. Login as an admin to backoffice (https\://\<engine-url>/backoffice/\\
2. On the **Domains** tab, select the Domain that represents the URL you wish to enable SAML2 for.
3. In the Authentication section click "Add new", select "**SAML2 / ADFS**"
4. In the "New Authentication Source" Window, 1. Enter a descriptive **Name** that represents your SAML2 Identity provider, e.g. "MyCompany ADFS" 2. Enter some temporary text (any text) into the "**IDP Target URL**" field. It will be edited later with the correct details. 3. Enter some temporary text (any text) into the "**Assertion URL**" field. It will be edited later with the correct details. 4. Paste the below default text into **Request Attributes**:

   ```
        [
            {
             "name": "http://schemas.microsoft.com/ws/2008/06/identity/claimsclaims/objectidentifier",
             "name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
             "friendly_name": "Unique ID"
            },
            {
                "name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
                "name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
                "friendly_name": "E-Mail Address"
            },
            {
                "name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
                "name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
                "friendly_name": "Given Name"
            },
            {
                "name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
                "name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
                "friendly_name": "Surname"
            }
        ]
   ```

   1. Paste the below default text into **Attribute Statements**:

      ```
        {
            "email": [
                "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
            ],
            "first_name": [
                "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
            ],
            "last_name": [
                "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
            ],
            "login_name": [
                "http://schemas.microsoft.com/identity/claims/objectidentifier"
            ]
        }
      ```
5. Click **Save.** The authentication source will be saved with some default values.
6. The new Authentication source will now appear in the list. **Copy the URL** to your clipboard.
7. Click edit on the Authentication source which was just created.
   1. In the Assertion URL field, paste the URL which was copied in the previous step, but edit it to include "/callback"
   2. Example: If you copied *"*<https://engine.example-organisation.com/auth/adfs?id=adfs-XXXXXXXX>*"* then set the Assertion URL to "<https://engine.example-organisation.com/auth/adfs/callback?id=**adfs-XXXXXXXX**>*"*
   3. Click Save

## Step 2: Register a new service/app in your authentication provider

### Prerequisites

You will need to enter these details from Step 1 into your SAML2 Identity provider dashboard:

1. The **Assertion URL** (also known as the **Callback URL**)
2. The **Issuer** (also known as the **Identifier**)
3. The **Login URL** which is simple the homepage of the app (e.g. <https://engine.example-organisation.com/app-name/>)
4. Optionally, the **SAML2 Metadata URL**. This can XML file contains the above information and can be fed into to some configuration dashboard (like ADFS). For the engine auth source you created above in step 1.7, the metadata url will be "<https://engine.example-organisation.com/auth/adfs/metadata?id=**adfs-XXXXXXXX**>*"*

Follow the instructions for your Identity Provider:

* [Azure AD](/deployment/single-sign-on/saml2-with-azure-ad)
* [ADFS](/deployment/single-sign-on/saml2-with-adfs)
* [Auth0](/deployment/single-sign-on/saml2-with-auth0)

## Step 3: Update the SAML2 authentication source settings

You will enter these details from Step 2 into Backoffice:

### Prerequisites

1. **Issuer** (also known as **Identifier**): If your ID provider defines an Identifier instead of letting you define one, Update the auth settings to use the required Identifier.

   For example, Azure AD defines fixed identifiers in the form *"spn:00000000-0000-0000-0000-000000000000"* where the 0 digits are the *Application (client) ID*, found on the Overview page of the Azure AD Application.
2. **IDP Target URL** (also known as **Assertion URL**): This is the url that Engine redirects users to in order to login with your SAML2 ID provider
   * Azure AD URLs are often in the format: [https://login.microsoftonline.com/\*\*\<tenant-ID>\*\*/saml2](https://login.microsoftonline.com/**<tenant-ID>**/saml2)
   * ADFS URLs are often in the format: <https://adfs.myorganistaion.com/adfs/ls>
   * Auth0 URLs are often in the format: <https://myorganisation.auth0.com/samlp/>

### Update Engine's new authentication settings

Start by clicking edit (pen icon) on the Authentication that was created in Step 1 *"Add a new SAML2 authentication source"* (top of this page)

1. Replace the **Issuer** field with the Issuer from your SAML2 ID provider (unless your SAML2 ID provider already matches)
2. Replace the **IDP Target URL** field with the SAML2 Identity provider login url that was generated in Step 2 *"Register a new service/app on your authentication provider"*
3. Click Save

### Test new settings

Now test logging in by copying the URL for the Authentication (e.g. <https://engine.example-organisation.com/auth/adfs?id=adfs-XXXXXXXX> ) and opening a new private browser window (e.g. Chrome incognito, Edge InPrivate session) and pasting in that url. You should be redirected to your SAML2 ID provider's login page, and if successfully logged in, redirected again back to a the engine domain <https://engine.example-organisation.com/>

If there are errors, the configuration on either the ID provider of Engine side may need to be tweaked to match. A [SAML2 troubleshooting guide](https://auth0.com/docs/protocols/saml/saml-configuration/troubleshoot) should be followed or an [Engine Service Desk](https://support.acaprojects.com/) ticket can be raised for assistance. Once there are no login issues, this SAML2 authentication can be set as the default for the whole domain.

### Make the new SAML2 authentication option the default login

Still on the Domains tab, edit the DOMAIN by clicking the pen icon at the top right (above the "Users" sub-tab)

1. Replace the **Login url** with */auth/login?provider=adfs\&id=**adfs-XXXXXXXXX**\&continue=* where ***adfs-XXXXXXXXX*** \_\*\*\_is the ID from Engine's SAML2 login url
2. Replace the **Logout url** with */auth/logout?continue=**\<SAML2\_LOGOUT\_URL>*** where <***SAML2\_LOGOUT\_URL>*** \_\*\*\_is the logout url provided by your SAML2 identity provider.
3. Azure AD logout URLs are often in the format: <https://login.microsoftonline.com/>*/saml2*
4. ADFS logout URLs are often in the format: <https://example-organisation.com/adfs/ls/?wa=wsignout1.0>
5. Auth0 logout URLs are often in the format: <https://example-organisation.com.auth0.com/samlp/>*/logout*


# SAML2 with Azure AD

If using Azure Active Directory for SSO a new or exisiting "App Registration" will need to be configured to provide be the SAML2 identity provider for Engine.

## Step 1 - New or Existing App Registration

* Login to portal.azure.com and browse to [Azure AD > App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps)
* Locate the existing app that was created for [o365 Graph API access](https://github.com/acaprojects/docs/tree/be220954cefb53b2ac2ca82f775a56993117e99d/deployment/single-sign-on/integrations/directory-services/microsoft-office365.md). If none has been created yet, then create a new app registration now, as this app can be used for both SSO and o365 Graph API access.
  * If creating a NEW app registration:
    * Name it and select the appropriate "Support Account types" (which is usually "Single tenant").
    * Paste the Engine **Assertion URL** (generated in Step 1 of [Configuring Engine for SAML2](https://github.com/acaengine/docs/tree/290606a3bdbee7adcb62f37e2da8c19d25352b53/deployment/single-sign-on/configuring-Engine-for-saml2.md)) into the **Reply URL** field. Leave the type as "Web". Click Register to finish.
  * If configuring an existing app registration:
    * Navigate to Overview -> Redirect URIs
    * Paste the Engine **Assertion URL** (generated in Step 1 of [Configuring Engine for SAML2](https://github.com/acaengine/docs/tree/290606a3bdbee7adcb62f37e2da8c19d25352b53/deployment/single-sign-on/configuring-Engine-for-saml2.md)) into the **Redirect URI** field. Leave the type as "Web". Click Save to finish.
* Confirm that you have access to the [SAML2 Federation Metadata URL](https://docs.microsoft.com/en-us/azure/active-directory/azuread-dev/azure-ad-federation-metadata) for your Azure Tenant. This is generally in the below format. Data in this XML file may be used later in Step 3, OR can be used to configure advanced custom claims.
  * [https://login.microsoftonline.com/\<Tenant](https://login.microsoftonline.com/<Tenant) ID or Domain Name>/FederationMetadata/2007-06/FederationMetadata.xml&#x20;

## Step 2 - Edit the App Manifest

Now two fields of the app Manifest need to be edited (`groupMembershipClaims` and `optionalClaims`) which will be described in the below steps.

* Select the new/existing app from Step 1 from the list of [App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) and then select Manfiest (near the bottom) from the menu on the left.
* In the editor, set [groupMembershipClaims](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims#configuring-group-optional-claims) to either `“All”` or `“SecurityGroup”`. [This page](https://blogs.msdn.microsoft.com/waws/2017/03/13/azure-app-service-authentication-aad-groups/) may help you decide which is most suitable for your organisation. If unsure, select `All`:
  * `“SecurityGroup”` - groups claim will contain the identifiers of all security groups of which the user is a member.
  * `“All”` - groups claim will contain the identifiers of all security groups and all distribution lists of which the user is a member
* Set the value of the `optionalClaims`to include these 4 claims: `firstname`, `lastname`, `upn`, `email` in the saml2Token. An example is below:

  ```
    "optionalClaims": {
        "idToken": [],
        "accessToken": [],
        "saml2Token": [
            {
                "name": "email",
                "essential": true
            },
            {
                "name": "upn",
                "essential": true
            },
            {
                "name": "family_name",
                "essential": true
            },
            {
                "name": "given_name",
                "essential": true
            }
        ]
    },
  ```
* Click Save

## Step 3 - Collect data required by Backoffice

The App Registration is now configured for Engine. Two pieces of info are required to be entered back into Backoffice (Step 3 of [Configuring Engine for SAML2](https://github.com/acaengine/docs/tree/290606a3bdbee7adcb62f37e2da8c19d25352b53/deployment/single-sign-on/configuring-Engine-for-saml2.md)):

### **Issuer**

For Azure AD the "Issuer" will be the "Application (client) ID" found on the Overview page of your App Registrating, but with "spn:" added to the front. E.g. *"spn:00000000-0000-0000-0000-000000000000"* where the 0 digits are the Application (client) ID from Azure AD. Paste this value into the Issuer field of the SAML2 auth object that was created in Engine.

### **IDP Target URL**

Also known as **SAML2 sign-on endpoint.** This is the url that Engine redirects users to in order to login with your SAML2 ID provider. For Azure AD it is : [**https://login.microsoftonline.com/**](https://login.microsoftonline.com/)**\<TENANT-ID>/*****saml2*** *\*\** where the Directoy (tenant) ID can be seen on the Overview tab of your Azure App Registration. Paste this into the IDP Target URL field of the SAML2 auth object that was created in Engine


# SAML2 with ADFS

If using ADFS, [these steps](https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/create-a-relying-party-trust) can generally be followed. Engine will use these four SAML2 claims:

* Firstname
* Lastname
* Email Address
* User ID (usually a login name, e.g. UPN or WindowsAccountName)


# SAML2 with Auth0

## Prerequesites

* You are an administrator of an Auth0 domain and would like Engine user to be redirected to this Auth0 domain for signup and SSO login.

## Auth0 configuration

* Follow [these instructions](https://auth0.com/docs/dashboard/guides/applications/register-app-spa) to create a register a single-page-app for SAML2 SSO using Auth0
* The `Name` of the applicaiton can be the domain name on which your instance of Engine is located.
* On the `Addons` tab, enable SAML2 Web App and use [these steps](https://auth0.com/docs/protocols/saml/saml2webapp-tutorial) as a guide.
* Set the `Application Callback URL` to match Engine's `Assertion URL` (e.g. [https:///auth/adfs/callback?id=adfs-XXXXXX\\](https:///auth/adfs/callback?id=adfs-XXXXXX\)\\)
* Paste in the below for `Settings`:

  ```
  {
  "mappings": {
    "email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
    "first_name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
    "last_name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
    "login_name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/windowsaccountname"
  }
  }
  ```


# SAML2 with GSuite


# OAuth2


# Supported Integrations

Place Technology provide a range of pre-built drivers for integrating with third party services and devices. Drivers are written in [Crystal Lang](https://crystal-lang.org/) and based on our [Framework](https://github.com/PlaceOS/driver).

If a driver for your device is not available, you can raise a request to have the driver written via our Service Desk or refer to the [Developer Guide -> Building Drivers](https://docs.acaengine.com/developer-guide/drivers) section to write your own:&#x20;

<https://github.com/PlaceOS/driver>

## Authentication

* OAuth2 (JWT)
* SAML2
* Azure AD B2C for external&#x20;

## Directory Services

* Azure AD (via MS Graph API)
* Hybrid on-premise Active Directory (via MS Graph API)
* Google GSuite
* IBM Domino

## Messaging

* Email (MS Graph, Gmail, SMTP, AWS SES)
* SMS (MessageMedia)
* MQTT
* Webhooks
* Node-RED

## Resource Booking

* Exchange Online (via MS Graph API)
* GSuite (Google Calendar API)
* IBM Domino
* Native PlaceOS resource booking

## Common Protocols/Standards

* HTTPS REST / JSON
* HTTPS Webhook (post and receive)
* TCP/IP
* MQTT
* SNMPv2
* KNX (via IP Gateway)
* BACnet (via IP Gateway)
* Modbus TCP

## Real Time Location

* Cisco Meraki RTLS
* Cisco CMX
* HP Aruba ALE

## Sensors

* MQTT ingest
* Azure IOT Hub
* Vergesense
* XY Sense
* Freespace
* Floorsense
* Officernd
* Pressac
* PointGrab Cognipoint

## Network

* Cisco Meraki
* Cisco CMX
* Cisco ISE
* Cisco Switches

## Building Access

* Lenel
* Gallagher
* Honeywell
* Johnson Controls

## Conferencing / UC

* Cisco Collaboration Endpoints (via Webex API)
* Microsoft Teams (via MS Graph)
* Pexip Management API
* Polycom RealPresence Group Series

## Displays

* LG (Displays)
* NEC (Displays/Projectors)
* Panasonic (Displays/Projectors)
* Sony (Displays/Projectors)
* Samsung (Displays)
* Sharp (Displays)
* Screen Technics (Screens/Lifts)
* PJLink projectors
* CommBox

## Video

* Extron (Switchers)
* Atlona (Video over IP)
* Lightware Switchers
* SVSI
* Kramer (Switchers)
* Echo360 Capture Appliances
* MediaSite Capture Appliances/Server
* Axis Cameras
* Sony Cameras (CGI, VISCA)
* Barco ClickShare
* TriplePlay
* Microsoft Surface Hub
* Wolfvision Document Cameras
* Lumens Document Cameras

## Audio

* QSC Q-SYS
* Biamp
* Shure
* ClearOne
* Denon
* Clock Audio
* Bose ControlSpace
* PowerSoft
* Symmetrix
* ClearOne

## Lighting

* KNX
* C-Bus
* DynaLite
* Lutron
* DALI
* Helvar

## Other

* Global Cache
* Kentix Sensors
* Foxtel STB
* Gantner Relaxx Lockers


# Directory Services

Options for integrating Engine with enterprise directory services.

Engine extends beyond IoT control/monitoring when integrated with one of several enterprise directory service options. Directory service integration enables the Engine Staff API, which allows apps to offer experiences such as room searching/booking, user directory searching and calendar event information/interactions.

Data from directory services can be combined with IoT and/or location services data to add meaningful context to user interactions within a space. The result can be used to trigger an event in the space in real time or stored for retrospective analytics.

The preferred directory service is Microsoft Office 365 via Graph API. Other options are Microsoft Exchange (via EWS) and IBM Domino (via Domino Access Service).


# Microsoft Office365

Engine integrates with Microsoft Office 365 via [Graph API](https://docs.microsoft.com/en-us/graph/overview). An Azure Active Directory admin must use Azure Portal to create an "App Registration" for Engine, and then details of this app registration will be configured in Engine.

## Create an App Registration in Azure Portal

The below Microsoft article and video can be referred to for additional context:

* Article: [Register an application with the Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app)
* Video: [Getting Started with Microsoft Graph and Application Registration](https://www.youtube.com/watch?v=93j0MmRruFo)

An Azure user with admin permissions for Azure Active Directory will need to perform these actions:

### Register the App

1. Login to the Azure Portal and view the ["App Registrations" page of the "Azure Active Directory" blade](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps).
2. If an existing App has been registered for Engine for use with Azure Single-Sign-On (SAML2), then we can re-use this app - select it. If not, then click "New registration"
3. Type a descriptive name for the application, set the Supported account Type to *"Accounts in this organizational directory only"* and leave the Redirect URI blank. Click "Register".

### Configure App permissions

While still in Azure Portal on the page for the above registered App:

1. In the menu on the left, select “API permissions” and click “Add a permission”. Then select “Microsoft Graph” as the API and select **Application Permissions** as the permission.
2. Typically, allow the below permissions (the list may vary depending on the desired functionality/restrictions on the web applications that will be using this Graph API integration):
   * `User.Read.All`
   * `Group.Read.All`
   * `Calendars.ReadWrite`
   * `Contacts.Read.All`
   * `Place.Read.All`
3. After adding the required Application permissions, click "Grant admin consent for ACA Projects" on the "API Permissions" page of the registered App, then click Yes.
4. On the "Overview" page of the App, copy the below two values, which will be used in the next section to configure Engine to connect to this Registered App:
   * `Application (client) ID`
   * `Directory (tenant) ID`
5. On the "Certificates & secrets" page of the App, click "New client secret":
   * Add a meaningful description
   * Set Expiry to "Never", or as appropriate (ACA will no longer be allowed to use this credential after expiry)
   * Copy the Value of the secret, as it will be used in the next section to configure Engine.

Now you should have collected 3 text values that will be used in the next section:

* `Application (client) ID`
* `Directory (tenant) ID`
* `Client secret`

### Optional: Restrict App access to specified AD Groups only

To restrict the Graph App's access to only the objects that exist in specified AD groups, [**Application Access Policies**](https://docs.microsoft.com/en-us/powershell/module/exchange/organization/new-applicationaccesspolicy?view=exchange-ps) can be used.

1. Ensure those resources (users, rooms) exist in those AD groups&#x20;
2. Use Powershell to add the new policy, e.g:

`# Restrict to a group of Rooms`  \
`New-ApplicationAccessPolicy -AppId <Graph App ID> -PolicyScopeGroupId <AD group that all bookable rooms exist in> -AccessRight RestrictAccess -Description "Restrict this app to Engine bookable rooms"`

`# Restrict to a group of Users`  \
`New-ApplicationAccessPolicy -AppId <Graph App ID> -PolicyScopeGroupId <AD group that all staff app users exist in> -AccessRight RestrictAccess -Description "Restrict this app to Engine app users"`

## Configure Engine to connect to Graph API

1. Login to https\://\<your-engine-url>/backoffice/#/drivers/ and select an existing or create a new “Office365 Room Booking Panel Logic” driver and click edit (pen icon at top right). Note down the driver ID, which you will see in the browser URL bar and looks like “dep-xxxxxxxx” (you will need this later)
2. Enter the o365 values (client, secret, tenant) into the the placeholders which you should see. These values are on the portal.azure.com page where you created the Azure App Registration (above), then click Save.
   * `“office_client_id”`
   * `“office_tenant”`
   * `“office_secret”`
   * Tip: Sensitive values (like office\_secret) can be encrypted by inserting `$` in front of the setting name (e.g. `“$office_secret”: "xxxx"`)
3. Test the configuration by navigating to a System which has a Device (module) instance of the above Driver (or create a Device instance).
   * Edit the System: Set the System’s Email to a real email address that exists on the o365 tenant
   * On the About page of the system, Select `Bookings 1` from "Execute command", then select the function `fetch_bookings` and click “Send”.&#x20;
   * An array of booking details (blue) should be returned (it might be empty `[]` if there are no bookings), or an error (red), if there is an issue grabbing the events from o365.
   * If blue, then the settings are correct and is currently being used for all Room Booking Panels. In the next step we'll configure Engine Staff API to use the same credentials.
   * If red, capture the error from javascript console (e.g. Chrome debug tools, Console tab) which will help yourself, an integration partner or ACA to pinpoint the cause.
4. Navigate to Domains (menu bar on left). Select the Domain that you’d like integrated with this Office 365 tenant and click it, then click edit (pen icon at top right).
5. In the "Config" box, ensure that the “o365\_driver” value exactly matches the driver ID of the “Office365 Room Booking Panel Logic” driver which you gathered in step 1 (e.g. "dep-xxxxxxxx”"). Click Save. If there is none, then create one like this:
   * `"o365_driver": "dep-xxxxxxxx"`
6. Test the Staff API integration by logging into an app that uses Staff API (e.g. Engine template Staff App) with a user who’s Calendar exists in the configured o365 tenant’s Exchange directory. You should be able to view/create events as this user.
   * If there are issues, note down the error information from requests like `/api/staff/bookings` which will be shown in Chrome/Firefox Debug tools, on then Network tab.
   * Full backend error logs can also be viewed when ssh'ed into the VM: `docker logs --tail 99 -f engine`


# IoT


# Device Drivers

For full list of PlaceOS Drivers, see [Supported Integrations](/integrations/supported-integrations#authentication).


# Node-RED

To enable rapid connectivity, experimentation / prototyping, or re-use of existing integrations [Node-RED](https://nodered.org/) may be deployed alongside Engine.

## Access to Node-RED web interface

By default, the Node-RED web interface is exposed *locally only* on the machine that is running Engine, at <http://127.0.0.1:1880>. If accessing the Node-RED web interface from a remote client, use methods such as SSH port forwarding to securely mirror the Engine server's 127.0.0.1:1880 to your machine's 127.0.0.1:1880.

## Installing nodes

Additional Node-RED nodes can be installed by running the below command on the machine running Engine:

`docker exec -it node-red npm install <package-name>`

For example, to install the [connector for Azure IOT Hub](https://flows.nodered.org/node/node-red-contrib-azure-iot-hub), run:

`docker exec -it node-red npm install node-red-contrib-azure-iot-hub`

## Connecting Engine to Node-RED

### Node-RED Websocket output

Engine connects to Node-RED via Node-RED's websockets modules (one for input and one for output). Drag an output websocket module into the Flow, double-click it and set the Type to `Listen on` and the Path to `/ws/`.

![Node-RED Websocket output module](/files/-LsGSUYwLV56y_DmB5QH)

### Engine websocket input

* On the Drivers page of Backoffice, import the [Node-RED Websocket](https://github.com/acaprojects/ruby-engine-drivers/blob/beta/modules/node_red/websocket.rb) driver by searching for it and clicking "Import".
* Now in any System, add a module of that driver and set:
  * IP: `node-red`
  * Port: `1880`

After creating the module, start it and then monitor the [debug messages](https://github.com/acaengine/docs/tree/da66522f4d00324648877ea8a6ba9f92333bb7a3/backoffice/debugging.md).

Messages coming out of the Node-RED websocket output should appear as console log output when debugging the Engine "Node-RED Websocket" module. The status variable "message\_received" will also update to show the last string recieved. Simple strings may be used as inputs for Triggers.

The "Node-RED Websocket" driver can be used as a template for creating new drivers that take actions upon receiving messages from Node-RED, or send websocket messages to a Node-RED Websocket Input module. One example of this is the [Pressac desk sensor driver](https://github.com/acaprojects/ruby-engine-drivers/blob/beta/modules/pressac/sensors/ws_protocol.rb), which uses Node-RED to subscribe to real time updates from Azure IOT Hub.


# Azure IOT Hub

[Many IOT devices](https://catalog.azureiotsolutions.com/) can be configured to push messages to Microsoft Azure IOT Hub. IOT Hub can receive and collate messages from thousands of devices, and provide a single interface for applications such as Engine to securely connect to in order to receive realtime updates from any connected devices.

## Connection via Node-RED

Engine supports connection to Azure IOT Hub via a local Node-RED docker container (at `node-red:1880`) which ships by default as part of all Engine deployments. Once the connection is configured, Engine modules will recieve realtime notifications from MS Azure IOT Hub via the Node-RED websocket.

`[Engine module] >websockets> [Node-RED]:1880 >AMQP over websockets> [MS Azure IOT Hub]`

### Configuring Node-RED to connect to Azure IOT Hub

Prerequisite: First, ensure you've read the article on how Engine works with [Node-RED](/integrations/iot/node-red)

1. Install the Node-RED [connector for Azure IOT Hub](https://flows.nodered.org/node/node-red-contrib-azure-iot-hub), by running the below command on the VM/machine where Engine is installed:

   ```
   docker exec -it node-red npm install node-red-contrib-azure-iot-hub
   docker restart node-red
   ```
2. Access the Node-RED web interface by visiting <http://localhost:1880> to configure Node-RED.
3. Create an "Azure IoT Hub Receiver" node. Connect it it to your IOT Hub by setting the `connectionstring`, which you will find in your Azure Portal, under your *IOT Hub > Shared Access policies > iothubowner > Connection string-primary key* See heading ["Reading all messages received into Azure IoT Hub"](https://flows.nodered.org/node/node-red-contrib-azure-iot-hub) for more details.
4. Create a "websocket output" node and connect the output of the Azure node to the input of the websocket node

![Azure IoT Hub Receiver connected to Node-RED Websocket Module](/files/-Lt9Gq2mGkD0HtZNwKnN)

For instructions on how to connect and Engine Module to Node-RED, see the page on [Node-RED](/integrations/iot/node-red)


# Location Services


# Locating Users on a Network

## Overview

Using existing infrastructure in an organisation, there is typically enough data available to accurately locate staff. Wireless networks provide a rough indication of location and cabled infrastructure accurately shows who is sitting at individual desk locations.

This can also be augmented with sensors, as required, however sensors can only be used to indicate desk usage - falling back to wifi for staff location.

![](https://docs.google.com/a/acaprojects.com/drawings/d/s6_6XidWyamd0Zk0Uf1gAcQ/image?w=642\&h=555\&rev=1\&ac=1\&parent=14XIJbnvJBg23Qc_oc3JN5Ub0geETTSmTWr8Sd8YryLM)

### The Lookup Process

1. Lookup the username or email address of the person in question (staff search)
2. Grab the device mappings for that user (as per the diagram above)
3. Check if any of those devices are plugged in to a switch port (or have a desk reserved)
4. Fallback to wireless lookup of username, email or wireless MAC address if no desk is found

## Desk Locating Requirements

* Switch IP addresses
* SNMP or SSH service enabled on the switch (SSH preferred as it is easier to troubleshoot and secure)
* A list of switch ports to desk mappings
* A method for pairing staff to their devices

Most switches expose an SNMP service for tracking details of port usage and the devices connected to each port. If using SSH method, each switch is queried approximately once every 5 seconds.

This is a standard common to most network hardware manufacturers and defined by the following standard <https://tools.ietf.org/html/rfc4293>

CISCO switches support SSH and Engine supports SSHv2 for secure data transfer <http://www.cisco.com/c/en/us/support/docs/security-vpn/secure-shell-ssh/4145-ssh.html>

## Laptop Docking Stations

Desk locating relies on device MAC addresses to identify staff as they move around a building.

As docking stations often sit between the laptop and the switch, we need to ensure that the MAC address exposed by the docking station is unique to each staff member.

All commercial docking solutions offer a method for passing through a unique MAC, if they don’t already do this by default. Two of the more common docking solutions are HP (BIOS or EFI configuration) and [Displaylink](http://www.displaylink.com/products/universal-docking-stations) USB docks (Dell, Lenovo, Fujitsu, Targus, Kensington, HP and Toshiba among others).

Displaylink provide a Powershell [script](https://drive.google.com/a/room.tools/file/d/1ie_cEe0tP4tmYvhw1vh3YXO45XSFIA3Y/view?usp=sharing) to automate the configuration which can be deployed via SCCM <https://support.displaylink.com/knowledgebase/articles/613455-how-to-configure-displaylink-ethernet#macclone>

This [alternative script](https://drive.google.com/a/room.tools/file/d/12VqNiXpX_WUoKtrnW-w06mG-dTkAPLbw/view?usp=sharing) provides detailed logging that can be useful when deploying.

## User Device Discovery

We automate the mapping of laptops and phones to staff.

This is a two-step process.

1. Firstly, we need to discover the IP addresses of the devices in use by a user.
2. Once we have the IP address, we need to find the associated MAC addresses.

This maintains a mapping of MAC addresses to user accounts, which can be used in conjunction with port usage to determine the location of users.

## User Account to IP Address Mapping

There are multiple ways to get this information, and these can be used simultaneously.

* Users connecting to the staff application
* Users logging on to their machines triggering an event on the Windows domain controller
* Users connecting to a file share or print server
* Custom tray application tracking the logged in user, any IP address changes, and associated MAC addresses

## Windows Domain Controller

The Windows domain controller is used to authenticate users as they log onto a device. This would typically a laptop, desktop computer or thin client.

By auditing credential validation events [https://technet.microsoft.com/en-us/library/cc787567(v=ws.10).aspx](https://technet.microsoft.com/en-us/library/cc787567%28v=ws.10%29.aspx) it is possible to query these logs to inform Engine of the user account and the corresponding IP address associated with the event.

<https://technet.microsoft.com/en-us/library/dd772679%28v=ws.10%29.aspx>

## File Share or Print Server

Similar to the Windows domain controller method, audit logging can be enabled for file share access events.

<https://technet.microsoft.com/en-us/library/dn311489%28v=ws.11%29.aspx>

<https://blogs.technet.microsoft.com/mspfe/2013/08/26/auditing-file-access-on-file-servers/>

## IP Address to MAC Address Resolution

* Engine will communicate with the switches over UDP port 161 or TCP port 22
* The switches may communicate to Engine over UDP port 162 (Not required for SSH connections)

At this point, we have a user account and an IP address. We need to lookup the MAC address associated with the IP address so we can associate the user to the MAC address/device.

## Switch DHCP Snooping Table

We query DHCP snooping tables on level 2 switches as they maintain a list of DHCP allocated IP addresses and the MAC addresses of assigned devices.

DHCP snooping is a [security feature](http://packetpushers.net/five-things-to-know-about-dhcp-snooping/) and enabling it has additional advantages beyond user locating.\
If DHCP snooping is undesirable, DHCP Gleaning can be used instead.

## Example Powershell Scripts

This covers the basics of user discovery using a domain controller. A 3rd party machine can be configured to query server logs remotely - see the detailed scripts for how this is achieved: <https://github.com/acaprojects/ruby-engine/blob/master/docs/capturing_user_devices.md>

It is possible to use additional events and change scripts as required for security compliance.\
For more details on how this is implemented please see our detailed [configuration guide](https://docs.google.com/document/d/1WJOAMgs8ZppFrIVzlkTWDiV8vgZ_KJf766XSpv9nnzw/edit#heading=h.nocikac03i2d).


# SVG Map Creation

A full SVG Map Creation Guide can be downloaded [here](https://drive.google.com/file/d/1kET2-SLBKJJUJZK0ra7pqyX9GvCI_08_/view).

## Intro

Maps are where function meets form, and are the starting point of any conversation between a user and the platform.

A bespoke map is like a tailored suit or custom jewellery piece - you can elevate something simple by perfecting the details which takes the final product to the next level. The choices you make along the way will define the story you tell with the finished product. This guide will step you through the process in creating a map that is easy to use and visually effective.

When going custom you’ll get to choose from a range of details, which can really elevate your suit to the next level. Linings, button colours and stitching choices can all be made. This is much like the maps ACA designs for its clients, we take your average engineering map and we give it a makeover to impress the user.

## Step 1 - Obtain Floor Plans

Get architectural floor plan from client and branding colours (if provided).

![](/files/-LlEx8k84sJ5btP1b_kU)

## Step 2 - File Set up & Import Architecture Drawing To AI&#x20;

When working in illustrator it is important to set your artboards to pixels (px) for web form

* Drag drop a jpeg version of the floor plan into ai
* To get started with the right settings we need to select preferences from Illustrator>preferences>General
* Select "scale corners" and "scale strokes and effects" this will make sure all your objects will scale to the stroke you set it at which provides freedom in scalability .
* Select "Units" from the Side options and double check everything is set to pixels as we are working in web.

![](/files/-LqsS1PBjE9HM_NGum-f)

![Make sure scale corners and scale strokes and effects are selected.](/files/-LqsSEN9a8wSEY6H2t7i)

![Select "Units" and double check everything is set to "Pixels"](/files/-LqsS_ioj1YnwFh4QI8A)

## Step 3 - Creating Your Layers

Before getting started on design it is important to **name and order your layers**.  The ideal way to create layers is to start from the bottom working your way up. So for a floor plan that would be to start from the simple features such as the overall skeleton shaping of a building, signage such as toilets, stairs, lifts and lastly adding furniture and room statuses towards the end.&#x20;

![Original floor plan should be locked to avoid it moving out of place while designing.](/files/-LqsW-qXpABiNjh8t5Bf)

## Step 4 - Creating Floors

In a new layer start outlining your architectural floor plan, fill in shape with colour (as outlined by brand guidelines), lock the layer and name layer “outline”

![](/files/-LlExlhiv-6evBhWZ7Iz)

## Step 5 - Floor Dimension&#x20;

Copy and paste outline and fill in with colour (new layer) and lock the layer - name layer “bkd”. Ensure this shape is shifted to the side or downward (building shapes vary) to give a shadow or building structure effect to the “outline” layer.

![](/files/-LlExroGhS3VvDShwmtI)

## Step 6 - Outlining Floors aka "The Skeleton"

It is important to note that the floor plan walls and outlines do not need the exact thickness of each wall illustrated. The plan needs to define wall areas that are entrances to spaces to rooms . The outline shape of the floor should be a little but thicker in point size compared to its inner walls to define boundaries. To get started on the skeleton -select the "pen tool" and select a point size that is best to outline walls. This can be anywhere from 2px-6px. Begin illustrating the custom map ignoring any outlines that are not needed eg pipes near bathrooms or electrical rooms can be outlined around and given a darker blank space in its fill.

![](/files/-LlExzo8lP38_YCSYjU6)

## Step 7 - Space Highlights

Once rooms have been outlined check the architectural floor plan to see which spaces require signage eg rest rooms, stairs or cafe spots. these spaces can have a filled space highlight that compliments the background colour of the map.

![](/files/-LlEy3pQHZYxFc-gLbJH)

## Step 8 - Compare Floor Plans

To make things easier check the architectural floor plan and begin placing furniture and other features in designated spaces and rooms. If you already have them designed, if not you will need to create your own according to the brand guidelines.

![](/files/-LlEyBj6AV0bAuFw5S0X)

## Step 9 - Completing Layers

Make sure furniture and other elements such as plants are proportional to the room size and layout. Ensure plants are kept simple and are not composed of a gradient. Once complete name layer “plants and furniture”. Once complete lock layer.

![](/files/-LlEyHBqEewewTKIY36I)

## Step 10 - Adding Bookable Rooms

In a new layer, using the “rectangle tool” begin create overlay for room bookings (bookable rooms), ensure the over layer is between 40%- 60% black. Room layer should be on a layer below lines (for appearances to look neater). Name layer “room bookings”.

![](/files/-LlEyNEE883wxNQKA1sa)

## Step 11 - Booking ID's

In this layer “room bookings” you will be required to give room ids, usually provided by the client. Clients usually give the room ids/ specification to which they would like. Capitalised or lower case, room and level number order etc... but all rooms should be labeled according to the following “area- level. room number-status”.

**IMPORTANT**: All IDs **MUST start with a LETTER** (i.e. must not start with a number or other character). This is due to the CSS selector conventions.

![](/files/-LlEyS5gtOikcPiMUV3x)

## Step 12 - Adding Text

Once each room layer is labeled you can create another layer “text” here you will label each room and section of the map. This text should be Arial and anything larger than 6pt text size. For rooms use Arial bold or medium for other sections of the map use Arial regular or light. Make sure text colour is white or a colour that stands out against the map background.

![](/files/-LlEyZ8JDbtGb-HFNgRR)

## Step 13 - Adding Icons

Lastly, on another layer, add icons to your map, icons should follow the branding guidelines e.g. style, colours, line weight/thickness etc... ensure colours chosen for icons also stand out against the map background.

![](/files/-LlEycm8mveQdcT148Ld)

## Step 14 - Checking Names/ID

Once all elements are on your map go back to the booking layer and be sure to check the name and number of each room. We would recommend placing the names in order to ensure there are no double ups. Remember the SVG map will not work on the front end if two rooms have the same id.

**IMPORTANT**: All IDs **MUST start with a LETTER** (i.e. must not start with a number or other character). This is due to the CSS selector conventions.

![](/files/-LlEyjVDv0TxvGABAWIR)

## Step 15 - Exporting

Go to file>export as>SVG> save to designated folder > ensure the following is correct:\
styling > internal CSS\
Font > SVG

Images > preserve\
Object IDs> Layer names Decimal > 3\
Tick > minify and responsive Select > ok

![](/files/-LlEyoaKti3YOwzV4lQA)

## Checklist

* [ ] Client Architectural floor plans
* [ ] Client room ids and room names
* [ ] Client/brand guidelines
* [ ] client/brand colour pallet
* [ ] create furniture and plants that follow client/brand guidelines
* [ ] Have the latest Adobe Illustrator CC2019
* [ ] Name the file "client\_map\_level"
* [ ] Name layers
* [ ] Save As.. (Always)
* [ ] Once you complete check room ids for no duplicates


# Cisco CMX


# Cisco Meraki RTLS


# Desk Sensors


# Backoffice

Backoffice is the primary admin interface used to design, manage and support Engine deployments. It may be access under `/backoffice/` on your deployment. E.g.

```http
https://aca.example.com/backoffice/
```

All actions performed by backoffice at also available via [the control API](/api/control), which may be used to automate system management.


# Systems

The Systems section (1) displays a list of all systems currently defined. This list can be filtered by using the search box (2).

![](/files/-LftdY8IEeqhYGcXXHls)

Once a System is selected from the list, it’s details will be displayed in the main section. The heading lists the system’s name and System ID (3) and below this are four tabs:

## About Tab

![](/files/-Lftd3CML-UWQQ1cWwD2)

This section will list the system’s Support URL (8) (which should usually be set to the URL of the main Interface for controlling the system), description and settings. The system description (metadata) can be entered in [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) format (more information in the [Devices](https://docs.google.com/document/d/14ckH_Jzy_2Vx3uoRy1eN8-o1T96YT6Q7qnHfDKOiEAo/edit#heading=h.icobgqifc1vy) section) and is inherited from Zones in the same way as settings. Manual commands to be executed on any of the devices (9).

## Devices Tab

![](/files/-LftdBma_ZFUDYhc4Wg4)

The devices tab allows the creation of new devices (1) or the addition of existing devices to the current system (2). The device list shows each device’s current online status (3) and provides handles for drag and drop re-ordering (4). The settings and properties of each device can be viewed by clicking on its name, and edited by clicking on the edit icon (5).

At the bottom of the list, a link is provided to generate and download a CSV (8) (Comma Separated Values) file with a list of all devices in this system (for offline reference purposes).

The checkbox (6) on the the left of the Class type enables live debugging output for this device to the browser console (F12 or Ctrl+Shift+I). Pressing the eye symbol (7) next to the device name will instantly output the device’s current status information to the console to assist in troubleshooting scenarios.

## Zones Tab

![](/files/-Lfteo7-peT21W_cSHg6)

This section lists the Zones that this system is a member of and allows Joining (1) and Leaving (4). See the Zones section under [Key Concepts](https://docs.google.com/document/d/14ckH_Jzy_2Vx3uoRy1eN8-o1T96YT6Q7qnHfDKOiEAo/edit#heading=h.dynovwa5zg81) for more information on the effects of Zones on Systems. Zones are listed in order of hierarchy (highest (2) to lowest (3) ) and can be re-ordered by dragging the handles on the left (5). Click the Zone name to edit the settings and properties for that Zone or to see the other systems that are part of that Zone.

## Triggers Tab

![](/files/-LftfKzLVzx7nVzdkUJT)

This tab will list the Triggers that have been added to this system. Click on the Trigger name to view or edit the Triggers actions and properties.

## Room Booking

When Engine is integrated with a Directory service (e.g. Exchange Online, via Graph API), certain System settings will affect whether the Engine Staff API will treat the system as a Bookable Room and with what kind of behaviour. If your account has System Admin access, then the edit button (a pen icon, at the top right of the system's page) will be available. Clicking this icon will show the above popup where the following details can be edited:

![](/files/-Lmw_E2-Bx7ddTIcxfSB)

* **Name**: The name of the system (room) that will be shown in Engine apps like the room booking panel and staff app.
* **Email**: The mailbox that represents the room's calendar. This email address' default calendar will be queried to check which bookings exist for this room. The email address will be invited to any room bookings that a user creates from an Engine app (e.g. Staff App). The mailbox should be confgured in the directory service (typically Exchange online) as a [Resource Mailbox](https://docs.microsoft.com/en-us/office365/admin/manage/room-and-equipment-mailboxes?view=o365-worldwide).
* **Support URL**: This optional link is useful for admins to click to immediately open a web app associated with the room (e.g. booking panel or meeting room control panel) in a new browser window.
* **Number of Touch Panels**: This number value should be set to the total number of fixed, physical touch panels that exist for this room. This includes room booking panels and meeting room (AV) control panels. The number is used to calculate the number of expected incoming web connections from fixed devices to apps relating to this system. When there are less panels connected than are expected, when a warning will show on the backoffice Metrics page.
* **Capacity**: This number indicates the number of seats that the room has. This will affect whether this room will be displayed in app search results that specify a minimum capacity.
* **Bookable Room**: This checkbox toggles whether the room should be listed in room available search results in Engine apps like the Staff App and Concierge. Only systems with this box checked will show up in search results and thus be available for users to book via Engine apps.
* **Description**: A text description for users, for keeping useful notes about the room. Does not affect any Engine functions.
* **Settings**: Any settings defined here  will be used by Devices (Modules) that exist in this room. Settings defined here will override any Zone or Driver settings that are inherited.

### Settings

* `map_id`: If a unique ID is entered here and that ID exists as a tag on the SVG map, then map enabled Engine apps will attempt to locate and pin that SVG element when requested by the user. The Engine app may also change the CSS colour of that element to indicate the system's availability.
  * Example: `"map_id": "MyOrg-AU-SYD-BLD01-99.01"`  &#x20;
* `allowed_groups`: An array optional of AD group names that are allowed to see and book this System in Engine apps. If this setting is NOT present, then all users will be allowed to book the System.
  * Example: `"allowed_groups": "All_Admins_Group"`&#x20;
* `extra_features`: A space separated list of IDs for room features can be listed here. Engine apps may use this setting to know that the system has that feature
  * Example: `"extra_features": "vc skype mics window dinner coffee"`


# Devices

The Devices section (1) will list all devices that have been added to Engine. This list can be filtered (2) by Name, IP Address or online status (3).

![](/files/-Lftd5hSWZHuQtAX5njt)

Clicking on a Device in the list will bring up that device’s details in the main section for viewing and editing.

New devices can be added by clicking the Add icon (4).

## Adding and Editing Devices

To Edit an existing device click the pen icon, to add a new device, click the '+' icon (4). This will display a popup window where the new Device details can be entered. Select the desired Dependency (1) (for more information on devices and dependencies, see [Key Concepts](https://docs.google.com/document/d/14ckH_Jzy_2Vx3uoRy1eN8-o1T96YT6Q7qnHfDKOiEAo/edit#heading=h.7eiwwwjsszu9)) and System (2) to add the device to. Both drop down lists are text searchable.

If applicable, enter the IP address (3) and TCP/UDP Port (4) of the hardware device being added. In most scenarios, the port value can be left blank (it will default to that dependency’s configured default).

Note: For devices being controlled by RS-232, the IP address should be set to the IP address of the RS-232 gateway device (e.g. Globalcache) that will be connected to the device’s serial port. In these scenarios, the Port value should be changed to the correct value for the IP to RS-232 gateway device (e.g. 4999 for Globalcache iTach units).

The next section of checkboxes define the nature of the connection to the device. This will usually be dependent on the requirements of the device itself (see the control protocol documentation for the device).

* If the TCP connection to the device is to be encrypted, check the TLS checkbox (5) (e.g. ACA PC Control Module).
* If the connection is to be made via UDP instead of TCP, check the UDP checkbox (6).
* If the TCP session to the device should always be terminated instead of kept open, enable Make-Break (7)
* If the device is expected to be offline regularly (e.g. a portable device) and it’s status should not be tracked on the ACA Metrics Dashboard, check Ignore connected (8)

![](/files/-LftdJb-9i-ucMvRa70n)

The URI field (9) is only for devices that are controlled by HTTP requests (instead of direct TCP commands). See Driver Types under [Key Concepts](https://docs.google.com/document/d/14ckH_Jzy_2Vx3uoRy1eN8-o1T96YT6Q7qnHfDKOiEAo/edit#heading=h.5vlc4vgj6sfu) for more information. If the Driver is type ‘Device’ then this field should be blank and the IP (3) should be entered. If the Driver is type ‘Service’ then the IP field should be blank and the control URI of the device should be entered (this should be documented in the device’s control protocol manual).

The Notes section (10) is for any text based metadata (for reference purposes). Notes are inherited from Dependencies in the same way as Settings. [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) format is supported:

![](/files/-LftfGFpl5qiG6RgLPwn)

Additional settings can be defined in the Settings section (11). See [Key Concepts](https://docs.google.com/document/d/14ckH_Jzy_2Vx3uoRy1eN8-o1T96YT6Q7qnHfDKOiEAo/edit#heading=h.dbxexbsp1oib) for more information on the function of Settings. Devices will always inherit settings defined in the Driver that they are an instance of. Settings must be in valid JSON format or else the field will be highlighted red and the ‘Create’ button will be disabled.

The Custom Name field (12) is for defining a name that can be used to refer to this device in the System’s settings. For example, if there are multiple video switchers in the system, one can be referred to as ‘Table Front’ and another as ‘Table Rear’ and these names can be used to differentiate between them in the System’s settings.

## Deleting Devices

Devices can be deleted by clicking the Bin icon. Deleting a device will immediately remove it from any Systems that it has been added to. To remove a device from a System without deleting it, visit that System’s device tab instead and click ‘Remove’


# Drivers

The Drivers section (1) will list all the drivers which have been added to Engine.

Settings and a list of Devices can be viewed for each Driver by clicking on the driver name.

New Drivers can be added by clicking the + icon.

![](/files/-Lftcka6mTolLoF-HIDg)

## Adding and Editing Drivers

Upon clicking + icon, the New Driver popup will appear. Engine will automatically detect the drivers available on the server and present a searchable drop down list (1). If the repository has been updated then a Scan for new drivers should be initiated (2).

Select a Driver to add from the list (1) and additional fields will appear:

* Name (2): Devices that are an instance of this driver will appear in the device list in the format “Name - IP Address”
* Role (8): Define whether the Driver should be added as a Device, Logic or Service. This should be left as default. See Key Concepts for more information
* Description (4): Markdown format description of the driver. It will be inherited by all devices that are an instance of this Driver
* Module Name (5): The module name defines what kind of hardware type the System Logic should treat the device as. This should be left as default. More information can be found by inspecting the Ruby code of the System Logic used
* Default Port (6): Defines the default IP port on which Engine should attempt to connect to all devices defined of this type. This should generally be left as default. Individual devices can still override the default defined here by defining a port for that device
* Settings (7): JSON format settings that will be inherited by all devices of this type. For more information on the function of Settings and their inheritance. See Key Concepts

![](/files/-LftfCGgXh0iPkXV8VxA)


# Zones

The Zones Section presents a searchable list of all defined Zones. Selecting a Zone will list that Zone’s Description, Settings (inherited by all Systems in that Zone) and Systems. New Zones can be added by clicking the + icon.

![](/files/-LftdRRUMh08gKixHCs3)


# Triggers

Triggers are a way to set process automation profiles in an Engine system. You can set a condition based on the available functions of any driver and set an action to any other driver or logic module. When the condition is met, the action is triggered. Triggers can be added or removed easily and becomes a way to make simple ongoing changes to your spaces without having to write logic code.

Triggers can be used for a mix of support actions based on device/system conditions (EG “if the display reaches 60^C, trigger a shutdown and email support”) and space logic (“EG “if Sensor status is “1”, trigger air conditioning pre-set “3”). You can stack up as many triggers in your system as you like.

![](/files/-LftdVpM7piy00FYw_U7)

## Creating a Trigger

From the Triggers section, click the + icon to add a new Trigger. Type in the Trigger name and a description.

### Defining Conditions

![](/files/-Lftf9-Cwi2k-1WweCao)

A template system must be selected before the list of available conditions and actions is populated. Choosing a template system does not apply this trigger to that system. Choose a template system that most closely matches the system(s) that this Trigger will be applied to.

Once a template system is selected, a Condition can be added by clicking the + icon next to the conditions heading. Conditions can be either Time or Value Comparison based. Status Values can be taken from the System or from any of it’s Devices and compared with constant values (such as true/false or numeric/text values) or to other Status Values.

### Defining Actions

![](/files/-Lftd0iVhucQLHuuDh7y)

Actions will be performed to the applied Systems every time the Conditions are met.

An email can be sent to a specified address or a Command can be executed on the System or any of its Devices.

Specifying no Action is a commonly used option, in this case a notification will be displayed on the Metrics Dashboard and no Command will be executed.

### Applying Systems

To Apply the new Trigger to a System, navigate to the Triggers tab of that System’s page, click ‘Add Trigger’ and select the Trigger.


# Metrics

ACA Metrics tracks the the online status of Devices and User Interfaces as well as lists any active Notifications that are a result of effected Triggers.

![](https://lh5.googleusercontent.com/YR-VxBX0G0h3KS2D0u55Mv2iyOcjlC8svS03hhE1UM0SwYc3-iA2elXLcGn9BfWOBVEXGWk4HQX0Ft0Tw4vAUi3abivyEw8cI0hhHbBcJNgKvbNBd1S5At9OT4Spx5Kg_VAmymRM)

The time period of the Graphs at the bottom/right side of the screen can be changed from the drop down menu (1). Hovering a mouse cursor over sections of each graph will display more detailed data from that specific point in time. Clicking and dragging horizontally across a graph will zoom into that period of time. Double click to zoom back out.

Issues and Notifications in the list can be Hidden (3) by clicking on the Acknowledge Icon (2).


# Users

Engine usually integrates with an external directory system (such as Active Directory) for authentication.

The Users Section lists all users who have signed in, their level of access and a History (log) of the Systems which were accessed (controlled from an Interface or viewed in Backoffice).

## Permissions

When first logging on, a user will have no permissions assigned and will be denied access to Backoffice. A System Administrator should then look up the User from the list in the User’s Section of Backoffice and set their permissions:

* Tech Support: Have access to view Backoffice and all objects but cannot create or edit Systems and Devices.
* System Admin: Full write access to all objects


# Domains


# Applications


# Development Environment

Engine is distributed as a set of docker containers that are designed to deploy to your on-prem infrastructure or private cloud. To keep things simple for development, testing, and experimentation this is also packaged as a prebuilt environment that you can run on your local machine.

This environment runs across MacOS, Windows, or Linux - feel free to pick where you work best.

## Required Tooling

In order to run this environment you will need two common tools:

* [ ] [Vagrant](https://www.vagrantup.com/downloads.html): a tool for managing virtual environments.
* [ ] [VirtualBox](https://www.virtualbox.org/wiki/Downloads): an open source, cross-platform virtualisation provider.

Outside of these, nothing will need to be installed or modified on your machine.

Setup these now, then drop back - we’ve prepared some music to play while you do this...

{% embed url="<https://youtu.be/S5PvBzDlZGs>" %}

Welcome back.

## Building Your Environment

Now that you have the required tools, choose a directory where you'd like to work, then run

```bash
vagrant init acaengine/dev-env
```

This will create a [Vagrantfile](https://www.vagrantup.com/docs/vagrantfile/) that contains your environment config.

{% hint style="success" %}
If you are planning on diving into [driver development](/developer-guide/drivers), you can also fork and clone the [open-source drivers repo](https://github.com/acaprojects/ruby-engine-drivers/), which contains a pre-initialised Vagrantfile ready for use.
{% endhint %}

{% hint style="warning" %}
Older versions of the development environment used the `vagrant-triggers` plugin. This is no longer needed, or compatible with modern versions of Vagrant. If you see a warning about this, run:`vagrant plugin uninstall vagrant-triggers`before continuing.
{% endhint %}

### Starting Up

Open a terminal window in the same directory as your vagrant file and run:

```bash
vagrant up
```

You will see some updates while your environment boots up. This may take a couple of minutes the first time it runs. When it’s complete you will be provided with a URL and authentication details to log in.

Congratulations you’re ready to go!

{% hint style="info" %}
Vagrant commands need to run within the folder containing the environment configuration (i.e. where your Vagrantfile is).

If you get an error that says: `A Vagrant environment or target machine is required to run this command` check that you’re in the right place.
{% endhint %}

### Shutting Down

When you’re done working with Engine use

```bash
vagrant halt
```

to shutdown your environment.

You can continue to use these two commands to start and stop your local Engine instance as you need.

### Starting Over

Changes that you make, such as adding or removing devices, systems, or zones will persist across restarts. To return to a fresh deployment run

```bash
vagrant destroy
```

The next time you run `vagrant up` you'll be presented with a fresh deploy.


# Building Drivers


# Discovery and Metadata

Drivers the system can access are represented by database entries. To simplify the creation of these entries available drivers can be scanned for and made available as a searchable list.

You can click scan from the Backoffice UI:&#x20;

![](https://cloud.githubusercontent.com/assets/368013/15009704/5d76706e-122b-11e6-8f8d-16fa3d39c85c.png)

Metadata used to describe the drivers, is defined as part of the driver. For Example:

```ruby
class Clipsal::CBus
    include ::Orchestrator::Constants
    include ::Orchestrator::Transcoder

    # Discovery Information
    tcp_port 10001
    descriptive_name 'Lighting Gateway'
    generic_name :Lighting

    # Communication settings
    tokenize delimiter: "\x0D"
    wait_response false
    delay between_sends: 100

    # ...
end
```

The discovery methods are made available to the driver by the line `include ::Orchestrator::Constants`. Without any discovery methods the driver is listed by its class name.

## Discovery Methods

| Method             | Arguments | Description                                                                                     |
| ------------------ | --------- | ----------------------------------------------------------------------------------------------- |
| `descriptive_name` | String    | Used to describe the driver. This is what will be displayed in backoffice                       |
| `generic_name`     | Symbol    | The default generic name value                                                                  |
| `implements`       | Symbol    | Defines the driver type, one of `:ssh, :device, :service or :logic`                             |
| `description`      | String    | A default description for the driver, supports markdown formatting                              |
| `default_settings` | Hash      | Default settings that will be saved as JSON                                                     |
| `tcp_port`         | Integer   | Sets the default port and calls `implements :device` or `implements :ssh` if the port is 22     |
| `udp_port`         | Integer   | Sets the default port and calls `implements :device`                                            |
| `uri_base`         | String    | Expects a URI origin, such as `http://overclockers.com.au:8080` and calls `implements :service` |
| `makebreak!`       |           | Indicates that the TCP connection should not be held open                                       |


# 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

{% hint style="danger" %}
Driver state is should always be considered public and as such, should never contain any sensitive information (auth details, access tokens etc).
{% endhint %}

* Any authenticated user can read the state
* Do not store sensitive material in state

## Exposing State

A driver [quacks](https://en.wikipedia.org/wiki/Duck_typing) like a hash

{% hint style="info" %}
All state keys must be [Symbols](https://ruby-doc.org/core-2.4.2/Symbol.html), [Strings](https://ruby-doc.org/core-2.4.2/String.html) or respond to `#to_sym`
{% endhint %}

* 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](https://en.wikipedia.org/wiki/Mutator_method).

{% hint style="success" %}
All state values should be limited to objects that can be converted to JSON for transmitting over the API.
{% endhint %}

* 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.

```ruby
# Subscribe to internal state

ref = subscribe(:state_variable) do |notify|
    notification.value     # => value of the status variable that triggered this notification
    notification.old_value # => the value of the variable before this change

    # Also comes with the subscription information
    notify.sys_name # => The system name
    notify.sys_id   # => The system ID this value originated from
    notify.mod_name # => The generic module name 
    notify.mod_id   # => The module database ID
    notify.index    # => The device index
    notify.status   # => the name of the status variable

    # And a reference to the subscription should you want to unsubscribe
    unsubscribe(notification.subscription)
end

# Optionally unsubscribe
unsubscribe(ref)
```

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.

```ruby
self[:power] = On  # => subscribers are notified of the change
self[:power] = On  # => no change detected, no action taken
self[:power] = Off # => subscribers are notified of the change
```

Change detection doesn’t work if you mutate a variable.

```ruby
self[:my_array] = [1, 2, 3] # => subscribers are notified of the change
self[:my_array] << 4        # => no action taken (change detection isn't run)

@my_copy = self[:my_array]
@my_copy << 5
self[:my_array] = @my_copy  # => no change detected (change detection did run)

# Only if you are really sure you know what you are doing!
signal_status(:my_array)    # => forces a change notification to subscribers
```

{% hint style="warning" %}
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.
{% endhint %}

The recommended method for updating complex state is:

1. **Duplicate** `my_array = ['my', 'array'].dup` or `{complex:['hash']}.deep_dup`
   * `.deep_dup` when in doubt
2. **Update** `my_array << 8`
3. **Apply** `self[:my_array] = my_array`

This can be achieved by using operations that create a new object

```ruby
self[:my_array] = [1, 2, 3] # => subscribers are notified of the change
self[:my_array] += [4]      # => subscribers are notified of the change
self[:my_array]             # => [1, 2, 3, 4]

# These will both trigger notifications
self[:my_hash] = { example: 1 }
self[:my_hash] = self[:my_hash].merge({ update: true })
self[:my_hash] # => { example: 1, update: true }
```


# Scheduling Actions

Every driver can schedule events to occur in a number of different ways. Schedules are automatically cleared when a driver is terminated.

* Integer arguments are in milliseconds. `1000` == 1 second
* Strings can be used for a friendly representation of the time
  * `'30s'` == 30 seconds
  * `'5m'` == 5 minutes
  * `'1w2d3h5m'` == 1 week, 2 days, 3 hours and 5 minutes
  * `'2Y1M'` == 2 years, 1 month

| Function         | Arguments         | Description                                                                                                                                                                                                  |
| ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `schedule.in`    | String or Integer | schedule a task to run once in the time specified (formats above)                                                                                                                                            |
| `schedule.at`    | Time or String    | schedule a task at a time represented by a [Time object](http://ruby-doc.org/core-2.5.0/Time.html) or a parsable [time string](http://ruby-doc.org/stdlib-2.5.0/libdoc/date/rdoc/DateTime.html#parse-method) |
| `schedule.every` | String or Integer | schedule a task to run every time period on repeat                                                                                                                                                           |
| `schedule.cron`  | String            | A schedule that will fire based on a [CRON](https://en.wikipedia.org/wiki/Cron) string                                                                                                                       |
| `schedule.clear` |                   | shortcut for cancelling any active schedules                                                                                                                                                                 |

## Examples

```ruby
schedule.every('1m') do
    # perform some action, such as polling
end

schedule.in(500) do
    # ...
end

schedule.at(Time.now + 2.hours) do
    # ...
end

schedule.at('2018-02-02T15:32:41+11:00') do
    # ...
end

# Every day at 8am
schedule.cron('0 8 * * *') do
    # ...
end

# Canceling an individual schedule
@email_sched = schedule.in(500) do
    send_email
end
@email_sched.cancel
```

There are often situations where you want to run the block immediately.

```ruby
def connected
    schedule.every('1m', :run_now) do
        poll_current_state
    end
end
```

CRON also supports time zones - which should always be configured

```ruby
schedule.cron('0 8 * * *', timezone: 'Sydney') do
    # ...
end
```

See the list of supported [time zone strings](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html) and information [about time zones](https://robots.thoughtbot.com/its-about-time-zones).


# Response Tokenisation

[Tokenisation](https://en.wikipedia.org/wiki/Lexical_analysis) is very important. When working with a stream of data, as provided by TCP and UDP, you most certainly want to

1. Wait for a complete response before processing
2. Want to process one response at a time

Whilst this might seem to occur naturally most of the time, network contention, network errors and high data rates will trip you up. CBus is one system where I’ve often see back to back messages returned in a single IO read.

Engine ships with two tokenisers to help you break up the incoming data. The default buffered tokeniser and the more advanced abstract tokeniser.

## Default Tokeniser

Usage:

```ruby
class Example::Driver
    # Device driver helper
    tokenize delimiter: "\x0D"
end
```

Options:

| Option       | Description                                                                                                                                  |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `delimiter`  | sequence to detect the end of message. Supports [strings and regular expressions](http://ruby-doc.org/core-2.2.0/String.html#method-i-split) |
| `indicator`  | sequence to detect the start of a message                                                                                                    |
| `msg_length` | can be used with an indicator if messages are always a fixed length                                                                          |
| `size_limit` | prevents buffering from using all your memory if the end of a message is never detected                                                      |
| `min_length` | can help prevent false positives                                                                                                             |
| `encoding`   | defaults to `ASCII-8BIT` to avoid invalid characters when dealing with binary data                                                           |

Example:

`tokenize indicator: "\x02", delimiter: "\x03"`

and data: `"yu\x03\x02hello\x03\x02world\x03\x02how"` Would have the following result:

* `yu\x03` would be discarded
* `hello` would be returned
* `world` would be returned
* `\x02how` would be buffered

## Abstract Tokeniser

The primary use case for this tokeniser is variable length messages, where length can be determined by the message contents. (commonly a length field in the header)

Usage:

```ruby
class Samsung::Displays::MdSeries
    tokenize indicator: "\xAA", callback: :check_length

    # Called by the Abstract Tokenizer
    def check_length(byte_str)
        # Check for minimum length
        return false if byte_str.bytesize <= 3
        response = str_to_array(byte_str)

        # data length byte + (header + checksum) == message length
        len = response[2] + 4

        if response.length >= len
            # return the length of this message (any excess will be buffered)
            return len
        else
            # false if the complete message hasn't arrived yet
            return false
        end
    end
end
```

Options:

| Option       | Description                                                                             |
| ------------ | --------------------------------------------------------------------------------------- |
| `callback`   | callable code, proc, lambda, method etc that will return an integer or false            |
| `indicator`  | sequence to detect the start of a message (string or regex)                             |
| `size_limit` | prevents buffering from using all your memory if the end of a message is never detected |
| `encoding`   | defaults to `ASCII-8BIT` to avoid invalid characters when dealing with binary data      |

## Further Reading

For a detailed overview of what these tokenisers are capable of, it is worth looking at their tests.

* [Buffered Tokeniser Spec](https://github.com/cotag/uv-rays/blob/master/spec/buffered_tokenizer_spec.rb)
* [Abstract Tokeniser Spec](https://github.com/cotag/uv-rays/blob/master/spec/abstract_tokenizer_spec.rb)


# Device Drivers

Device drivers implement a protocol on a raw transport stream, TCP or UDP. For examples see our [open repository of device modules](https://github.com/acaprojects/aca-device-modules).

Backoffice can be used to define settings.

![image](https://cloud.githubusercontent.com/assets/368013/15108556/e70b07e4-1619-11e6-8646-6a5af7d71caf.png)

* Use Transport Layer Security (TLS) for encrypted communications
* UDP for stateless communication (UDP connections are always considered connected)
  * Multicast IP’s are automatically detected and subscribed to the multicast group
* Use Make Break if you want the module to disconnect when there is nothing to communicate
  * Some devices require you work this way, this helps automate the process

## Typical Layout

```ruby
class Manufacturer::Type::ModuleName
  # Called on module load before the connection has initiated
  def on_load; end

  # Called before the module is cleaned up (disconnected already)
  def on_unload; end

  # Called after dependency reload and settings updates
  def on_update; end

  # Called once the connection to the device is ready
  def connected; end

  # Called once the connection closes
  def disconnected; end

  # Called when the device transmits data
  def received(data, deferrable, command)
    # data == data from the device as a String
    # deferrable == allows you to decide if a result was a success asynchronously
    # command == last command sent that hasn’t been resolved (raw command + metadata)
  end
end
```

## Transmitting a Request

Requests are added the transmit queue by calling `send(raw_cmd, options_hash)`.

The `raw_cmd` can be in a number of formats:

* String => will be transmitted as is
* Array of bytes => will be automatically converted to a string for sending
* Hex String => will be converted to a binary string if requested `send('0xbeef', hex_string: true)`

The options are as follows:

| Option             | Default Value | Description                                                                                                                     |
| ------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `wait`             | true          | do we want to wait for a response before we continue processing                                                                 |
| `delay`            | 0             | minimum delay time between sends (milliseconds)                                                                                 |
| `delay_on_receive` | 0             | time to delay the next transmit after receiving data (milliseconds)                                                             |
| `max_waits`        | 3             | number of times we’ll accept an ignore response before retrying the request                                                     |
| `retries`          | 2             | number of times we’ll retry a command if it has failed                                                                          |
| `timeout`          | 5000          | amount of time we’ll wait for a response to a command before retrying (milliseconds)                                            |
| `priority`         | 50            | so we can perform commands in preference to others (see section on priorities)                                                  |
| `force_disconnect` | false         | causes the transport to disconnect once a response has been received                                                            |
| `clear_queue`      | nil/false     | removes any other queued commands once it starts transmitting                                                                   |
| `name`             | nil           | command type (e.g. `:power`). Queued commands of the same time will be overridden.                                              |
| `emit`             | nil           | callback to occur when that request completes. Will not be called if another request with the same name overrides this request. |
| `on_receive`       | nil           | alternative receive function or block. Called in stead of `received` function                                                   |

## Processing a Response

It’s expected that the `received` function or `on_receive` callback return a result for the current command. When data is received, this function is called to see if

* The command was a success
* This data has nothing to do with the command in question
  * Some devices push data
* Some kind of failure

The expected values that should be returned by this function:

| Value                                                      | Description                                                                                           |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `true, :success` (or result if not on of the values below) | We’ve finished processing this command, move on to the next                                           |
| `false, :retry, :failed, :fail`                            | The command didn’t have the desired result, maybe the device was busy. Please send this command again |
| `nil, :ignore`                                             | This data was not in response to our action. Continue waiting.                                        |
| `:abort`                                                   | The command failed and it should not be retried. Abort differs from success as it is logged.          |
| `:async`                                                   | Waits for the command to resolved by the deferrable passed to the `received` function                 |

## Priorities

Priorities ensure requests are processed in a sane order. For example if you are polling a projector for status and it only accepts one command every 300 milliseconds you don’t want to wait for the polling, which might be 4 or 5 requests, to complete before executing a more important request like selecting a new input.

Sometimes a query might be made for control flow purposes so it is often useful to differentiate between user initiated requests and polling.

```ruby
# A best practice query function
def power?(opts = {}, &block)
    opts[:emit] = block if block_given?
    opts[:name] = :power_query
    send('power_query', opts)
end

# Example polling function
def poll
    power? priority: 0 do
        if self[:power] == On
            input? priority: 0
            volume? priority: 0
        end
    end
end
```

Priorities are also increased on a contextual basis. This is how retries, for instance, make their way back to the front of the queue - which is what you would expect. There is a configuration option called `priority_bonus` which increases the priority of a command in the following circumstances:

* Any request made when processing a response to a request
  * If you request volume status whilst processing the switching of an input, the query will receive a bonus and jump towards the front of the queue
* When a command fails and is retried, it also receives a bonus.

NOTE:: given default values, if you send commands in the received function and then return failed so the command is retried, the commands sent will be executed before the retry. You can counter this effect by providing lower priorities in this context.

## Helper functions

Helper functions interact with the local state of a driver. Defining a setting, for instance, will not overwrite any shared system state.

| Name                                  | Description                                                                       |
| ------------------------------------- | --------------------------------------------------------------------------------- |
| `disconnect`                          | disconnects the current connection. It does not wait to send any buffered data    |
| `remote_address`                      | returns the IP address or hostname defined in the database                        |
| `remote_port`                         | returns the port number defined in the database                                   |
| `defaults` (options hash)             | allows you to set custom default options for commands                             |
| `config` (options hash)               | allows you to set custom processing configurations                                |
| `set_connected_state` (true or false) | overrides the default connection indicator (useful for UDP devices)               |
| `define_setting` (:key, value)        | persists a setting value in the database that impacts the current driver instance |

For the various defaults and configuration options see the [command processor](https://github.com/acaprojects/ruby-engine/blob/master/lib/orchestrator/device/processor.rb).

When you `include ::Orchestrator::Constants` some common configuration and default options are exposed in a more declarative manner.

```ruby
tokenize delimiter: "\xAA"  # See the page on Tokenisation
delay between_sends: 200, on_receive: 100
wait_response false
queue_priority default: 50, bonus: 20
clear_queue_on_disconnect!
flush_buffer_on_disconnect!

before_transmit :run_function
def run_function(data, command)
    # You can modify the data at the last minute here.
    # A request in the queue might require an update before sending.
    # Example: https://github.com/acaprojects/aca-device-modules/blob/master/modules/panasonic/projector/tcp.rb
    return data
end

# For make break connections, in milliseconds
inactivity_timeout 5000
```


# SSH Drivers

SSH modules interface with devices or servers that communicate using the SSH 1.99/2.0 protocol. SSH 1 is considered obsolete due to inherent security flaws and not supported.

SSH modules are similar to Device Drivers with an additional `exec` function, where supported by the remote. Unfortunately most AV devices seem to only support interactive shell, which is akin to telnet.

## Authentication

Supports the following authentication methods:

* none
* public key
* password

These settings must be defined in the JSON settings (dependency or driver instance) and would typically look like:

```javascript
{
    "ssh": {
        "username":  "account_name",
        "$password": "password", // $ sign will encrypt the password and/or private key
        "$key_data": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAqccvUza8FCinI4X8HSiXwIqQN6TGvcNBJnjPqGJxlstq1IfU\nkFa3S9eJl+CBkyjfvJ5ggdLN0S2EuGWwc/bdE3LKOWX8F15tFP0=\n-----END RSA PRIVATE KEY-----"
    }
}
```

## Sending an exec request

If supported requests can be sent using the `exec` function. There are two modes of operation, depending on how much response data is required and requests can be performed in parallel.

```ruby
# =================
# Request semantics
# =================
# Simple request. (these are equivalent)
exec('command –a')
exec('command', '-a')

# Simple request. Complex arguments
exec('setname -full Stephen von Takach') # This will fail
exec('setname', '-full', 'Stephen von Takach') # Will succeed, escaped properly


# ==============================
# Accessing responses (promises)
# ==============================

# .value pauses execution and waits for the response before continuing
exec('command', '-a').value  # => return all output as a string or raise error

# .then provides callbacks so execution is not paused
exec('command', '-a').then { |response|
    # Process response
}.catch { |error|
    # Handle error
}

# Commands are queued by default as many devices can only handle a single request at a time.
# However you can perform requests in parallel
tasks = []
tasks << exec('command1', '-a', wait: false)
tasks << exec('command2', '-a', wait: false)
tasks << exec('command3', '-a', wait: false)

# Wait until all commands have completed (optional)
response_array = thread.all(tasks).value
```

There is a more complicated request form that provides access to exit codes and individual data streams.

```ruby
status = exec('uname', '-a') do |channel, stream, data|
    logger.debug { data } if stream == :stdout
    logger.error { data } if stream == :stderr
end
status.value # => {exit_code: 137, exit_signal: 9}
```

### Exec request options

| Option             | Default Value | Description                                                                          |
| ------------------ | ------------- | ------------------------------------------------------------------------------------ |
| `wait`             | true          | do we want to wait for a response before we continue processing                      |
| `delay`            | 0             | minimum delay time between sends (milliseconds)                                      |
| `delay_on_receive` | 0             | time to delay the next transmit after receiving data (milliseconds)                  |
| `retries`          | 2             | number of times we’ll retry a command if it has timed out                            |
| `timeout`          | 5000          | amount of time we’ll wait for a response to a command before retrying (milliseconds) |
| `priority`         | 50            | so we can perform commands in preference to others                                   |


# Service Drivers

Service drivers interface with devices that communicate using the HTTP(S) protocol. Currently HTTP versions 1.0 and 1.1 are supported.

They are similar to Device Drivers with two major differences:

1. It’s not recommended to use the common `received` function
2. They do not have a `send` function

This is because HTTP is much more contextual than many protocols. It’ll often only return success whereas many custom device protocol responses can be interpreted without knowledge of the original request.

## Sending a Request

Each request should either set the `on_receive` callback option or provide a block for response processing.

| Method  | Arguments                       | Description                         |
| ------- | ------------------------------- | ----------------------------------- |
| request | verb, path, options = {}, \&blk | allows you to pass in a custom verb |
| get     | path, options = {}, \&blk       |                                     |
| post    | path, options = {}, \&blk       |                                     |
| put     | path, options = {}, \&blk       |                                     |
| delete  | path, options = {}, \&blk       |                                     |

```ruby
# Example usage:

def query_position
    # Get request will look like:
    # http://domain.or.ip/api/status_of?coordinates=detailed
    get('/api/status_of', {
        query: {
            coordinates: :detailed
        }
    }) do |data, resolve, command|
        check_response(data) do |resp|
            # Update status (made available to interfaces)
            self[:position] = resp['coords']
        end
    end
end

def check_response(data)
    # Check response status
    # (might have been 500 or 404, depends on what you are expecting)
    if data.status == 200
        begin
            # We're assuming a JSON response and we are passing that data
            # back to the calling function and assuming success at this point
            yield ::JSON.parse(data.body) if block_given?
            return :success
        rescue => e
            logger.print_error e
        end 
    end

    # Fail if there are any issues
    # Obviously this behaviour depends on the service etc
    :abort
end
```

### Request Options

| Option      | Example                                                                                                                                                                                | Effect                                                                             |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `query`     | <p><code>query: "me=bob\&other=rain"</code> or<br> <code>query: {</code><br>  <code>me: :bob,</code><br>  <code>other: :rain</code><br><code>}</code></p>                              | UR&#x49;**?me=bob\&other=rain**                                                    |
| `body`      | <p><code>body: "data=hello\&other=world"</code> or<br> <code>body: {</code><br>  <code>data: :hello,</code><br>  <code>other: :world</code><br><code>}</code></p>                      | when body is a string it will be sent as is. When a hash, it will be form encoded. |
| `headers`   | <p><code>headers: {</code><br>  <code>Name: 'value'</code></p><p><code>}</code></p>                                                                                                    | Some headers are transformed further. See bellow                                   |
| `file`      | `file: 'path/to/file.ext'`                                                                                                                                                             | Will send the file as the body                                                     |
| `keepalive` | `keepalive: false`                                                                                                                                                                     | Will close the connection once the request has completed                           |
| `ntlm`      | <p><code>ntlm: {</code><br>  <code>user: 'u',</code><br>  <code>password: 'p',</code><br>  <code>domain: 'd'</code><br><code>}</code></p>                                              | Will perform a request with an endpoint that requires NTLM auth                    |
| `digest`    | <p><code>digest: {</code><br>  <code>user: 'u',</code><br>  <code>password: 'p',</code><br>  <code>domain: 'd'</code><br><code>}</code></p>                                            | Will perform a request with an endpoint that requires digest auth                  |
| `proxy`     | <p><code>proxy: {</code><br>  <code>host: '1.2.3.4',</code><br>  <code>port: 80,</code><br>  <code>username: 'bob',</code><br>  <code>password: 'hunter2'</code><br><code>}</code></p> | Use a proxy server for the request.                                                |

NOTE:: Both NTLM and Digest auth are challenge response protocols and won’t work with HTTP 1.0 or keep alive false

### Basic Authentication

Basic auth is supported natively along with NTLM and digest authentication techniques. All that is required is to set the `authorization` header like so:

```ruby
options = {
    headers: {
        authorization: [username, password]
    }
}
```

For more advanced methods of authentication see Utilities and Helpers.

## Handling a Response

The response object is passed to your received block and looks like this:

```ruby
get '/' do |data|
    # Response body as a string
    data.body

    # HTTP version the server is using (a string)
    data.http_version

    # The status code returned as an integer
    data.status

    # Was the connection kept alive for possible further requests
    data.keep_alive

    # What cookies have been stored at this path (as a Hash)
    data.cookies
    data.cookies['user_id']

    # The data object itself is a hash of all the headers
    data['Content-Type'] # => 'text/html'
end
```

## Cookies

Cookies are handled in the background in the same way a browser would handle cookies.

There is a helper method that can be used to clear cookies: `clear_cookies`

You can set cookies by setting the `cookie` header field. Supports both strings and hashes.

## Proxy Support

When building drivers that need to communicate with external endpoints, its good practice to provide proxy support. This can be achieved by passing the `proxy` parameter with requests. To provide user support of these, these can be loaded from settings and set as part of the defaults for every request.

```ruby
def on_update
    proxy = setting(:proxy)
    if proxy
        config({
            proxy: {
                host: proxy[:host],
                port: proxy[:port]
            }
        })
    end
end
```


# Logic Drivers

Logic modules define processes and can interface with drivers. They are coupled with a system, unlike device and service drivers, which can be in more than one system.

They help separate concerns. In [model - view - controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) terminology, Logic modules are controllers whereas devices and services are models.

As such, they exist primarily to communicate with other drivers.

## Inter-driver communication

Engine guarantees that a single thread will access driver code at any point in time. Communication between driver instances is scheduled to ensure serial access. Furthermore the driver in question might not even be running on the same server.

Because of this, all calls are asynchronous and always return a promise which allows you to access the return value of the function call or catch any errors that might have occurred. Error catching is optional and the error will be logged.

Driver instances are grouped by Systems that provide metadata on how to access the driver instances.

i.e A system may be defined with some drivers as follows:

| Driver             | Generic Name |
| ------------------ | ------------ |
| NEC Display        | Display      |
| Samsung Display    | Display      |
| Room Logic         | Room         |
| CBus Lights        | Lights       |
| GlobalCache Relays | Blinds       |

NOTE:: Devices are not aware of the systems they are in, unlike logic modules.

To access the Samsung display from the Room Logic you would perform the following: (Strings or Symbols can be used)

* `system[:Display_2]`
* `system.get(:Display, 2)`
* `system.get_implicit(:Display_2)`

When there is a single device of any any class, the index doesn’t need to be defined

* `system[:Lights]`
* `system.get(:Lights)`

It is possible to send a request to all modules of a type in a system. For instance turning off all the displays:

```ruby
system.all(:Display).power Off
```

A [promise](http://documentup.com/kriskowal/q/) is returned to obtain the returned result

```ruby
system[:Display].firmware_version.then do |version|
    logger.info "Display firmware is #{version}"
end
```

Or using [futures](https://msdn.microsoft.com/en-us/library/ff963556.aspx) call `.value` on a promise

```ruby
response = system[:Display].firmware_version.value
```

If you want to communicate with a driver in another system you need to know the system id or system name of that system.

* By ID: `systems('sys_1-10A')[:Display]`
* By Name: `lookup_system('Ant Building - Room 213')[:Display]` (not recommended as it is slower and the name can be changed)

When the system is booting, it is probable that some other logic modules will not have finished loading and any attempts to communicate with them may fail. If you need to communicate with other logic modules in the `on_load` callback there is a helper method:

```ruby
system.load_complete do
    # All modules are guaranteed to be loaded in this callback
end
```

This is only required for logic modules as the load order is:

1. SSH modules
2. Device modules
3. Service modules
4. Logic modules
5. Triggers

## Watching Status Variables

You can subscribe to status variable updates so it is easy to react to changes when they occur.

```ruby
device_index = 1
ref = system.subscribe(:Device, device_index, :status_variable) do |notification|
    notification.value     # => value of the status variable that triggered this notification
    notification.old_value # => the value of the variable before this change

    # Also comes with the subscription information
    notification.sys_name  # => The system name
    notification.sys_id    # => The system ID this value originated from
    notification.mod_name  # => The generic module name 
    notification.mod_id    # => The module database ID
    notification.index     # => The device index
    notification.status    # => the name of the status variable

    # And a reference to the subscription should you want to unsubscribe
    unsubscribe(notification.subscription)
end

# Then to unsubscribe (you'll have to keep track of the subscription reference)
unsubscribe(ref)
```

Unsubscribe is done locally (not on the system proxy) as the subscription is stored locally. This is so it can be tracked and unsubscribed automatically when the module is stopped.

NOTE:: Subscriptions can be made before a driver loads or even exists.


# Testing

Tests describe the behaviour of a driver and can be used to help with development. They are also useful in describing the intended usage of a driver.

For example usage, see:

* [Extron DXP spec](https://github.com/acaprojects/aca-device-modules/blob/master/modules/extron/switcher/dxp_spec.rb)
* [Denon BluRay spec](https://github.com/acaprojects/aca-device-modules/blob/master/modules/denon/bluray/dn500bd_spec.rb)
* [ClearOne Converge spec](https://github.com/acaprojects/aca-device-modules/blob/master/modules/clear_one/converge_spec.rb)
  * Waits for login prompt before processing commands
* [Panasonic Projector spec](https://github.com/acaprojects/aca-device-modules/blob/master/modules/panasonic/projector/tcp_spec.rb)
  * Waits for encryption key
  * Injects password before transmitting data

## Checking Values

The Engine test framework uses [rspec-expectations](https://github.com/rspec/rspec-expectations) for verifying status variables and command results returned expected values.

## Helper Functions

| Helper                  | Arguments                        | Description                                                                          |
| ----------------------- | -------------------------------- | ------------------------------------------------------------------------------------ |
| `transmit` / `responds` | string / hex string / byte array | emulates a device sending data to the module                                         |
| `exec`                  | `function_name, *arguments`      | calls the function requested on the module with the arguments provided               |
| `should_send`           | string / hex string / byte array | this is the data the driver is expected to send to the device                        |
| `result`                |                                  | provides access to the result of the last executed request                           |
| `status`                |                                  | provides access to the current status of the driver                                  |
| `temporary_disconnect`  |                                  | emulates a connection drop followed by re-establishment                              |
| `device_offline`        |                                  | emulates a connection drop where communications have failed to be re-established     |
| `device_online`         |                                  | emulates connection re-establishment. Should only be called after a `device_offline` |
| `wait_tick`             | times = 1                        | resumes the test after the specified number of ticks through the event loop          |
| `wait`                  | milliseconds                     | resumes the test after waiting the specified amount of time                          |

## Debugging

[Byebug](https://github.com/deivid-rodriguez/byebug) makes it possible to

1. Step through code one line at a time
2. Pause the driver at some event or specified instruction, to examine the current state.
3. Interact with the state by executing code dynamically at the breakpoint

Byebug Resources:

* [Usage Tutorial](https://www.sitepoint.com/the-ins-and-outs-of-debugging-ruby-with-byebug/)
* [Video Tutorial](https://www.youtube.com/watch?v=toZrovVX4ug)

## Running Tests

There is a rake task where you can specify the file containing the driver spec. Drivers themselves are resolved and loaded internally, as they would be in a running system.

* `rake module:test['../aca-device-modules/modules/extron/switcher/dxp_spec.rb']`

To simplify the process of running tests, when using the [development setup](https://github.com/acaprojects/setup-dev), you can run tests in the browser.

* Your system is scanned and spec files are listed on the right
* Selecting a spec will attempt to load the driver file
  * You will see any load errors that occur (such as syntax errors)
  * Discovery information is listed in a table
  * The test is executed in an interactive console, allowing for interactive debugging
  * Click the refresh icon to re-run the test at any point

![](https://user-images.githubusercontent.com/368013/28649530-e6ecf98c-72b8-11e7-86d8-1977aef69007.png)


# Live Monitoring

Engine features live, in-browser debugging of operational systems via backoffice. All debug messages are outputted to the browser console (F12 or Ctrl+Shift+I, then Console tab).

Ensure that the Console logging is set to enable Verbose messages, as highlighted below:

![](https://lh5.googleusercontent.com/gC3SWQZi72o9VJN20__6t2Gm98EOkW6auqqcK-7hlmHqhAwyKX287Ideh3hOr2lWWVkH6eCk2QixB9vti4vlp-uwA3ZR8Ur6jdAnmARPaPBXMMfkEqTtHdi18R_WuNAgV2ADXWRi)

## Viewing Debug Messages

Debugging is done from the Systems section. Select the system to debug and then click the Device tab.

![](https://lh3.googleusercontent.com/bUE_wgSIhyteqyXzLPOUkay0LDrCYC9v2NY4NFanjqRMMLOaoXQ_NYuqZuUEK8oIu6lCwZSIzSgcHbMhvcv14PTa1OXN_nthiB2w6QD3-PKBdKcZ9QshWlr2_dydBlxfkzQLKNMC)

The checkbox (6) on the the left of the Class type enables live debugging output for this Device to the browser console. Pressing the Eye symbol (7) next to the device name will instantly output the device’s current status information to the console to assist in troubleshooting scenarios.

## Sending Manual Commands

The About tab of the System page contains a drop down list (9) that allows the selection of a device to send a manual control command to.

![](/files/-LftdEzRzI1ozTeJJSc4)

The device’s response to these commands will be output to the bottom of right of the window and (if enabled - above) to the browser console.

Some commands will require parameters. When blank, the text field will describe the type of parameter required by the function.

In the event that Engine is not able to send a command to the device, an error message (red instead of blue) will appear at the bottom of the screen with the details.


# Logging

The `logger` is automatically mixed into all driver classes and has the usual logger levels:

* `debug`: use debug often for verbose output - not saved to log files by default
* `info`: anything you are interested in seeing in the log file
* `warn`: something might be wrong, possibly worth investigation.
* `error`: something went wrong, definitely worth investigation
* `fatal`: something that should never go wrong, went wrong. Requires immediate investigation / resolution

If text being passed to the logger requires some string manipulation or other processor intensive operation, it is worth performing this work in a [block](http://www.eriktrautman.com/posts/ruby-explained-blocks-procs-and-lambdas-aka-closures) in case the result is not recorded - this is preferred with debug statements as they are discarded when nobody is watching.

```ruby
def received(data, resolve, command)
    logger.debug {
        cmd = String.new("Device sent 0x#{byte_to_hex(data)}")
        cmd << " for command #{command[:name]}" if command
        cmd # return the text to be displayed if we are debugging
    }
end
```

There is also a handy helper method for formatting errors:

```ruby
begin
    raise 'whoa!'
rescue => e
    logger.print_error e, 'optional additional description of error'
end
```


# Security

Authentication is mandatory and authenticated users have access to all systems and drivers within. They can’t edit or see settings, can’t list systems or change anything however they can, by default, access all functions defined in drivers if they know the system id. This is via the websocket API, most restful API’s are out of bounds to a regular user.

A global callback can be defined to check if a user should be able to access a system:

In a [Rails initialiser](http://guides.rubyonrails.org/configuring.html#using-initializer-files):

```ruby
# Returning true means access should be granted
Rails.application.config.orchestrator.check_access = proc { |system_id, user|
    if system_id == 'sys-nuclear-warheads'
        user.sys_admin ? true : false
    else
        # We only want to block access to the warheads
        true
    end
}
```

All drivers have a helper method for accessing the user details so you can manually manage permissions:

```ruby
def some_method_in_driver
    user = current_user
    if user.nil?
        # Method was invoked internally - timer, onload callback etc
    else
        logger.info "Method called by user #{user.email} (#{user.id})"
    end
end
```

You can also protect methods using `protect_method`. The last `protect_method` call for any function is the one that will be used.

```ruby
class Some::Device::Driver
    include ::Orchestrator::Security

    # By default both Tech Support and Admin users have access to these methods
    # Regular users will be rejected
    protect_method :method_1, :method_2

    # if you provide a block then it can be used to decide if a user should have access
    protect_method :method_1, :method_2 do |user|
        user.sys_admin || user.name == 'service account' || check_room_bookings(user)
    end

    def method_1; end
    def method_2; end
end
```

you can also check if a user has access to a method

```ruby
can_access? :method_name
# by default it checks against the current user, this can be overridden
can_access? :method_name, user
```

NOTE:: the current user is maintained across asynchronous function calls and timers.

i.e. `Browser (user: Bob) -> LogicModule.do_something_weird -> Display.reset_to_factory_new`

If Bob is a regular user and the `reset_to_factory_new` function is protected then `reset_to_factory_new` will not be executed.

Finally all system access is logged and saved for a few months to make it fairly easy to track down bad actors within an organisation.

## Encrypted Settings

Passwords often need to be stored in the database for accessing secure devices. To have a setting stored securely, you enter the key with a `$` sign prefix.

```javascript
{
    "$password": "secret"
}
```

once saved, the setting is encrypted with 256 bit [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) using [GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) ciphers to prevent tampering

You can review the code here: <https://github.com/acaprojects/ruby-engine/blob/master/lib/orchestrator/encryption.rb>


# Utilities and Helpers

Engine comes packaged with some handy helper functions that make interfacing with the wide variety of common IoT protocols easier.

## Functions and Constants

These can be included into your driver class

```ruby
include ::Orchestrator::Constants
```

| Helper                                 | Type                                 | Description                                        |
| -------------------------------------- | ------------------------------------ | -------------------------------------------------- |
| `On, Down, Open`                       | true                                 | Constants that can make code more readable         |
| `Off, Up, Close, Short`                | false                                | Constants that can make code more readable         |
| `in_range(input_number, max, min = 0)` | returns a number in the range        | if input exceeds the limits, the limit is returned |
| `is_affirmative? value`                | returns true if value is affirmative | values such as: `true 'yes' :On`                   |
| `is_negatory? value`                   | returns true if value is negative    | values such as: `false 'no' :Inactive`             |

The Constants include module also contains Configuration Helpers

```ruby
include ::Orchestrator::Transcoder
```

| Helper               | Type                      | Description                                                                                                             |
| -------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `hex_to_byte(data)`  | returns binary string     | accepts any string containing hex characters and supports common formatting such as `"0xDEADBEEF"`, `"De:ad:Be:ef"` etc |
| `byte_to_hex(data)`  | returns an ASCII string   | accepts binary strings or arrays of bytes                                                                               |
| `str_to_array(data)` | returns an array of bytes | accepts strings                                                                                                         |
| `array_to_str(data)` | returns a binary string   | accepts array of bytes                                                                                                  |

## Protocols

### WebSockets

Enables [WebSocket communication](https://en.wikipedia.org/wiki/WebSocket) using a standard TCP socket driver.

* For more details on the websocket API see <https://github.com/faye/websocket-driver-ruby#driver-api>

```ruby
require 'protocols/websocket'

class WebsocketClient
    include ::Orchestrator::Constants
    include ::Orchestrator::Transcoder

    generic_name :Websocket
    descriptive_name 'Websocket example'

    tcp_port 80
    wait_response false

    def connected
        new_websocket_client
    end

    def disconnected
        # clear the keepalive ping
        schedule.clear
    end

    # Send a text message
    def some_request
        @ws.text "hello"

        # or json format etc

        @ws.text({
            some: "message",
            count: 234
        }.to_json)
    end

    # send a binary message
    def binary_send
        @ws.binary("binstring".bytes)

        # or

        @ws.binary hex_to_byte("0xdeadbeef")
    end

    protected

    def new_websocket_client
        # NOTE:: you must use wss:// when using port 443 (TLS connection)
        @ws = Protocols::Websocket.new(self, "ws://#{remote_address}/path/to/ws/endpoint")
        # @ws.add_extension # https://github.com/faye/websocket-extensions-ruby
        # @ws.set_header(name, value) # Sets a custom header to be sent as part of the handshake
        @ws.start
    end

    def received(data, resolve, command)
        @ws.parse(data)
        :success
    end

    # ====================
    # Websocket callbacks:
    # ====================

    # websocket ready
    def on_open
        logger.debug { "Websocket connected" }
        schedule.every('30s') do
            @ws.ping('keepalive')
        end
    end

    def on_message(raw_string)
        logger.debug { "received: #{raw_string}" }

        # Process request here
        # request = JSON.parse(raw_string)
        # ...
    end

    def on_ping(payload)
        logger.debug { "received ping: #{payload}" }
        # optional
    end

    def on_pong(payload)
        logger.debug { "received pong: #{payload}" }
        # optional
    end

    # connection is closing
    def on_close(event)
        logger.debug { "closing... #{event.code} #{event.reason}" }
    end

    # connection is closing
    def on_error(error)
        logger.debug { "ERROR! #{error.message}" }
    end

    # ====================
end
```

### Telnet

Implements the [telnet standard](https://en.wikipedia.org/wiki/Telnet) so that it is easy to communicate with devices that implement control codes or require negotiation.

```ruby
require 'protocols/telnet'

class TelnetClient
    def on_load
        new_telnet_client

        # Telnet client returns only relevant data for buffering
        config before_buffering: proc { |data|
            @telnet.buffer data
        }
    end

    def disconnected
        # Ensures the buffer is cleared
        new_telnet_client
    end


    def some_request
        # Telnet deals with end of line characters
        # (may have been negotiated on initial connection)
        send @telnet.prepare('some request')
    end

    protected

    def new_telnet_client
        # Telnet client needs access to IO stream
        @telnet = Protocols::Telnet.new do |data|
            send data
        end
    end
end
```

### KNX

Constructs [KNX standard](https://en.wikipedia.org/wiki/KNX_%28standard%29) datagrams that make it easy to communicate with devices on KNX networks.

For more information see: <https://github.com/acaprojects/ruby-knx>

### BACnet

Constructs [BACnet](http://www.bacnet.org/) datagrams that make it easy to communicate with devices on BACnet networks.

For more information see: <https://github.com/acaprojects/ruby-bacnet>

### OAuth

For secure delegated access to services that implement it see [wikipedia for details](https://en.wikipedia.org/wiki/OAuth)

```ruby
require 'protocols/oauth'

class HttpClient
    # =====================================
    # Hook into HTTP request via middleware
    # All requests will be sent with OAuth
    # =====================================
    def on_update
        connected
    end

    # This is called directly after on_load.
    # Middleware is not available until connected
    def connected
        @oauth = Protocols::OAuth.new({
            key:    setting(:consumer_key),
            secret: setting(:consumer_secret),
            site:   remote_address
        })
        update_middleware
    end

    protected

    def update_middleware
        # middleware is service helper function
        mid = middleware
        mid.clear
        mid << @oauth
    end
end
```

### SNMP

Provides an evented IO proxy for [ruby-netsnmp](https://github.com/swisscom/ruby-netsnmp)

```ruby
require 'protocols/snmp'

class SnmpClient
    include ::Orchestrator::Constants
    udp_port 161

    def on_unload
        @client.close
    end

    # This is called directly after on_load.
    # Middleware is not available until connected
    def connected
        proxy = Protocols::Snmp.new(self)
        @client = NETSNMP::Client.new({
            proxy: proxy, version: "2c",
            community: "public"
        })
    end

    def query_something
        self[:status] = @client.get(oid: '1.3.6.1.2.1.1.1.0')
    end

    def set_something(val)
        @client.set('1.3.6.1.2.1.1.3.0', value: val)
        self[:something] = val
    end

    protected

    def received(data, resolve, command)
        # return the data which resolves the request promise.
        # the proxy uses fibers to provide this to the NETSNMP client
        data
    end
end
```

### SOAP Services

Probably the easiest way to use these services at the moment via a Logic module. There are a number of supported ruby gems:

* <https://github.com/savonrb/savon>
* <https://github.com/unwire/handsoap>

#### **Savon usage:**

```ruby
# Ensure we are not blocking the IO reactor loop
require 'httpi/adapter/libuv'
require 'savon'
HTTPI.adapter = :libuv

# Make requests as per the savon documentation
client = Savon.client(wsdl: 'https://aca.im/service.wsdl')
logger.debug { "Available operations: #{client.operations}" }
```

#### **Handsoap usage:**

```ruby
# Ensure we are not blocking the IO reactor loop
require 'handsoap/http/drivers/libuv_driver'
Handsoap.http_driver = :libuv

# Make requests as per the handsoap documentation
```

### Wake on LAN

Wake on LAN is available to drivers of all types

```ruby
# Supports any string with the correct number of hex digits
#  as well as common formats (these are some examples)
mac_address_string = '0x62f81d4b6f00'
mac_address_string = '62:f8:1d:4b:6f:00'
mac_address_string = '62-f8-1d-4b-6f-00'

# Defaults to broadcast address `'255.255.255.255'`
wake_device(mac_address_string)

# You can define a VLan gateway for a directed broadcast (most common in enterprise)
wake_device(mac_address_string, '192.168.3.1')
```

### ICMP (ping)

Uses the operating systems `ping` utility to perform a connectivity check.

```ruby
# perform the ping
ping = ::UV::Ping.new(remote_address)
ping.ping      # true / false to indicate success / failure

# check out the ping results
ping.pingable  # true / false to indicate success / failure
ping.ip        # IP pinged (remote_address can be a domain name)
ping.exception # any error messages
ping.warning   # any warning messages
```

### CRC Checks

[github project](https://github.com/dearblue/ruby-crc) and [supported CRC checks](https://github.com/dearblue/ruby-crc/blob/master/lib/crc/_modules.rb)

* `gem install crc`

Usage

```ruby
require 'crc'
crc = CRC::CRC16_CCITT.new
crc.update "\x12\x34\x56\x78\x90"
crc.digest # => "ca"
```


# User Interfaces

Building an interface for Engine is the same as building any generic web application. We try to follow industry best practices and leverage the tools used by most developers in the industry.

* NOTE:: Windows users are recommended to install the [Linux subsystem for Windows](https://msdn.microsoft.com/en-au/commandline/wsl/install_guide).
  * Tools such as git and your preferred text editor make sense to run on Win32
  * Whilst both NodeJS and Ruby can run natively on Windows they are much harder to manage and use effectively when compared to their Linux counterparts.

## What you will need

Please install the following applications as they are core requirements for interface development

1. [git](https://git-scm.com/) - version control
2. A text editor
   * [Sublime Text](https://www.sublimetext.com/)
   * [Atom](https://atom.io/)
   * [Visual Studio Code](https://code.visualstudio.com/)
   * [Font Ligatures](https://github.com/tonsky/FiraCode) are cool
3. [NodeJS](https://nodejs.org/) - development platform
4. [Ruby](https://rvm.io/rvm/install)

## Install some core tools

The tools and libraries that help with development are available via `npm` which is the [Node Package Manager](https://www.npmjs.com/). We recommend installing the following command line helpers

* [Angular CLI](https://cli.angular.io/) `npm install -g @angular/cli`
* [Gulp](https://gulpjs.com/) `npm install --global gulp-cli`

## Running the Demo UI

This is a project that can run against the Engine development environment, for those who want some instant gratification.

1. Create a folder for storing your interface projects
2. Open a command prompt at that location
3. Clone the [Composer Starter](https://github.com/acaprojects/ngx-composer-starter) `git clone https://github.com/acaprojects/ngx-composer-starter.git`
4. `cd ngx-composer-starter` to move into the folder
5. `npm install` to install the project dependencies
6. `gulp serve` to run the development server

NB: If you are planning to run the user interface project without the Engine Developer environment, in Step 6 above you will need to use `gulp serve --mock` this will allow the interface to run 'headless' with mock data for development purposes.

NB: While the existing UIs are relatively plug'n'play with a locally spun up [Engine environment](/developer-guide/getting-started#starting-up), a domain must first be set for localhost or your machine's IP address before authentication will be functional. Ensure the domain that's set is the same as what you're accessing the frontend app with and matches the composer config in [settings.json](/developer-guide/user-interfaces/settings-json). i.e. What's typed into the browser address bar matches the settings.json and the domain configured in Engine.

## Angular Resources

Angular is our preferred web application framework. We recommend being familiar with it before continuing.

* <https://angular.io/guide/quickstart> - for a basic understanding of Angular applications
* <https://angular.io/tutorial> - fundamentals of Angular

## Updating your environment

This will keep you running on the latest version of the platform as there are periodic updates.

* Updating NPM: `npm install npm@latest -g`
* Updating NodeJS (Windows users should download the latest MSI, unless using the Linux subsystem)
  * `sudo npm cache clean -f`
  * `sudo npm install -g n`
  * `sudo n stable`


# Composer

[Composer](https://github.com/acaprojects/ngx-composer) is an Angular library that simplifies interacting with Engine. It abstracts the complexity of the WebSocket API and manages the following:

* Directives for binding to Status variables
* Calling functions in the Driver
* Resource access (database manipulation)
* Authentication with Engine
* Driver debug binding directives

The magic of Angular is that it allows you to build a dynamic web page in a declarative manner.

> NOTE:: for universal support between mouse and touch devices we use the [Hammer library](http://hammerjs.github.io/recognizer-press/) which has action recognizers, such as pressing, swiping, pinching, etc.

## Status Variable Bindings

Imagine the driver below running on Engine.

* <https://github.com/acaprojects/aca-device-modules/blob/master/modules/philips/display/sicp_protocol.rb#L117>
* It is a type of Display
* It has a status variable called input
* Input can be changed on the device by calling `Display.switch_to(input_name)`

You can request the status values available in drivers so you can present these graphically to a user. This is performed using the `binding` directive and the data requested is updated in real time as the status changes.

```
<span binding [sys]="system" mod="Display" bind="input" [(value)]="model.input" exec="switch_to" [params]="[model.input]">
    Display's current input: {{input}}
</span>
<button (press)="model.input = 'hdmi'">Switch to HDMI</button>
<button (press)="model.input = 'dvi'">Switch to DVI</button>
<button (press)="model.input = 'usb'">Switch to USB</button>
```

Our aim with the client library was to make the interface code as self describing as possible, no abstract channel codes or feedback numbers. Let’s break this line down a little more.

* `binding`: this tells Angular that we want to use the binding directive
* `[sys]`: this is the system ID we want to connect to. The `[]` indicate that this is an input variable
* `mod`: this is a raw string indicating the driver we would like to reference.
  * by default the driver index is 1 so in the example we are referencing `Display_1`
  * this could be more explicit: `mod="Display_1"`
  * or even more explicit `mod="Display" [index]="model.selected_index"`
* `[(value)]`: this is the variable that will hold the status value requested
  * The binding is two way `[()]` so we can send any changes to the server / device
  * If the value is changed, by a user interacting with the interface, the function defined by `exec` will be called on the device module.
  * This is the primary method for updating state on the server / device
* `exec` this is the function to run on the device module if the status value changes
  * The function is called with the parameters defined by `[params]`
  * There are times where execute can be simplified.

When the status variable and function used to update that status variable have the same name, the exec process can be simplified. This is an example of controlling volume and power for the same device.

```markup
<!-- Power Toggle Button -->
<button
    binding
    [sys]="system"
    mod="Display"
    bind="power"
    [(value)]="model.power"
    exec
    (press)="model.power = !model.power"
>Touch to Power {{ model.power ? 'Off' : 'On' }}</button>

<!-- Hide unless powered on -->
<div *ngIf="model.power == true">
    <span
        binding
        [sys]="system"
        mod="Display"
        bind="volume"
        [(value)]="model.volume"
        exec
    >Volume is {{model.volume}}</span>
    <input #volSlider type="range" (change)="model.volume = volSlider.value" min="0" max="100" />
</div>
```

You can see that exec is present with no value configured. The result of this code is a power toggle button for the device and when the device is on there is a volume slider available.

## Calling Functions in the Driver

You can call functions without binding to values first.

```markup
<!-- template method -->
<button
    binding
    [sys]="system"
    mod="Display"
    <!-- a change to this value will cause execute to fire, inital value is ignored -->
    [value]="model.power"
    exec="power"
    <!-- params that will be passed to the function can be customised -->
    [params]="[model.power, model.index]"

    <!-- Event triggering the exec -->
    (press)="model.power = !model.power"
>Touch to Toggle Power</button>
```

You can also execute functions from type script

```javascript
import { SystemsService } from `@acaprojects/ngx-composer`
@Component({ ... })
class DemoExec {
  constructor(private service: SystemsService) { }

  // use route or query params or local storgage for system ID
  power_on_display() {
    this.service.get(`sys-B0`).get(`Display`).exec(`power`, true);
  }
}
```

## Resource Access

Engine exposes a rich set of APIs that can be easily accessed via composer resources on the `SystemsService`

```javascript
import { SystemsService } from `@acaprojects/ngx-composer`
@Component({ ... })
class DemoExec {
  constructor(private service: SystemsService) { }

  get_list_of_systems() {
    // Returns a list of systems matching the seatch query
    this.service.resources.get(`System`).get({
      q: `search`
    });

    // Gets the system information for this system
    this.service.resources.get(`System`).get({
      id: `sys-B0`
    });
  }
}
```

You can `get` the following resource factories:

* Dependency: available drivers
* Trigger: trigger CRUD
* System: control system CRUD
* Module: module CRUD
* Zone: zone CRUD
* User: Current user details
* Log: Access logs

## Authentication

Authentication with Engine is handled automatically by composer.

1. Composer will request `/auth/authority` which contains information about how to authenticate
2. If composer doesn’t have a valid OAuth2 token it will redirect to the defined login page
3. A light weight page `oauth-resp.html` is used to extract the token and save it in local storage
4. A client side redirect back to the initial route

For this to occur composer must be configured as part of the applications load sequence.

> See the example of this in [Composer Starter](https://github.com/acaprojects/ngx-composer-starter/blob/master/src/app/app.component.ts)

The composer starter example

1. ensures the request to `/auth/authority` succeeds
2. configures connection and OAuth2 details
3. configures mode: production, development and if a mock / virtual control system should be used
4. calls `this.systems.setup(config);` which kicks off the authentication process as required

## Driver Debug Bindings

It is possible to request debug logging to be redirected to your browser. This provides a real time window into the inner workings of the driver as it is executing.

```markup
<div
    debug
    [sys]="model.system_id"
    [mod]="model.module_id"
    numLines="20"
    [(output)]="model.output"
>{{model.output.join("<br />")}}</div>
```

Useful for technical or administration pages.


# Virtual Systems

Interface development should not rely on having access to physical hardware.

> Systems engineers should work with the user experience team to provide accurate models of real systems

## Development Settings

There is a [settings file](https://github.com/acaprojects/ngx-composer-starter/blob/master/src/assets/settings.json) that defines how you would like to work.

* The `"env"` key can be set to either `"dev"` or `"prod"`
  * When in `"dev"` mode the system will talk to the virtual systems
  * When in `"prod"` mode the system expects to talk to a live Engine instance.

Production mode can also be used for development.

* The `"domain"` key is used to proxy requests from your development machine to an Engine instance.
  * This allows real-world testing to occur before building and deploying your interface.
  * All requests to `http://localhost:dev_port` are proxied to the remote instance at `"domain"`

## Building a System Mock

Please take a look at the demo [mock system](https://github.com/acaprojects/ngx-composer-starter/blob/master/src/app/shared/mock/system.mockup.ts)

> It is worth going over Key Concepts before continuing

### Basic Structure

A system is a collection of drivers and logic. Drivers are accessed using a generic name and an index.

An example system:

* System: Presentation Room 2 (`sys-b0W12`)
  * Presentation Logic (`Presentation_1`)
  * Sony Projector Left (`Display_1`)
  * Hitachi Projector Right (`Display_2`)
  * Extron HDMI Switcher (`Switcher_1`)
  * Dynalite Lighting (`Lighting_1`)

Each driver in the system is made up of state. For example:

* Power is On or Off - true or false.
* Volume is 80
* Max volume is 120

Drivers also expose functions:

* For modifying the power state: `power(state)` accepting true or false
* `volume(level)` accepting an integer and will modify the volume state

> When building a mock system only the functions and state variables that matter need to be represented.

### TypeScript Representation

You’ll find that the mock version of a system looks and feels much like the code it is emulating. In some cases it looks like the settings you might provide a real system.

* The system definitions are stored on the `window` object at `window.control.systems`
* The keys of `control.systems` act as the system IDs.

A representation of the example system, above, looks like:

```javascript
window.control.systems['sys-b0W12'] = {
    Presentation: [{
        ...
    }],
    Display: [
        {
            // Display_1
        },
        {
            // Display_2
        }
    ],
    Switcher: [{
        ...
    }],
    Lighting: [{
        ...
    }]
};
```

The keys of system `sys-b0W12` represent the generic name of the drivers present in the system. These keys are an array of drivers where the position of that driver in the array defines it’s index.

### Mock Driver Definition

Drivers have state and functions.

* State is defined as any of the keys in the driver
* Functions are marked as functions by adding a `$` sign before the key

Functions are scoped to the driver. So you can modify the state of driver by using `this`.

```javascript
// example Display driver

{
    // Initial state values
    power: false,
    volume: 0,
    mute: true,
    input: `hdmi`

    // Functions
    $power: (state: boolean) => {
        this.power = state;
        this.mute = state;
    },
    $volume: (level: number) => {
        this.volume = level;
    },
    $mute: (state: boolean) => {
        this.mute = state;
    },
    $input: (name: string) => {
        this.input = name;
    }
}
```

> You only need to define functions that modify the state that you are tracking. The absence of a mock function won’t throw errors.

Logic modules, such as `Presentation` in the example, will communicate with devices in a system. This is achieved by providing a helper `$system` which provides access to the system definition.

```javascript
// example Presentation logic

{
    // Initial state values
    state: `shutdown`,
    inputs: {
        `Laptop HDMI`: 1,
        `Wireless Presenter`: 2,
        `Document Camera`: 3
    },
    outputs: {
        `Left`: 1,
        `Right`: 2
    }

    // Functions
    $powerup: () => {
        this.$system.Display.forEach((display) => {
            display.$power(true);
        });
    },
    $shutdown: () => {
        // Iterate over all the displays
        this.$system.Display.forEach((display) => {
            display.$power(false);
        });
    },
    $switch_input: (input: string, display: string) => {
        this.$powerup();

        // Access Switcher_1
        this.$system.Switcher[0].$switch(
            this.inputs[input], this.outputs[display]
        );
    }
}
```


# Widgets

ACA provides an extensive range of widgets designed with control interfaces in mind.

> Links to the available components and documentation can be found on the [project page](https://github.com/acaprojects/a2-widgets)

## Nuanced Behaviour

Widgets must be designed to meet the expectations of the user. We recommend using our widgets before looking at third party libraries for the following reasons:

### 1. They are user aware

When a page loads, widgets are often loaded with default values while they wait for the official value from the server. You don’t want these default values triggering change events and the defaults being sent to the server.

> Only a users actions should send requests to the server

Default values or value updates coming from the server should not trigger change events that would have the value sent to the server.

### 2. They provide instantaneous feedback

Let’s use a volume slider as an example.

* The slider needs to update its value as it is being moved.
* Users require the real world volume to change as they move the slider to find their preferred level.

The server will also be sending value updates during the period of interaction. These should be ignored while the user is touching the slider to avoid jumping around, which users find disconcerting.

### 3. They are scope aware

To reduce required bandwidth, we recommend that you only bind to values required to display the current screen. This is quite trivial with Angular as bindings are created and destroyed with life cycle hooks.

Angular is optimised for redraw which means [life cycle events](https://www.bennadel.com/blog/3064-ng-content-life-cycle-is-controlled-by-the-parent-view-not-the-consumer-in-angular-2-beta-11.htm) might not occur as expected.

* Tabs and pop-ups are the most common offenders
* ACA widgets avoid this pitfall


# Settings.json

Configure certain aspects of the existing front-ends without the need for a recompile.

The settings.json file exists primarily to allow configurable settings that don't require a full rebuild of a frontend - also some visual elements can be controlled by these settings like headings, positions, icons, and colors etc. Some of the configurable settings include:

* Composer settings, like domain, protocol, and auth method.
* Basic App-level visuals; title, meta details, logos, etc.
* Top-level menu listings
* Banner child element organisation and appearance.
* Page routes and titles.
* Bookings - granular conditional settings

The settings.json file can become monolithic but this, for the moment, allows backend devs, or anyone not frontend-familiar with git repo access, the ability to change visuals and other aspects of an app easily.

When approaching an existing frontend first check to see if the change can be made in the settings.json file; it might save some headache.

## Overview

### Composer config

```javascript
{
    ...

    "composer": {
        "domain": "",
        "route": "",
        "protocol": "http:",
        "use_domain": true,
        "local_login": false
    }

    ...
}
```

The default composer config is setup for simple local development, if you require your app to work in conjunction with a local [Engine instance](/developer-guide/getting-started) then the `domain` setting will have to match the corresponding domain listing in the admin panel of the instance (Backoffice).

For example:

```javascript
{
    ...

    "composer": {
        "domain": "localhost:4200",
        "route": "",
        "protocol": "http:",
        "use_domain": true,
        "local_login": false
    }

    ...
}
```

NB: This assumes the app is being served with default settings with the `gulp serve` terminal command - i.e. utilizing port `4200`. A production `settings.json` file will be created from this with modified settings at compile time. These production values overrides will exist within `/config/default.ts`.

### Cosmetics/Supplementary Details

```javascript
{
    ...

    "app": {
        "title": "ACA Staff App",
        "name": "Staff App",
        "description": "ACA Projects Staff Application made in Angular",
        "short_name": "ACA",
        "code": "ACA",
        "login": {
            "forgot": false
        },
        "analytics": {
            "enabled": false,
            "tracking_id": "UA-123456789"
        },
        "logo": {
            "type": "img",
            "src": "assets/img/logo.svg",
            "inverse": "assets/img/logo-inverse.svg",
            "background": ""
        },
        "banner": {
            "enabled": true,
            "header": true,
            "home": false,
            "type": "color",
            "full": true,
            "allow_image_on_home": false,
            "src": "assets/img/background-hero.jpg"
        },
        "copyright": "Copyright 2020 ACA Projects",

        ...
    }

    ...
}
```

In this example basic app-wide details are specified; these include page-wide titles/meta data details, logos, GA code, and toggles for when to show hero banners. All things that can be configured post build.

### Granular Configurations for Sections

This snippet shows the configuration options for room booking on the default ACA Staff App.

```javascript
{
    ...

    "booking": {
            "min_attendees": 0,
            "external_visitors": false,
            "recurrence": false,
            "max_days_ahead": 180,
            "max_length": 480,
            "default_length": 30,
            "min_length": 30,
            "multi_room": false,
            "title_prefix": "",
            "charge": false,
            "terms": false,
            "old_filters": false,
            "select_room_first": true,
            "privacy": false,
            "control": false,
            "lock_cancel": 0,
            "external_floors": [],
            "return_id": "",
            "fields": [
                {
                    "key": "date",
                    "label": "Date",
                    "icon": {
                        "class": "material-icons",
                        "value": "event"
                    },
                    "simple": true,
                    "description": "",
                    "control_type": "custom",
                    "required": true
                },
                {
                    "key": "start",
                    "label": "Start",
                    "icon": {
                        "class": "material-icons",
                        "value": "access_time"
                    },
                    "refs": [
                        "date"
                    ],
                    "simple": true,
                    "description": "",
                    "control_type": "custom",
                    "required": true
                },
                {
                    "key": "duration",
                    "label": "End",
                    "icon": {
                        "class": "material-icons",
                        "value": "access_time"
                    },
                    "refs": [
                        "date"
                    ],
                    "simple": true,
                    "description": "",
                    "control_type": "custom",
                    "required": true
                }

                ...

            ],
            "banner": {
                "enabled": true,
                "links": [
                    {
                        "id": "book",
                        "name": "Book Space"
                    },
                    {
                        "id": "book/visitor",
                        "name": "Book Visitor"
                    }
                ]
            }
        }

    ...
}
```


# Authentication

API interactions require authentication.

Access to ACAEngine is secured via [OAuth2](https://www.oauth.com/). Before interacting, your app or integration will need to authenticate and obtain a valid access token. Once authenticated, this token must accompany all requests.

This can either be included as an `Authorization` header (recommended):

{% tabs %}
{% tab title="HTTPie" %}

```bash
http example.com/api/control/systems 'Authorization:bearer <access token>'
```

{% endtab %}

{% tab title="cURL" %}

```bash
curl -H "Authorization: bearer <access token>" example.com/api/control/systems
```

{% endtab %}
{% endtabs %}

Or, as a query parameter:

{% tabs %}
{% tab title="HTTPie" %}

```bash
http example.com/api/control/systems bearer_token==<access token>
```

{% endtab %}

{% tab title="cURL" %}

```bash
curl "example.com/api/control/systems?bearer_token=<access token>"
```

{% endtab %}
{% endtabs %}

## Registering Your Application

All applications using the ACAEngine API need to be registered in Backoffice. Details on this can be found in the Backoffice user guide.

{% content-ref url="/pages/-LmsUk7gcRQgAL3oqZkL" %}
[Domains](/administration/backoffice/domains)
{% endcontent-ref %}

Once registered, take note of the `client_id` as well as the `client_secret` or `redirect_uri`, depending on the auth flow your application will use.

## Obtaining An Access Token

Using OAuth2, a few approaches are available to obtain an access token. Here's some recommendations based on common API uses:

| Auth flow          | Recommended use case                                                                                      |
| ------------------ | --------------------------------------------------------------------------------------------------------- |
| Implicit           | Web, mobile and desktop apps communicating directly with the ACAEngine API.                               |
| Authorization Code | Web, mobile and desktop apps where your back-end communicates with the ACAEngine API on behalf of a user. |
| Password Grant     | Server-to-server integration and highly trusted environments.                                             |

### Implicit

Authentication and direct API access from client side applications can be safely achieved via the OAuth implicit flow without your application needing to intercept any user details. This allows federated identity services to be used and your users security and privacy to be preserved.

All interaction within this flow takes place within a client-side user agent, making it a great choice for standalone apps.

To authenticate you will need to direct users to the authorisation endpoint, accompanied by your registered application details.

<mark style="color:blue;">`GET`</mark> `https://example.com/auth/oauth/authorize`

#### Path Parameters

| Name           | Type   | Description                                                                                                                    |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------ |
| client\_id     | string | Application client ID                                                                                                          |
| redirect\_uri  | string | Your registered redirect URI.                                                                                                  |
| response\_type | string | "token"                                                                                                                        |
| state          | string | A unique token, generated by your application. This will be included in the response and is recommended to avoid CSRF attacks. |

{% tabs %}
{% tab title="302 " %}

```
```

{% endtab %}
{% endtabs %}

Your users will then be prompted to authenticate via your configured identity provider and authorize your application. Once access is granted they will then be returned to your redirect URL with the access token included as a URI fragment.

```http
https://<your registered redirect URI>#access_token=<token>
```

Your application may then parse this token and use it for API requests.

{% hint style="info" %}
URI fragments can be accessed from JavaScript with `document.location.hash`.
{% endhint %}

{% hint style="warning" %}
If you included a `state` parameter (you should), this will also be returned and should be validated against your original request prior to commencing any interaction.
{% endhint %}

### Authorization Code

When building application that contains server-side components you may find a need to provide users the ability to grant your infrastructure access to interact with the ACAEngine on their behalf. This is commonly encountered if you have staff or venue app and your ACAEngine deployment is not accessible from public networks, or you may be interacting with the API asynchronously or in response to events from other systems.

As with the implicit flow, at no point does your application require direct knowledge of user credentials.

Using this flow, users first authorize your application by creating a short-lived authorization code. This is then passed to your back-end components, where it can be redeemed for an access token.

To generate the authorisation code, direct your users to the authorisation endpoint with `code` as the requested response type.

<mark style="color:blue;">`GET`</mark> `https://example.com/auth/oauth/authorize`

#### Path Parameters

| Name           | Type   | Description                                                                                                                    |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------ |
| client\_id     | string | Application client ID                                                                                                          |
| redirect\_uri  | string | Your registered redirect URI.                                                                                                  |
| response\_type | string | "code"                                                                                                                         |
| state          | string | A unique token, generated by your application. This will be included in the response and is recommended to avoid CSRF attacks. |

{% tabs %}
{% tab title="302 " %}

```
```

{% endtab %}
{% endtabs %}

After authenticating and authorising your application, users will be redirected to your configured redirect URI with an authorisation code as part of the request parameters.

```http
https://<your registered redirect URI>/?code=<authorisation code>
```

{% hint style="warning" %}
This response will also include the `state` parameter if it was included in your original request. This should be validated before continuing.
{% endhint %}

Your backend infrastructure may then extract this and exchange it for an access token that can be used to perform actions as the authorising user by using the token endpoint.

<mark style="color:green;">`POST`</mark> `https://example.com/auth/oauth/token`

#### Request Body

| Name           | Type   | Description                                                 |
| -------------- | ------ | ----------------------------------------------------------- |
| grant\_type    | string | "authorization\_code"                                       |
| client\_id     | string | Application client ID.                                      |
| client\_secret | string | Application secret.                                         |
| code           | string | The authorization code from the previous step.              |
| redirect\_uri  | string | The redirect URI used in the authorization code generation. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
  "created_at": 1559193360,
  "expires_in": 1209600,
  "refresh_token": "ac56ce17189cd8c2afc6c0fc7feeb4710bcc7985847b6f428a9a54ad6450cee0",
  "scope": "public",
  "token_type": "bearer"
}
```

{% endtab %}
{% endtabs %}

### Password Grant

The Oauth2 password grant flow provides a good option for server-to-server integration, or when designing a system where direct knowledge of both user, and application secrets is acceptable.

{% hint style="danger" %}
This flow should not be used as part of any components distributed to users or un-trusted endpoints. This includes usage within client side code for web apps or mobile apps, including in compiled form.
{% endhint %}

This flow provides the ability to directly exchange a username and password for an access token as a single request.

<mark style="color:green;">`POST`</mark> `https://example.com/auth/oauth/token`

#### Request Body

| Name           | Type   | Description                            |
| -------------- | ------ | -------------------------------------- |
| grant\_type    | string | "password"                             |
| username       | string | The user to authenticate as.           |
| password       | string | The password to authenticate with.     |
| authority      | string | The domain ID to authenticate against. |
| client\_id     | string | Application client ID.                 |
| client\_secret | string | Application client secret.             |
| scope          | string |                                        |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
  "created_at": 1559193360,
  "expires_in": 1209600,
  "refresh_token": "ac56ce17189cd8c2afc6c0fc7feeb4710bcc7985847b6f428a9a54ad6450cee0",
  "scope": "public",
  "token_type": "bearer"
}
```

{% endtab %}
{% endtabs %}

## Session Lifetime

When receiving a token, the server response will include a token expiry - `expires_in` - which is the number of seconds the token is valid for. By default, this is 14 days. When this period has elapsed your application will need to obtain a new access token. This can be done either by repeating the original authentication flow, or using a refresh token if provided.

### Refresh Tokens

Along with the `access_token`, successful authentication requests may also contain a `refresh_token`. This can be used to renew the session at any time, extending access as long as both the application registration and user are still valid.

<mark style="color:green;">`POST`</mark> `https://example.com/auth/oauth/token`

#### Request Body

| Name           | Type   | Description                |
| -------------- | ------ | -------------------------- |
| grant\_type    | string | "refresh\_token"           |
| refresh\_token | string | The refresh token to use.  |
| client\_id     | string | Application client ID.     |
| client\_secret | string | Application client secret. |

{% tabs %}
{% tab title="200 " %}

```
{
  "access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
  "created_at": 1559193360,
  "expires_in": 1209600,
  "refresh_token": "ac56ce17189cd8c2afc6c0fc7feeb4710bcc7985847b6f428a9a54ad6450cee0",
  "scope": "public",
  "token_type": "bearer"
}
```

{% endtab %}
{% endtabs %}

### Token Revocation

To end a session, applications should request a token revocation. This will invalidate the token, preventing further use.

<mark style="color:green;">`POST`</mark> `https://example.com/auth/oauth/revoke`

#### Path Parameters

| Name  | Type   | Description              |
| ----- | ------ | ------------------------ |
| token | string | The token to invalidate. |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}


# Control

The control API provides interactions with Engine core.

The control API is the primary point for querying, structuring, defining and interacting with Engine deployments. The endpoints that it provides enable interaction between drivers operating on an instance and the outside world.

The exposed capabilities are dependent on the drivers loaded to an instance. Using this API provides a high level structure for discovering this configuration and interacting with the environment that it connects.

All entites—[systems](/api/control/systems), [modules](/api/control/modules), [dependencies](/api/control/dependencies) and [zones](/api/control/zones)—provide a model and methods for discovery, management (CRUD) and direct interactions.


# Systems

The `/systems` endpoint provides methods for discovering, creating and interacting with systems. For more on the role that systems play, see:

{% content-ref url="/pages/-LZYOTU8V7JM1jh1Z-xA" %}
[Systems](/key-concepts/systems)
{% endcontent-ref %}

All systems provide a base set of metadata that helps to describe their role and capabilities, as well as provide references to the modules they contain, and the zones they exist in.

## Model

| Attribute              | Type      | Description                                                                                |
| ---------------------- | --------- | ------------------------------------------------------------------------------------------ |
| id                     | `string`  | The system's unique ID.                                                                    |
| edge\_id               | `string`  | ID of the preferred engine node to run on.                                                 |
| name                   | `string`  | The system's primary identifier.                                                           |
| zones                  | `array`   | Zone IDs that this system is a member of.                                                  |
| modules                | `array`   | Module ID's that this system contains.                                                     |
| description            | `string`  | Markdown formatted text that describes the system.                                         |
| email                  | `string`  | Calendar email that represents this system. Typically used for room scheduling / bookings. |
| capacity               | `integer` | Number of people this space can accommodate.                                               |
| features               | `string`  | List of features in the room for searching and filtering spaces.                           |
| bookable               | `boolean` | Flag for signifying the space is bookable.                                                 |
| installed\_ui\_devices | `integer` | Expected number of fixed installation touch panels.                                        |
| settings               | `object`  | JSON object representing the system's configuration.                                       |
| created\_at            | `integer` | Timestamp of creation.                                                                     |
| support\_url           | `string`  | A URL linking to the primary interface for controlling this system.                        |
| version                | `integer` | Incremental counter for handling stale updates.                                            |

## Discovery

## Search

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/systems`

Direct queries to the systems endpoint list, or search for existing systems.

#### Query Parameters

| Name       | Type    | Description                                |
| ---------- | ------- | ------------------------------------------ |
| q          | string  | A search query for the system metadata.    |
| limit      | integer | Max results to return (default 20).        |
| offset     | integer | The offset within the result set.          |
| zone\_id   | string  | Limit to systems within this zone.         |
| module\_id | string  | Limit to systems that contain this module. |

{% tabs %}
{% tab title="200 A list of systems matching the search criteria." %}

```javascript
{
    "total": 3,
    "results": [
        {
            "edge_id": "edge-QC03B3OM",
            "name": "Room 1",
            "description": null,
            "email": "room1@example.com",
            "capacity": 10,
            "features": "",
            "bookable": true,
            "installed_ui_devices": 0,
            "zones": [
                "zone-rGhCRp_aUD"
            ],
            "modules": [
                "mod-rJRCVYKVuB",
                "mod-rJRGK21pya",
                "mod-rJRHYsZExU"
            ],
            "settings": {},
            "created_at": 1562041110,
            "support_url": null,
            "version": 5,
            "id": "sys-rJQQlR4Cn7"
        },
        {
            "edge_id": "edge-QC03B3OM",
            "name": "Room 2",
            "description": null,
            "email": "room2@example.com",
            "capacity": 10,
            "features": "",
            "bookable": true,
            "installed_ui_devices": 0,
            "zones": [
                "zone-rGhCRp_aUD"
            ],
            "modules": [
                "mod-rJRJOM27Kb",
                "mod-rJRLE4_PQ7",
                "mod-rJRLwe72Mo"
            ],
            "settings": {},
            "created_at": 1562041127,
            "support_url": null,
            "version": 4,
            "id": "sys-rJQSySsELE"
        },
        {
            "edge_id": "edge-QC03B3OM",
            "name": "Room 3",
            "description": null,
            "email": "room3@example.com",
            "capacity": 4,
            "features": "",
            "bookable": true,
            "installed_ui_devices": 0,
            "zones": [
                "zone-rGhCRp_aUD"
            ],
            "modules": [
                "mod-rJRNrLDPNz",
                "mod-rJRQ~JwE7U",
                "mod-rJRV1qokbH"
            ],
            "settings": {},
            "created_at": 1562041145,
            "support_url": null,
            "version": 4,
            "id": "sys-rJQVPIR9Uf"
        }
    ]
}
```

{% endtab %}
{% endtabs %}

Queries default to searching for any of the entered terms (words). A small query language provides the ability to structure complex queries.

| Operator    | Action                                            |                       |
| ----------- | ------------------------------------------------- | --------------------- |
| `+`         | Matches both terms.                               |                       |
| \`          | \`                                                | Matches either terms. |
| `-`         | Negates a single token.                           |                       |
| `"`         | Wraps tokens to form a phrase.                    |                       |
| `(` and `)` | Provide precedence.                               |                       |
| `~N`        | Specifies edit distance (fuzziness) after a word. |                       |
| `~N`        | Specifies slop amount (deviation) after a phrase. |                       |

## Management

## Create

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/systems`

Defines a new system. Systems names must be unique within the instance they are running on and all systems must have at least one zone associated. All other attributes are optional at the time of creation.

#### Request Body

| Name                   | Type    | Description |
| ---------------------- | ------- | ----------- |
| name                   | string  |             |
| zones                  | array   |             |
| edge\_id               | string  |             |
| description            | string  |             |
| email                  | string  |             |
| capacity               | integer |             |
| bookable               | boolean |             |
| installed\_ui\_devices | integer |             |
| modules                | string  |             |
| settings               | string  |             |
| support\_url           | string  |             |

{% tabs %}
{% tab title="200 " %}

```javascript
{
    "edge_id": "edge-QC03B3OM",
    "name": "Example Room",
    "description": "Example room description containing further cotnext",
    "email": "room@example.com",
    "capacity": 10,
    "features": "",
    "bookable": true,
    "installed_ui_devices": 0,
    "zones": [
        "zone-rGhCRp_aUD"
    ],
    "modules": [],
    "settings": {},
    "created_at": 1562041110,
    "support_url": "https://example.com/foo",
    "id": "sys-rJQQlR4Cn7"
}
```

{% endtab %}
{% endtabs %}

## Retrieve

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/systems/{id}`

Retrieve all metadata associated with the system.

#### Path Parameters

| Name | Type   | Description                   |
| ---- | ------ | ----------------------------- |
| id   | string | ID of the system to retrieve. |

#### Query Parameters

| Name     | Type    | Description                                                                                   |
| -------- | ------- | --------------------------------------------------------------------------------------------- |
| complete | boolean | Include full models of all modules and zones associated with the system rather than their ID. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
    "edge_id": "edge-QC03B3OM",
    "name": "Example Room",
    "description": "Example room description containing further context",
    "email": "room@example.com",
    "capacity": 10,
    "features": "",
    "bookable": true,
    "installed_ui_devices": 0,
    "zones": [
        "zone-rGhCRp_aUD"
    ],
    "modules": [
        "mod-rJRCVYKVuB",
        "mod-rJRGK21pya",
        "mod-rJRHYsZExU"
    ],
    "settings": {},
    "created_at": 1562041110,
    "support_url": "https://example.com/foo",
    "version": 3,
    "id": "sys-rJQQlR4Cn7"
}
```

{% endtab %}
{% endtabs %}

## Update

<mark style="color:orange;">`PUT`</mark> `https://example.com/api/control/systems/{id}`

Updates system attributes. Any selection of attributes form the request - unspecified items will keep their current values. All requests must include a **version** parameter that matches the current system version.

#### Path Parameters

| Name | Type   | Description                 |
| ---- | ------ | --------------------------- |
| id   | string | ID of the system to update. |

#### Request Body

| Name                   | Type    | Description                                                                                                       |
| ---------------------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| version                | integer | The system metadata version. This must match the current version and increments following each successful update. |
| name                   | string  |                                                                                                                   |
| description            | string  |                                                                                                                   |
| email                  | string  |                                                                                                                   |
| capacity               | integer |                                                                                                                   |
| bookable               | boolean |                                                                                                                   |
| installed\_ui\_devices | integer |                                                                                                                   |
| zones                  | array   |                                                                                                                   |
| modules                | string  |                                                                                                                   |
| settings               | string  |                                                                                                                   |
| support\_url           | string  |                                                                                                                   |

{% tabs %}
{% tab title="200 " %}

```javascript
{
    "edge_id": "edge-QC03B3OM",
    "name": "Example Room",
    "description": "Example room description containing further context",
    "email": "room@example.com",
    "capacity": 10,
    "features": "",
    "bookable": true,
    "installed_ui_devices": 0,
    "zones": [
        "zone-rGhCRp_aUD"
    ],
    "modules": [],
    "settings": {},
    "created_at": 1562041110,
    "support_url": "https://example.com/foo",
    "id": "sys-rJQQlR4Cn7"
}
```

{% endtab %}

{% tab title="409 The specified version does not match the current system version." %}

```
```

{% endtab %}
{% endtabs %}

## Delete

<mark style="color:red;">`DELETE`</mark> `https://example.com/api/control/systems/{id}`

Removes a system. This will stop, and remove any modules that are not associated with other systems.

#### Path Parameters

| Name | Type   | Description                   |
| ---- | ------ | ----------------------------- |
| id   | string | ID of the system to retrieve. |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

## Interaction

## Start

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/systems/{id}/start`

Starts all modules associated with the system.

#### Path Parameters

| Name | Type   | Description                |
| ---- | ------ | -------------------------- |
| id   | string | ID of the system to start. |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

## Stop

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/systems/{id}/stop`

Stops all modules associated with the system.

#### Path Parameters

| Name | Type   | Description               |
| ---- | ------ | ------------------------- |
| id   | string | ID of the system to stop. |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

## Exec

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/systems/{id}/exec`

Run behaviour exposed by a module. The associated method will execute and the response returned. If this includes asynchronous or long running behaviour, the result will be awaiting up until a timeout value.

#### Path Parameters

| Name | Type   | Description                          |
| ---- | ------ | ------------------------------------ |
| id   | string | ID of the system to execute within.a |

#### Request Body

| Name   | Type    | Description                                                  |
| ------ | ------- | ------------------------------------------------------------ |
| module | string  | Class name of the module. i.e. \`Display\`, \`Bookings\` etc |
| index  | integer | (default 1) Module index in the system.                      |
| method | string  | The name of the method to execute.                           |
| args   | array   | Method arguments.                                            |

{% tabs %}
{% tab title="200 Response values are always wrapped in an outer array. This ensures that method which return primatives (strings, numbers, booleans or null) still provide a valid JSON response." %}

```javascript
[]
```

{% endtab %}
{% endtabs %}

## State

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/systems/{id}/state`

Query the state exposed by a module within the system.

#### Path Parameters

| Name | Type   | Description                        |
| ---- | ------ | ---------------------------------- |
| id   | string | ID of the system the module is in. |

#### Query Parameters

| Name   | Type    | Description                                                                |
| ------ | ------- | -------------------------------------------------------------------------- |
| module | string  | Class name of the module.                                                  |
| index  | integer | (default 1) Index of the module.                                           |
| lookup | string  | A status key of interest. If included, the response filters to this value. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "foo": "abc",
  "bar": 42
}
```

{% endtab %}
{% endtabs %}

## Funcs

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/systems/{id}/funcs`

Query the behaviour exposed by a module within the system.

#### Path Parameters

| Name | Type   | Description                             |
| ---- | ------ | --------------------------------------- |
| id   | string | ID of the system that the module is in. |

#### Query Parameters

| Name   | Type    | Description                      |
| ------ | ------- | -------------------------------- |
| module | string  | Class of the module.             |
| index  | integer | (default 1) Index of the module. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "function_name": {
    "arity": 1,
    "params": [
      "string"
    ]
  }
}
```

{% endtab %}
{% endtabs %}

## Count

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/systems/{id}/count`

Counts the instances of a driver type within a system.

#### Path Parameters

| Name | Type   | Description                |
| ---- | ------ | -------------------------- |
| id   | string | ID of the system to query. |

#### Query Parameters

| Name   | Type   | Description                         |
| ------ | ------ | ----------------------------------- |
| module | string | Class name of the modules to count. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "count": 3
}
```

{% endtab %}
{% endtabs %}

## Types

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/systems/{id}/types`

Query the types of modules available within a system.

#### Path Parameters

| Name | Type   | Description                |
| ---- | ------ | -------------------------- |
| id   | string | ID of the system to query. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "Booking": 1,
  "Display": 2,
  "VidConf": 1
}
```

{% endtab %}
{% endtabs %}


# Modules

The `/modules` endpoint provides creation, management and direct interaction with modules outside of a system context. For more information on the role that modules play, see:

{% content-ref url="/pages/-LZYWTmew5\_uwlwgJJ\_P" %}
[Modules](/key-concepts/modules)
{% endcontent-ref %}

## Model

| Attribute | Type | Description |
| --------- | ---- | ----------- |

| id | `string` | A universally unique ID for this module. |
| -- | -------- | ---------------------------------------- |

| dependency\_id | `string` | ID of the driver that defines this module. |
| -------------- | -------- | ------------------------------------------ |

| control\_system\_id | `string` | ID of the system this module is bound to (logic modules only). |
| ------------------- | -------- | -------------------------------------------------------------- |

| edge\_id | `string` | ID of the preferred engine node to run on. |
| -------- | -------- | ------------------------------------------ |

| ip | `string` | IP address or resolvable hostname of the device this module connects to. |
| -- | -------- | ------------------------------------------------------------------------ |

| tls | `boolean` | True if the device communicates securely. |
| --- | --------- | ----------------------------------------- |

| udp | `boolean` | Protocol uses UDP rather that TCP. |
| --- | --------- | ---------------------------------- |

| port | `integer` | The TCP or UDP port that the associated device communicates on. |
| ---- | --------- | --------------------------------------------------------------- |

| makebreak | `boolean` | If enabled, provides an ephemeral connection that disconnects during idle periods. |
| --------- | --------- | ---------------------------------------------------------------------------------- |

| uri | `string` | The based URI of the remote service (service modules only). |
| --- | -------- | ----------------------------------------------------------- |

| custom\_name | `string` | The modules class name (`Display`, `Lighting` etc) if it should differ from the default defined in the dependency. |
| ------------ | -------- | ------------------------------------------------------------------------------------------------------------------ |

| settings | `object` | A JSON object containing module configuration. |
| -------- | -------- | ---------------------------------------------- |

| updated\_at | `integer` | Timestamp of last update. |
| ----------- | --------- | ------------------------- |

| created\_at | `integer` | Timestamp of creation. |
| ----------- | --------- | ---------------------- |

| role | `integer` | <p>The module type. One of:</p><p><code>0</code> ssh</p><p><code>1</code> device</p><p><code>2</code> service</p><p><code>3</code> logic</p> |
| ---- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |

| notes | `string` | Markdown formatted text that describes this module. |
| ----- | -------- | --------------------------------------------------- |

| connected | `boolean` | Flag for connectivity state. |
| --------- | --------- | ---------------------------- |

| running | `boolean` | Module start/stop state. |
| ------- | --------- | ------------------------ |

| ignore\_connected | `boolean` | If enabled, system metrics ignore connectivity state. |
| ----------------- | --------- | ----------------------------------------------------- |

| ignore\_startstop | `boolean` | If enabled, system level start and stop actions are ignored. This is recommended for modules shared by many systems (e.g. a lighting gateway). |
| ----------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |

## Search

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/modules`

List or search for existing modules.

#### Query Parameters

| Name           | Type    | Description                                                      |
| -------------- | ------- | ---------------------------------------------------------------- |
| q              | string  | A search filter to apply.                                        |
| limit          | integer | (default 20) Max results to return.                              |
| offset         | integer | The offset within the result set.                                |
| system\_id     | string  | Return modules associated with the specified system.             |
| dependency\_id | string  | Return modules that are an instance of the specified dependency. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "total": 1,
  "results": [
    {
      "dependency_id": "dep-wJHShR4Ffa",
      "control_system_id": null,
      "edge_id": "edge-E9vIruSZ",
      "ip": "10.45.6.3",
      "tls": false,
      "udp": false,
      "port": 8192,
      "makebreak": false,
      "uri": null,
      "custom_name": null,
      "settings": {},
      "updated_at": 1572412023,
      "created_at": 1572392714,
      "role": 1,
      "connected": true,
      "running": true,
      "notes": null,
      "ignore_connected": false,
      "ignore_startstop": false,
      "id": "mod-wJHYeHm6Yn"
    }
  ]
}
```

{% endtab %}
{% endtabs %}

Queries default to searching for any of the entered terms (words). A small query language provides the ability to structure complex queries.

| Operator    | Action                                            |                       |
| ----------- | ------------------------------------------------- | --------------------- |
| `+`         | Matches both terms.                               |                       |
| \`          | \`                                                | Matches either terms. |
| `-`         | Negates a single token.                           |                       |
| `"`         | Wraps tokens to form a phrase.                    |                       |
| `(` and `)` | Provide precedence.                               |                       |
| `~N`        | Specifies edit distance (fuzziness) after a word. |                       |
| `~N`        | Specifies slop amount (deviation) after a phrase. |                       |

## Management

## Create

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/modules`

Creates a new module.

#### Request Body

| Name                | Type    | Description                         |
| ------------------- | ------- | ----------------------------------- |
| dependency\_id      | string  |                                     |
| edge\_id            | string  |                                     |
| control\_system\_id | string  | required for logic modules          |
| ip                  | string  | required for ssh and device modules |
| udp                 | boolean |                                     |
| port                | integer |                                     |
| makebreak           | boolean |                                     |
| uri                 | string  | required for service modules        |
| custom\_name        | string  |                                     |
| settings            | object  |                                     |
| notes               | string  |                                     |
| ignore\_connected   | boolean |                                     |
| ignore\_startstop   | boolean |                                     |

{% tabs %}
{% tab title="200 Successful creations will return the full module." %}

```javascript
{
  "dependency_id": "dep-wJHShR4Ffa",
  "control_system_id": null,
  "edge_id": "edge-E9vIruSZ",
  "ip": "10.45.6.3",
  "tls": false,
  "udp": false,
  "port": 8192,
  "makebreak": false,
  "uri": null,
  "custom_name": null,
  "settings": {},
  "updated_at": 1572412023,
  "created_at": 1572412023,
  "role": 1,
  "connected": true,
  "running": true,
  "notes": null,
  "ignore_connected": false,
  "ignore_startstop": false,
  "id": "mod-wJHYeHm6Yn"
}
```

{% endtab %}

{% tab title="406 Missing or invalid module configuration." %}

```javascript
{
  "dependency_id": ["can't be blank"]
}
```

{% endtab %}
{% endtabs %}

## Retrieve

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/modules/{id}`

Retrieve all metadata associated with a module.

#### Path Parameters

| Name | Type   | Description                    |
| ---- | ------ | ------------------------------ |
| id   | string | ID of the modules to retrieve. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "dependency_id": "dep-wJHShR4Ffa",
  "control_system_id": null,
  "edge_id": "edge-E9vIruSZ",
  "ip": "10.45.6.3",
  "tls": false,
  "udp": false,
  "port": 8192,
  "makebreak": false,
  "uri": null,
  "custom_name": null,
  "settings": {},
  "updated_at": 1572412023,
  "created_at": 1572412023,
  "role": 1,
  "connected": true,
  "running": true,
  "notes": null,
  "ignore_connected": false,
  "ignore_startstop": false,
  "id": "mod-wJHYeHm6Yn"
}
```

{% endtab %}

{% tab title="404 " %}

```
```

{% endtab %}
{% endtabs %}

## Update

<mark style="color:orange;">`PUT`</mark> `https://example.com/api/control/modules/{id}`

Updates module attributes or configuration.

#### Path Parameters

| Name | Type   | Description                 |
| ---- | ------ | --------------------------- |
| id   | string | ID of the module to update. |

#### Request Body

| Name                | Type    | Description |
| ------------------- | ------- | ----------- |
| control\_system\_id | string  |             |
| edge\_id            | string  |             |
| ip                  | string  |             |
| udp                 | boolean |             |
| port                | integer |             |
| makebreak           | boolean |             |
| uri                 | string  |             |
| custom\_name        | string  |             |
| settings            | object  |             |
| notes               | string  |             |
| ignore\_connected   | boolean |             |
| ignore\_startstop   | boolean |             |

{% tabs %}
{% tab title="200 Module updated." %}

```javascript
{
  "dependency_id": "dep-wJHShR4Ffa",
  "control_system_id": null,
  "edge_id": "edge-E9vIruSZ",
  "ip": "10.45.6.3",
  "tls": false,
  "udp": false,
  "port": 8192,
  "makebreak": false,
  "uri": null,
  "custom_name": null,
  "settings": {},
  "updated_at": 1572412023,
  "created_at": 1572414543,
  "role": 1,
  "connected": true,
  "running": true,
  "notes": null,
  "ignore_connected": false,
  "ignore_startstop": false,
  "id": "mod-wJHYeHm6Yn"
}
```

{% endtab %}

{% tab title="403 The user does not have permissions to update this module." %}

```
```

{% endtab %}

{% tab title="404 The passed module ID does not exist." %}

```
```

{% endtab %}

{% tab title="406 Validation error." %}

```
```

{% endtab %}
{% endtabs %}

## Delete

<mark style="color:red;">`DELETE`</mark> `https://example.com/api/control/modules/{id}`

Removes a module. Modules that are associated with multiple systems be removed from all.

#### Path Parameters

| Name | Type   | Description                 |
| ---- | ------ | --------------------------- |
| id   | string | ID of the module to delete. |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}

{% tab title="403 " %}

```
```

{% endtab %}

{% tab title="404 " %}

```
```

{% endtab %}
{% endtabs %}

## Interaction

## Start

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/modules/{id}/start`

Starts a module on it's associated control node.

#### Path Parameters

| Name | Type   | Description                |
| ---- | ------ | -------------------------- |
| id   | string | ID of the module to start. |

{% tabs %}
{% tab title="200 Module started." %}

```
```

{% endtab %}

{% tab title="403 " %}

```
```

{% endtab %}

{% tab title="404 " %}

```
```

{% endtab %}

{% tab title="500 An error occurred that prevented the module from starting." %}

```
```

{% endtab %}
{% endtabs %}

## Stop

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/modules/{id}/stop`

Stops the module. Exposed state will still be available but will not update.

#### Path Parameters

| Name | Type   | Description               |
| ---- | ------ | ------------------------- |
| id   | string | ID of the module to stop. |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}

{% tab title="403 " %}

```
```

{% endtab %}

{% tab title="404 " %}

```
```

{% endtab %}
{% endtabs %}

## Ping

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/modules/{id}/ping`

Performs a connectivity check with the associated device or service.

#### Path Parameters

| Name | Type   | Description                |
| ---- | ------ | -------------------------- |
| id   | string | ID of the module to check. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "host": "10.45.5.2",
  "pingable": true,
  "warning": null,
  "exception": null
}
```

{% endtab %}

{% tab title="406 The module specified is a logic module." %}

```
```

{% endtab %}
{% endtabs %}

## State

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/modules/{id}/state`

Gets the state information exposed by a module.

#### Path Parameters

| Name | Type   | Description                |
| ---- | ------ | -------------------------- |
| id   | string | ID of the module to query. |

#### Query Parameters

| Name   | Type   | Description                                                              |
| ------ | ------ | ------------------------------------------------------------------------ |
| lookup | string | Status key of interest. If included, the response filters to this value. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "foo": "abc",
  "bar": 42
}
```

{% endtab %}
{% endtabs %}


# Dependencies

When a driver is loaded into Engine, it becomes a *dependency*. Dependencies are available as the blueprint from which modules are created. The `/dependencies` endpoint provide methods to discover, load and update these. For more on the role the drivers play, see:

{% content-ref url="/pages/-LZYWPvkHMhwM04lvR4r" %}
[Drivers](/key-concepts/drivers)
{% endcontent-ref %}

## Model <a href="#model" id="model"></a>

| Attribute | Type | Description |
| --------- | ---- | ----------- |

| id | `string` | The dependency's unique ID. |
| -- | -------- | --------------------------- |

| name | `string` | Human readable name for the dependency. |
| ---- | -------- | --------------------------------------- |

| class\_name | `string` | The Ruby class name of the driver. |
| ----------- | -------- | ---------------------------------- |

| module\_name | `string` | The kind of module this instantiates (e.g. `Display`). |
| ------------ | -------- | ------------------------------------------------------ |

| role | `string` | One of `ssh`, `device`, `service`, or `logic`. |
| ---- | -------- | ---------------------------------------------- |

| description | `string` | Additional information that describes the dependency. |
| ----------- | -------- | ----------------------------------------------------- |

| default | <p><code>string</code> or</p><p><code>integer</code></p> | A URL or port number that is typical for modules using this. |
| ------- | -------------------------------------------------------- | ------------------------------------------------------------ |

| ignore\_connected | `boolean` | Default state of connectivity monitoring for instances. |
| ----------------- | --------- | ------------------------------------------------------- |

| settings | `object` | A JSON object containing configuration shared by all instances. |
| -------- | -------- | --------------------------------------------------------------- |

| created\_at | `integer` | Timestamp of creation. |
| ----------- | --------- | ---------------------- |

## Search

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/dependencies`

List or search for loaded dependencies.

#### Query Parameters

| Name   | Type    | Description                         |
| ------ | ------- | ----------------------------------- |
| q      | string  | A search filter to apply.           |
| limit  | integer | (default 20) Max results to return. |
| offset | integer | The offset within the result set.   |
| role   | string  | Filter to a specific role.          |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "total": 1,
  "results": [
    {
      "name": "Pexip Management API",
      "description": "Pexip VMR management",
      "role": "service",
      "default": null,
      "class_name": "::Pexip::Management",
      "module_name": "Meeting",
      "ignore_connected": false,
      "settings": {},
      "created_at": 1562041127
    }
  ]
}
```

{% endtab %}
{% endtabs %}

Queries default to searching for any of the entered terms (words). A small query language provides the ability to structure complex queries.

| Operator    | Action                                            |                       |
| ----------- | ------------------------------------------------- | --------------------- |
| `+`         | Matches both terms.                               |                       |
| \`          | \`                                                | Matches either terms. |
| `-`         | Negates a single token.                           |                       |
| `"`         | Wraps tokens to form a phrase.                    |                       |
| `(` and `)` | Provide precedence.                               |                       |
| `~N`        | Specifies edit distance (fuzziness) after a word. |                       |
| `~N`        | Specifies slop amount (deviation) after a phrase. |                       |

## Management

## Create

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/dependencies`

Defines a new dependency. The driver this references must be available on the engine nodes running this instance of Engine. Available drivers can be listed by using the discovery endpoint.

#### Request Body

| Name              | Type    | Description |
| ----------------- | ------- | ----------- |
| name              | string  |             |
| class\_name       | string  |             |
| module\_name      | string  |             |
| role              | string  |             |
| description       | string  |             |
| default           | string  |             |
| ignore\_connected | boolean |             |
| settings          | object  |             |

{% tabs %}
{% tab title="200 Created." %}

```javascript
{
  "name": "Pexip Management API",
  "description": "Pexip VMR management",
  "role": "service",
  "default": null,
  "class_name": "::Pexip::Management",
  "module_name": "Meeting",
  "ignore_connected": false,
  "settings": {},
  "created_at": 1562041127
}
```

{% endtab %}

{% tab title="406 Validation exception." %}

```
```

{% endtab %}
{% endtabs %}

## Retrieve

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/dependencies/{id}`

Gets dependency information

#### Path Parameters

| Name | Type   | Description                       |
| ---- | ------ | --------------------------------- |
| id   | string | ID of the dependency to retrieve. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "name": "Pexip Management API",
  "description": "Pexip VMR management",
  "role": "service",
  "default": null,
  "class_name": "::Pexip::Management",
  "module_name": "Meeting",
  "ignore_connected": false,
  "settings": {},
  "created_at": 1562041127
}
```

{% endtab %}
{% endtabs %}

## Update

<mark style="color:orange;">`PUT`</mark> `https://example.com/api/control/dependencies/{id}`

Updates dependency metadata.

#### Path Parameters

| Name | Type   | Description                      |
| ---- | ------ | -------------------------------- |
| id   | string | ID of the dependency to update.i |

#### Request Body

| Name              | Type    | Description |
| ----------------- | ------- | ----------- |
| name              | string  |             |
| class\_name       | string  |             |
| module\_name      | string  |             |
| role              | string  |             |
| description       | string  |             |
| default           | string  |             |
| ignore\_connected | boolean |             |
| settings          | object  |             |

{% tabs %}
{% tab title="200 Updated." %}

```javascript
{
  "name": "Pexip Management API",
  "description": "Pexip VMR management",
  "role": "service",
  "default": null,
  "class_name": "::Pexip::Management",
  "module_name": "Meeting",
  "ignore_connected": false,
  "settings": {},
  "created_at": 1562041127
}
```

{% endtab %}

{% tab title="406 Validation error." %}

```
```

{% endtab %}
{% endtabs %}

## Delete

<mark style="color:red;">`DELETE`</mark> `https://example.com/api/control/dependencies/{id}`

Unloads a driver.

#### Path Parameters

| Name | Type   | Description                     |
| ---- | ------ | ------------------------------- |
| id   | string | ID of the dependency to remove. |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

## Interaction

## Reload

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/dependencies/{id}/reload`

Live reloads the latest version of the driver code and updates all modules using this.

#### Path Parameters

| Name | Type   | Description                     |
| ---- | ------ | ------------------------------- |
| id   | string | ID of the dependency to reload. |

{% tabs %}
{% tab title="200 Reload successful." %}

```
```

{% endtab %}

{% tab title="500 An error was detected in the updated driver and the reload is blocked. If this occurs, the previously operating driver will continue to run uninterrupted." %}

```
```

{% endtab %}
{% endtabs %}


# Zones

The `/zones` endpoint provide access to discover, create and manage zones available. For more information on the role that zones play, see:

{% content-ref url="/pages/-LZYToMgPSpYkw7lXhV9" %}
[Zones](/key-concepts/zones)
{% endcontent-ref %}

## Model <a href="#model" id="model"></a>

| Attribute   | Type      | Description                                                                      |
| ----------- | --------- | -------------------------------------------------------------------------------- |
| id          | `string`  | Unique ID the represents the zone.                                               |
| name        | `string`  | Human readable name.                                                             |
| description | `string`  | Long form description of the zone.                                               |
| tags        | `string`  | Tags that provide context for the zone use. E.g. `org`, `buidling`, `level`.     |
| settings    | `object`  | JSON object containing configuration linked to this zone.                        |
| triggers    | `array`   | List of trigger ID's to be applied to all systems that associate with this zone. |
| created\_at | `integer` | Timestamp of creation.                                                           |

## Discovery

## Search

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/zones`

List or search for zones.

#### Query Parameters

| Name   | Type    | Description                         |
| ------ | ------- | ----------------------------------- |
| q      | string  | A search filter to apply.           |
| limit  | integer | (default 20) Max results to return. |
| offset | integer | The offset within the result set.   |
| tags   | string  | Return zones of this tag only.      |

{% tabs %}
{% tab title="200 " %}

```javascript
{
  "total": 3,
  "results": [
    {
        "name": "ACA",
        "description": null,
        "tags": "org",
        "settings": {
            "discovery_info": {
                "buildings": [
                    {
                        "name": "Bourke St",
                        "zone_id": "zone-oOj4O_ijKl"
                    }
                ]
            }
        },
        "triggers": [],
        "created_at": 1555995992,
        "id": "zone-oOj2lGgszq"
    },
    {
        "name": "Bourke St",
        "description": null,
        "tags": "building",
        "settings": {
            "discovery_info": {
                "levels": [
                    {
                        "level_id": "zone-oOj57Msk19",
                        "level_name": "Level 1",
                        "map_url": "assets/maps/level_01.svg"
                    }
                ]
            }
        },
        "triggers": [],
        "created_at": 1555996004,
        "id": "zone-oOj4O_ijKl"
    },
    {
        "name": "Level 1",
        "description": null,
        "tags": "level",
        "settings": {},
        "triggers": [],
        "created_at": 1555996010,
        "id": "zone-oOj57Msk19"
    }
  ]
}
```

{% endtab %}
{% endtabs %}

Queries default to searching for any of the entered terms (words). A small query language provides the ability to structure complex queries.

| Operator    | Action                                            |                       |
| ----------- | ------------------------------------------------- | --------------------- |
| `+`         | Matches both terms.                               |                       |
| \`          | \`                                                | Matches either terms. |
| `-`         | Negates a single token.                           |                       |
| `"`         | Wraps tokens to form a phrase.                    |                       |
| `(` and `)` | Provide precedence.                               |                       |
| `~N`        | Specifies edit distance (fuzziness) after a word. |                       |
| `~N`        | Specifies slop amount (deviation) after a phrase. |                       |

## Management

## Create

<mark style="color:green;">`POST`</mark> `https://example.com/api/control/zones`

Defines a new zone.

#### Request Body

| Name        | Type   | Description |
| ----------- | ------ | ----------- |
| name        | string |             |
| description | string |             |
| tags        | string |             |
| settings    | object |             |
| triggers    | array  |             |

{% tabs %}
{% tab title="200 Zone created." %}

```javascript
{
    "name": "ACA",
    "description": null,
    "tags": "org",
    "settings": {},
    "triggers": [],
    "created_at": 1555995992,
    "id": "zone-oOj2lGgszq"
}
```

{% endtab %}
{% endtabs %}

## Retrieve

<mark style="color:blue;">`GET`</mark> `https://example.com/api/control/zones/{id}`

Lookup an existing zone.

#### Path Parameters

| Name | Type   | Description                 |
| ---- | ------ | --------------------------- |
| id   | string | ID of the zone to retrieve. |

{% tabs %}
{% tab title="200 " %}

```javascript
{
    "name": "ACA",
    "description": null,
    "tags": "org",
    "settings": {
        "discovery_info": {
            "buildings": [
                {
                    "name": "Bourke St",
                    "zone_id": "zone-oOj4O_ijKl"
                }
            ]
        }
    },
    "triggers": [],
    "created_at": 1555995992,
    "id": "zone-oOj2lGgszq"
}
```

{% endtab %}
{% endtabs %}

## Update

<mark style="color:orange;">`PUT`</mark> `https://example.com/api/control/zones/{id}`

Updates metadata associated with a zone.

#### Path Parameters

| Name | Type   | Description               |
| ---- | ------ | ------------------------- |
| id   | string | ID of the zone to update. |

#### Request Body

| Name        | Type   | Description |
| ----------- | ------ | ----------- |
| name        | string |             |
| description | string |             |
| tags        | string |             |
| settings    | object |             |
| triggers    | array  |             |

{% tabs %}
{% tab title="200 " %}

```javascript
{
    "name": "ACA",
    "description": null,
    "tags": "org",
    "settings": {
        "discovery_info": {
            "buildings": [
                {
                    "name": "Bourke St",
                    "zone_id": "zone-oOj4O_ijKl"
                }
            ]
        }
    },
    "triggers": [],
    "created_at": 1555995992,
    "id": "zone-oOj2lGgszq"
}
```

{% endtab %}
{% endtabs %}

## Delete

<mark style="color:red;">`DELETE`</mark> `https://example.com/api/control/zones/{id}`

Removes a zone.

#### Path Parameters

| Name | Type   | Description               |
| ---- | ------ | ------------------------- |
| id   | string | ID of the zone to remove. |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}


# Websocket

The `/websocket` endpoint is used to provide real-time interaction with modules running on Engine. It provides an interface to build efficient, responsive user interfaces, monitoring systems and other extensions which require live, two-way or asynchronous interaction.

If you are building browser-based experiences we have a pre-built AngularJS client library ready to go:

{% content-ref url="/pages/-Lfw5AbX6Eny9fXJEEJF" %}
[Composer](/developer-guide/user-interfaces/composer)
{% endcontent-ref %}

Otherwise, if you are working with other frameworks, or would like to build your own, read on.

## Opening the Connection

A connection to the real-time API can be established by requesting the `/control/websocket` endpoint with valid access token. The method for this will vary depending on the tooling used for your app, but as a simple example in JavaScript this can be achieved by creating a new WebSocket object:

```javascript
let socket = new WebSocket('wss://example.com/control/websocket?bearer_token=<access token>');
```

When opened, this will provide a full-duplex stream for communications.


# Commands

Command messages are the basis for interacting with the real-time API. All commands take the form of a JSON payload, and will return a JSON response.

| Attribute | Type                 | Description                                                                                                                                                                 |
| --------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id        | `number` or `string` | A unique ID to associated with the command. This is returned as part of the response. Generally an incrementing counter, however any string or numerical value may be used. |
| cmd       | `string`             | The command type. One of `bind`, `unbind`, `exec`, `debug`, or`ignore`.                                                                                                     |
| sys       | `string`             | The system ID that the command targets.                                                                                                                                     |
| mod       | `string`             | The name of the module that the command targets.                                                                                                                            |
| name      | `string`             | Method, or status variable name to be interacted with.                                                                                                                      |


# bind

The `bind` command is used to subscribe module state changes on the current connection. When creating the binding, the current state will be returned immediately.

Bindings are ephemeral, being discarded when the connection is closed.

```javascript
{
    "id": 1,                 // tracking id, to pair request and response
    "cmd": "bind",           // request type
    "sys": "sys-OEtOZqd_2J", // the system containing the target
    "mod": "Display",        // module name
    "index": 1,              // module index in the system
    "name": "power"          // status variable you are interested in
}
```

A typical response to the above request:

```javascript
{
    "id": 1,                  // tracking id
    "type": "success",        // request status (success or error)
    "meta": {                 // meta data that might be useful
        "sys": "sys-YNQ8uNfJvF",
        "mod": "Display",
        "index": 1,
        "name": "power"
    }
}
```

Once an active binding is in place, the server will push value changes.

```javascript
{
    "type": "notify",
    "value": true,
    "meta": {
        "sys": "sys-YNQ8uNfJvF",
        "mod": "Display",
        "index": 1,
        "name": "power"
    }
}
```


# unbind

The `unbind` command cancels any subscriptions to the specified state for the current connection.

```javascript
{
    "id": 2,
    "cmd": "unbind",
    "sys": "sys-YNQ8uNfJvF",
    "mod": "Display",
    "index": 1,
    "name": "power"
}
```

This will always result in a successful response

```javascript
{
    "id": 2,
    "type": "success"
}
```


# exec

The `exec` command performs an action on a module within the system. It is a remote procedure call.

```javascript
{
    "id": 3,                 // tracking id
    "cmd": "exec",           // request type
    "sys": "sys-YNQ8ucvndO", // system id
    "mod": "Display",        // module name
    "index": 2,              // module index in the system
    "name": "switch_to",     // the driver function to call
    "args": ["hdmi"]         // The function arguments (if required)
}
```

The return value of the function is returned in the response, assuming it can be serialised into JSON.

```javascript
{
    "id": 3,
    "type": "success",
    "value": ["hdmi"]
}
```

If an error was raised, the error message is returned.

```javascript
{
    "id": 3,
    "type": "error",
    "code": 3,
    "msg": "ZeroDivisionError: divided by 0"
}
```


# debug

This lowers the drivers log level to debug and forwards messages to the connection.

```javascript
{
    "id": 4,
    "cmd": "debug",
    "sys": "sys-Z6XXA-Kc_v",
    "mod": "Bookings",
    "index": 1,
    "name": "debug"
}
```

Responds with the module ID that uniquely identifies the code being monitored

```javascript
{
    "id": 4,
    "type": "success",
    "mod_id": "mod-Z6XXB1doL4",
    "meta": {
        "sys": "sys-Z6XXA-Kc_v",
        "mod": "Bookings",
        "index": 1
    }
}
```

Log messages are then sent to the browser

```javascript
{
    "type": "debug",
    "mod": "mod-Z6XXB1doL4",
    "klass": "::Some::Display",
    "level": "debug",
    "msg": "input changed to HDMI"
}
```


# ignore

The `ignore` command cancels any debug subscriptions and the log level is restored (if no other connections are debugging).

```javascript
{
    "id": 5,
    "cmd": "ignore",
    "sys": "sys-Z6XXA-Kc_v",
    "mod": "mod-Z6XXB1doL4",
    "index": null,
    "name": "ignore"
}
```

responds

```javascript
{
    "id": 5,
    "type": "success"
}
```


# Heartbeat

The client can periodically send a raw string as an application layer keep-alive.

`ping`

the server will respond

`pong`


# Errors

| Name               | Code | Description                                                                   |
| ------------------ | ---- | ----------------------------------------------------------------------------- |
| parse error        | `0`  | invalid JSON sent to the server                                               |
| bad request        | `1`  | request was missing required fields                                           |
| access denied      | `2`  | you don’t have permission to access this system, the access attempt is logged |
| request failed     | `3`  | an error was raised or a promise rejected when processing the request         |
| unknown command    | `4`  | the command type unknown, the connection is logged as suspicious              |
| system not found   | `5`  | the system does not exist                                                     |
| module not found   | `6`  | the module does not exist in the system                                       |
| unexpected failure | `7`  | a framework level error occurred (this should never happen)                   |


