Developing tutorial

From ago control wiki
Jump to: navigation, search

Contents

Data Handling within the ago control web admin

Below is ago control developer Hari's explanation of internal data handling data within the ago control web admin:


Within ago control we have the resolver which manages the inventory. Each device entry in the inventory has:

  • a state value and
  • a values map, where other values can be stored

these values are filled with statuschanged and <whatever>changed events. The resolver just listens on all events and when it sees one of these events, it will update the matching device by uuid with the event values. For example, an event.environment.humiditychanged, with event values: uuid (of the device we're talking about), level (humidity level in this case), unit (percent in this case). Here is a humidty sensor from my inventory:

"3669aa90-61f9-48bb-b9f7-45da644faf55": {
  "devicetype": "brightnesssensor",
  "handled-by": "zwave",
  "internalid": "16/1-Luminance",
  "lastseen": 1383225812,
  "name": "",
  "room": "",
  "stale": 0,
  "state": "0",
  "values": {
    "brightness": {
      "level": "0",
      "timestamp": "1381158563",
      "unit": "%"
    }
  }
},


You can see that the state is "0", as nobody sent a state changed event for it (this would not make sense for such this kind of sensors as it doesn't have a definable state). When you look at the values, you have a quantity key, named "brightness", below that, you have a level, the unit, and the timestamp when this was last updated.

Of course there are also devices with multiple values, like in this example:

  "values": {
    "humidity": {
      "level": 70,
      "timestamp": "1380721587",
      "unit": "percent"
    },
    "temperature": {
      "level": 9,
      "timestamp": "1380721586",
      "unit": "degC"
    }
  }

There are a lot of these quantities used, like "power" for powermeters:

"power": {
        "level": "40.450",
        "timestamp": "1383180526",
        "unit": "W"
      }


From an ago control perspective, the resolver does not care whatever you announce in an "event.environment.<quantitiy>changed" event, as it will just take the level and the unit from the event, and store it under the <quantity> key in the values map of that device in the inventory.

Parallel to that is the device state. This is kind of "legacy" as it was only used to have a state between 0-255. While this is fine for dimmers, switches, and binary sensors, it did not allow to cover whatever values these "old" states are changed with in the "event.device.statechanged" events.

When you open the web interface, the browser opens a connection to the internal webserver of the agorpc component. The embedded mongoose webserver of ago control serves the whole html/javascript pages to the client. The javascript contains the knockout.js application. The agorpc has some special URLs on the internal webserver, too like /jsonrpc . This does not serve a static page, but it is a RPC interface that talks the JSONRPC protocol, version 2. The app now uses that interface, to talk to the other ago control components so when it starts up in the browser after the page is fully loaded, it sends an "inventory" request message out this is honoured by the resolver, which replies a complete inventory to the Knockout JS app. Knockout JS supports a MVC (model, view, controller).

The Javascript app parses the inventory into an internal datamodel and the KO controller connects that with the view, which are the html templates. When it draws the grid on the main page, it puts a view there for each device with the underlying data model hence you can access the device name, the room, the values, the state, etc from the model in the specific template. There is a default template, and there are specific templates for some devicetypes which override the default template. In the template you can use html code to do the shiny layout, and use Knockout specific model "accessors" to tie into the datamodel. This is not only about getting data out, as when you change a value in the GUI, the MVC will trigger a setter function. An example: when you click on "ON" on a device, the Knockout MVC will trigger the javascript method which sends a command out via the JSON-RPC interface.

With this nice and smart MVC thing, we have a good separation between view and data model hence a web designer does not need to care about where the values for each html template come because the Knockout app does all the magic bits by talking to ago control and keeping the datamodel current. This even goes so far, that the Knockout app does not need to refresh the inventory. After pulling the inventory initially, it stays up to date by listening to the events. So in fact, when your app is already running in the browser, and you send that humiditychanged event above not only does the agoresolver store it in its inventory, but also, the agorpc will reply that event on the long standing ajax request to the Knockout app in the browser thus letting the Knockout app update its internal data model. Then the MCV bits come into play and all of a sudden, the browser view is updated with the live values.

Controlling Data within the ago control

We'll use the above data for reference:

"3669aa90-61f9-48bb-b9f7-45da644faf55": {
  "devicetype": "brightnesssensor",
  "handled-by": "zwave",
  "internalid": "16/1-Luminance",
  "lastseen": 1383225812,
  "name": "",
  "room": "",
  "stale": 0,
  "state": "0",
  "values": {
    "brightness": {
      "level": "0",
      "timestamp": "1381158563",
      "unit": "%"
    }
  }
},

Use within a Device

To use information stored in ago to display information about the device, the html file must contain some specific information. Here is a snippet of the associated html file:

<div data-bind="attr: {'data-uuid': uuid}" class="col-md-3 col-sm-6 col-xs-12 dashboard-widget">
    <div class="info-box">
        <div class="info-box-content no-margin-left">
            <div class="info-box-title">
                <ko opts="if:name">
                  <h3 data-bind="text:name"/>
                </ko>
                <ko opts="ifnot:name">
                  <h3 data-bind="text:devicetype"/>
                </ko>
            </div>
            <div class="well">
                <ko opts="foreach: valueList">
                <div class="row">
                    <div class="col-xs-6 col-md-6 text-center">
                        <h4 data-bind="text:name" class="nowrap"/>
                    </div>
                    <div class="col-xs-6 col-md-6 text-center">
                        <h4><strong data-bind="text:levelUnit" class="nowrap"/></h4>
                    </div>
                </div>
                </ko>
            </div>
         </div>
    </div>
</div>


The first line:

<div data-bind="attr: {'data-uuid': uuid}" class="col-md-3 col-sm-6 col-xs-12 dashboard-widget">

binds the information with a particular uuid to the following code. As this code is contained within the template for a particular device, we can write the code specific accordingly.

We can also check if certain information is available and respond accordingly:

  <ko opts="if:name">
    <h3 data-bind="text:name"/>
  </ko>
  <ko opts="ifnot:name">
    <h3 data-bind="text:devicetype"/>
  </ko>

Here we check if the "name" field is populated and if it is, use the value from the name field. If the name field is empty, use the information from the devicetype. For our sample data, the "name" field is empty, so the devicetype data ("brightnessensor") would be used.

Special note- most variable names listed in inventory match the variable names you need to use in the code, but at the time of writing one does not. Here's a breakdown:

Inventory Name HTML Equivalent
devicetype devicetype
handled-by handledBy
internalid internalid
lastseen lastseen
name name
room room
stale stale
state state

Once we get to the variables listed under values, the syntax changes a little. For our example where there's a single variable, we can get by as in the example:

 <ko opts="foreach: valueList">
 ...
     <h4><strong data-bind="text:levelUnit" class="nowrap"/></h4>
 ...
 </ko>

If there were multiple variables listed under values, that code would display all of the levels for all of the variables.

  "values": {
    "brightness": {
      "level": "0",
      "timestamp": "1381158563",
      "unit": "%"
    }
    "bulbsize": {
      "size": "A19",
      "timestamp": "1381158563",
      "unit": ""
    }
  }

To pull the information from a particular variable, we need to add some criteria. For example, consider if our example data had an extra variable under values. You could pull the field size and the timestamp from the bulbsize variable:

 <ko data-bind="foreach: valueList">
   <ko opts="if: name == 'bulbsize'">
     <span data-bind="text: size"></span>
     <span data-bind="text: timestamp"></span>
   </ko>
 </ko>

We can also react to multiple criteria. For the sake of example, say we wanted to display the bulbsize if the brightness level was above 50%:

 <ko data-bind="foreach: valueList">
   <ko opts="if: (name == 'brightness' && $parent.valueList()[1].level > '50')">
     <div class="row">
       <span data-bind="text: $parent.valueList()[2].size"></span>
     </div>
   </ko>
 </ko>

Use within a Protocol

TO FOLLOW

Personal tools