Polyglot Virtual Node Server Framework

Polyglot Virtual Node Server Framework is an application that makes it easy and quick to both develop and maintain virtual node servers for the ISY-994i home automation controller by Universal Devices Inc. Using virtual node servers, the ISY-994i is able to communicate with and control third-party devices to which the ISY-994i cannot natively connect.

Polyglot is written primarily with Python 2.7 and makes it easy to develop new Virtual Node Servers with Python 2.7. It should, however, by noted, that Virtual Node Servers may by developed using any language. Polyglot is intended to be run on a Raspberry Pi 2 Model B, but could potentially run on any ARM based machine running Linux with Python 2.7. FreeBSD, OSX, and x64 linux binaries are provided as well.

This document will document the usage of and development for Polyglot. For additional help, please reference the UDI Forum.

Usage

Installation

Polyglot is open source software provided under the MIT license. There are two ways to install Polyglot. Either a binary compiled version or the pure python (from source) version.

Version 0.0.6 Released November 30th, 2016

These methods are for linux debian x86 or raspbian on a rpi.

Get the system pre-requisites:

apt-get install python-git python-pip python3-pip libjpeg-dev

Pure Python (aka non-compiled source)

Clone the repository to a directory under the user you wish to run Polyglot. This will not run as root. eg. /home/pi/ the following line will create a directory called Polyglot so no need to do that.

git clone https://github.com/UniversalDevicesInc/Polyglot.git

Move yourself into the Polyglot directory

cd Polyglot

Install the Python required modules this does require root as we want to install them globally for Python to access.

sudo pip install -r requirements.txt

Run Polyglot!

python -m polyglot -v

This will launch Polyglot and create a directory titled config in the current directory. Polyglot will store all of its configuration and its log inside of this directory. You may specify a manual path for this directory using the command line flags.

COMPILED version (aka all-in-one)

We will need to create a home for Polyglot

mkdir Polyglot && cd $_

We still need the python pre-requisites:

sudo pip install -r https://github.com/UniversalDevicesInc/Polyglot/raw/unstable-release/requirements.txt

Download the polyglot binary for your system. One of these:

For ARM (Raspberry Pi’s) <https://github.com/UniversalDevicesInc/Polyglot/raw/unstable-release/bin/polyglot.linux.armv7l.pyz>

For x86 Linux flavors (Built with Debian sid): <https://github.com/UniversalDevicesInc/Polyglot/raw/unstable-release/bin/polyglot.linux.x86_64.pyz>

For MAC (Built on Yosemite) <https://github.com/UniversalDevicesInc/Polyglot/raw/unstable-release/bin/polyglot.osx.x86_64.pyz>

Make the file executable. Use the filename you downloaded. Example is the ARM version.

chmod 755 polyglot.linux.arm7l.pyz

Run Polyglot!

./polyglot.linux.arm7l.pyz -v

Command line flags

-h, --help            show this help message and exit
-c CONFIG_DIR, --config CONFIG_DIR
                      Polyglot configuration directory
-v, --verbose         Enable verbose logging
-vv                   Enable very verbose logging

While running in its default mode, Polyglot will log all warnings and errors. Verbose logging will include info messages. Very verbose mode adds debug messages that could be useful when developing a new node server.

OSX Instructions

Install XCODE Developer Tools (enables git) The easiest way to do this is to go to the console and type:

git

This will automatically launch the XCODE installer.

Once XCODE is installed run:

sudo easy_install pip

This installs pip 8.1.1 and now we are ready to get our binary or clone the github repository as instructed above.

Start Polyglot on Boot

If you are running the module you already have the polyglot.service file in your Polyglot root folder. If not then get it like so:

wget https://github.com/UniversalDevicesInc/Polyglot/raw/unstable-release/polyglot.service

Edit the file polyglot.service with your favorite editor. Modify WorkingDirectory to be your root Polyglot directory. eg. /home/pi/Polyglot

WorkingDirectory=/home/pi/Polyglot

Modify ExecStart to be how you start it. Full path needed. For pure Python(Non-compiled):

ExecStart=/usr/bin/python -m polyglot -v

For the compiled binary:

ExecStart=/home/pi/Polyglot/polyglot.linux-arm7l.pyz -v

Change the user to the user account that will run polyglot (NOT ROOT)

User=pi

Copy polyglot.service to /lib/systemd/system/ You need sudo as /lib/systemd/system is a system directory.

sudo cp /home/pi/Polyglot/polyglot.service /lib/systemd/system/

Enable systemctl (Make sure polyglot isn’t already running):

sudo systemctl enable polyglot
sudo systemctl start polyglot

Logging locations

Log file is found at config/polyglot.log. To watch the live action:

tail -fn 50 /home/pi/Polyglot/config/polyglot.log

User Interface

Once Polyglot is running, the user interface may be accessed by opening your favorite browser and navigating to:

http://localhost:8080

The default username and password are both admin.

If you are accessing the frontend from another machine, replace localhost with the IP Address or URL of the machine running Polyglot. If you are having trouble accessing the user interace from a remote machine, check your firewall settings.

_images/settings.png

The user interface is designed to be simple and intuitive to use. Pictured above is the settings page. Using the menu bar on the left, new node servers can be added and existing node servers may be monitored. The button on the bottom of the menu will open Polyglot’s log in a new browser window.

The user interface is fully compatible with both tablet and mobile devices.

Settings

The settings view allows the user to alter settings for Polyglot’s HTTP server as well as Polyglot’s connection to the ISY controller. It is recomended that the username and password are changed from the default. If a new different port is desired, it may be set in the Server Configuration block.

It is also necessary to set the username, password, host name, and port required for connecting to the ISY. These may be configured in the ISY Configuration block.

Adding Node Server

_images/add_ns.png

To add a node server, navigate to the Add Node Server view using the menu. This view is pictured above.

Populate this form with the details for the new node server. Select a type from all installed types using the drop down. Give the node server any name allows for easy recognition. Finally, populate the Node Server ID field with an ID that is available in the ISY. Press ADD when complete.

The node server will now be available in Polyglot. You may navigate to it using the menu. The node server view in Polyglot will show the Node Server ID, Base URL, and allow for the Profile to be downloaded.

In order to access the node server from the ISY, it must be added to the ISY. To do this, inside of the ISY console, navigate to Node Servers then Configure then the Node Server ID that was set while creating the node server. This will open a dialog that accepts all the information from the node server view. Populate this with the Profile Name and Base URL from the node server view. The User ID, Passsword, Host Name, and Port here must be the values used for connecting to Polyglot. Timeout may be left as 0, and the Isy User should be set to the appropriate user ID that was configured in Polyglot. If you are unsure, use 0.

Click the Upload Profile button and navigate to the zip file obtained from Polyglot’s node server view. Once this has been uploaded, click Ok and restart the ISY controller. Once the ISY has fully rebooted, restart the node server in Polyglot using the node server view.

Managing Node Servers

_images/view_ns.png

Clicking a Node Server in the menu will activate the node server view. In this view, there is a menu bar at the top. This menu bar will indicate is the node server is Running or Stopped. It also provides buttons to download the profile, restart the node server, or delete the node server.

Also in this view are instructions for using this node server. Different node servers may have their own instructions on how to use them in the ISY. Any open-source, third party libraries that were used for the development of the node server are also credited here.

If the node server were to crash, a red X will appear next to it in the menu and it will be indicated in the menu bar on the top of the node server view. If this happens, it is best to save the log for debugging and then restart the node server using the button in the menu bar.

Viewing Polyglot Log

There is a file icon below all the main menu items. Clicking this icon will open Polyglot’s log in a new browser window. This log file is critical for debugging issues with Polyglot.

Node Server Development

Background

Node servers in Polyglot are nothing more than stand alone processes that are managed by Polyglot. Polyglot communicates with the node servers by reading the STDOUT and STDERR streams as well as writing to the STDIN stream. STDIN and STDOUT messages are JSON formatted commands that are documented in Polyglot Node Server API.

As of Polyglot 0.0.6 MQTT is available as a communication mechanism as well. See MQTT.

File Structure

Node servers are defined in self contained folders. The name given to this folder will be the node server ID and must be unique form all other node servers. New node servers can be stored in Polyglot’s configuration directory in the folder titled node_servers. Inside of this folder, at least the following three files must exist.

  • profile.zip is the profile that must be uploaded to the ISY describing the node server. This file is documented in the ISY Node Server API documentation.
  • instructions.txt should be a file containing instructions on the use of the node server documented using markdown. The contents of this file will be formatted and displayed on the frontend.
  • server.json is the metadata used by Polyglot to identify the node server. This file is documented in the next section.

The rest of the node server’s folder should contain the code required to execute the node server and all necessary libraries with the exception of those explicitly included as part of the Polyglot distribution.

Node servers are executed in special directories in the user’s configuration directory. Each node server type is assigned its own directory. Any required inforation may be written to this directory. Keep in mind, that all running node servers of the same type will share the same directory.

Server Metadata

The server.json file in the node server source directory is a JSON formatted file that informs Polyglot of how the node server is executed as well as other important details about the node server. The file contains a dictionary formatted object with specific fields. A sample server.json is included below. It has been extracted from the Philips Hue node server.

{
    "name": "Phillips Hue",
    "docs": "https://www.universal-devices.com/",
    "type": "python",
    "executable": "hue.py",
            "configfile": "config.yaml",
            "interface": "Default",
    "description": "Connect Phillips Hue Personal Wireless Lighting system to the ISY994.",
    "notice": "\"Hue Personal Wireless Lighting\" is a trademark owned by Koninklijke Philips Electronics N.V., see www.meethue.com for more information. This Node Server is neither developed by nor endorsed by the Philips organization.",
    "credits": [
        {
            "title": "phue: A Python library for Philips Hue",
            "author": "Nathanaël Lécaudé (studioimaginaire)",
            "version": "0.9",
            "date": "May 18, 2015",
            "source": "https://github.com/studioimaginaire/phue/tree/c48845992b476f4b1de9549ea5b5277399f56581",
            "license": "https://raw.githubusercontent.com/studioimaginaire/phue/c48845992b476f4b1de9549ea5b5277399f56581/LICENSE"
        }
    ]
}

