Bindings Integrations - tagyoureit/nodejs-poolController GitHub Wiki

Bindings

Currently included are bindings for:

  • SmartThings/Hubitat
  • Vera
  • InfluxDB
  • Valve Relays
  • MQTT
  • Relay Equipment Manager
  • Home Assistant (Community provided)
  • Custom bindings

SmartThings/Hubitat

Add/configure the following under interfaces in config.json.

       "smartThings": {
        "name": "SmartThings/Hubitat",
        "enabled": true,
        "fileName": "smartThings-Hubitat.json",
        "globals": {},
        "options": {
          "host": "192.168.0.2", 
          "port": 39501 
        }
      },

Note: SmartThings and Hubitat use the same drivers and are listed separately in the default config.json. SmartThings runs on port 39500 and Hubitat runs on 39501.

Vera

Add/configure the following under interfaces in config.json.

       "vera": {
        "name": "Vera",
        "enabled": false,
        "fileName": "vera.json",
        "vars": {
          "deviceId": 0
        },
        "options": {
          "host": "",
          "port": 3480
        }
      }

Valve Relays

poolController supports the ability to emit http requests to an external valve relay controller. This can be a Raspberry Pi, Arduino, or standalone relay board that supports http operation. For the most part these bindings are a starting point for anyone out there who would like to add valve support beyond the capabilities of their OCP. What poolController does is monitor the circuits assigned to the specified valve. When the state changes, it then triggers valve state changes that indicate the desired state of the valve. The state is either diverted or not at this point. When that state changes, this interface is triggered during this change, an http request is called to the identified controller.

IMPORTANT: Be very careful with your valve configurations. You should always ensure there is no way to dead head a pump. This could cause damage to the equipment if you do. This is true for any (automated or not) valve configuration on your pool. Be warned I have had several CV24s over the years fail to move when commanded. The motors can go bad, the cams can crack after years of use (in this case the valve will spin around and around), and the micro-switches can fail. In this instance, you are also adding some equipment that could be down at the time it is triggered. Design carefully and design for potential failures!

Add/configure the following under interfaces in config.json.

       "valveRelay": {
        "name": "Valve Relays",
        "enabled": true,
        "fileName": "valveRelays.json",
        "options": {
          "host": "",
          "port": 3480
        }
      }

You will need to set the url used to trigger the relay. Each time the state of a valve needs to change, the interface will be triggered. In turn the http request will be sent to the defined valve controller based upon your settings.

