How to build recipes?

Introduction to QCode

QCode is the scripting language for Recipes. It is a high-level, untyped and interpreted language that supports basic constructs, common to most scripting languages. It can be leveraged to automate the execution of simple tasks. Programs written in QCode are called recipes, and are run in a special runtime environment on the Qopper Platform.

Recipe Structure
  • A recipe is composed of a set of statements.
  • Statements are terminated by a semicolon ";" or ";"\n
  • Statements can be expressions, assignments or control structures.
  • One symbol table contains all local variables, and is accessible to all statements in the recipe.
  • Payload is passed to the recipe as the local variable @input
  • Variables can be persisted and accessed across invocations of the recipe by using the config data structure on the asset.
Comments Comments can be inserted in QCode as follows:

/* this is a comment */

Variables
  • Literal string supported with single or double quotes "string" or ‘string‘.
  • Local variables are referenced with an "@" symbol.
       @var = "Hello Recipe" ;
  • this is a reserved keyword, used to reference the "current" asset. The parser swaps this for the [asset] at runtime.
  • variables can be persisted or retreived using db.setProperty and db.getProperty. See built-in functions (below).
Operators

Supports basic math operators "+", "-", "*", "/", "+=", "-=", "*=", "/=", "++" and "--"

/* addition */
@var = 1+2;
@newvar = @var+1;
@var++;
@newvar += 1;


/* subtraction */
@var = 2-1;
@newvar = @var-1;
@var--;
@var -= 1;

/* multiply */
@var = 2*3;
@newvar = @var*3;
@var *= 3;
@newvar = @var * (7*@oldvar);


/* divide */
@var = 6/3;
@newvar = @var/2;
@var /= 2;

Control Structures
  • Supports if( ) { … } as well as if( ) { … } else { … }
  • Supports basic comparison operators "==", "!=", "&&", "||", ">", ">=", "<" and "<="within if ( … )

    /* equal check */
    if( @var == 1 ) {
       /* do something */
       /* nested if( ) NOT supported */
    }

    /* greater than || less than */
    if( @var > 1 ) {
       /* do something */
    } else {
       /* do something else */
       /* nested { } NOT supported */
    }

    /* greater than-equal || less than-equal */
    if( @var <= @oldvar ) {
       /* do something */
    }

    /* boolean operators */
    if( (@var == @oldvar) && (@var != 0) ) {
       /* do something */

    }
    if( (@var != @oldvar) || (@var > 0) ) {
       /* do something */
    }
Functions
  • Built-in functions are available within recipes and can be called as system.* (system capabilities),calendar.* (date-time manipulation) or json.* (manipulating json structures)
  • No arguments are passed explicitly to the built-in function. A copy of the entire symbol table is passed with the function call. Arguments needed for each built-in function are documented with the glossary of built-in functions here.
       /* takes @seed as argument and returns a random number */
       @seed = 123;
       @randomNumber = system.random;

Samples

Sample Payload “metrics”: [
       {
              “temp”: “28”,
              “humidity”: “86”
       }
    ]
Recipe Invocation

A recipe is invoked for each element in the metrics array. When invoked for metrics[0], the payload can be accessed within the recipe using the following QCode:

@source = @input;
@t = json.temp;
@h = json.humidity;
Sample Recipe #1 (HighHumidityAlert) /* parse humidity from payload */
@source = @input;
@humidity = json.humidity;


/* check humidity condition and create alert */
if ( @humidity > 84 ) {
@title = 'High Humidity Alert - '+@humidity;
db.newAlert;
}
Sample Recipe #2 (HumidityStats) /* parse humidity from payload */
@source = @input;
@humidity = json.humidity;


/* check aggregate value */
@property = 'aggregateHumidity';
@isThere = db.hasProperty;
if(@isThere == false) {

    /* first time */
    @aggregateHumidity = 0;
}
@aggregateHumidity += @humidity;


/* save aggregate value */
@property = 'aggregateHumidity';
@value = @aggregateHumidity;
db.setProperty;


/* check aggregate value */
@property = 'aggregateHumidityCount';
@isThere = db.hasProperty;
if( @isThere == false) {

    /* first time */
    @aggregateHumidityCount = 0;
}
@aggregateHumidityCount += 1;


/* save aggregate value */
@property = 'aggregateHumidityCount';
@value = @aggregateHumidityCount;
db.setProperty;