Below is a description of the required fields:

  • name is the name of the node server type as it will be displayed to the user.
  • docs is a link to an appropriate website about the node server. This value is not currently displayed anywhere.
  • type is the node server executable type. This instructs Polyglot as to how the node server should be launched. Currently, only python is accepted.
  • executable is the file that Polyglot should execute to start the node server process.
  • description is a short description of the node server that will be displayed to the user on the frontend.
  • notice contains any important notices the user might need to know.
  • credits is a list of dictionaries indicating all third party library used in the node server. Some open source projects require that they be credited some where in the project. Others do not. Either way, it is nice to give credit here. When including a third party library in your node server, ensure that it is licensed for commercial use.

In the credits list:

  • title is the title of the third party library.
  • author is the author of the third party library.
  • version is the appropriate versioning tag used to identify the third party library.
  • date is the date the third party library was either released or obtained.
  • source is a link to the library’s source code.
  • license is a link to the library’s license file. Ensure that this is a static link whose contents cannot be changed. Linking to a specific GitHub commit is handy for this.

It can be a good idea to check the formatting of this file with a JSON linter before attempting to load the node server in Polyglot. If this file cannot be read, for whatever reason, the node server will not appear in the Polyglot frontend and an error will be logged.

Python Development

A Python 2.7 compatible implimentation of the API is provided with Polyglot to assist in Node Server development. It may be easily imported as shown below. In the future, more libraries may be made available and more languages may be supported.

from polyglot import nodeserver_api

The provided Node Server Library exposes all of the ISY controller’s Node Server RESTful API as is. Data recieved by Polyglot’s web server is parsed and directed immediately to the node server process via this library. The library will also send messages back up to Polyglot to be transmitted directly to the ISY. The only exception to this rule is that node ID’s will not have the node server ID prefix prepended to them. It will also be expected that the node server will not prepend these prefixes. Polyglot will handle the node ID prefixes on behalf of the node servers.

There also exists, in the Python library, some abstract classes that may be used to ease the development of a new node server. Except in rare cases where it may not be appropriate, it is recomended that these be used.

When Python is used to develop node server, the Polyglot environment is loaded into the Python path. This environment includes the Requests library.

Python Polyglot Library

Summary

This library consists of four classes and one function to assist with node server development. The classes polyglot.nodeserver_api.NodeServer and polyglot.nodeserver_api.SimpleNodeServer and basic structures for creating a node server. The class polyglot.nodeserver_api.Node is used as an abstract class to crate custom nodes for node servers. The class polyglot.nodeserver_api.PolyglotConnector is a bottom level implementation of the API used to communicate between Polyglot and your node server. Finally, included in this library is a method decorator, polyglot.nodeserver_api.auto_request_report(), that wraps functions and methods to automatically handle report requests from the ISY.

Custom Node Types

When creating a new node server, each node type that will be controlled by the server must be defined. This abstract class may be used as a skeleton for each node type. When inheriting this class, a new method should be defined for each command that the node can perform. Additionally, the _drivers and _commands attributes should be overwritten to define the drivers and commands relevant to the node.

class polyglot.nodeserver_api.Node(parent, address, name, primary=True, manifest=None)[source]

Abstract class for representing a node in a node server.

Parameters:
  • parent (polyglot.nodeserver_api.NodeServer) – The node server that controls the node
  • address (str) – The address of the node in the ISY without the node server ID prefix
  • name (str) – The name of the node
  • primary (polyglot.nodeserver_api.Node or True if this node is the primary.) – The primary node for the device this node belongs to, or True if it’s the primary.
  • manifest (dict or None) – The node manifest saved by the node server
_drivers = {}

The drivers controlled by this node. This is a dictionary of lists. The key’s are the driver names as defined by the ISY documentation. Each list contains at least three values: the initial value, the UOM identifier, and a function that will properly format the value before assignment. The fourth value is optional – if provided, and set to “False”, the driver’s value will not be recorded in the manifest (this is useful to reduce I/O when there is no benefit to restoring the driver’s value on restart).

Insteon Dimmer Example:

_drivers = {
    'ST': [0, 51, int],
    'OL': [100, 51, int],
    'RR': [0, 32, int]
}

Pulse Time Example:

_drivers = {
    'ST': [0, 56, int, False],
}
_commands = {}

A dictionary of the commands that the node can perform. The keys of this dictionary are the names of the command. The values are functions that must be defined in the node object that perform the necessary actions and return a boolean indicating the success or failure of the command.

_report_driver_cb(driver, status_code, **kwargs)[source]

Private method - updates ISY syncronization flag based on the success/fail of the status update API call to the ISY.

add_node()[source]

Adds node to the ISY

Returns boolean:
 Indicates success or failure of node addition
get_driver(driver=None)[source]

Gets a driver’s value

Parameters:driver (str or None) – The driver to return the value for
Returns:The current value of the driver
manifest

The node’s manifest entry. Indicates the current value of each of the drivers. This is called by the node server to create the full manifest.

Type:dict
node_def_id = ''

The node’s definition ID defined in the node server’s profile

query()[source]

Abstractly queries the node. This method should generally be overwritten in development.

Returns boolean:
 Indicates success or failure of node query
report_driver(driver=None)[source]

Reports a driver’s current value to ISY

Parameters:driver (str or None) – The name of the driver to report. If None, all drivers are reported.
Returns boolean:
 Indicates success or failure to report driver value
report_isycmd(isycommand, value=None, uom=None, timeout=None, **kwargs)[source]

Sends a single command from the node server to the ISY, optionally passing in a value.

No formatting, and little validation, of the isy cmd, value, or uom is done by this simple low-level API. It is up to the caller to ensure correctness.

Parameters:
  • isycommand (str) – Name of the ISY command to send (e.g. ‘DON’)
  • value (string, float, int, or None) – (optional) The value to be sent for the command
  • uom (int or None) – (optional) - The value’s unit of measurement. If provided, overrides the uom defined for this command
  • timeout (int or None) – (optional) - the number of seconds before this command expires and is discarded
Returns boolean:
 

Indicates success or failure to queue for sending (Note: does NOT indicate if actually delivered)

run_cmd(command, **kwargs)[source]

Runs one of the node’s commands.

Parameters:
  • command (str) – The name of the command
  • kwargs (dict) – The parameters specified by the ISY in the incoming request. See the ISY Node Server documentation for more information.
Returns boolean:
 

Indicates success or failure of command

set_driver(driver, value, uom=None, report=True)[source]

Updates the value of one of the node’s drivers. This will pass the given value through the driver’s formatter before assignment.

Parameters:
  • driver (str) – The name of the driver
  • value – The new value for the driver
  • uom (int or None) – The given values unit of measurement. This should correspond to the UOM IDs used by the ISY. Refer to the ISY documentation for more information.
  • report (boolean) – Indicates if the value change should be reported to the ISY. If False, the value is changed silently.
Returns boolean:
 

Indicates success or failure to set new value

smsg(strng)[source]

Logs/sends a diagnostic/debug, informative, or error message. Individual node servers can override this method if they desire to redirect or filter these messages.

Polyglot API Implimentation

This class impliments the Polyglot API and calls registered functions when the API is invoked. This class is a singleton and will not allow itself to be initiated more than once. This class binds itself to the STDIN stream to accept commands from Polyglot.

To create a connection in your node server to Polyglot, use something similar to the following. This creates the connection, connect to Polyglot, and then waits for the Node Server’s configuration to be received. The configuration will be the first command received from Polyglot and will never be sent again after the first transmission.:

poly = PolyglotConnector()
poly.connect()
poly.wait_for_config()

Then, commands can be sent upstream to Polyglot or to the ISY by using the connector’s methods.:

poly.send_error('This is an error message. It will be in Polyglot\'s log.')
poly.add_node('node_id_1', 'NODE_DEFINITION', 'node_id_0', 'New Node')
poly.report_status('node_id_1', 'ST', value=55, uom=51)
poly.remove_node('node_id_1')

To respond to commands received from Polyglot and the ISY, handlers must be registered for events. The handlers arguments will be the parameters specified in the API for that event. This will look something like the following.:

def status_handler(node_address, request_id=None):
    print('Status Event Handler Called')

poly.listen('status', status_handler)

Now, when the ISY requests a status update from Polyglot, this function will be called. Handlers will not be called in the node server’s main thread.

class polyglot.nodeserver_api.PolyglotConnector[source]

Polyglot API implementation. Connects to Polyglot and handles node server IO.

Raises:RuntimeError
add_node(node_address, node_def_id, primary, name, timeout=None, seq=None)[source]

Adds a node to the ISY. To make this node the primary, set primary to the same value as node_address.

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘dimmer_1’)
  • node_def_id (str) – The id of the node definition to use for this node
  • primary (str) – The primary node for the device this node belongs to
  • name (str) – The name of the node
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
change_node(node_address, node_def_id, timeout=None, seq=None)[source]

Changes the node definition to use for an existing node. An example of this is may be to change a thermostat node from Fahrenheit to Celsius.

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘dimmer_1’)
  • node_def_id (str) – The id of the node definition to use for this node
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
commands = ['config', 'install', 'query', 'status', 'add_all', 'added', 'removed', 'renamed', 'enabled', 'disabled', 'cmd', 'ping', 'exit', 'params', 'result', 'statistics']

Commands that may be invoked by Polyglot

connect()[source]

Connects to Polyglot if not currently connected

connected

Indicates if the object is connected to Polyglot. Can be set to control connection with Polyglot.

Type:boolean
disconnect()[source]

Disconnects from Polyglot. Blocks the thread until IO stream is clear

exit(*args, **kwargs)[source]

Tells Polyglot that this Node Server is done.

get_params(**kwargs)[source]

Get the params from nodeserver and makes them available to the nodeserver api

install(*args, **kwargs)[source]

Abstract method to install the node server in the ISY. This has not been implemented yet and running it will raise an error.

