Connio

The Connio API Developer Hub

Welcome to the Connio API developer hub. You'll find comprehensive guides and documentation to help you start working with Connio API as quickly as possible, as well as support if you get stuck. Let's jump right in!

Get Started    API Reference

Writing Methods

Connio methods are analog to methods in Object Oriented Programming. They provide behaviour to Device and App objects. All methods gets single argument called value that can be any primitive or object value. Object's private, protected and public methods can be called from other methods of the same object using object type specific prefix (ie. Device.myMethod(), or App.myMethod()).

Each method is executed within its own context. While a method is executing, it can interact with the rest of the system through its context which is constructed during run-time. The information within the context depends on whether the method is part of a Device or an App. For both cases, the context will contain all properties and methods of the parent object and allow local access to them. The context also contains a set of built-in methods (see below).

In order to maintain a highly performant script execution environment, all script calls are asynchronous in nature. As a result, a done function is used to signal completion of your function. It conforms to the "error-first callback" convention, which always interprets the first argument as an error. For example, the following would be considered a successful outcome:

done();
done(null, 'Success!');

Whereas the following would be considered an error:

done(true);
done(new Error('I am an error!'));

You should always invoke the done function when your script has completed. Otherwise, your script may continue to run until it is timed out by the system. Code that is in scope after the done function has been called is not guaranteed to execute. You should ensure that your script has completed all its work, including any asynchronous processes, prior to calling the done function.

Please note that within a script, both callbacks and promises are supported. All internal API methods are promise-based. However, the only way to signal the completion of a script is through the done function.

Best Practices

  • Always call done
    The end of script execution is signalled by calling the done function. If you fail to call the done function in your execution flow, your script will time out.

  • Write idempotent functions
    Try to write scripts which produce the same result even if they are called multiple times. This lets you retry a call if the previous call fails partway through your code.

Device Methods

Device methods can call other user defined and built-in device methods. The following private built-in methods can be called from user defined methods:

Method Description Signature
setProperty Write given value into device property setProperty(<property name>, <datapoint>)
getProperty Gets given property object getProperty(<property name>, ...)
log Write into device log log(<log type>, <message>)
readData Read historical data readData(<property name>, <query object*>)

* See Query Object format.

Device.api.setProperty('temperature', {
  value: 23.4,
  time: '20190204T00:21:00.513Z' // or new Date().toISOString() for now
}).then((property) => {
  // property should be equal to:
  //  {'value': 23.4, 'time':'20190204T00:21:00.513Z' }
  done(null, property.value)
}).catch((error) => {
  done(error, null);
});
Device.api.getProperty('temprature').then((property) => {
  // Assuming that the last temperature measurement is 23.4,   
  // property should be equal to:
  //  {'meta': {...}, 'value': 23.4, 'time':'20190204T00:21:00.513Z' }
  // where meta is property meta data such as type and other attributes
  done(null, property.value)
}).catch((error) => {
  done(error, null);
});

// Possible log types are:
//  error, debug, info, warning
Device.api.log('info', 'Hello world').then(() => {
  done(null, 'Log written')
}).catch((error) => {
  done(error, null);
});
// Get 10 days average of device temperature
let query = {
  "startRelative": {
    "value": 10,
    "unit": "days"
  },
  "aggregators": [
    {
      "name": "avg",
      "sampling": {
      "value": 10,
      "unit": "days"
    }
   }
  ]
};

Device.api.readData("temperature", query).then(resultSet => {
  // resultSet should be in the following format:  
  // {
  //     "sampleSize": 131,
  //     "results": [
  //         {
  //             "ref": {
  //                 "id": "_prp_548784729265699151",
  //                 "qname": "logika-l26$temperature",
  //                 "objectId": "_dev_548788743005351253"
  //             },
  //             "values": [
  //                 {
  //                     "t": "2019-01-28T04:47:55.876Z",
  //                     "v": 62
  //                 }
  //             ],
  //             "groupBy": [
  //                 {
  //                     "GroupByType": {
  //                         "type": "number",
  //                         "group": []
  //                     }
  //                 }
  //             ],
  //             "attributes": {
  //                 "protocol": [
  //                     "method"
  //                 ],
  //                 "source": [
  //                     "external_client"
  //                 ]
  //             }
  //         }
  //     ]
  // }
  
  let tempAvg = resultSet.results[0].values[0].v;
  done(null, '10 days temprature average is ' + tempAvg.toString());
}).catch((error) => {
  done(error, null);
});
  
