Skip to content

How to add you own feature to the api

thetrueavatar edited this page Feb 16, 2021 · 30 revisions

How to work with my github

  • Do a fork of my github project. You'll do your update in this fork. Once you have done your dev, then create a pull request. This request will ask me to merge your change with the main code. I will do a review before merging.
  • I'm working on the develop branch. Master is for release.
  • The new method have to be added in the ViessmannAPI.php file
  • Feature have to be as constant in ViessmannFeature.php
  • To test a feature, you'll have to rebuild the phar. There is a script in script/createPhar.sh. However to speed up dev you should use the example/bootstrap-dev.php in your include. This will use the code from src directory instead of the phar so you won't have to rebuild the phar for every change.

Understand how data is structured

This is in depth explanation but you don't have to read to implements new feature. Just skip to the next section if you don't want to understand the full data structure.

The Restfull api provided by Viessmann is using an Hypermedia As The Engine of Application State(HATEOAS) architectural pattern. The idea behind is that you should navigate in your api from the root and follow links till you reach the final leaf.

TBH, I think it's a theorical approach that is working fine for application but not for home automation as we don't want to do the search every time and in the end we try to reach the leaf directly... Moreover this is not a protocol that is standardized and so there is no standadization about how HATEOAS json should be defined. There are several implementation HAL, JSON-LD, Siren, JSON-API, ION, Spring which are not cross-compatible.