Raises:NotImplementedError
listen(event, handler)[source]

Register an event handler. Returns True on success. Event names are defined in commands. Handlers must be callable.

Parameters:
  • event (str) – Then event name to listen for.
  • handler (callable) – The callable event handler.
logger = None

logger is initialized after the node server wait_for_config completes by the setup_log method and the log file is located in the node servers sandbox. Once wait_for_config is complete, you can call poly.logger.info(‘This variable is set to %s’, variable)

pong(*args, **kwargs)[source]

Sends pong reply to Polyglot’s ping request. This verifies that the communication between the Node Server and Polyglot is functioning.

remove_node(node_address, timeout=None, seq=None)[source]

Removes a node from the ISY. A node cannot be removed if it is the primary node for at least one other node.

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘dimmer_1’)
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
report_command(node_address, command, value=None, uom=None, timeout=None, seq=None, **kwargs)[source]

Sends a command to the ISY that may be used in programs and/or scenes. A common use of this is a physical switch that somebody turns on or off. Each time the switch is used, a command should be reported to the ISY. These are used for scenes and control conditions in ISY programs.

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘switch_1)
  • command (str) – The command to perform (e.g. ‘DON’, ‘CLISPH’, etc.)
  • value (str, int, or float) – Optional unnamed value the command used
  • uom (int or str) – Optional units of measurement of value
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
  • <pN>.<uomN> (optional) – Nth Parameter name (e.g. ‘level’) . Unit of measure of the Nth parameter (e.g. ‘seconds’, ‘uom58’)
report_request_status(request_id, success, timeout=None, seq=None)[source]

When the ISY sends a request to the node server, the request may contain a ‘requestId’ field. This indicates to the node server that when the request is completed, it must send a fail or success report for that request. This allows the ISY to in effect, have the node server synchronously perform tasks. This message must be sent after all other messages related to the task have been sent.

For example, if the ISY sends a request to query a node, all the results of the query must be sent to the ISY before a fail/success report is sent.

Parameters:
  • request_id (str) – The request ID the ISY supplied on a request to the node server.
  • success (bool) – Indicates if the request was sucessful
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
report_status(node_address, driver_control, value, uom, timeout=None, seq=None)[source]

Updates the ISY with the current value of a driver control (e.g. the current temperature, light level, etc.)

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘dimmer_1’)
  • driver_control (str) – The name of the status value (e.g. ‘ST’, ‘CLIHUM’, etc.)
  • value (str, float, or int) – The numeric status value (e.g. ‘80.5’)
  • uom (int or str) – Unit of measure of the status value
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
request_stats(*args, **kwargs)[source]

Sends a command to Polyglot to request a statistics message.

restcall(api, timeout=None, seq=None)[source]

Calls a RESTful api on the ISY. The api is the portion of the url after the “https://isy/rest/” prefix.

Parameters:
  • api (str) – The url for the api to call
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
send_config(config_data)[source]

Update the configuration in Polyglot.

Parameters:config_data (dict) – Dictionary of updated configuration
Raises:ValueError
send_error(err_str)[source]

Enqueue an error to be sent back to Polyglot.

Parameters:err_str (str) – Error text to be sent to Polyglot log
smsg(str)[source]

Logs/sends a diagnostic/debug, informative, or error message. Individual node servers can override this method if they desire to redirect or filter these messages.

uptime

The number of sections the connection with Polyglot has been alive

Type:float
wait_for_config()[source]

Blocks the thread until the configuration is received

write_nodeserver_config(default_flow_style=False, indent=4)[source]

Writes any changes to the nodeserver custom configuration file. self.nodeserver_config should be a dictionary. Refrain from calling this in a poll of any kind. Typically you won’t even have to write this unless you are storing custom data you want retrieved on the next run. Saved automatically during normal Polyglot shutdown. Returns True for success, False for failure.

Parameters:
  • default_flow_style (boolean) – YAML’s default flow style formatting. Default False
  • indent (int) – Override the default indent spaces for YAML. Default 4

Node Server Classes

class polyglot.nodeserver_api.NodeServer(poly, shortpoll=1, longpoll=30)[source]

It is generally desireable to not be required to bind to each event. For this reason, the NodeServer abstract class is available. This class should be abstracted. It binds appropriate handlers to the API events and contains a suitable run loop. It should serve as a basic structure for any node server.

Parameters:
  • poly (polyglot.nodeserver_api.PolyglotConnector) – The connected Polyglot connection
  • optional shortpoll (int) – The seconds between poll events
  • optional longpoll (int) – The second between longpoll events
add_node(node_address, node_def_id, node_primary_addr, node_name, callback=None, timeout=None, **kwargs)[source]

Add this node to the polyglot

Returns bool:True on success
long_poll()[source]

Called every longpoll seconds for less important polling.

on_add_all(request_id=None)[source]

Received add all command from ISY

Parameters:optional request_id (str) – Status request id
Returns bool:True on success
on_added(node_address, node_def_id, primary_node_address, name)[source]

Received node added report from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • node_def_id (str) – The node definition id
  • primary_node_address (str) – The node server’s primary node address
  • name (str) – The node’s friendly name
  • optional request_id (str) – Status request id
Returns bool:

True on success

on_cmd(node_address, command, value=None, uom=None, request_id=None, **kwargs)[source]

Received run command from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • command (str) – The command to run
  • value (optional) – The value of the command’s unnamed parameter
  • uom (optional) – The units of measurement for the unnamed parameter
  • optional request_id (str) – Status request id
  • <pN>.<uomN> (optional) – The value of parameter pN with units uomN
Returns bool:

True on success

on_config(**data)[source]

Received configuration data from Polyglot

Parameters:data (dict) – Configuration data
Returns bool:True on success
on_disabled(node_address)[source]

Received node disabled report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_enabled(node_address)[source]

Received node enabled report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_exit(*args, **kwargs)[source]

Polyglot has triggered a clean shutdown. Generally, this method does not need to be orwritten.

Returns bool:True on success
on_install(profile_number)[source]

Received install command from ISY

Parameters:profile_number (int) – Noder Server’s profile number
Returns bool:True on success
on_query(node_address, request_id=None)[source]

Received query command from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • optional request_id (str) – Status request id
Returns bool:

True on success

on_removed(node_address)[source]

Received node removed report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_renamed(node_address, name)[source]

Received node renamed report from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • name (str) – The node’s friendly name
Returns bool:

True on success

on_result(seq, status_code, elapsed, text, retries, reason_code, **kwargs)[source]

Handles a result message, which contains the result from a REST API call to the ISY. The result message is uniquely identified by the seq id, and will always contain at least the numeric status.

on_statistics(**kwargs)[source]

Handles a statistics message, which contains various statistics on the operation of the Polyglot server and the network communications.

on_status(node_address, request_id=None)[source]

Received status command from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • optional request_id (str) – Status request id
Returns bool:

True on success

poll()[source]

Called every shortpoll seconds to allow for updating nodes.

poly = None

The Polyglot Connection

Type:polyglot.nodeserver_api.PolyglotConnector
register_result_cb(func, **kwargs)[source]

Registers a callback function to handle a result. Returns the unique sequence ID to be passed to the function whose result is to be handled by the registered callback.

report_status(node_address, driver_control, value, uom, callback=None, timeout=None, **kwargs)[source]

Report a node status to the ISY

Returns bool:True on success
restcall(api, callback=None, timeout=None, **kwargs)[source]

Sends an asynchronous REST API call to the ISY. Returns the unique seq id (that can be used to match up the result later on after the REST call completes).

run()[source]

Run the Node Server. Exit when triggered. Generally, this method should not be overwritten.

setup()[source]

Setup the node server. All node servers must override this method and call it thru super. Currently it only sets up the reference for the logger.

smsg(strng)[source]

Logs/sends a diagnostic/debug, informative, or error message. Individual node servers can override this method if they desire to redirect or filter these messages.

tock()[source]

Called every few seconds for internal housekeeping.

class polyglot.nodeserver_api.SimpleNodeServer(poly, shortpoll=1, longpoll=30)[source]

Simple Node Server with basic functionality built-in. This class inherits from polyglot.nodeserver_api.NodeServer and is the best starting point when developing a new node server. This class implements the idea of manifests which are dictionaries that contain the relevant information about all of the nodes. The manifest gets sent to Polyglot to be saved as part of the configuration. This allows the node server to automatically recall its last known values when it is restarted.

add_node(*args, **kwargs)[source]

Add node to the Polyglot and the nodes dictionary.

Parameters:node (polyglot.nodeserver_api.Node) – The node to add
Returns boolean:
 Indicates success or failure of node addition
exist_node(address)[source]

Check if a node exists by its address.

Parameters:address (str) – The node address
Returns bool:True if the node exists
get_node(address)[source]

Get a node by its address.

Parameters:address (str) – The node address
Returns polyglot.nodeserver_api.Node:
 If found, otherwise False
nodes = OrderedDict()

Nodes registered with this node server. All nodes are automatically added by the add_node method. The keys are the node IDs while the values are instances of polyglot.nodeserver_api.Node. Classes inheriting can access this directly, but the prefered method is by using get_node or exist_node methods.

on_add_all(*args, **kwargs)[source]

Adds all nodes to the ISY. Also sends requests reponses when necessary.

Parameters:optional request_id (str) – Status request id
Returns bool:True on success
on_added(node_address, node_def_id, primary_node_address, name)[source]

Internally indicates that the specified node has been added to the ISY.

Parameters:
  • node_address (str) – The address of the node to act on
  • node_def_id (str) – The node definition id
  • primary_node_address (str) – The node server’s primary node address
  • name (str) – The node’s friendly name
  • optional request_id (str) – Status request id
Returns bool:

True on success

on_cmd(*args, **kwargs)[source]

Runs the specified command on the specified node. Also sends requests reponses when necessary.

Parameters:
  • node_address (str) – The address of the node to act on
  • command (str) – The command to run
  • value (optional) – The value of the command’s unnamed parameter
  • uom (optional) – The units of measurement for the unnamed parameter
  • optional request_id (str) – Status request id
  • <pN>.<uomN> (optional) – The value of parameter pN with units uomN