// Assuming that this device has a private method called 'convertToCelsius'
Device.convertToCelsius(value, (error, result) => {
  if (error) done(error);
  else done(null, 'Given temperature in Celsius is equal to: ' + result.toString());
});
// Setting device serial number by property value and returning device object
async function f() {    
    Device.customIds["sn"] = await Device.api.getProperty("serialNumber").then(p=>p.value || "-");
  
    let device = {
        id: Device.id,
        name: Device.name,
        friendlyName: Device.friendlyName,
        customIds: Device.customIds,
    };
  
    return device;
}
return Promise.resolve(f());

The following device attributes can be accessed from user defined methods:

async function f() {    
    let device = {
        id: Device.id,
        accountID: Device.accountId,
        name: Device.name,
        friendlyName: Device.friendlyName,
        customIds: Device.customIds,
        status: Device.status,
        apps: Device.apps,
        location: Device.location,
        profileID: Device.profileId,
        properties: Device.properties,
    };
  
    done(null, device);
}
f();

App Methods

App methods can call other user defined methods, device methods and built-in app methods. The following private built-in methods can be called from user defined methods:

Method Description Signature
setProperty Write given value into app property setProperty(<property name>, <datapoint>)
getProperty Gets given property object getProperty(<property name>, ...)
log Write into app log log(<log type>, <message>)
readData Read historical data readData(<property name>, <query object>)
findDevices Get devices* by given filter findDevices(<query object>**)
executeDeviceMethod Executes device method from app method executeDeviceMethod(<device id>, <method name>, <argument>)
setDeviceProperty Set device property from app method setDeviceProperty(<device id>, <property name>, <datapoint>)
getDeviceProperty Get device property from app method getDeviceProperty(<device id>, <property name>)

**See Device Filters format.

App.api.findDevices({ 'sn': 'F858AB0494-494' })
  .then(results => {
    
    // `results` is the list of matching device ids (or empty list)
    // in this case we expect only one match
    let deviceId = results[0];
        
    // Execute device method
    App.api.executeDeviceMethod(deviceId, 'setData', arg).then(result => { 
         done(null, result);
     })
     .catch(error => done(error, null));           
  })
  .catch(error => done(error, null));
App.api.findDevices({ 'sn': 'F858AB0494-494' })
  .then(results => {
    
    // `results` is the list of matching device ids (or empty list)
    // in this case we expect only one match
    let deviceId = results[0];
            
    // Write into device property
    let dp = { 
        value: 12,
        time: new Date().toISOString()
    };
    App.api.setDeviceProperty(deviceId, "status", dp).then( property => { 
        done(null, property);
    })
    .catch(error => done(error, null));
    
  })
  .catch(error => done(error, null));
const extractResultField = ({ result }) => result;

const getDeviceLocation = d => App.api.executeDeviceMethod(d, 'getLocation', value);

const getDevices = async (devices) => {
    const requests = devices.map(getDeviceLocation);
    const result = await Promise.all(requests);

    return result.map(extractResultField);
}

App.api.findDevices({ 'sn': 'SN-0934930-DV' })
  .then(getDevices)
  .then((results) => { devices
      done(null, results);
  })
  .catch(error => done(error, null));

The following app attributes can be accessed from user defined methods:

async function f() {    
    let app = {
        id: App.id,
        accountID: App.accountId,
        name: App.name,
        friendlyName: App.friendlyName,
        status: App.status,
        profileID: App.profileId,
        properties: App.properties,
    };
  
    done(null, app);
}
f();

Promises

A promise is an object that wraps an asynchronous operation and notifies us when it’s done. This sounds exactly like callbacks, but the improvement is in how we use promises. Instead of providing a callback, a promise has its own methods which you call to tell the promise what will happen when it is successful or when it fails. The standard methods which a promise provides are the then() method for when a successful result is available and the catch() method for when something goes wrong. Using promises, a readFile function becomes:

readFile('foo.txt')
.then(function(result) {
  doSomethingWithData(data);
})
.catch(function(error) {
  console.log("An error has happened!");
});

However, the improvement from using promises becomes more apparent when we look at our nested callback example:

readFile('foo.txt')
.then(function(data) {
  return doSomethingWithData(data);
})
.then(function(result) {
  return writeFile('result.txt', result);
})
.then(function() {
  console.log('success');
})
.catch(function(error) {
  console.log("An error has happened!");
});

