Indigo Web Server Design Notes 2022.2

In Indigo v2022.2, we’re completely redesigning the web server plugin (commonly referred to as IWS). The major components of the new web server are:

We’re introducing two new APIs:

  1. Websocket API – (which the new Indigo Touch Web UI will use), and
  2. HTTP API – will share as much of the messaging construction with the websocket interface as is practical.

Both of these APIs are authenticated with HTTP Digest and API Keys (either as a query string or an Authorization header which is preferable) depending on how the user configures it in the Start Local Server dialog. We’re deprecating HTTP Basic authentication. This version of the plugin will support all the necessary endpoints for Indigo Touch as outlined below.

We also intend to deprecate the REST API (though we may reverse course if users demand it).

Python vs JavaScript

In these APIs, we’re using JSON (JavaScript Object Notation) as the message format for communicating between the Websocket and HTTP APIs and IWS. In JavaScript, an “object” definition looks (almost) exactly like a Python dictionary (and vice versa). So we may refer to an object or dictionary (dict): for the purposes of this document, they refer to the same JSON construct. A simple example, we may call this an object or a dict:

{
  "key1": "value 1",
  "key2": 2
}

We expect that there will be both Python and JavaScript users integrating our APIs, so we wanted to explicitly call this out. As a primarily Python organization, you may notice a bias towards “dict”. It’s also important to note that throughout this article, there are examples of JSON objects that contain comments marked by %%%%. These are for illustration only. The JSON specification does not support comments, and these comments will not be present in messages sent by the server and should be removed from client-side messages. <code json> { These comments can not be in the actual JSON payload and should be removed. “key1”: “value 1”, “key2”: 2 } </code> Python developers will notice the use of null in the message descriptions. This corresponds to the Python None object. Also of note are the booleans true and false, which are capitalized in Python but not in JSON. Here’s a handy cheat sheet: ^Python^JSON Equivalent^ |True |true | |False |false | |float |Number | |int |Number | |None |null | |dict |Object | |list |Array | |tuple |Array | ===== Versioning ===== Starting with Indigo 2022.2, we’re implementing an API versioning scheme. For the original URLs, we’ll implement these for Indigo Touch older than 3.0 (or maybe 2023.1 or whatever): * %%/servercommand%% * %%/serverrequest%% * %%/images/%% * %%/resolveimagepath%% * %%/refreshingimage%% Moving forward, all APIs will be versioned under the following scheme: * %%/v2/%% - this is the top level version number and will change as necessary ===== API Command Summary ===== ==== Websocket API V2 ==== * %%/v2/ws/%% - path to all websocket endpoints * %%/v2/ws/devices-feed%% - the device websocket endpoint * %%/v2/ws/variable-feed%% - the variable websocket endpoint * %%/v2/ws/action-feed%% - the action group websocket endpoint * %%/v2/ws/page-feed%% - the control websocket endpoint * %%/v2/ws/log-feed%% - the event log websocket endpoint ==== HTTP API V2 ==== * %%/v2/api/%% - path to the new API endpoints (REST replacement) * %%/v2/api/command%% - path to the endpoint when sending Indigo a command (device control, variable update, action execution). Use a POST with the message payloads described below, use an API key to authenticate - preferable as an Authorization header but also can be specified as api_key query arg. * %%/v2/api/devices%% - path to the endpoint to get a list of devices (TBD may allow specifying some kind of smaller message rather than the full device dict) * %%/v2/api/devices/12345%% - path to the endpoint to get a specific device object (the device dict described below) * %%/v2/api/variables%% - path to the endpoint to get a list of variables * %%/v2/api/variables/12345%% - path to the endpoint to specific variable object (dict to be described below) * %%/v2/api/actiongroups%% - path to the endpoint to get a list of action groups * %%/v2/api/actiongroups/12345%% - path to the endpoint to get a specific action group (dict to be described below) * %%/v2/api/controlpages%% - path to the endpoint to get a list of control pages * %%/v2/api/controlpages/12345%% - path to the endpoint to get a specific control page (dict to be described below) ==== Control Page HTML rendering ==== * %%/v2/controlpage/IDOFCONTROLPAGE%% - the actual HTML control page rendering endpoint One exception is %%/index.html%% (or no path at all) - this will always launch the web UI. For this version, it’s a Single Page Application (SPA) built using Svelte, SvelteUI, and the new websocket API. ===== Websocket API v2 ===== This is the API for websockets in the web server in Indigo v2022.2. Websockets are bidirectional TCP connections, which clients can read from and write to – much like you can a socket or serial connection. This initial API is meant to support the current Indigo Touch (iOS and Web) functionality that was previously provided via the old client API (delivered over IWS polls). It’s expected that any other client apps (like DomoticsPad) that want to present a control interface should be able to use this API to create full-fledged clients. It’s important to note that this version will not present functionality enough to build a full-fledged configuration client like the Indigo Mac client. Authentication will be accomplished via OAuth, API Key, or HTTP Digest auth. There are 5 websocket feeds that work over local IP or via the reflector: * device-feed %%ws:localhost:8176/v2/ws/device-feed%% * action-feed %%ws:yourreflector.indigodomo.net/v2/ws/action-feed%% * variable-feed %%ws:yourreflector.indigodomo.net/v2/ws/variable-feed%% * page-feed %%ws:yourreflector.indigodomo.net/v2/ws/page-feed%% * log-feed %%ws:yourreflector.indigodomo.net/v2/ws/log-feed%% Each feed (except log-feed) will send the following server CRUD messages: * add (when a new Indigo object is added) * refresh (this message will return the entire Indigo object collection or a single Indigo object if one was requested) * update (when an Indigo object changes) * delete (when the Indigo object is deleted) When a websocket connection is made, the client will receive two refresh messages: one with the entire collection of Indigo objects and the other with the entire collection of Indigo folders corresponding to the feed (device folders in the device-feed, etc). From that point on, the client will receive the add / refresh / update / delete / messages. At any time, the client can request a refresh, and Indigo will return the entire object hierarchy again (or the specific object) in a refresh message. If an %%objectId%% is specified in the refresh message, only the specified Indigo object will be retrieved rather than the full list. The log-feed is different in that it will only send add messages with the appropriate log event object defined below. ==== JavaScript Example ==== For JavaScript developers, the following functions are examples of how to use the typeVal to determine rendering colors for various aspects of the log line display: <code javascript> / * This function will return the color of the border that we draw around event * log entries based on the typeVal of the message (see the EventTypes * enumeration for details). * * @param le - the log event object * */ function getBorderColor(le) { let eType = le.typeVal; if (eType === EventTypes.Error || eType === EventTypes.Error_Client) { return 'red'; } else if (eType === EventTypes.Warning || eType === EventTypes.Warning_Client) { return 'orange'; } else if ( eType === EventTypes.Debug_Client || eType === EventTypes.Debug_Plugin || eType === EventTypes.Debug_Server) { return 'green'; } else { return theme.colors[“gray500”].value; } } / * This function will determine the color of the message or the color of the type * string in log view. * * @param le - the log event object * @param part - return the color for this part of the message (primarily the message * or the typeStr) */ function getTextColor(le, part=“message”) { let eType = le.typeVal; if (eType === EventTypes.Error || eType === EventTypes.Error_Client) { return 'red'; } else if (eType === EventTypes.Warning || eType === EventTypes.Warning_Client) { return 'orange'; } else if ( eType === EventTypes.Debug_Client || eType === EventTypes.Debug_Plugin || eType === EventTypes.Debug_Server) { return 'green'; } else { if (part === “message”) { return theme.colors.secondary; } else { return theme.colors[“gray600”].value; } } } </code> Messages from clients to the server are specific to the feed type and are described below. ===== HTTP API v2 ===== Messages sent to the HTTP API are formatted in exactly the same way as those used with the websockets API. ==== Python API Command Example ==== For Python developers, here is a sample script that sends a device toggle request to the HTTP API: <code python> import requests API_KEY = “API_TOKEN_HERE” COMMAND_URL = “http://127.0.0.1:8176/v2/api/command” headers = {“Authorization”: f“Bearer {API_KEY}”} toggle_message_target_id = 1198679701 # THE INDIGO ID OF THE TARGET DEVICE toggle_message = { “id”: “someid”, # RESERVED FOR FUTURE USE “message”: “indigo.device.toggle”, “objectId”: toggle_message_target_id, # THE INDIGO ID OF THE TARGET DEVICE “parameters”: { “delay”: 5, “duration”: 5 } } print(f“Sending toggle message to device {toggle_message_target_id}”) reply = requests.post(url=COMMAND_URL, headers=headers, json=toggle_message) print(f“Reply received: {reply.status_code}”) print(f“Reply JSON: {reply.json}) </code> ===== API Object Types ===== ==== Folder Objects ==== First, every object type (except logs) may have folders. This is an example of a folder object. It is the same for any folder in any feed (%%child%% or %%children%% is the generic name for the Indigo objects contained in the folder – device, variable, etc.) Note that folder messages are added to the feed by the server; there are no folder endpoints to manage folders from a client at this time. <code json> { “id”: 617272302, “class”: “indigo.Folder”, “name”: “Airfoil”, “remoteDisplay”: true } </code> Folder messages work in any of the websockets (except the log-feed websocket). The following are messages that you’ll receive from the server. === Example add folder message === <code json> { “message”: “add”, “objectType”: “indigo.Device.Folder”, or indigo.Variable.Folder, etc “objectDict”: {} A folder object defined above } </code> === Example update folder message === <code json> { “message”: “refresh”, we use refresh here because the folder object is small “objectType”: “indigo.Device.Folder”, or indigo.Variable.Folder, etc “objectDict”: {} A folder object defined above } </code> === Example delete folder message === <code json> { “message”: “delete”, “objectType”: “indigo.Device.Folder”, or indigo.Variable.Folder, etc “objectId”: 1234567 } </code> === Example refresh folder server message === <code json> { “message”: “refresh”, “id”: “someid”, Reserved for future use. “objectType”: “indigo.Device.Folder”, “objectDict”: {} A folder object defined above } </code> === Example refresh all folders server message === <code json> { “message”: “refresh”, “id”: “someid”, Reserved for future use. “objectType”: “indigo.Device.Folder”, or indigo.Variable.Folder, etc “list”: [] A list of folder objects defined above } </code> ===== Device Feed ===== We’re using the %%dict(device)%% functionality added a few releases back and then converting that %%dict%% to %%JSON%% for all device messages sent to clients. This contains everything about the device. <code> ws:localhost:8176/v2/ws/device-feed wss:yourreflector.indigodomo.net/v2/ws/device-feed http://localhost:8176/v2/api/devices http://yourreflector.indigodomo.net/v2/api/devices/12345 </code> ==== Server messages ==== You’ll receive full device JSON objects which will represent an Indigo device. Each device will be slightly different based on the device class and the definition (if a custom device). See the Indigo Object Model docs for device details. === Example device object (dictionary in Python) === <code json> { “address”: “3B.04.7A”, “batteryLevel”: null, “blueLevel”: null, “brightness”: 0, “buttonConfiguredCount”: 0, “buttonGroupCount”: 1, “class”: “indigo.DimmerDevice”, added class key in 2022.2 “configured”: true, “defaultBrightness”: 100, “description”: “valve”, “deviceTypeId”: ”“, “displayStateId”: “brightnessLevel”, “displayStateImageSel”: “DimmerOff”, “displayStateValRaw”: 0, “displayStateValUi”: “0”, “enabled”: true, “energyAccumBaseTime”: null, “energyAccumTimeDelta”: null, “energyAccumTotal”: null, “energyCurLevel”: null, “errorState”: ”“, “folderId”: 0, “globalProps”: { “com.indigodomo.indigoserver”: {} }, “greenLevel”: null, “id”: 1508839119, “lastChanged”: “2021-09-25T08:24:22”, “lastSuccessfulComm”: “2021-09-25T08:24:22”, “ledStates”: [], “model”: “LampLinc (dual-band)”, “name”: “Insteon Dimmer”, “onBrightensToDefaultToggle”: true, “onBrightensToLast”: false, “onState”: false, “ownerProps”: {}, “pluginId”: ”“, “pluginProps”: {}, “protocol”: “indigo.kProtocol.Insteon”, changed to the full enum in 2022.2 “redLevel”: null, “remoteDisplay”: true, “sharedProps”: {}, “states”: { “brightnessLevel”: 0, “onOffState”: false }, “subModel”: “Plug-In”, “subType”: “Plug-In”, “supportsAllLightsOnOff”: true, “supportsAllOff”: true, “supportsColor”: false, “supportsRGB”: false, “supportsRGBandWhiteSimultaneously”: false, “supportsStatusRequest”: true, “supportsTwoWhiteLevels”: false, “supportsTwoWhiteLevelsSimultaneously”: false, “supportsWhite”: false, “supportsWhiteTemperature”: false, “version”: 67, “whiteLevel”: null, “whiteLevel2”: null, “whiteTemperature”: null } </code> Here are some examples of the server messages that clients will receive on the device feed. === Example add device message === <code json> { “message”: “add”, “objectType”: “indigo.Device”, “objectDict”: {} Device object as outlined above } </code> === Example update device message === <code json> { “message”: “patch”, we use a patch rather than send the entire updated device “objectType”: “indigo.Device”, “patch”: {} A patch object - see the Object Patches below for details } </code> Device patch objects are created via the dictdiffer python module, by comparing the device dictionary (%%dict(some_device)%%) for the old device with the one for the new dictionary as they are received in the %%device_updated()%% Plugin method call. === Example delete device message === <code json> { “message”: “delete”, “objectType”: “indigo.Device”, “objectId”: 1234567 } </code> === Example refresh a single device message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.Device”, “objectDict”: {} Device object as outlined above } </code> === Example refresh all devices message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.Device”, “list”: [] a list of Device objects as outlined above } </code> ==== Client messages ==== Messages sent from clients to the server. This will most often be commands to control a device, but there are a few other commands (refresh). === Device refresh messages === These messages are sent to the server to refresh a single device, the entire device list, a single device folder, or the entire device folder list. === refresh device object(s) client message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, Specify the object type “objectType”: “indigo.Device”, Specifying an objectId is optional. If it's included the server will send a refresh message for the specified object. If no id is specified, the whole object list will be returned “objectId”: 123456 } </code> === refresh device folder(s) client message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.Device.Folder”, Specifying a objectId is optional. If it's included the server will send a refresh message for the specified folder. If no folder is specified, the whole folder list will be returned “objectId”: 123456 } </code> ==== Device command messages ==== === indigo.device === These commands can be used on any Indigo device type - it can be considered the “base” class for messages status request <code json> { Note, this won't necessarily cause a device update message - if the device didn't have any changes after the status request, there will be no updates to the device in the server, so no update message will be received. “id”: “someid”, Reserved for future use. “message”: “indigo.device.statusRequest”, “objectId”: 123456 the device id } </code> toggle <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.device.toggle”, “objectId”: 123456, the device id “parameters”: { “delay”: 5, “duration”: 10 } } </code> turn off <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.device.turnOff”, “objectId”: 123456, the device id “parameters”: { “delay”: 5, “duration”: 10 } } </code> turn on <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.device.turnOn”, “objectId”: 123456, the device id “parameters”: { “delay”: 5, “duration”: 10 } } </code> lock <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.device.lock”, “objectId”: 123456, the device id “parameters”: { “delay”: 5, “duration”: 10 } } </code> unlock <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.device.unlock”, “objectId”: 123456, the device id “parameters”: { “delay”: 5, “duration”: 10 } } </code> === indigo.dimmer === brighten <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.dimmer.brighten”, “objectId”: 123456, the device id “parameters”: { “by”: 5, “delay”: 10 } } </code> dim <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.dimmer.dim”, “objectId”: 123456, the device id “parameters”: { “by”: 5, “delay”: 10 } } </code> set brightness <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.dimmer.dim”, “objectId”: 123456, the device id “parameters”: { “value”: 50, “delay”: 10 } } </code> set brightness <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.dimmer.dim”, “objectId”: 123456, the device id “parameters”: { “value”: 50, “delay”: 10 } } </code> === indigo.iodevice === set binary output <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.iodevice.setBinaryOutput”, “objectId”: 123456, the device id “parameters”: { “index”: 5, “value”: true } } </code> === indigo.sensor === set on state <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.sensor.setOnState”, “objectId”: 123456, the device id “parameters”: { “value”: true } } </code> === indigo.speedcontrol === decrease speed index <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.speedcontrol.decreaseSpeedIndex”, “objectId”: 123456, the device id “parameters”: { “by”: 2, “delay”: 5 } } </code> increase speed index <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.speedcontrol.increaseSpeedIndex”, “objectId”: 123456, the device id “parameters”: { “by”: 2, “delay”: 5 } } </code> set speed index <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.speedcontrol.setSpeedIndex”, “objectId”: 123456, the device id “parameters”: { “value”: 2, “delay”: 5 } } </code> set speed level <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.speedcontrol.setSpeedLevel”, “objectId”: 123456, the device id “parameters”: { “value”: 50, “delay”: 5 } } </code> === indigo.sprinkler === next zone <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.sprinkler.nextZone”, “objectId”: 123456 the device id } </code> previous zone <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.sprinkler.previousZone”, “objectId”: 123456 the device id } </code> pause schedule <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.sprinkler.pause”, “objectId”: 123456 the device id } </code> resume schedule <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.sprinkler.resume”, “objectId”: 123456 the device id } </code> run schedule <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.sprinkler.run”, “objectId”: 123456, the device id “parameters”: { “schedule”: [10,15,8, 0, 0, 0, 0, 0] } } </code> stop schedule <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.sprinkler.stop”, “objectId”: 123456 the device id } </code> set active zone <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.sprinkler.setActiveZone”, “objectId”: 123456, the device id “parameters”: { “index”: 2 } } </code> === indigo.thermostat === decrease heat setpoint <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.thermostat.decreaseHeatSetpoint”, “objectId”: 123456, the device id “parameters”: { “value”: 2 } } </code> increase heat setpoint <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.thermostat.increaseHeatSetpoint”, “objectId”: 123456, the device id “parameters”: { “value”: 2 } } </code> %%%%set heat setpoint <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.thermostat.setHeatSetpoint”, “objectId”: 123456, the device id “parameters”: { “value”: 76 } } </code> decrease cool setpoint <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.thermostat.decreaseCoolSetpoint”, “objectId”: 123456, the device id “parameters”: { “delta”: 2 } } </code> increase cool setpoint <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.thermostat.increaseCoolSetpoint”, “objectId”: 123456, the device id “parameters”: { “delta”: 2 } } </code> set cool setpoint <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.thermostat.setCoolSetpoint”, “objectId”: 123456, the device id “parameters”: { “value”: 76 } } </code> set hvac mode <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.thermostat.setHvacMode”, “objectId”: 123456, the device id “parameters”: { “value”: “indigo.kHvacMode.HeatCool” } } </code> set fan mode <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.thermostat.setFanMode”, “objectId”: 123456, the device id “parameters”: { “value”: “indigo.kFanMode.AlwaysOn” } } </code> ===== Variable Feed ===== We implemented the %%dict(variable)%% functionality that parallels what we already had in place for devices. <code> ws:localhost:8176/v2/ws/variable-feed wss:yourreflector.indigodomo.net/v2/ws/variable-feed http://localhost:8176/v2/api/variables http://yourreflector.indigodomo.net/v2/api/variables/12345 </code> ==== Server messages ==== You’ll receive full variable JSON objects which will represent an Indigo variable. === Example variable object (dictionary in Python) === <code json> { “class”: “indigo.Variable”, “description”: ”“, “folderId”: 0, “globalProps”: { “com.indigodomo.indigoserver”: {} }, “id”: 345633244, “name”: “house_status”, “pluginProps”: {}, “readOnly”: false, “remoteDisplay”: true, “sharedProps”: {}, “value”: “home” } </code> Here are some examples of the server messages that clients will receive on the variable feed. === Example add variable message === <code json> { “message”: “add”, “objectType”: “indigo.Variable”, “objectDict”: {} Variable object as outlined above } </code> === Example update variable message === <code json> { “message”: “patch”, we use a patch rather than send the entire updated device “objectType”: “indigo.Variable”, “patch”: {} A patch object - see the Object Patches below for details } </code> Variable patch objects are created via the dictdiffer python module](https:%%%%dictdiffer.readthedocs.io/en/latest/), by comparing the variable dictionary (%%dict(some_variable)%%) for the old variable with the one for the new dictionary as they are received in the %%variable_updated()%% Plugin method call. === Example delete variable message === <code json> { “message”: “delete”, “objectType”: “indigo.Variable”, “objectId”: 1234567 } </code> === Example refresh a single variable message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.Variable”, “objectDict”: {} Variable object as outlined above } </code> === Example refresh all variables message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.Variable”, “list”: [] a list of Variable objects as outlined above } </code> ==== Client messages ==== Messages sent from clients to the server. This will most often be commands to update a variable, but there are a few other commands (refresh). ==== Refresh messages ==== These messages are sent to the server to refresh a single variable, the entire variable list, a single variable folder, or the entire variable folder list. === refresh variable object(s) client message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, Specify the object type “objectType”: “indigo.Variable”, Specifying an objectId is optional. If it's included the server will send a refresh message for the specified object. If no id is specified, the whole object list will be returned “objectId”: 123456 } </code> === refresh variable folder(s) client message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.Variable.Folder”, Specifying a objectId is optional. If it's included the server will send a refresh message for the specified folder. If no folder is specified, the whole folder list will be returned “objectId”: 123456 } </code> ==== Variable command messages ==== This is the only command message you can send to the variable feed. === updateValue === <code json> { Note, values passed in the parameter dictionary must be strings. You can pass in an empty string (”“) to clear the variable value. “id”: “someid”, Reserved for future use. “message”: “indigo.variable.updateValue”, “objectId”: 123456 the variable id to update “parameters”: { “value”: “Some string value” } } </code> ===== Action Group Feed ===== We implemented the %%dict(action_group)%% functionality that parallels what we already had in place for devices. <code> ws:localhost:8176/v2/ws/action-feed wss:yourreflector.indigodomo.net/v2/ws/action-feed http://localhost:8176/v2/api/actiongroups http://yourreflector.indigodomo.net/api/ws/actiongroups/12345 </code> ==== Server messages ==== You’ll receive full variable JSON objects which will represent an Indigo action group. === Example action group object (dictionary in Python) === <code json> { “class”: “indigo.ActionGroup”, “description”: ”“, “folderId”: 532526508, “globalProps”: { “com.indigodomo.indigoserver”: { “speakDelayTime”: “5”, “speakTextVariable”: “speech_string” } }, “id”: 94914463, “name”: “Movie Night”, “pluginProps”: {}, “remoteDisplay”: true, “sharedProps”: { “speakDelayTime”: “5”, “speakTextVariable”: “speech_string” } } </code> Here are some examples of the server messages that clients will receive on the action feed. === Example add action group message === <code json> { “message”: “add”, “objectType”: “indigo.ActionGroup”, “objectDict”: {} ActionGroup object as outlined above } </code> === Example update action group message === <code json> { “message”: “patch”, we use a patch rather than send the entire updated device “objectType”: “indigo.ActionGroup”, “patch”: {} A patch object - see the Object Patches below for details } </code> Variable patch objects are created via the dictdiffer python module, by comparing the action dictionary (%%dict(some_action_group)%%) for the old action with the one for the new dictionary as they are received in the %%action_group_updated()%% Plugin method call. === Example delete action group message === <code json> { “message”: “delete”, “objectType”: “indigo.ActionGroup”, “objectId”: 1234567 } </code> === Example refresh a single action group message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.ActionGroup”, “objectDict”: {} ActionGroup object as outlined above } </code> === Example refresh all action groups message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.ActionGroup”, “list”: [] a list of ActionGroup objects as outlined above } </code> ==== Client messages ==== Messages sent from clients to the server. This will most often be commands to execute an action group, but there are a few other commands (refresh). ==== Refresh messages ==== These messages are sent to the server to refresh a single action group, the entire action group list, a single action group folder, or the entire action group folder list. === refresh action group object(s) client message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, Specify the object type “objectType”: “indigo.ActionGroup”, Specifying an objectId is optional. If it's included the server will send a refresh message for the specified object. If no id is specified, the whole object list will be returned “objectId”: 123456 } </code> === refresh action group folder(s) client message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.ActionGroup.Folder”, Specifying a objectId is optional. If it's included the server will send a refresh message for the specified folder. If no folder is specified, the whole folder list will be returned “objectId”: 123456 } </code> ==== Action Group command messages ==== There is only one command message you can send to the action feed. === execute === <code json> { “id”: “someid”, Reserved for future use. “message”: “indigo.ActionGroup.execute”, “objectId”: 123456 the action group id to execute } </code> ===== Control Page Feed ===== We implemented the %%dict(control_page)%% functionality that parallels what we already had in place for devices. <code> ws:localhost:8176/v2/ws/page-feed wss:yourreflector.indigodomo.net/v2/ws/page-feed http://localhost:8176/v2/api/controlpages http://yourreflector.indigodomo.net/v2/api/controlpages/12345 </code> ==== Server messages ==== You’ll receive full variable JSON objects which will represent an Indigo control page. === Example variable object (dictionary in Python) === <code json> { “class”: “indigo.ControlPage”, “backgroundImage”: ”“, “description”: ”“, “folderId”: 0, “globalProps”: {}, “hideTabBar”: true, “id”: 963336187, “name”: “Weather Images”, “pluginProps”: {}, “remoteDisplay”: true, “sharedProps”: {} } </code> Here are some examples of the server messages that clients will receive on the variable feed. === Example add page message === <code json> { “message”: “add”, “objectType”: “indigo.ControlPage”, “objectDict”: {} ControlPage object as outlined above } </code> === Example update page message === <code json> { “message”: “patch”, we use a patch rather than send the entire updated control page “objectType”: “indigo.ControlPage”, “patch”: {} A patch object - see the Object Patches below for details } </code> Page patch objects are created via the dictdiffer python module, by comparing the device dictionary (%%dict(some_page)%%) for the old page with the one for the new dictionary as they are received in the %%control_page_updated()%% Plugin method call. === Example delete page message === <code json> { “message”: “delete”, “objectType”: “indigo.ControlPage”, “objectId”: 1234567 } </code> === Example refresh a single page message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.ControlPage”, “objectDict”: {} ControlPage object as outlined above } </code> === Example refresh all pages message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.ControlPage”, “list”: [] a list of ControlPage objects as outlined above } </code> ==== Client messages ==== Messages sent from clients to the server. This consists only of refresh requests since there are no other actions that can be taken on control pages. ==== Refresh messages ==== These messages are sent to the server to refresh a single page, the entire page list, a single page folder, or the entire page folder list. === refresh page object(s) client message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, Specify the object type “objectType”: “indigo.ControlPage”, Specifying an objectId is optional. If it's included the server will send a refresh message for the specified object. If no id is specified, the whole object list will be returned “objectId”: 123456 } </code> === refresh variable folder(s) client message === <code json> { “id”: “someid”, Reserved for future use. “message”: “refresh”, “objectType”: “indigo.ControlPage.Folder”, Specifying a objectId is optional. If it's included the server will send a refresh message for the specified folder. If no folder is specified, the whole folder list will be returned “objectId”: 123456 } </code> ===== Log Feed ===== We convert the event Indigo dictionary into a python dictionary in the %%event_log_line_received%% plugin method. <code> ws:localhost:8176/v2/ws/log-feed wss:yourreflector.indigodomo.net/v2/ws/log-feed </code> ==== Server messages ==== You’ll receive full variable JSON objects which will represent an Indigo log entry. === Example log object (dictionary in Python) === <code json> { “message”: “add”, “objectDict”: { “message”: “Stopping plugin \”Web Server 2022.2.0\” (pid 1020)“, “timeStamp”: “2022-12-01T12:03:27.759000”, “typeStr”: “Application”, “typeVal”: 0 }, “objectType”: “indigo.LogEvent” } </code> When you first open a websocket connection to the log feed, you’ll immediately receive the last 25 event log entries (individually) and any further event log entries as long as the connection is open. You only need one handler for messages coming from the log feed since they will always be objects as described above (the %%message%% key will always be %%add%%). Note that folder messages are added to the feed by the server; there are no folder endpoints to manage folders from a client at this time. Note that typeVal will be one of the following values: <code python> export enum EventTypes { Application = 0, Error, Error_Client, Warning, Warning_Client, Debug_Server, Debug_Client, Debug_Plugin, Custom, LastBaseEnumItem } </code> You can use the values above to help determine any kind of decoration you want to use when displaying or otherwise interpreting the log event. ===== Error Messaging ===== Error messages will be returned to API clients in the event that something didn’t go as planned. The structure of messages returned will depend on what went wrong and how the message was sent. Note that folder messages are added to the feed by the server; there are no folder endpoints to manage folders from a client at this time. A generic example message is provided in JSON format: == Generic Error Message == <code json> { “error”: “Some error description”, “id”: “the id sent from the client in the message, or null if there wasn't one”, “validationErrors”: { “field1”: “some error that occurred in the message with the key field1” } } </code> where the value of the JSON name (or key) %%validationErrors%% is a dictionary with a field name and a description of the error in that field that came from the client. For instance, if you pass a float %%value%% to the %%indigo.variable.updateValue%% message: == Example API Call == <code json> { “id”: “a-random-id-for-this-message”, “message”: “indigo.variable.updateValue”, “objectId”: 123456 “parameters”: { “value”: 1234.56 } } </code> You will receive the following error response: == Resulting Error Message == <code json> { “validationErrors”: { “value”: “variable values must be strings” }, “error”: “invalid command payload received, id: my-set-var-command”, “id”: “a-random-id-for-this-message” } </code> The %%error%% key is the indicator that the response is some kind of error. %%validationErrors%% is a dict that contains all the validation errors for the message, in this case the %%value%% that was passed in was not a string (it was the float %%1234.56%%). The possible keys in the error message reply %%validationErrors%% could be: - %%message%%, - %%objectId%%, - %%parameters%%, and - %%value%% based on the %%indigo.variable.updateValue%% message format (we do no validation on the %%id%% value passed in, we just pass it through, and if your message doesn’t contain one then the value will be %%null%%). Only the keys from your message that have errors will be returned. So in the example error above, only the %%value%% key had a validation error (because we passed in a float) so that was the only key returned. === Invalid JSON === One other type of error that you may receive would be if you POST a string (or something else, like XML, etc.) that’s not JSON. This will result in the following message return: == Example Invalid JSON Message == <code json> { “request_body”: “this is not valid JSON”, “error”: “invalid JSON” } </code> We will just return the entire request body since it isn’t valid JSON and we don’t know what else to do with it.