Returns bool:

True on success

on_disabled(node_address)[source]

Received node disabled report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_enabled(node_address)[source]

Received node enabled report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_exit(*args, **kwargs)[source]

Triggers a clean shut down of the node server by saving the manifest, clearing the IO, and stopping.

Returns bool:True on success
on_query(*args, **kwargs)[source]

Queries each node and reports all control values to the ISY. Also responds to report requests if necessary.

Parameters:
  • node_address (str) – The address of the node to act on
  • optional request_id (str) – Status request id
Returns bool:

True on success

on_removed(node_address)[source]

Internally indicates that a node has been removed from the ISY.

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_renamed(node_address, name)[source]

Changes the node name internally to match the ISY.

Parameters:
  • node_address (str) – The address of the node to act on
  • name (str) – The node’s friendly name
Returns bool:

True on success

on_status(*args, **kwargs)[source]

Reports the requested node’s control values to the ISY without forcing a query. Also sends requests reponses when necessary.

Parameters:
  • node_address (str) – The address of the node to act on
  • optional request_id (str) – Status request id
Returns bool:

True on success

update_config(replace_manifest=False)[source]

Updates the configuration with new node manifests and sends the configuration to Polyglot to be saved.

Parameters:replace_manifest (boolean) – replace or merge existing manifest

Helper Functions

polyglot.nodeserver_api.auto_request_report(fun)[source]

Python decorator to automate request reporting. Decorated functions must return a boolean value indicating their success or failure. It the argument request_id is passed to the decorated function, a response will be sent to the ISY. This decorator is implemented in the SimpleNodeServer.

MQTT

MQTT communication mechanism has been developed for communications between Polyglot and the nodeservers. This becomes useful so that you can connected many different nodes to your nodeserver without having the distributed code running directly on the same platform Polyglot resides on. For example, I have a system of nodes that all communicates over MQTT to keep updated, and I don’t want to wrap that entire system into Polyglot. I can develop a simple nodeserver that runs and listens to your existing MQTT to allow communications without having to completely redevelop the system as a polyglot nodeserver. This lays the framework of allowing for completly distributed nodeservers.

Usage

To use the MQTT Subsystem for communications you must include the following into your server.json of your nodeserver:

"interface": "mqtt",
"mqtt_server": "192.168.1.20",
"mqtt_port": "1883",

When Polyglot reads the server.json file and see’s the MQTT interface command it enables the MQTT subsystem for that particular nodeserver. It then sends these params along with the normal params and config to the nodeserver over STDIN, which allows for dynamic passing of the mqtt_server and mqtt_port information to the nodeserver. This is done to avoid having to set the connection information on both server.json AND your nodeserver. If the “interface” setting is missing or anything other than “mqtt” case insensitive, then the standard STDIN/STDOUT mechansims will be used, regardless of if mqtt_server and mqtt_port are provided.

When the Polyglot nodeserver manager enables the MQTT interface, it automatically connects to the MQTT broker and subscribes to the topic:

udi/polyglot/<nodeserver name>/poly

This topic is where your nodeserver will publish any commands destined for Polyglot for example the pong keepalive messages. These messages are formatted in JSON exactly like the exisiting STDOUT messages.

When creating a nodeserver that uses MQTT, you should listen on both STDIN and MQTT and respond based on which mechanism was used. MQTT is a network resource, therefore inherently it is possible to have network disruption or connection issues. When using MQTT in your nodeserver you will subscribe to the topic:

udi/polyglot/<nodeserver name>/node

There is a retianed message that you will get upon subscription to the topic above reflecting the current connection state of the Polyglot side. The json messages are listed below.:

{"connected": {}}
or
{"disconnected": {}}

This state is how you should respond to Polyglot. Using STDOUT if disconnected or MQTT if connected. A “Last Will and Testament” message is configured on the Polyglot side to always make sure the state is accurate even on catastrophic failure. The nodeserver should be configured with this same feature. An example is provided in the Node Server example section of the documentation.

MQTT Subsystem Class

class polyglot.nodeserver_manager.mqttSubsystem(parent)[source]

mqttSubsystem class instantiated if interface is mqtt in server.json

Parameters:parent (polyglot.nodeserver_manager.NodeServer) – The NodeServer object that called this function
start()[source]

The client start method. Starts the thread for the MQTT Client and publishes the connected message.

stop()[source]

The client stop method. If the client is currently connected stop the thread and disconnect. Publish the disconnected message if clean shutdown.

Python Node Server Example

The following is a brief example of some impliemented node servers written in Python. The examples included are pulled from the Philips Hue Node Server and may not be current with the actual code used in that node server and is redacted a bit for clarity, but will serve as a solid jumping off point for defining the process by which a new node server can be developed.

Node Type Definition

Some may find it easiest to start by developing all the types of nodes that the node server may be controlling. As these are being defined in code, it may be best to also define them in the file that will eventually make up the profile.zip file. Documentation for profile files is available in the ISY Virtual Node Server API documentation.

Below is the definition for a Hue color changing light.

from converters import RGB_2_xy, color_xy, color_names
from functools import partial
from polyglot.nodeserver_api import Node

def myint(value):
    """ round and convert to int """
    return int(round(float(value)))


def myfloat(value, prec=4):
    """ round and return float """
    return round(float(value), prec)

class HueColorLight(Node):
    """ Node representing Hue Color Light """

    def __init__(self, parent, address, name, lamp_id, manifest=None):
        super(HueColorLight, self).__init__(parent, address, name, manifest)
        self.lamp_id = int(lamp_id)

    def query(self):
        """ command called by ISY to query the node. """
        updates = self.parent.query_node(self.address)
        if updates:
            self.set_driver('GV1', updates[0], report=False)
            self.set_driver('GV2', updates[1], report=False)
            self.set_driver('ST', updates[2], report=False)
            self.report_driver()
            return True
        else:
            return False

    def _set_brightness(self, value=None, **kwargs):
        """ set node brightness """
        # pylint: disable=unused-argument
        if value is not None:
            value = int(value / 100. * 255)
            if value > 0:
                command = {'on': True, 'bri': value}
            else:
                command = {'on': False}
        else:
            command = {'on': True}
        return self._send_command(command)

    def _on(self, **kwargs):
        """ turn light on """
        status = kwargs.get("value")
        return self._set_brightness(value=status)

    def _off(self, **kwargs):
        """ turn light off """
        # pylint: disable=unused-argument
        return self._set_brightness(value=0)

    def _set_color_rgb(self, **kwargs):
        """ set light RGB color """
        color_r = kwargs.get('R.uom56', 0)
        color_g = kwargs.get('G.uom56', 0)
        color_b = kwargs.get('B.uom56', 0)
        (color_x, color_y) = RGB_2_xy(color_r, color_g, color_b)
        command = {'xy': [color_x, color_y], 'on': True}
        return self._send_command(command)

    def _set_color_xy(self, **kwargs):
        """ set light XY color """
        color_x = kwargs.get('X.uom56', 0)
        color_y = kwargs.get('Y.uom56', 0)
        command = {'xy': [color_x, color_y], 'on': True}
        return self._send_command(command)

    def _set_color(self, value=None, **_):
        """ set color from index """
        ind = int(value) - 1

        if ind >= len(color_names):
            return False

        cname = color_names[int(value) - 1]
        color = color_xy(cname)
        return self._set_color_xy(
            **{'X.uom56': color[0], 'Y.uom56': color[1]})

    def _send_command(self, command):
        """ generic method to send command to hue hub """
        responses = self.parent.hub.set_light(self.lamp_id, command)
        return all(
            [list(resp.keys())[0] == 'success' for resp in responses[0]])

    _drivers = {'GV1': [0, 56, myfloat], 'GV2': [0, 56, myfloat],
                'ST': [0, 51, myint]}
    """ Driver Details:
    GV1: Color X
    GV2: Color Y
    ST: Status / Brightness
    """
    _commands = {'DON': _on, 'DOF': _off,
                 'SET_COLOR_RGB': _set_color_rgb,
                 'SET_COLOR_XY': _set_color_xy,
                 'SET_COLOR': _set_color}
    node_def_id = 'COLOR_LIGHT'

As can be seen here, one method is defined for each of the commands that the node may run. The query method from the Node ABC is also overwritten to provide the desired functionality. An additional method called _send_command is also created. This is not called by the ISY directly, but is a helper used to send information to the Hue device. This method calls a method from a third party library that connects to the Hue lighting system.

Additionally, the _drivers, _command, and node_def_id properties are overwritten. This must be done by every node class as it instructs the node server classes on how to interact with this node. Custom formatters myint and myfloat are used to format the control values.

This process must be repeated for each type of node that is desired.

Node Server Creation

Once all the nodes are defined, the node server class can be created.

from polyglot.nodeserver_api import SimpleNodeServer, PolyglotConnector
# ... additional imports are redacted for clarity

