tagyoureit / nodejs-poolController

An application to control pool equipment from various manufacturers.
GNU Affero General Public License v3.0
326 stars 95 forks source link

Integration example for 6.0 #178

Closed rsnodgrass closed 4 years ago

rsnodgrass commented 4 years ago

Would be nice to have one (or a few) very simple integration examples included with nodejs-Controller, e.g. an integration directory with one of these:

Right now it isn't clear how to build an integration as the vera example is complex to unravel.

Also, integrations (or are they called interfaces now) should be able to be injected into nodejs-poolController without having to update code. E.g. to install:

  1. copy the integration/interface code into a standard directory (perhaps including a config file for that integration)
  2. enable the integration through the user's config

I was going to suggest that #2 should even be skipped...that it just auto-loads the integration (allow disabling an integration by manually editing config), but perhaps safer to initially require the additional configuration step.

rsnodgrass commented 4 years ago

My plan is to either update the existing MQTT plugin, or rewrite a new one that is 6.0 compatible, that forwards any equipment state changes as MQTT messages, and allows MQTT messages to set states.

var plugin = (function (api) {
    var config = container.settings.getConfig();    

    function init() {
        // intentionally left blank: this plugin has no initialization code
    }

    var module = { init: init };
    return module;
})(api);
rsnodgrass commented 4 years ago

Additionally, what is the standard way for a integration plugin to specify its dependencies so they can get installed by npm? Should a plugin be a small directory:

integrations/
   mqtt/
      plugin.js               // plugin code
      package.json       // npm install dependencies
      config.json           // any plugin specific config to be injected into nodejs-poolController
      README.md
      LICENSE

There must already exist a "plugin" mechanism library for node that could be leveraged that has thought through all these issues?

rstrouse commented 4 years ago

The Vera integration is not really all that complex and the .json file located in the web/bindings directory is only a description of the bindings. Hubiutat, SmartThings, and Vera all use this same HTTP bindings file to describe the emitted HTTP calls when changes occur to the state of equipment installed in the poolController.

While documentation is still needed, perhaps I can get you started. First there are two sections of this file that drive the emitted HTTP calls. The first section is the context which defines the overall execution context for the interface. The second section is events. You will find a description of the events and their associated data in Version 6 Data Structures. This document outlines what information is sent. When this information is sent is only when it changes.

Tokens are defined in this file as @bind=XXXXX; tags. So when a change occurs for a circuit for instance, the circuit event is emitted. The interface takes the defaults defined in the config.json, the context section of the .json and the data from the interface and builds an HTTP call. After the tokens have been replaced it will send this via HTTP.

So if I simply want to send an HTTP GET when the circuit changes to an ip address. I simply define an event for the circuit as follows.

"context": {
   "name":"MyInterface",
   "options" {
      "method": "GET",
      "headers": { "CONTENT-TYPE": "text/html" }
   },
   vars: {}
},
"events": [
   {
      "name": "circuit",
      "options": {
         "path": "?circuitid=@bind=data.id;ison=@bind=data.isOn;"
      }
   }
]

So every time the state changes for the circuit it will emit an HTTP GET to the ip address identified in the config.json for the interface. If you look at the defaults for the vera interface in your config.json you will see settings for the interface to include the deviceId (this will be familiar if you are familiar with Vera), the ip address of the controller and the port that it operates on. NOTE: In this instance I am not sending any JSON data in the body but I could easily send JSON in the body that is completely different than the format of the data in the aforementioned data structure.

You are also not limited to the data in that output. The following objects are available in the @bind=XXXX; tags and you can also execute script. data = The payload data defined in Version 6 Data Structures. vars = The merged vars object starting with the config.json, then the context section and finally, the event section. sys = All data defined in the poolConfig.json state = All data defined in the poolState.json eventName = The event name that is currently being raised. options = The nodejs http options object merged the same way from the config.json, context, and finally to the event.

In the end the expectation is that we need only write a single set of interface code for each protocol. While currently only the HTTP client is available, this same method can be used to create an SMTP client, native MQTT broker, and upnp interfaces.

Integration servers beyond this can subscribe directly to the websockets in poolController if needed.

rsnodgrass commented 4 years ago

Ok, from what I can tell reading the code, the nodejs-poolController-veraPlugin adds a UI for configuring the integration with vera, but doesn't actually pass information between nodejs-poolController and vera. Am I correct in that?

The MQTT interface should only take "all" nodejs-poolController state updates and republish them via MQTT to any subscribers who want to listen to those updates. And, additionally, have a state update messages that follow nodejs-poolController state update API that would normally go over HTTP. To that end, it seems like I should focus on web/bindings and possibly replacing web/httpInterface.ts, since instead of webhook style HTTP callbacks, I want to publish MQTT messages. Is that correct?

A picture in the documentation might be thousand words...especially how nodejs-poolController works with devices, communication flows between devices/servers/etc, integrations. I think this would go a long way towards helping people understand nodejs-poolController a little better.

rstrouse commented 4 years ago

Ok, from what I can tell reading the code, the nodejs-poolController-veraPlugin adds a UI for configuring the integration with vera, but doesn't actually pass information between nodejs-poolController and vera. Am I correct in that?

No there is no interface in Vera for configuration of the pool equipment. All state data is sent to Vera through HTTP hooks and the Vera has complete control over the pool. If you go to the git for the Vera plugin you will see what it is capable of. Vera Plugin. It is of course all written in Lua.

The rub for things like Vera, SmartThings, OpenHAB, and ISY is that the communication to and from these things are very limited in what can be done. Nearly all of them do support an HTTP interface so long as you send the data in a form that it can recognize. For Vera and I presume the SmartThings/Hubitat interface, you can control all aspects of the pool and all state is sent instantly through the WebSocket interface built into poolController.

For MQTT the thing to do would be to create an MQTT client that translates the emits for the WebSocket into publish and subscribe for the broker. I don't have the bandwidth right now to take that on but the way that would be done is to derive a class from ProtoServer in Server.ts (see HttpInterfaceServer) and have it translate the incoming socket data into MQTT publish, Conversely, the MQTT subscribe would be translated into Api calls. The endpoints are built on the Rest side of things so that they are very much a single async call. The rest is error processing.

As for the sausage in poolController, the primary concept boils down to two primary data structures. First, there is configuration data that defines the what. This is done out of necessity simply because a pool is the sum of its parts. All of this originates from the RS485 data extracted off the OCP.

The other side of poolController and likely the part that you are most interested in is State. This is the primary data that is emitted over the websocket and is sent out to ProtoServer based clients. These are also the data structures that are documented in the aforementioned link. The best part about this is that clients are only notified when an actual change occurs and only for the items for which they are subscribed. In the case of Vera the * event is a wildcard meaning send state changes for every piece of equipment on the pool.

baudfather commented 4 years ago

Let me know if you need a hand testing a MQTT plugin - I'm using Homeseer with a few other MQTT integrations.

tagyoureit commented 4 years ago

Another user is working on this (as posted in Gitter. And the existing bindings are now documented in the wiki. Closing this for now... looks like we are close.