/* check standard deviation and alert */
@averageHumidity = @aggregateHumidity/@aggregateHumidityCount;
@pctDeviation = (@humidity-@averageHumidity)*100/@averageHumidity;

if( @pctDeviation > 20 ) {
    @title = 'Sharp rise in Humidity - '+@humidity;
    db.newAlert;
}

if( @pctDeviation < -20 ) {
    @title = 'Sharp drop in Humidity - '+@humidity;
    db.newAlert;
}
Sample Recipe #3 (MinorTempIncident) /* parse temperature from payload */
@source = @input;
@temp = json.temp;


/* check temperature condition and create incident */
if (@temp > 80) {
    @summaryTitle = 'High Temperature';
    @description = 'Temperature exceeding acceptable limits: ' + @temp + 'F, exceeds 80F limit';
    @priority = 'MINOR';
    @assignedTo = 'rob';
    @incidentType = 'TEMPERATURE';
    db.newIncident;
}
Sample Recipe #4 (UpgradeTempIncident) /* parse temperature from payload */
@source = @input;
@temp = json.temp;


/* check temperature and upgrade incident if temperature above critical limit */
if (@temp > 120) {
    /* upgrade priority for incident */
    @priority = 'CRITICAL';
    @incidentType = 'TEMPERATURE';
    db.updateIncidentPriority;
}

Sample Recipe #5 (ResolveTempIncident) /* parse temperature from payload */
@source = @input;
@temp = json.temp;


/* check temperature and resolve incident if temperature returns below normal */
if (@temp < 80) {
    @incidentType = 'TEMPERATURE';
    db.resolveIncident;
}

Built-In Functions Reference Docs



system.session /* returns a session ID, which is unique across all recipes invoked for an API call */
@sessionId = system.session;
arguments:
    n/a
return:
    @sessionId - session Id
example:
    @sessionId = system.session;
system.random /* used to generate a stream of pseudorandom numbers. Uses a seed, which is modified using a linear congruential formula */
@randomNumber = system.random;
arguments:
    @seed - seed variable
return:
    @randomNumber - random number between 0 and 1.
example:
    @seed = 123;
    @randomNumber = system.random;
system.uuid /* used to generate a cryptographically strong 16-digit random number */
@uniqueId = system.uuid;
arguments:
    n/a
return:
    @uniqueId - 16-char alpha-numeric unique Id
example:
    @uniqueId = system.uuid;
system.int /* drops values after decimal to convert to an int */
@intVal = system.int;
arguments:
    @value - numeric value
return:
    @intVal - new integer value
example:
    @value = '123.445';
    @intVal = system.int;     /* @intVal is '123' */
system.error /* Console log a message with a severity level = ERROR */
system.error;
arguments:
    @message - Message to log
return:
    n/a
example:
    @message = 'There is a problem';
    system.error;
system.warn /* Console log a message with a severity level = WARN */
system.warn;
arguments:
    @message - Message to log
return:
    n/a
example:
    @message = 'There is a problem';
    system.warn;
system.info /* Console log a message with a severity level = INFO */
system.info;
arguments:
    @message - Message to log
return:
    n/a
example:
    @message = 'There is a problem';
    system.info;
system.debug /* Console log a message with a severity level = DEBUG */
system.debug;
arguments:
    @message - Message to log
return:
    n/a
example:
    @message = 'There is a problem';
    system.debug;
db.newAlert /* creates a new alert */
db.newAlert;
arguments:
    @title - alert message title
return:
    n/a
example:
    @title = 'Temperature Alert';
    db.newAlert;
db.newIncident /* creates a new incident */
@incidentId = db.newIncident;
arguments:
    @assignedTo - name of user the incident is assigned to (defaults to assignedTo config on the asset)
    @priority - priority = MINOR|MAJOR|CRITICAL
        @summaryTitle - title string
    @description - description string
return:
    @incidentId - incident Id
example:
    @summaryTitle = 'High Temperature';
    @description = 'Temperature exceeding acceptable limit';
    @priority = 'MINOR';
    @assignedTo = 'rob';
    @incidentId = db.newIncident;
db.updateIncidentPriority /* update the priority for an incident */
db.updateIncidentPriority;
arguments:
    @incidentid - ID for the incident
    @priority - new priority = MINOR|MAJOR|CRITICAL
return:
    n/a
example:
    @priority = 'CRITICAL';
    @incidentid = 'ojs3k8tovt8pplku';
    db.updateIncidentPriority;