class HueNodeServer(SimpleNodeServer):
    """ Phillips Hue Node Server """

    hub = None

    def setup(self):
        """ Initial node setup. """
        super(SimpleNodeServer, self).setup()
        # define nodes for settings
        manifest = self.config.get('manifest', {})
        HubSettings(self, 'hub', 'Hue Hub', manifest)
        self.connect()
        self.update_config()

    def connect(self):
        """ Connect to Phillips Hue Hub """
        # get hub settings
        hub = self.get_node('hub')
        ip_addr = '{}.{}.{}.{}'.format(
            hub.get_driver('GV1')[0], hub.get_driver('GV2')[0],
            hub.get_driver('GV3')[0], hub.get_driver('GV4')[0])

        # ... Connects to the hub and validate connection. Redacted for clarity.

    def poll(self):
        """ Poll Hue for new lights/existing lights' statuses """

        # ... Connects to Hue Hub and gets current values for lights,
        #     stores in dictionary called lights. Redacted for clarity.

        for lamp_id, data in lights.items():
            address = id_2_addr(data['uniqueid'])
            name = data['name']

            lnode = self.get_node(address)
            if not lnode:
                # Add the light to the Node Server if it doesn't already
                # exist. Sets the primary to the 'hub' Node.
                # This automatically adds the light to the ISY.
                lnode = HueColorLight(self, address,
                                     name, lamp_id,
                                      self.get_node('hub'), manifest)

            (color_x, color_y) = [round(val, 4)
                                  for val in data['state']['xy']]
            brightness = round(data['state']['bri'] / 255. * 100., 4)
            brightness = brightness if data['state']['on'] else 0
            lnode.set_driver('GV1', color_x)
            lnode.set_driver('GV2', color_y)
            lnode.set_driver('ST', brightness)

        return True

    def query_node(self, lkp_address):
        """ find specific node in api. """

        # ... Polls Hue Hub for current specified light values, and updates
        #     Node object with new values. Works very similarly to poll
        #     above. Redacted for clarity.

    def _get_api(self):
        """ get hue hub api data. """

        # ... Uses third party library to get updated Hue Hub information.
        #     Redacted for clarity.

    def long_poll(self):
        """ Save configuration every 30 seconds. """
        self.update_config()
        # In this example, the configuration is autoatically saved every
        # 30 seconds. Make sure your node server saves its configuration
        # at some point.

This example class contains four methods that are not part of the abstract class. They are setup, connect, query_node, and _get_api. These functions will probably not appear in all node servers and are very specific to this one.

However, the setup method is a good way to handle any node server setup that must be done that is specific to your node server. In this example, the primary node, the Hue Hub, is created and a connection is attempted.

This class also stores an object called hub as an attribute. This objet is an instance of a class from the third party library used. This object is the actual connection to the Hue Hub. It may be best to follow a similar method when creating node servers so that the code that handles the connection is differentiated from the code that organizes the nodes.

The poll and long_poll methods from the abstract class are used in this example. The Hue Hub sends no event stream, so it must be polled for updates. This is done in the poll method. The long_poll method is utilized to ensure the configuration data is saved consistently. These methods do not need to be manually called anywhere as they are automatically invoked from the run loop every (approximately) 1 second and 30 seconds respectively.

Starting the Node Server

Finally, your program must be able to initialize itself and begin running the node server. In Python, it will very nearly look like this.

def main():
    """ setup connection, node server, and nodes """
    poly = PolyglotConnector()
    nserver = HueNodeServer(poly)
    poly.connect()  # begin listening for Polyglot commands
    poly.wait_for_config()  # This is best practice to not start until
                            # Polyglot has begun communicating. This way,
                            # Polyglot will not miss messages sent from
                            # the node server.
    nserver.setup()  # setup method is specific to this example
    nserver.run()  # begin node server run loop


if __name__ == "__main__":
    main()

Installing the Node Server

Once all of this has been coded and all the appropriate files (documented in the last section) have been created, the node server directory can be placed in the configuration directory in a subfolder called node_servers. Polyglot should then be restarted to trigger the discovery of new node server types. If there is an issue with your node server, it will appear in the log.

Custom Node Server Configuration File

You may specify a custom configuration file in the server.json file as such:

"configfile": "customfile.yaml"

This should be placed in the top level of configuration, for example right after “executable”. If no “configfile” is specified, Polyglot will look for “config.yaml” in the root node_server folder(the same location as the server.json). If either file is found, then the contents will be loaded into a dictionary for consumption. The poly.nodeserver_config variable holds this dictionary.

Your node server may modify this dictionary as necessary and use the function

write_nodeserver_config():

This method has two parameters that are optional. The defaults are shown here:

default_flow_style = False
indent = 4

The default_flow_style is the formatting of the YAML file, look at the PyYAML documentation for specifics. The indent parameter is the number of spaces indented for each subline in the file. The default for our method is 4 because Python...

This method checks for any differences in the running configuration and the existing file, and refrains from writing if they are identical. This method is also automatically called upon a normal shutdown of Polyglot. If Polyglot shuts down abnormally, it will not record any changes that you made if you did not call the write_nodeserver_config() method.

Polyglot Node Server API

Documented here is the JSON API used for communication between Polyglot and the Node Server processes. This API will never be referenced directly by either by an end user and will rarely be referenced by a developer. It is documented here for continuity. Nearly each command and its arguments maps to a command and arguments specified in the ISY Virtual Node Server API documentation. The only exceptions are the additions of some commands necessary for Polyglot’s operation.

General Format

In general, each API message is formatted as such:

{COMMAND: {ARG_NAME_1: ARG_VALUE_1, ..., ARG_NAME_N: ARG_VALUE_N}}

All of the arguments are named. Each message ends with a new line and will contain no new lines. Each message will contain only one command. Never will multiple command be sent in the same message.

Node Server STDIN - Polyglot to Node Server

The following messages may be sent from Polyglot to the Node Server to trigger an action inside of the Node Server.

  • {‘config’: {... arbitrary data saved by the node server ...}}
    This command is the first one sent to the node server and is only sent once. The arguments dictionary will be of an arbitrary structure and will match what the Node Server had last saved.
  • {‘install’: {‘profile_number’: ...}}
    Instructs the node server to install itself with the specified profile_number.
  • {“params”: {“profile”: 8, “pgver”: “0.0.4”, “name”: “nodeservername”, “pgapiver”: “1”, “sandbox”: “/home/Polyglot/config/nodeservername”, “configfile”: “config.yaml”, “interface”: “mqtt”, “path”: “/home/Polyglot/config/node_servers/nodeservername”, “isyver”: “5.0.4”, “mqtt_server”: “pi3”, “mqtt_port”: “1883”}}
    Params passed back from Polyglot to the node server with info about the node server.
  • {‘query’: {‘node_address’: ..., ‘request_id’: ...}}
    Instructs the node server to query a node. request_id is optional.
  • {‘status’: {‘node_address’: ..., ‘request_id’: ...}}
    Requests the node server to send current node status to the ISY. request_id is optional.
  • {‘add_all’: {‘request_id’: ...}}
    Requests that the node server add all its nodes to the ISY. request_id is optional.
  • {‘added’: {‘node_address’: ..., ‘node_def_id’: ..., ‘primary_node_address’: ..., ‘name’: ...}}
    Indicates that the node has been added to the ISY.
  • {‘removed’: {‘node_address’: ...}}
    Indicates that the node has been removed from the ISY.
  • {‘renamed’: {‘node_address’: ..., ‘name’: ...}}
    Indicates that the node has been renamed in the ISY.
  • {‘enabled’: {‘node_address’: ...}}
    Indicates that the node has been enabled in the ISY.
  • {‘disabled’: {‘node_address’: ...}}
    Indicates that the node has been disabled in the ISY.
  • {‘cmd’: {‘node_address’: ..., ‘command’: ..., *’value’: ...., *’uom’: ..., *’<pn>.<uomn>’: ..., *’request_id’: ...}}
    Instructs the node server to run the specified command on the specified node. value and uom are optional and described the unnamed parameter. They will always appear together. <pn>.<uomn> will be repeated as necessary to described the unnamed parameters. They are also optional. request_id is optional.
  • {‘ping’: {}}
    This is a command from Polyglot requesting a Pong response. This is handled in the PolyglotConnector class.
  • {‘exit’: {}}
    This command is Polyglot instructing the node server to cleanly shut down.

Node Server STDOUT - Node Server to Polyglot

The following messages are accepted by Polyglot from the Node Server and will typically instruct Polyglot to send a response upstream to the ISY.

  • {‘config’: {... arbitrary data saved by the node server ...}}
    Sends configuration data to Polyglot to be saved. This data will be sent back to the Node Server, exactly as it has been sent to Polyglot, the next time the Node Server is started.
  • {‘install’: {}}
    Install the node server on the ISY. This has not been implemented yet.
  • {‘status’: {‘node_address’: ..., ‘driver_control’: ..., ‘value’: ..., ‘uom’: ...}}
    Reports a node’s driver status.
  • {‘command’: {‘node_address’: ..., ‘command’, ..., ‘value’: ...., ‘uom’: ..., ‘<pn>.<uomn>’: ...}}
    Reports that a command has been run on a node. value and uom are optional and described the unnamed parameter. They will always appear together. <pn>.<uomn> will be repeated as necessary to described the unnamed parameters. They are also optional.
  • {‘add’: {‘node_address’: ..., ‘node_def_id’: ..., ‘primary’: ..., ‘name’: ...}}
    Adds a node to the ISY.
  • {‘change’: {‘node_address’: ..., ‘node_def_id’: ...}}
    Changes the node’s definition in the ISY.
  • {‘remove’: {‘node_address’: ...}}
    Instructs the ISY to remove a node.
  • {‘request’: {‘request_id’: ..., ‘result’: ...}}
    Replies to the ISY indicating that a request has been finished either successfully or unsuccessfully. The result parameter must be a boolean indicating this.
  • {‘pong’: {}}
    The proper response to a Ping command. Must be recieved within 30 seconds of a Ping command or Polyglot assumes the Node Server has stalled and kills it. This is handled automatically in the PolyglotConnector class.
  • {‘exit’: {}}
    Indicates to Polyglot that the node server has exited and is now closing. This is the last message sent from a node server. All messages following this will be ignored. It is not guaranteed that the node server process will continue to run after this command is sent.

Node Server STDERR - Node Server to Polyglot

STDERR messages have no structured formatting, they are free flowing text. Anything recieved by Polyglot through this stream will not be processed and will be immediately logged as an error. Do not send personal information in error messages as they will always be logged regardless of the log verbosity.

Polyglot Methods and Classes

Module

These are the classes and methods from the Polyglot module.

Config Manager

The configuration management module for Polyglot

class polyglot.config_manager.ConfigManager(config_dir)[source]

Configuration Manager class

Parameters:config_dir – The configuration directory to use
decode(encoded)[source]

Decode passwords and return decoded

encode()[source]

Encode passwords and return an encoded copy

make_path(*args)[source]

make a path to a file in the config directory

nodeserver_sandbox(url_base)[source]

make and return a sandbox directory for a node server.

read()[source]

Reads configuration file