The default defined path for the valve is <ip:port>/<pinId>/<on|off>. This is done by defining the binding for the path as /@bind=data.pinId;/@bind=data.isDiverted ? 'on' : 'off'; which at runtime is tokenized and translated into the appropriate url (e.g. http://<ip:port>/20/on).

If you wish to modify the bindings, make a copy of the valveRelays.json in the web/bindings directory. Then either change the filename identified in the config.json section or add a new section with a new name (e.g "myValveRelay"). Up to 100 virtual valves can be defined in the dashPanel configuration pages.

HTTP Bindings

In the json files are a context section at top:

//(example from SmartThings template)
  "context": {
    "name":  "Your_HTTP_application",  // name of the binding
    "vars": {},  // not used
    "options": {
      "method": "NOTIFY",  // PUT/GET/Notify/ etc.  Valid http methods.
      "path": "/notify",   // URL path
      "headers": { // add/modify any HTTP headers here
        "CONTENT-TYPE": "application/json",  
        "X-EVENT-TYPE": "@bind=eventName;"
      }
    }
  }

The events section is an array of objects that will be parsed:

    // an example
    {
      "name": "justAnExample", // this name must match the eventName or "*" for a catchall eventName
      "description":  "Shows how the binding can be used to send variable data to SmartThings.  This can be any combination of data.",
      // the example below binds the body of the HTTP notify event to a JSON string that will be parsed with the sample data
      "body": "{\"air\":\"@bind=data.air;@bind=data.units.name.substring(0, 1);\", \"total\":@bind=data.solar + data.waterSensor1;, \"status\":\"@bind=state.equipment.model;\"}",
      "options": {
        "headers": { // headers that will overwrite or append to the context headers in the HTTP call
          "X-EVENT2": "new_header"
        }
      },
      "enabled": true // optional boolean to disable this event
    }

Influx

Influx bindings

These work in a similar way to the HTTP bindings with some changes.

// Context section
"context": {
    "name": "InfluxDB",
    "options": {
      "tags": [ // these are global tags that will be added to every point written
        {
          "name": "sourceIP",
          "value": "@bind=Server_1.webApp.ip();"
        },
        {
          "name": "sourceApp",
          "value": "njspc"
        }
      ]
    }
  }

InfluxDB v1.8

Add/configure the following under interfaces in config.json.

       "influxDB": {
        "name": "InfluxDB",
        "type": "influx",
        "enabled": true,
        "fileName": "influxDB.json",
        "options": {
          "version": 1,
          "protocol": "http",  // http/https
          "host": "192.168.0.1",
          "port": 32770,
          "username": "",  // optional
          "password": "",  // optional
          "database": "pool",
          "retentionPolicy": "autogen",
          "batchSize": 100 // optional, # of points to write in each batch.  Default = 100
        }
      }

Sample Grafana Dashboard Examples:

InfluxDB v2.0

Add/configure the following under interfaces in config.json. The token is now the preferred way to represent a username/password. The org should be a string and is now required. The bucket should be the ID and is now the combination of the database/retention policy.

       "influxDBv2": {
        "name": "InfluxDBv2",
        "type": "influx",
        "enabled": true,
        "fileName": "influxDB.json",
        "options": {
          "version": 2,
          "protocol": "http",
          "host": "192.168.0.1",
          "port": 9999,
          "token": "...LuyM84JJx93Qvc7tfaXPbI_mFFjRBjaA==",
          "org": "example-org",
          "bucket": "57ec4eed2d90a50b",
          "batchSize": 100 // optional, # of points to write in each batch.  Default = 100
        }
      }

You can override the above options, and also fine tune your measurements that are stored in Influx by editing the bindings file. The file is ./web/bindings/influxDB.json. The events section is an array of events and these define the points, measurements, tags and fields.

// events section
{
      "name": "body",
      "description": "Bind bodies to measurements",
      "points": [
        {
          "measurement": "bodyTemps",
          "storePrevState": true, // optional, special boolean to write the prev state of the boolean 1s before the new state.
                                  // see https://community.influxdata.com/t/storing-duration-in-influxdb/4669
          "tags": [ // tags that will be added to the point written 
            {
              "name": "heatMode",
              "value": "@bind=data.heatMode.desc;"
            },
            {
              "name": "heatStatus",
              "value": "@bind=data.heatStatus.desc;"
            },
            {
              "name": "body",
              "value": "@bind=data.name;"
            }
          ],
          "fields": [ // fields that will be added to the point written
            {
              "name": "@bind=data.name;",
              "value": "@bind=data.temp;",
              "type": "int"  // int, float, boolean or string
            },
            {
              "name": "@bind=data.name+'Setpoint';",
              "value": "@bind=data.setPoint;",
              "type": "int"
            }
          ]
        }
      ]
    }

MQTT

MQTT is released with version 6.1 of njsPC.

  1. Add/configure the following under interfaces in config.json.
"mqtt": {
  "name": "MQTT",
  "type": "mqtt",
  "enabled": true,
  "fileName": "mqtt.json",
  "globals": {},
  "options": {
    "protocol": "mqtt://",
    "host": "10.0.0.170",
    "port": 1883,
    "username": "",
    "password": "",
    "rootTopic": "pool",
    "retain": true,
    "qos": 0
  }
}
  1. There is an alternative mqttAlt.json you can use in the file name that more closely follows the MQTT spec.
  2. Make sure to run npm i if you intend to use the MQTT so dependencies are installed.

Topics

mqtt.json takes standard websocket emits and converts them to MQTT messages.

      "events": [
        {
            "name": "circuit",
            "description": "Populate the circuits topics",
            "topics": [
                {
                    "topic": "state/circuits/@bind=data.id;/@bind=data.name;",
                    "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.isOn?'\"on\"':'\"off\"';}",
                    "desciption": "Bind 'on'/'off' as a message to the state topic."
                }
            ]
        },
        {
            "name": "feature",
            "description": "Populate the features topics",
            "topics": [
                {
                    "topic": "state/features/@bind=data.id;/@bind=data.name;",
                    "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.isOn?'\"on\"':'\"off\"';}",
                    "desciption": "Bind 'on'/'off' as a message to the state topic."
                }
            ]
        },
        ...
    ]

The way to read the above is:

  1. "events" will be searched for all event (emit) names
  2. "name":"circuit" will be matched to the circuit (for a single circuit) emit.
  3. Each topic in the topics array will be processed. In the example above we are taking the circuit emit and turning it into 4 distinct MQTT messages. This could be useful if you want a separate channel for each attribute of the circuit.
  4. The topic will be evaluated with the formatter for each name. See below for the formatter.
  5. The examples above shows how you can convert the data to a string, boolean, or an object.

Custom formatter

The custom formatter allows you to define how the variables in the topic will be processed. The order is:

  1. @bind=...; will be evaluated. EG @bind=data.name will evaluate to Water Scuppers.
  2. The formatter is then applied to the result.
  • transform {"transform":".toLowerCase()}" will be evaluated as ('Water Scuppers').toLowerCase() ==> water scuppers.
  • regex replace
  {
      "regexkey": "\\s",
      "replace": "",
      "description": "Remove whitespace"
  },

will be evaluated as new RegExp(regexkey, replace). water scuppers ==> waterscuppers 3. Formatters will be applied in the order they are listed and when combined with the @bind=...; statements are very powerful.

Data bindings and settings

  1. What's the relationship between config.json and mqtt.json? The MQTT interface (like the other interfaces) will use the options in config.json and merge them with the mqtt.json context section. Think of this as a waterfall effect. EG, if we start with
{"mqtt":{
  "options": {
    "rootTopic": "pool"
    }
  }
}

then the root of the topics will be pool. However, if mqtt.json overwrites this with:

    "context": {
        "options": {
            "rootTopic": "@bind=(state.equipment.model).replace(' ','-').replace('/','').toLowerCase();"
        }
    },

then this context value will be used. In this case, we are using a variable binding. If your pool system is "EasyTouch2 8" this custom binding will set the root topic as "easytouch2-8".

Further, some options (like formatter) can be defined globally in the mqtt/options/context and overridden in each mqtt/events/topics.

  1. What's the formatter? With MQTT it's very important to be consistent in naming topics and avoiding special characters like ␣(space) and /. Camelcase, uppercase, and lowercase can be set via preferences. In the mqtt.json is a formatter option that will be applied to all variables in the topic name.

Set values with MQTT

The MQTT binding will also listen for:

  * rootTopic/state/+/setState,
  * rootTopic/state/+/setstate,
  * rootTopic/state/+/toggleState,
  * rootTopic/state/+/togglestate,
  * rootTopic/state/body/setPoint,
  * rootTopic/state/body/setpoint,
  * rootTopic/state/body/heatMode,
  * rootTopic/state/body/heatmode,
  * rootTopic/state/+/setTheme,
  * rootTopic/state/+/settheme,
  * rootTopic/state/temps,
  * rootTopic/config/tempSensors,
  * rootTopic/config/chlorinator,
  * rootTopic/state/chlorinator,
  * rootTopic/config/chemController,
  * rootTopic/state/chemController

(both camelCase and lowercase are simply for compatibility)

Payload/message should follow the same format as the api. Turn turn off circuit 9 you can send:

  • topic: easytouch2-8/state/circuits/setState
  • payload: {"id": 9, "isOn": true}

Home Assistant (HA)

@gadget-monk helped implement an auto-discovery HA service. You can read about the background here. His work is now included in the default bindings since HA is becoming more popular. This binding has some missing components in it. Feel free to dive in (see below) and help complete the binding.

Custom bindings or Customize the bindings

You may want to create a new binding or customize existing bindings. This lets you add or tailor the information that is sent to an outbound service. The built-in bindings files referenced in the configurations can be found in web/bindings directory.

If you want to develop or modify existing bindings this is the route for you. This is preferred to editing the existing bindings in-place because when you want to next do a git pull and update the app NPM will complain. These custom bindings (and the directory) are not part of the Github updates and will not effect pulling new versions of the app.

Step 1: Start with an existing bindings file or create a new one (see any of the included examples above or use a same like home assistant - basic).

Step 2: Open dashPanel and navigate to the interfaces tab. Then select the Add Interface button.

Step 3: Click on the Upload Bindings button and upload the homeAssistant.json file to the njsPC server. It will place this file in the web/bindings/custom directory.

Step 4: Select the options as you see in the screenshot. Then press the Add Interface button.

Add a binding image

Step 5: The previous step will add an interface into the configuration for an MQTT client. Open the newly created interface and enter the server information for your broker. Don't forget to check the enabled checkbox to start the interface.

"@bind=read_this;"

In the bindings files you will see events and data can be bound with @bind=somejs;. The bindings will replace the "somejs" with valid javascript.

See data structures for a list of events and the associated data.

  • eventName = name of the socket output event
  • data = the data that is sent with the output event
⚠️ **GitHub.com Fallback** ⚠️