db.resolveIncident /* resolve incident */
db.resolveIncident;
arguments:
    @incidentid - ID for the incident
return:
    n/a
example:
    @incidentid = 'ojs3k8tovt8pplku';
    db.resolveIncident;
db.hasProperty /* check if a property exists on the asset */
db.hasProperty;
arguments:
    @property - name of property to check
return:
    true|false
example:
    @property = 'color';
    @isThere = db.hasProperty;
    if( @isThere == true ) {
    /* do something */
    }
db.getProperty /* get the value of a property on the asset */
@val = db.getProperty;
arguments:
    @property - Name of property to get
return:
    @val - value of the property, == null, if no value found
example:
    @property = 'ip';
    @val = db.getProperty;
    if( @val != null ) {
    /* do something */
    }
db.setProperty /* set the value of a property on the asset */
db.setProperty;
arguments:
    @property - name of property
    @value - value of property
return:
    n/a
example:
    @property = 'color';
    @value = 'blue';
    db.setProperty;
db.resetProperty /* reset the value of a property on the asset */
db.resetProperty;
arguments:
    @property - name of property to get
return:
    n/a
example:
    @property = 'color';
    db.resetProperty;
db.getStatistics /* get the statistic value */
@val = db.getStatistics; arguments:
    @statName - Name of statistic to get
return:
    value of the statistic, == null, if no value found
example:
    @statName = 'AVERAGETEMP_temp';
    @val = db.getStatistics;
    if( @val != null ) {
    /* do something */
    }
db.setStatistics /* set the value of a statistic on the asset */
db.setStatistics;
arguments:
    @name - name of statistic
    @... - statistics attribute. The name needs to start with the statistic
return:
    n/a
example:
    @name = 'AVERAGETEMP';
    @AVERAGETEMP_temp = '85';
    @AVERAGETEMP_formattedData = '85°F';
    db.setStatistics;
system.base64encode /* Base64Encode the input string */
@encString = system.base64encode; arguments:
    @inputput string
return:
    n/a
example:
    @input = 'defaultKey';/d>
    @encString = system.base64encode;>eQ==' */
system.substitute /* string replace */
@newStr = system.substitute;
arguments:
    @source - source string
    @orig - original substring to search and replace
    @repl - replacement string to replace original with
return:
    @newStr - new string post-substitution
example:
    @source = 'replace oldWord with newWord';
    @orig = 'oldWord';
    @repl = 'newWord';
    @newStr = system.substitute; /* @newStr is 'replace newWord with newWord' */
system.pad02 /* pad a single digit number with leading 0s. 1 will become 01, etc. */
@newNum = system.pad02; arguments:
    @sourceut digit
return:
    @newNum number
example:
    @source = '1';
    @newNum = system.pad02;>/* @newNum is '01' */
system.searchString /* returns index of keyword within a larger string. returns -1, if keyword is not found */
@idx = system.searchString;
arguments:
    @source - input string
    @keyword - search keyword
return:
    @idx - index of keyword or -1
example:
    @source = 'search for thisWord in source string';
    @keyword = 'thisWord';
    @idx = system.searchString; /* @idx is 11 */
system.subString /* returns substring starting at given index within source */
@subStr = system.subString;
arguments:
    @source - input string
    @fromIdx - index of the start of the substring within source
return:
    @subStr - substring within source or original string, if no substring found
example:
    @source = 'extract substring starting at thisWord in source string';
    @fromIdx = 30;
    @subStr = system.subString; /* @subStr is 'thisWord in source string' */
system.formatNumber /* returns formatted number */
@formattedNumber = system.formatNumber;
arguments:
    @number - input string
    @pattern - Support patterns such as ###,###,###.##, #.##, #,###k, #.##M. By default we use #.## , #k and #M
return:
    @formattedNumber - The formatted number based on the provided pattern
example:
    @number = 100000000;
    @pattern = '#M';
    @formattedNumber = system.formatNumber; /* @formattedNumber is 1M */
system.jsonObject /* add key: value to JSON object */
@newJobj = system.jsonObject;
arguments:
    @jobj - existing json object /* optional */
    @key - new key
    @value - new value. can itself be a json object
return:
    @newJobj - new JSON object with key: value added to it
example:
    @key = 'name';
    @value = 'John Doe';
    @jobj = system.jsonObject;
    @key = 'age';
    @value = '42';
    @newJobj = system.jsonObject; /* @newJobj is '{"age":"42","name":"John Doe"}' */