update(*args, **kwargs)[source]

Update the configuration with values in dictionary

write()[source]

Writes configuration file

Core

The primary functionality for the Polyglot application

class polyglot.core.Polyglot(config_dir)[source]

Core class

Parameters:config_dir – Directory where configuration is stored
Variables:config – Dictionary of current config
get_log()[source]

Read and return the log file contents.

run()[source]

Run the Polyglot server

setup()[source]

Setup Polyglot to resume the last known state

stop(*args)[source]

Stops the Polyglot server

update_config()[source]

Signal Polyglot to fetch updated configuration.

Nodeserver API

This library consists of four classes and one function to assist with node server development. The classes polyglot.nodeserver_api.NodeServer and polyglot.nodeserver_api.SimpleNodeServer and basic structures for creating a node server. The class polyglot.nodeserver_api.Node is used as an abstract class to crate custom nodes for node servers. The class polyglot.nodeserver_api.PolyglotConnector is a bottom level implementation of the API used to communicate between Polyglot and your node server. Finally, included in this library is a method decorator, polyglot.nodeserver_api.auto_request_report(), that wraps functions and methods to automatically handle report requests from the ISY.

class polyglot.nodeserver_api.Node(parent, address, name, primary=True, manifest=None)[source]

Abstract class for representing a node in a node server.

Parameters:
  • parent (polyglot.nodeserver_api.NodeServer) – The node server that controls the node
  • address (str) – The address of the node in the ISY without the node server ID prefix
  • name (str) – The name of the node
  • primary (polyglot.nodeserver_api.Node or True if this node is the primary.) – The primary node for the device this node belongs to, or True if it’s the primary.
  • manifest (dict or None) – The node manifest saved by the node server
_drivers = {}

The drivers controlled by this node. This is a dictionary of lists. The key’s are the driver names as defined by the ISY documentation. Each list contains at least three values: the initial value, the UOM identifier, and a function that will properly format the value before assignment. The fourth value is optional – if provided, and set to “False”, the driver’s value will not be recorded in the manifest (this is useful to reduce I/O when there is no benefit to restoring the driver’s value on restart).

Insteon Dimmer Example:

_drivers = {
    'ST': [0, 51, int],
    'OL': [100, 51, int],
    'RR': [0, 32, int]
}

Pulse Time Example:

_drivers = {
    'ST': [0, 56, int, False],
}
_commands = {}

A dictionary of the commands that the node can perform. The keys of this dictionary are the names of the command. The values are functions that must be defined in the node object that perform the necessary actions and return a boolean indicating the success or failure of the command.

add_node()[source]

Adds node to the ISY

Returns boolean:
 Indicates success or failure of node addition
get_driver(driver=None)[source]

Gets a driver’s value

Parameters:driver (str or None) – The driver to return the value for
Returns:The current value of the driver
manifest

The node’s manifest entry. Indicates the current value of each of the drivers. This is called by the node server to create the full manifest.

Type:dict
node_def_id = ''

The node’s definition ID defined in the node server’s profile

query()[source]

Abstractly queries the node. This method should generally be overwritten in development.

Returns boolean:
 Indicates success or failure of node query
report_driver(driver=None)[source]

Reports a driver’s current value to ISY

Parameters:driver (str or None) – The name of the driver to report. If None, all drivers are reported.
Returns boolean:
 Indicates success or failure to report driver value
report_isycmd(isycommand, value=None, uom=None, timeout=None, **kwargs)[source]

Sends a single command from the node server to the ISY, optionally passing in a value.

No formatting, and little validation, of the isy cmd, value, or uom is done by this simple low-level API. It is up to the caller to ensure correctness.

Parameters:
  • isycommand (str) – Name of the ISY command to send (e.g. ‘DON’)
  • value (string, float, int, or None) – (optional) The value to be sent for the command
  • uom (int or None) – (optional) - The value’s unit of measurement. If provided, overrides the uom defined for this command
  • timeout (int or None) – (optional) - the number of seconds before this command expires and is discarded
Returns boolean:
 

Indicates success or failure to queue for sending (Note: does NOT indicate if actually delivered)

run_cmd(command, **kwargs)[source]

Runs one of the node’s commands.

Parameters:
  • command (str) – The name of the command
  • kwargs (dict) – The parameters specified by the ISY in the incoming request. See the ISY Node Server documentation for more information.
Returns boolean:
 

Indicates success or failure of command

set_driver(driver, value, uom=None, report=True)[source]

Updates the value of one of the node’s drivers. This will pass the given value through the driver’s formatter before assignment.

Parameters:
  • driver (str) – The name of the driver
  • value – The new value for the driver
  • uom (int or None) – The given values unit of measurement. This should correspond to the UOM IDs used by the ISY. Refer to the ISY documentation for more information.
  • report (boolean) – Indicates if the value change should be reported to the ISY. If False, the value is changed silently.
Returns boolean:
 

Indicates success or failure to set new value

smsg(strng)[source]

Logs/sends a diagnostic/debug, informative, or error message. Individual node servers can override this method if they desire to redirect or filter these messages.

class polyglot.nodeserver_api.NodeServer(poly, shortpoll=1, longpoll=30)[source]

It is generally desireable to not be required to bind to each event. For this reason, the NodeServer abstract class is available. This class should be abstracted. It binds appropriate handlers to the API events and contains a suitable run loop. It should serve as a basic structure for any node server.

Parameters:
  • poly (polyglot.nodeserver_api.PolyglotConnector) – The connected Polyglot connection
  • optional shortpoll (int) – The seconds between poll events
  • optional longpoll (int) – The second between longpoll events
add_node(node_address, node_def_id, node_primary_addr, node_name, callback=None, timeout=None, **kwargs)[source]

Add this node to the polyglot

Returns bool:True on success
long_poll()[source]

Called every longpoll seconds for less important polling.

on_add_all(request_id=None)[source]

Received add all command from ISY

Parameters:optional request_id (str) – Status request id
Returns bool:True on success
on_added(node_address, node_def_id, primary_node_address, name)[source]

Received node added report from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • node_def_id (str) – The node definition id
  • primary_node_address (str) – The node server’s primary node address
  • name (str) – The node’s friendly name
  • optional request_id (str) – Status request id
Returns bool:

True on success

on_cmd(node_address, command, value=None, uom=None, request_id=None, **kwargs)[source]

Received run command from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • command (str) – The command to run
  • value (optional) – The value of the command’s unnamed parameter
  • uom (optional) – The units of measurement for the unnamed parameter
  • optional request_id (str) – Status request id
  • <pN>.<uomN> (optional) – The value of parameter pN with units uomN
Returns bool:

True on success

on_config(**data)[source]

Received configuration data from Polyglot

Parameters:data (dict) – Configuration data
Returns bool:True on success
on_disabled(node_address)[source]

Received node disabled report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_enabled(node_address)[source]

Received node enabled report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_exit(*args, **kwargs)[source]

Polyglot has triggered a clean shutdown. Generally, this method does not need to be orwritten.

Returns bool:True on success
on_install(profile_number)[source]

Received install command from ISY

Parameters:profile_number (int) – Noder Server’s profile number
Returns bool:True on success
on_query(node_address, request_id=None)[source]

Received query command from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • optional request_id (str) – Status request id
Returns bool:

True on success

on_removed(node_address)[source]

Received node removed report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_renamed(node_address, name)[source]

Received node renamed report from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • name (str) – The node’s friendly name
Returns bool:

True on success

on_result(seq, status_code, elapsed, text, retries, reason_code, **kwargs)[source]

Handles a result message, which contains the result from a REST API call to the ISY. The result message is uniquely identified by the seq id, and will always contain at least the numeric status.

on_statistics(**kwargs)[source]

Handles a statistics message, which contains various statistics on the operation of the Polyglot server and the network communications.

on_status(node_address, request_id=None)[source]

Received status command from ISY

Parameters:
  • node_address (str) – The address of the node to act on
  • optional request_id (str) – Status request id
Returns bool:

True on success

poll()[source]

Called every shortpoll seconds to allow for updating nodes.

poly = None

The Polyglot Connection

Type:polyglot.nodeserver_api.PolyglotConnector
register_result_cb(func, **kwargs)[source]

Registers a callback function to handle a result. Returns the unique sequence ID to be passed to the function whose result is to be handled by the registered callback.

report_status(node_address, driver_control, value, uom, callback=None, timeout=None, **kwargs)[source]

Report a node status to the ISY

Returns bool:True on success
restcall(api, callback=None, timeout=None, **kwargs)[source]

Sends an asynchronous REST API call to the ISY. Returns the unique seq id (that can be used to match up the result later on after the REST call completes).

run()[source]

Run the Node Server. Exit when triggered. Generally, this method should not be overwritten.

setup()[source]

Setup the node server. All node servers must override this method and call it thru super. Currently it only sets up the reference for the logger.

smsg(strng)[source]

Logs/sends a diagnostic/debug, informative, or error message. Individual node servers can override this method if they desire to redirect or filter these messages.

tock()[source]

Called every few seconds for internal housekeeping.

class polyglot.nodeserver_api.PolyglotConnector[source]

Polyglot API implementation. Connects to Polyglot and handles node server IO.

Raises:RuntimeError
add_node(node_address, node_def_id, primary, name, timeout=None, seq=None)[source]

Adds a node to the ISY. To make this node the primary, set primary to the same value as node_address.

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘dimmer_1’)
  • node_def_id (str) – The id of the node definition to use for this node
  • primary (str) – The primary node for the device this node belongs to
  • name (str) – The name of the node
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
change_node(node_address, node_def_id, timeout=None, seq=None)[source]

Changes the node definition to use for an existing node. An example of this is may be to change a thermostat node from Fahrenheit to Celsius.

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘dimmer_1’)
  • node_def_id (str) – The id of the node definition to use for this node
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
commands = ['config', 'install', 'query', 'status', 'add_all', 'added', 'removed', 'renamed', 'enabled', 'disabled', 'cmd', 'ping', 'exit', 'params', 'result', 'statistics']