Instead of nesting callbacks inside callbacks inside callbacks, you chain then methods together making it more readable and easier to follow. Every then method should either return a new Promise or just a value or object which will be passed to the next then() method in the chain. Another important thing to notice is that even though we are doing two different asynchronous requests we only have one catch() method where we handle our errors. That’s because any error that occurs in the Promise chain will stop further execution and an error will end up in the next catch() method in the chain.

It is important to note that, just like with callbacks, these are still asynchronous operations. The code that is executed when the request has completed — that is the subsequent then() method calls — is put on the event loop just like a callback function would be. This means you cannot access any variables passed to or declared in the Promise chain outside the Promise. The same goes for errors thrown in the Promise chain. You must also have at least one catch() method at the end of your Promise chain for you to be able to handle errors that occur. If you do not have a catch() method, any errors will silently pass and you will have no idea why your Promise does not behave as expected:

try {
  readFile('foo.txt')
  .then(function(result) {
    doSomethingWithData(data);
  });
} catch(error) {
  console.log("An error has happened!"); // You will never reach here even if an error is thrown inside the Promise chain
}
const PRESSURE_PROPERTY = 'pressure';
const TEMPERATURE_PROPERTY = 'temperature';
const VIEW1_PROPERTY = 'view1';

async function main(context) {
    Object.assign(context, {
        costOfkWh: value,
    });

    // Acquire temperature and pressure values in parallel
    let [pressureProp, temperatureProp] = await Promise.all([
       Device.api.getProperty(PRESSURE_PROPERTY),
       Device.api.getProperty(TEMPERATURE_PROPERTY)
    ]);

    context.pressure.value = pressureProp.value || 0;
    context.pressure.unit = pressureProp.meta.measurement && pressureProp.meta.measurement.unit && pressureProp.meta.measurement.unit.symbol;

    if (pressureProp.meta.boundaries) {
        context.pressure.range = {
            min: pressureProp.meta.boundaries.min,
            max: pressureProp.meta.boundaries.max,
        };
    }
    
    context.temperature.value = temperatureProp.value || 0;
    context.temperature.unit = temperatureProp.meta.measurement && temperatureProp.meta.measurement.unit && temperatureProp.meta.measurement.unit.symbol;

    if (temperatureProp.meta.boundaries) {
        context.temperature.range = {
            min: temperatureProp.meta.boundaries.min,
            max: temperatureProp.meta.boundaries.max,
        };
    }

    // Call the methods below in parallel.
    let results = await Promise.all([
        Device.getCompressorInfo(),
        Device.queryWarningAlarmSummary(context),
        Device.getTimeToMaintenance(context),
        Device.hasInverter(context),
    ]);
    
    // Merge results into context
    results.forEach(result => Object.assign(context, result));

    return context;
};

Device.api.getProperty(VIEW1_PROPERTY)
 .then(property => property.value || Device.getEmptyView())
 .then(main)
 .then(context => Device.api.setProperty(VIEW1_PROPERTY, { value: context, time: new Date().toISOString() }))
 .then(property => done(null, property.value));

Accessing Historical Data From Methods

You can access to time-series database from the device and app methods. This will allow you to use the historical data in your logic or create custom view for your systems. Below is an example of querying average of a property for 4 different pierods. Please see Reading historical data section for query details.

/**
 * @private
 * @param {{ results: { values: { v: any }[] }[] }} response
 * @returns {any}
 */
function extractValue(response) {
  if (
    response.results &&
    response.results.length &&
    response.results[0].values &&
    response.results[0].values.length
  ) {
    return response.results[0].values[0].v;
  }

  return void 0;
}

function buildQueryWithAggregate(agg) {
  /**
    Builds time-series queries to collect 4 different stats for given property
  */
  return [
        { startRelative: { value: 24, unit: 'hours' }, aggregators: [ {name: agg, sampling: { value: 24, unit: 'hours'} } ] },
        { startRelative: { value: 7, unit: 'days' }, aggregators: [ {name: agg, sampling: { value: 7, unit: 'days'} } ] },
        { startRelative: { value: 30, unit: 'days' }, aggregators: [ {name: agg, sampling: { value: 30, unit: 'days'} } ] },
        { startRelative: { value: 12, unit: 'months' }, aggregators: [ {name: agg, sampling: { value: 12, unit: 'months'} } ] }
    ];
}

/**
 * @async
 * @param {{ agg: string, pname: string }} value
 * @returns {Promise<Array>}
 */
async function main(value) {
  const queries = buildQueryWithAggregate(value.agg);
  const requests = queries.map((q) => Device.api.readData(value.pname, q));
  const responses = await Promise.all(requests);

  return responses.map(extractValue);
}

return main(value);

Writing Methods


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.