json.[key] /* given key, fetch value from JSON object */
@val = json.key;
arguments:
    @source - existing json object
return:
    @val - value for the key in json.key
example:
    @source = @newJobj;
    @n = json.name;/* @n is 'John Doe' */
    @a = json.age; /* @a is '42' */
system.arrayIndex /* return value of an index within an array */
@idxVal = system.arrayIndex;
arguments:
    @source - input array
    @index - index within the array
return:
    @idxVal - value at the array index
example:
    @source = '["one", "two"]';
    @index = 1;
    @idxVal = system.arrayIndex; /* @idxVal is "two" */
system.jsonArraySize /* return size of array */
@arrSize = system.jsonArraySize;
arguments:
    @source - input array
return:
    @arrSize - size of array
example:
    @source = '["one", "two"]';
    @arrSize = system.jsonArraySize; /* @arrSize is 2 */
calendar.utc /* fetch current UTC time */
@utcTime = calendar.utc;
arguments:
    n/a
return:
    @utcTime - UTC time
example:
    @utcTime = calendar.utc;
calendar.parseUtc /* parse UTC from date-time string "dd-MMM-yyyy HH:mm:ss" */
@utcTime = calendar.parseUtc;
arguments:
    @datetime - date time string in the format "dd-MMM-yyyy HH:mm:ss"
return:
    @utcTime - UTC time for datetime string provided
example:
    @datetime = '30-Jun-2019 15:52:40';
    @utcTime = calendar.parseUtc;
calendar.time /* fetch current time string in specific timezone */
@timeStr = calendar.time;
arguments:
    @timezone - timezone in the format "America/Los_Angeles"
return:
    @timeStr - time string in the specified timezone
example:
    @timezone = 'America/Los_Angeles';
    @timeStr = calendar.time;
calendar.year /* fetch current year */
@year = calendar.year;
arguments:
    n/a
return:
    @year - current year
example:
    @year = calendar.year;
calendar.month /* fetch current month */
@month = calendar.month;
arguments:
    n/a
return:
    @month - current month
example:
    @month = calendar.month;
calendar.dayOfMonth /* fetch current day of month */
@dayOfMonth = calendar.dayOfMonth;
arguments:
    n/a
return:
    @dayOfMonth - current day of month
example:
    @dom = calendar.dayOfMonth;
calendar.dayOfWeek /* fetch current day of week */
@dayOfWeek = calendar.dayOfWeek; arguments:
    n/a
return:
    @dayOfWeekt day of week
example:
    @dow = calendar.dayOfWeek;
calendar.weekOfYear /* fetch current week of year */
@weekOfYear = calendar.weekOfYear;
arguments:
    n/a
return:
    @weekOfYear - current week of year
example:
    @woy = calendar.weekOfYear;
calendar.weekOfMonth /* fetch current week of month */
@weekOfMonth = calendar.weekOfMonth;
arguments:
    n/a
return:
    @weekOfMonth - current week of month
example:
    @wom = calendar.weekOfMonth;
calendar.hour /* fetch current hour */
@hour = calendar.hour; arguments:
    n/a
return:
    @hour - current hour
example:
    @hour = calendar.hour;
calendar.hourOfDay /* fetch current hour of day */
@hourOfDay = calendar.hourOfDay;
arguments:
    n/a
return:
    @hourOfDay - current hour of day
example:
    @hod = calendar.hourOfDay;
calendar.tzHourOfDay /* fetch current hour of day in given timezone */
@tzHourOfDay = calendar.tzHourOfDay;
arguments:
    @timezone - timezone in the format "America/Los_Angeles" (default)
return:
    @tzHourOfDay - current hour of day in the specified timezone
example:
    @timezone = 'America/Los_Angeles';
    @tzHOD = calendar.tzHourOfDay;
calendar.minute /* fetch current minute */
@minute = calendar.minute; arguments:
    n/a
return:
    @minute - current minute
example:
    @min = calendar.minute;
calendar.second /* fetch current second */
@second = calendar.second;
arguments:
    n/a
return:
    @second - current second
example:
    @sec = calendar.second;
calendar.millisecond /* fetch current millisecond */
@millisecond = calendar.millisecond;
arguments:
    n/a
return:
    @millisecond - current millisecond
example:
    @msec = calendar.millisecond;