Commands that may be invoked by Polyglot

connect()[source]

Connects to Polyglot if not currently connected

connected

Indicates if the object is connected to Polyglot. Can be set to control connection with Polyglot.

Type:boolean
disconnect()[source]

Disconnects from Polyglot. Blocks the thread until IO stream is clear

exit(*args, **kwargs)[source]

Tells Polyglot that this Node Server is done.

get_params(**kwargs)[source]

Get the params from nodeserver and makes them available to the nodeserver api

install(*args, **kwargs)[source]

Abstract method to install the node server in the ISY. This has not been implemented yet and running it will raise an error.

Raises:NotImplementedError
listen(event, handler)[source]

Register an event handler. Returns True on success. Event names are defined in commands. Handlers must be callable.

Parameters:
  • event (str) – Then event name to listen for.
  • handler (callable) – The callable event handler.
logger = None

logger is initialized after the node server wait_for_config completes by the setup_log method and the log file is located in the node servers sandbox. Once wait_for_config is complete, you can call poly.logger.info(‘This variable is set to %s’, variable)

pong(*args, **kwargs)[source]

Sends pong reply to Polyglot’s ping request. This verifies that the communication between the Node Server and Polyglot is functioning.

remove_node(node_address, timeout=None, seq=None)[source]

Removes a node from the ISY. A node cannot be removed if it is the primary node for at least one other node.

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘dimmer_1’)
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
report_command(node_address, command, value=None, uom=None, timeout=None, seq=None, **kwargs)[source]

Sends a command to the ISY that may be used in programs and/or scenes. A common use of this is a physical switch that somebody turns on or off. Each time the switch is used, a command should be reported to the ISY. These are used for scenes and control conditions in ISY programs.

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘switch_1)
  • command (str) – The command to perform (e.g. ‘DON’, ‘CLISPH’, etc.)
  • value (str, int, or float) – Optional unnamed value the command used
  • uom (int or str) – Optional units of measurement of value
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
  • <pN>.<uomN> (optional) – Nth Parameter name (e.g. ‘level’) . Unit of measure of the Nth parameter (e.g. ‘seconds’, ‘uom58’)
report_request_status(request_id, success, timeout=None, seq=None)[source]

When the ISY sends a request to the node server, the request may contain a ‘requestId’ field. This indicates to the node server that when the request is completed, it must send a fail or success report for that request. This allows the ISY to in effect, have the node server synchronously perform tasks. This message must be sent after all other messages related to the task have been sent.

For example, if the ISY sends a request to query a node, all the results of the query must be sent to the ISY before a fail/success report is sent.

Parameters:
  • request_id (str) – The request ID the ISY supplied on a request to the node server.
  • success (bool) – Indicates if the request was sucessful
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
report_status(node_address, driver_control, value, uom, timeout=None, seq=None)[source]

Updates the ISY with the current value of a driver control (e.g. the current temperature, light level, etc.)

Parameters:
  • node_address (str) – The full address of the node (e.g. ‘dimmer_1’)
  • driver_control (str) – The name of the status value (e.g. ‘ST’, ‘CLIHUM’, etc.)
  • value (str, float, or int) – The numeric status value (e.g. ‘80.5’)
  • uom (int or str) – Unit of measure of the status value
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
request_stats(*args, **kwargs)[source]

Sends a command to Polyglot to request a statistics message.

restcall(api, timeout=None, seq=None)[source]

Calls a RESTful api on the ISY. The api is the portion of the url after the “https://isy/rest/” prefix.

Parameters:
  • api (str) – The url for the api to call
  • timeout (str, float, or int) – (optional) timeout (seconds) for REST call to ISY
  • seq (str or int) – (optional) set to unique id if result callback desired
send_config(config_data)[source]

Update the configuration in Polyglot.

Parameters:config_data (dict) – Dictionary of updated configuration
Raises:ValueError
send_error(err_str)[source]

Enqueue an error to be sent back to Polyglot.

Parameters:err_str (str) – Error text to be sent to Polyglot log
smsg(str)[source]

Logs/sends a diagnostic/debug, informative, or error message. Individual node servers can override this method if they desire to redirect or filter these messages.

uptime

The number of sections the connection with Polyglot has been alive

Type:float
wait_for_config()[source]

Blocks the thread until the configuration is received

write_nodeserver_config(default_flow_style=False, indent=4)[source]

Writes any changes to the nodeserver custom configuration file. self.nodeserver_config should be a dictionary. Refrain from calling this in a poll of any kind. Typically you won’t even have to write this unless you are storing custom data you want retrieved on the next run. Saved automatically during normal Polyglot shutdown. Returns True for success, False for failure.

Parameters:
  • default_flow_style (boolean) – YAML’s default flow style formatting. Default False
  • indent (int) – Override the default indent spaces for YAML. Default 4
class polyglot.nodeserver_api.SimpleNodeServer(poly, shortpoll=1, longpoll=30)[source]

Simple Node Server with basic functionality built-in. This class inherits from polyglot.nodeserver_api.NodeServer and is the best starting point when developing a new node server. This class implements the idea of manifests which are dictionaries that contain the relevant information about all of the nodes. The manifest gets sent to Polyglot to be saved as part of the configuration. This allows the node server to automatically recall its last known values when it is restarted.

add_node(*args, **kwargs)[source]

Add node to the Polyglot and the nodes dictionary.

Parameters:node (polyglot.nodeserver_api.Node) – The node to add
Returns boolean:
 Indicates success or failure of node addition
exist_node(address)[source]

Check if a node exists by its address.

Parameters:address (str) – The node address
Returns bool:True if the node exists
get_node(address)[source]

Get a node by its address.

Parameters:address (str) – The node address
Returns polyglot.nodeserver_api.Node:
 If found, otherwise False
nodes = OrderedDict()

Nodes registered with this node server. All nodes are automatically added by the add_node method. The keys are the node IDs while the values are instances of polyglot.nodeserver_api.Node. Classes inheriting can access this directly, but the prefered method is by using get_node or exist_node methods.

on_add_all(*args, **kwargs)[source]

Adds all nodes to the ISY. Also sends requests reponses when necessary.

Parameters:optional request_id (str) – Status request id
Returns bool:True on success
on_added(node_address, node_def_id, primary_node_address, name)[source]

Internally indicates that the specified node has been added to the ISY.

Parameters:
  • node_address (str) – The address of the node to act on
  • node_def_id (str) – The node definition id
  • primary_node_address (str) – The node server’s primary node address
  • name (str) – The node’s friendly name
  • optional request_id (str) – Status request id
Returns bool:

True on success

on_cmd(*args, **kwargs)[source]

Runs the specified command on the specified node. Also sends requests reponses when necessary.

Parameters:
  • node_address (str) – The address of the node to act on
  • command (str) – The command to run
  • value (optional) – The value of the command’s unnamed parameter
  • uom (optional) – The units of measurement for the unnamed parameter
  • optional request_id (str) – Status request id
  • <pN>.<uomN> (optional) – The value of parameter pN with units uomN
Returns bool:

True on success

on_disabled(node_address)[source]

Received node disabled report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_enabled(node_address)[source]

Received node enabled report from ISY

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_exit(*args, **kwargs)[source]

Triggers a clean shut down of the node server by saving the manifest, clearing the IO, and stopping.

Returns bool:True on success
on_query(*args, **kwargs)[source]

Queries each node and reports all control values to the ISY. Also responds to report requests if necessary.

Parameters:
  • node_address (str) – The address of the node to act on
  • optional request_id (str) – Status request id
Returns bool:

True on success

on_removed(node_address)[source]

Internally indicates that a node has been removed from the ISY.

Parameters:node_address (str) – The address of the node to act on
Returns bool:True on success
on_renamed(node_address, name)[source]

Changes the node name internally to match the ISY.

Parameters:
  • node_address (str) – The address of the node to act on
  • name (str) – The node’s friendly name
Returns bool:

True on success

on_status(*args, **kwargs)[source]

Reports the requested node’s control values to the ISY without forcing a query. Also sends requests reponses when necessary.

Parameters:
  • node_address (str) – The address of the node to act on
  • optional request_id (str) – Status request id
Returns bool:

True on success

update_config(replace_manifest=False)[source]

Updates the configuration with new node manifests and sends the configuration to Polyglot to be saved.

Parameters:replace_manifest (boolean) – replace or merge existing manifest
polyglot.nodeserver_api.auto_request_report(fun)[source]

Python decorator to automate request reporting. Decorated functions must return a boolean value indicating their success or failure. It the argument request_id is passed to the decorated function, a response will be sent to the ISY. This decorator is implemented in the SimpleNodeServer.

Nodeserver Helpers

Helper functions for managing Node Servers.

polyglot.nodeserver_helpers.available_servers()[source]

list all available elements

polyglot.nodeserver_helpers.dirs_in(path, include_path=True)[source]

Returns a list of directories in a path.

Parameters:path – The path to search.
polyglot.nodeserver_helpers.get_path(platform)[source]

Find Node Server Platform path

polyglot.nodeserver_helpers.ignore_dir(path, dir_name)[source]

Deterermine if a directory should be ignored.

Nodeserver Manager

The element management module for Polyglot

class polyglot.nodeserver_manager.NodeServer(pglot, ns_platform, profile_number, nstype, nsexe, nsname, config, sandbox, configfile=None, interface=None, mqtt_server=None, mqtt_port=None)[source]

Node Server Class

alive

Indicates if the Node Server is running.

definition

Return the server defintion from server.json

kill()[source]

Kill the node server process.

profile

Return the profile.zip data.

responding

Indicates if the Node Server is responding.

restart()[source]

restart the nodeserver

send_addall(request_id=None)[source]

Send add all request to Node Server.

send_added(node_address, node_def_id, primary_node_address, name)[source]

Send node added confirmation to Node Server.

send_cmd(node_address, command, value=None, uom=None, request_id=None, **kwargs)[source]

Send run command signal to Node Server.

send_config()[source]

Send configuration to Node Server.

send_disabled(node_address)[source]

Send node disabled confirmation to Node Server.