Viessmann use Siren as implementation of this pattern(https://github.com/kevinswiber/siren). They have defined features. Each features defines a couple of field such as:

  • links: metadata that define how to go deeper in the structure and access sub-feature

  • class: the name of the feature itself

    "class": [
     "heating.circuits.0.heating.curve",
     "feature"
      ]
    
  • properties: which contains data. This is the main source for our data read:

    "properties": {
    "shift": {
    "type": "number",
    "value": -3
     },
    "slope": {
    "type": "number",
    "value": 0.7
    }
    }
    
  • actions: which contains defintion on how to write data.

    "actions": [
    {
    "method": "POST",
    "isExecutable": true,
    "href": "https://api.viessmann-platform.io/operational-data/v1/installations/X/gateways/X/devices/0/features/heating.circuits.0.heating.curve/setCurve",
    "name": "setCurve",
    "title": "setCurve",
    "fields": [
      {
        "name": "slope",
        "required": true,
        "type": "number",
        "min": 0.2,
        "max": 3.5,
        "stepping": 0.1
      },
      {
        "name": "shift",
        "required": true,
        "type": "number",
        "min": -13,
        "max": 40,
        "stepping": 1
      }
     ],
    "type": "application/json"
    }
    ]
    
  • Entities: They defined "components" which are the subfeature. The link to the subfeature can be found in the rel>href part. For instance on heating.cicruits.O.heating I have got :

     "entities": [
     {
    "rel": [
      "http://schema.viessmann.com/link-relations#feature-meta-information",
      "https://wiki.viessmann.com/display/VPL/Relations#Relations-feature-meta-information",
      "https://wiki.viessmann.com/display/VPL/Amounts#Amounts-unique"
    ],
    "properties": {
      "apiVersion": 1,
      "isEnabled": true,
      "isReady": true,
      "gatewayId": "X",
      "feature": "heating.circuits.0.heating",
      "uri": "/v1/gateways/X/devices/0/features/heating.circuits.0.heating",
      "deviceId": "0",
      "timestamp": "2019-11-12T20:15:18.441Z"
    }
    },
    {
    "rel": [
      "http://schema.viessmann.com/link-relations#curve",
      "http://schema.viessmann.com/link-relations#feature-component",
      "https://wiki.viessmann.com/display/VPL/Relations#Relations-curve",
      "https://wiki.viessmann.com/display/VPL/Amounts#Amounts-unique"
    ],
    "href": "https://api.viessmann-platform.io/operational-data/v1/installations/X/gateways/X/devices/0/features/heating.circuits.0.heating.curve"
    },
    {
    "rel": [
      "http://schema.viessmann.com/link-relations#schedule",
      "http://schema.viessmann.com/link-relations#feature-component",
      "https://wiki.viessmann.com/display/VPL/Relations#Relations-schedule",
      "https://wiki.viessmann.com/display/VPL/Amounts#Amounts-unique"
    ],
    "href": "https://api.viessmann-platform.io/operational-data/v1/installations/X/gateways/X/devices/0/features/heating.circuits.0.heating.schedule"
    },
    {
    "rel": [
      "http://schema.viessmann.com/link-relations#feature-components",
      "https://wiki.viessmann.com/display/VPL/Relations#Relations-feature-components",
      "https://wiki.viessmann.com/display/VPL/Amounts#Amounts-unique"
    ],
    "properties": {
      "components": [
        "curve",
        "schedule"
      ]
     }
     }
     ]
    

There is no properties values but we know that there are to sub-feature on heating.circuits.0.heating which are curve and schedule. This means we are not yet on a leaf feature and we have to go deeper and check heating.circuits.0.heating.curve and heating.circuits.0.heating.schedule.

Basically, you'll navigate through feature until you find a feature with a property or action defined. This will be your leaf feature that can be used for read/write.

I advise you to use a call such as this:

echo $viessmannApi->getRawJsonData("heating.circuits.0.heating");

check for property/action. If there isn't any property/actions then check for a Entity->properties->components and then try a new call with the feature obtain from modules such as this :

     echo $viessmannApi->getRawJsonData("heating.circuits.0.heating.curve");

Once you get a property/action you dit it ! You have got you're leaf feature.

Quick way to discover leaf feature and find interesting information

I have implemented a method that returns every feature having property or actions field. $viessmannApi->getAvailableFeatures()

This will provide you with a list of feature having either a property to read and/or an actions. After this, I advise you to do a call to get the json response for this feature to check what are the interesting informations:

 $viessmannApi->getRawJsonData("heating.circuits.0.operating.programs.normal") 

this will return a json with information that you'll have to use in the data read/write section

  {
 "links": [
{
  "rel": [
    "self"
  ],
  "href": "https://api.viessmann-platform.io/operational-data/v1/installations/Y/gateways/X/devices/0/features/heating.circuits.0.operating.programs.normal"
},
{
  "rel": [
    "up"
  ],
  "href": "https://api.viessmann-platform.io/operational-data/v1/installations/Y/gateways/X/devices/0/features"
  },
{
  "rel": [
    "http://schema.viessmann.com/link-relations#live-updates",
    "https://wiki.viessmann.com/display/VPL/Relations#Relations-live-updates"
  ],
  "href": "/operational-data/installations/Y/gateways/X/devices/0/features/heating.circuits.0.operating.programs.normal"
}
],
 "class": [
"heating.circuits.0.operating.programs.normal",
"feature"
],
"properties": {
"active": {
  "type": "boolean",
  "value": true
},
"temperature": {
  "type": "number",
  "value": 20
}
},
"entities": [
{
  "rel": [
    "http://schema.viessmann.com/link-relations#feature-meta-information",
    "https://wiki.viessmann.com/display/VPL/Relations#Relations-feature-meta-information",
    "https://wiki.viessmann.com/display/VPL/Amounts#Amounts-unique"
  ],
  "properties": {
    "apiVersion": 1,
    "isEnabled": true,
    "isReady": true,
    "gatewayId": "X",
    "feature": "heating.circuits.0.operating.programs.normal",
    "uri": "/v1/gateways/X/devices/0/features/heating.circuits.0.operating.programs.normal",
    "deviceId": "0",
    "timestamp": "2019-11-13T09:54:48.819Z"
  }
}
],
 "actions": [
{
  "method": "POST",
  "isExecutable": true,
  "href": "https://api.viessmann-platform.io/operational-data/v1/installations/Y/gateways/X/devices/0/features/heating.circuits.0.operating.programs.normal/setTemperature",
  "name": "setTemperature",
  "title": "setTemperature",
  "fields": [
    {
      "name": "targetTemperature",
      "required": true,
      "type": "number",
      "min": 3,
      "max": 37,
      "stepping": 1
    }
  ],
  "type": "application/json"
   }
   ]
   }

How to implement data read

Basically there are 2 case:

  1. your feature has a digit (ex:heating.circuits.0.circulation.pump ). This means it depends of the circuitId. For this, I'm using this kind of :

    public function getCirculationPumpStatus($circuitId = NULL)
    {
     return $this->getEntity($this->buildFeature($circuitId, self::CIRCULATION_PUMP))->getProperty("status")["value"];
    

    } the buildFeature private method will take the "heating.circuits", append the current circuitId(can be ovewritten throug param method) and at last add the CIRCULATION_PUMP constant="circulation.pump". This will provide you the heating.circuits.0.circulation.pump string. The getEntity method do the call with the provided feature, and convert the siren/json response into a Siren object. Most of the time, you just have to get the property (here it's status) and ask the value from it.

  2. You don't have any digit then you can just call directly the getEntity method on the feature. I add the feature directly as constant in ViessmannFeature:

    public function getOutsideTemperature(): string
     {
         return $this->getEntity(ViessmannFeature::HEATING_SENSORS_TEMPERATURE_OUTSIDE)->getProperty("value")["value"];
     }
    
  • To help you to know which property value to useyou can call the getRawJsonData on the feature which will show you the json response. For instance:

    <?php
     include __DIR__ . '/bootstrap.php';
     echo $viessmannApi->getRawJsonData(\Viessmann\API\ViessmannFeature::HEATING_SENSORS_TEMPERATURE_OUTSIDE);
    

    will give you something like:

          {
    "links": [
      {
        "rel": [
          "self"
        ],
        "href": "https://api.viessmann-platform.io/operational-data/v1/installations/XX/gateways/YYY/devices/0/features/heating.sensors.temperature.outside"
      },
      {
        "rel": [
          "up"
        ],
        "href": "https://api.viessmann-platform.io/operational-data/v1/installations/X/gateways/Y/devices/0/features"
      },
      {
        "rel": [
          "http://schema.viessmann.com/link-relations#live-updates",
          "https://wiki.viessmann.com/display/VPL/Relations#Relations-live-updates"
        ],
        "href": "/operational-data/installations/X/gateways/X/devices/0/features/heating.sensors.temperature.outside"
      }
    ],
    "class": [
      "heating.sensors.temperature.outside",
      "feature"
    ],
    "properties": {
      "status": {
        "type": "string",
        "value": "connected"
      },
      "value": {
        "type": "number",
        "value": **6.5**
      }
    },
    "entities": [
      {
        "rel": [
          "http://schema.viessmann.com/link-relations#feature-meta-information",
          "https://wiki.viessmann.com/display/VPL/Relations#Relations-feature-meta-information",
          "https://wiki.viessmann.com/display/VPL/Amounts#Amounts-unique"
        ],
        "properties": {
          "apiVersion": 1,
          "isEnabled": true,
          "isReady": true,
          "gatewayId": "YYYY",
          "feature": "heating.sensors.temperature.outside",
          "uri": "/v1/gateways/YYY/devices/0/features/heating.sensors.temperature.outside",
          "deviceId": "0",
          "timestamp": "2019-11-12T12:21:02.127Z"
        }
      }
    ],
    "actions": []
    } 
    

The interesting part it the property part which will give you information about what to specify in the getProperty method on the Entity object.

  • I use string as return value for single value, for status a bool is more appropriate. If you need to encode/decod string in json I'm using the json_encode and json_decode method from php. Don't forget to pass true as second parameter cause this parameter will tell the decoder to map the json string into an objet structure allowing you to navigate in this structure.

How to implement data write

For setter, it's a little bit more tricky. I usually do a get and have a look at the actions part on the feature which define the format of the data to send. For instance:

  public function setCurve($shift, $slope, $circuitId = NULL)
  {
    $this->setRawJsonData($this->buildFeature($circuitId, self::HEATING_CURVE), "setCurve", "{\"shift\":" . $shift . ",\"slope\":" . $slope . "}");
  }

the "setCurve" and format "{"shift":" . $shift . ","slope":" . $slope . "}" is coming from the actions part from the get on heating.circuits.0.heating.curve:

"actions": [
{
  "method": "POST",
  "isExecutable": true,
  "href": "https://api.viessmann-platform.io/operational-data/v1/installations/XXX/gateways/YYY/devices/0/features/heating.circuits.0.heating.curve/setCurve",
  "name": "setCurve",
  "title": "setCurve",
  "fields": [
    {
      "name": "slope",
      "required": true,
      "type": "number",
      "min": 0.2,
      "max": 3.5,
      "stepping": 0.1
    },
    {
      "name": "shift",
      "required": true,
      "type": "number",
      "min": -13,
      "max": 40,
      "stepping": 1
    }
  ],
  "type": "application/json"
}
]

I you get a bad request(400)/gateway(502) error then this usually means you're not give the right format for parameter. A common error is to forget to wrap the key value with a ". The start of a string is with " but to insert a quote inside you have to escape it. This is done by using the escape caractère \ before the ":

"{\"shift\":" . $shift . ",\"slope\":" . $slope . "}"

To append string together you have to use the .