send_enabled(node_address)[source]

Send node enabled confirmation to Node Server.

send_exit()[source]

Send exit command to the Node Server.

send_install(profile_number=None)[source]

Send install command to Node Server.

send_params()[source]

Send parameters to Node Server.

send_ping()[source]

Send Ping request to the Node Server.

send_query(node_address, request_id=None)[source]

Send query command to Node Server.

send_removed(node_address)[source]

Send node removed confirmation to Node Server.

send_renamed(node_address, name)[source]

Send node renamed confirmation to Node Server.

send_status(node_address, request_id=None)[source]

Send status request to Node Server.

start()[source]

start the node server

class polyglot.nodeserver_manager.NodeServerManager(pglot)[source]

Node Server Manager

Parameters:

pglot – The parent Polyglot object

Variables:
  • pglot – The parent Polyglot object
  • servers – Dictionary of active Node Servers
config

Node Server configuration block.

delete(base_url)[source]

Remove a server from Polyglot.

load()[source]

Initial load of the active Node Servers

start_server(ns_platform, profile_number, nsname=None, base=None, config=None)[source]

starts a node server

unload()[source]

Unload all node servers

class polyglot.nodeserver_manager.mqttSubsystem(parent)[source]

mqttSubsystem class instantiated if interface is mqtt in server.json

Parameters:parent (polyglot.nodeserver_manager.NodeServer) – The NodeServer object that called this function
start()[source]

The client start method. Starts the thread for the MQTT Client and publishes the connected message.

stop()[source]

The client stop method. If the client is currently connected stop the thread and disconnect. Publish the disconnected message if clean shutdown.

polyglot.nodeserver_manager.random_string(length)[source]

Generate a random string of uppercase, lowercase, and digits

Utils

Generic utilities used by Polyglot.

class polyglot.utils.AsyncFileReader(fd, handler)[source]

Helper class to implement asynchronous reading of a file in a separate thread. Pushes read lines on a queue to be consumed in another thread.

Source: http://stefaanlippens.net/python-asynchronous-subprocess-pipe-reading

run()[source]

The body of the thread: read lines and put them on the queue.

class polyglot.utils.LockQueue(*args, **kwargs)[source]

Python queue with a locking utility

put(*args, **kwargs)[source]

Put item into queue

put_nowait(*args, **kwargs)[source]

Put item into queue without waiting

Optional Components

Docker

These are the instructions for Docker configuration provided by UDI forum and Polyglot contributor i814u2.

Original Threads:

http://forum.universal-devices.com/topic/19807-%20polyglot-docker-%20image/#entry187776 http://forum.universal-devices.com/topic/19807-%20polyglot-docker-%20image/page-2

Linux x86_64

Create a file named “Dockerfile” (case-sensitive) and place these commands in there:

ARG binfile=polyglot.linux.x86_64.pyz
ARG user=polyglot
ARG group=polyglot
ARG uid=1000
ARG gid=1000


RUN addgroup -g ${gid} ${group} \
    && adduser -h /home/${user} -s /bin/sh -G ${group} -D -u ${uid} ${user}


WORKDIR /home/${user}
COPY custom.txt .


RUN pip install --upgrade pip \
&& pip install -r https://raw.githubusercontent.com/UniversalDevicesInc/Polyglot/unstable-release/requirements.txt \
&& while read line; do $line; done < custom.txt


RUN wget https://github.com/UniversalDevicesInc/Polyglot/raw/unstable-release/bin/${binfile}-P Polyglot \
&& chown -R ${user}:${group} /home/${user} \
&& chmod 755 /home/${user}/Polyglot/${binfile}


USER ${user}
WORKDIR /home/${user}/Polyglot
ENTRYPOINT ["./polyglot.linux.x86_64.pyz","-v"]

Create a file named “custom.txt” and add whatever python modules and node servers you’d like installed. Here is the example I have for adding Sonos and Nest:

pip install soco
pip install python-nest
git clone https://github.com/Einstein42/sonos-polyglot Polyglot/config/node_servers/sonos-polyglot
git clone https://github.com/Einstein42/nest-polyglot Polyglot/config/node_servers/nest-polyglot

Create a folder to store your config:

mkdir -p ~/docker/polyglot

(If you already have polyglot up and running, backup your config folder and then just point this to your existing config folder instead of the one above. This should work, but always make a backup.)

The image is built by running this command (from the folder where you Dockerfile and custom.txt files are located):

docker build -t polyglot .

After that completes, you should be able to run a command like this:

docker run -d --name=polyglot -v ~/docker/polyglot:/home/polyglot/Polyglot/config --net=host -t polyglot-docker

The polyglot page should be available via your docker host machine’s IP and port 8080. If that needs to change, allow it to start, then stop the container (docker stop polyglot), edit the configuration.json in the config folder on your docker host. Then start it back up (docker start polyglot). I chose to use the non-compiled variation so that it would run across more systems. Another item I’d like to improve is only using the compiled version in order to save space.

Ready to use image for linux x86_64

Run this command, assuming you already have docker setup and running for your user on your linux machine.

mkdir -p ~/docker/polyglot && docker run -d --name=polyglot -v ~/docker/polyglot:/home/polyglot/Polyglot/config --net=host -t i814u2/udi-polyglot

Then re-pull the image in order to update it, and re-create the container (this will also start the container, of course):

Raspberry Pi

Use NOOBS to install Raspbian on your Raspberry Pi (see here: https://www.raspberrypi.org/documentation/installation/noobs.md)

Once the Raspberry Pi is booted, open the terminal and enter the following command:

curl -sSL https://get.docker.com | sh && sudo usermod -aG docker pi

This may take a while, depending on the speed of the machine and SD card.

Once the above command is complete, logout and log back in, or just reboot if you prefer.

Run this command to download and start the docker container (using the pre-setup image in my docker hub repository):

mkdir ~/docker/polyglot && docker run -d --name=polyglot -v ~/docker/polyglot:/home/polyglot/Polyglot/config --net=host -t i814u2/rpi-udi-polyglot

Image contains the default Kodi and Hue nodes and also the Sonos and Nest nodes. Second command updated to include the creation of the local config directory to ensure proper permissions.

If you’ve followed my instructions so far, and want to update, here is what you can do: (This assumes you followed the instructions and have your config files pointed to a local directory. Always make a backup of your config first, just in case):

docker stop polyglot
docker rm polyglot
docker pull i814u2/rpi-udi-polyglot
docker run -d --name=polyglot -v ~/docker/polyglot:/home/polyglot/Polyglot/config --net=host -t i814u2/rpi-udi-polyglot

(4 separate commands, one per line, each starts with the word ‘docker’)

Again, the last line assumes you followed the copy/paste style info in my previous posts for a RaspberryPi setup. Adjust, as needed, for your own directory structure.

Quick note: this will take a while on initial startup. It’s upgrading packages when the container is started (in order to avoid the need to re-pull the image each time).

More detail on how the image is setup:

Follow the normal guides to install Raspbian on your RPi, then run this command to install Docker:

curl -sSL https://get.docker.com | sh

When that is complete, it will mention adding your user to the docker group in order to run without sudo. That is accomplished with this command:

sudo usermod -aG docker pi

That assumes you’re using the “pi” user, and not your own. You’ll need to log out, then log back on in order for that change to take effect.

Here is the Dockerfile info for a Raspberry Pi:

FROM fnphat/rpi-alpine-python:2.7

ARG binfile=polyglot.linux.armv7l.pyz
ARG user=polyglot
ARG group=polyglot
ARG uid=1000
ARG gid=1000

RUN addgroup -g ${gid} ${group} \
        && adduser -h /home/${user} -s /bin/sh -G ${group} -D -u ${uid} ${user}

WORKDIR /home/${user}
COPY custom.txt .

RUN apk add --update \
        build-base python-dev linux-headers git ca-certificates wget openssl-dev \
        && rm -rf /var/cache/apk/*

RUN pip install --upgrade pip \
    && pip install -r https://raw.githubusercontent.com/UniversalDevicesInc/Polyglot/unstable-release/requirements.txt \
    && while read line; do $line; done < custom.txt

RUN wget https://github.com/UniversalDevicesInc/Polyglot/raw/unstable-release/bin/${binfile} -P Polyglot \
    && chown -R ${user}:${group} /home/${user} \
    && chmod 755 /home/${user}/Polyglot/${binfile}

USER ${user}
WORKDIR /home/${user}/Polyglot
ENTRYPOINT ["./polyglot.linux.armv7l.pyz", "-v"]
  1. Use NOOBS to install Raspbian on your Raspberry Pi (see here: https://www.raspberr...lation/noobs.md)
  2. Once the Raspberry Pi is booted, open the terminal and enter the following command:
curl -sSL https://get.docker.com | sh && sudo usermod -aG docker pi

That will take a while, depending on the speed of the machine and SD card. 3. Once the above command is complete, logout and log back in, or just reboot if you prefer. 4. Run this command to download and start the docker container (using the pre-setup image in my docker hub repository):

mkdir -p ~/docker/polyglot && docker run -d --name=polyglot -v ~/docker/polyglot:/home/polyglot/Polyglot/config --net=host -t i814u2/rpi-udi-polyglot

That image contains the default Kodi and Hue nodes and also the Sonos and Nest nodes. Re-pulling the images and restarting new containers is easily done by running these commands:

docker stop polyglot
docker rm polyglot

Then re-pull the image in order to update it, and re-create the container (this will also start the container, of course):

docker pull i814u2/rpi-udi-polyglot && docker run -d --name=polyglot -v ~/docker/polyglot:/home/polyglot/Polyglot/config --net=host -t i814u2/rpi-udi-polyglot

Changelog

This is the changelog for Polyglot

0.0.6

  • Added MQTT nodeserver communication functionality -e42
  • Updated Documentation - e42
  • Added NodeJS nodeserver groundwork - e42
  • Modified params message to add “interface”, “mqtt_server”, “mqtt_port” keypairs - e42
  • Fixed nodeserver_api log format error on write_nodeserver_config - evilpete

0.0.5

0.0.4