Phi: a simple, real-time scripting language for IoT assets and data aggregation

Assetwolf supports Phi, a simple language for running calculations and small programs in real-time.

Phi is based on Twig (version 2), more info at https://twig.symfony.com/doc/2.x/ which is a simplified form of PHP.

Where is Phi used?

When data arrives from assets

When data arrives from assets into an Assetwolf portal, your application may require some calculations to be made on that data. So to make this transparent, the calculations are performed in Phi, when the data arrives.

So an asset may have any number of incoming data fields, and any number of calculated fields. The values of the calculated fields are worked out by the Phi script for the asset. 

When bubbling up in near-real time

Assets are usually arranged in groups, and those groups in a hierarchy.

For example, you may have a location, that contains floors, that contain rooms, that contain assets. (That example has four levels, but Assetwolf can handle any number of levels). Phi calculations can be put in place at any or all of these levels.

A specific location, floor, or room (in this example) are known as data pools. Data pools are a way of aggregating data from assets, with calculations bubbling up from assets at the "bottom", through levels of data pools, to a single location at the "top" of a hierarchy. The idea is, that Assetwolf can present data that is summarised, or aggregated for a location, or in any amount of detail, for the various data pools and levels below it.

Continuing this example: when all of the assets in a room have communicated once, or any one asset has communicated twice, the Phi script for the room is run, and those calculations are prepared for the level above, i.e. the floor

Similarly, when all of the rooms on a floor have prepared their calculations (or one room has been calculated twice), the Phi script on the floor runs. And so on, going up the hierarchy, eventually to a location level. A location can also have a Phi script to make high level summaries of that location's activity, such as for management reporting.

For metrics

Assetwolf supports metrics, which again can be calculated on any asset or data pool level, up to location level.

Metrics are calculated at regular intervals, such as every month, every day, every hour or every 10 minutes. These are typically used for key performance information, billing or other statistics. 

Again, metrics are written in Phi.

For triggering alarms

Assetwolf supports alarms. It's possible to define triggers for alarms (and what procedure is then carried out). 

Alarm triggers can be defined simply in the alarm trigger interface (such as some measured value exceeding an allowed range); but when more complex conditions need to be detected, Phi script can be written so as to cause a trigger to fire.

So in this way, complex rules can be written, that not only consider assets' conditions, but also data pools' conditions at any level, such as some mean value being exceeded.

What data can Phi scripts access?

Phi is different from regular Twig because it can access Assetwolf's own pools of data.

When a Phi script runs, for an asset, it can access:

  • latest values in the data received by the current asset (which can be known for sure, or assumed, if the data isn't sent every time)
  • latest values in the data from sibling assets
  • previous historical data from the current asset.

The result is usually to set the values of various calculated fields for the asset, which then are available for the data pools above.

Similarly when a Phi script runs for a data pool, it can access:

  • latest values in the data of the current data pool
  • latest values in the data from sibling data pools
  • previous historical data from the current data pool.

Phi can perform scientific calculations on any of the above data (such as sine, cosine, mean, standard deviation, etc.), and simple program logic, such as "for" loops, and "if...else" logic.

How do I access the Phi interface?

You can see the Phi scripting interface when editing a Schema — a description of an asset type, or a data pool type — in an Assetwolf portal.

The Schemas page of an Assetwolf portal specifies:

  • meta data fields (i.e. static data fields)
  • for an asset schema, incoming data fields
  • for a data pool schema, source data fields from the level below
  • the data fields that will be calculated
  • the Phi script to define the calculations.

What does Phi code look like?

Phi code is a language with C-style syntax. Variables can be defined like this:

a = 1
b = 'List of Important Bird Areas in Michigan'

String concatenation is done using the ~ operator:

message = greetingOfChoice ~ ' ' ~ recipient

...or the paste() function (which automatically adds a space):

message = paste(greetingOfChoice, recipient)

You can use single or multi-line comments, e.g.:

#Single line comment (using a hash-sign)
//Single line comment (using two forward-slashes)
/*
	Multi-line comment
*/

You can use standard mathematical operations, e.g.:

a = 1 + 2
print(a)	#3

a += 1
print(a)	#4

a++
print(a)	#5

a = 2 * 3
print(a)	#6

a = 8 - 1
print(a)	#7

a = 2 ^ 3
print(a)	#8

a = 21 % 12
print(a)	#9

While writing Phi code, you can use the print() function to check the contents of variables. (This only has an effect while testing; the data-processor ignores this function.)

if statements work like this:

shaken = true
stirred = false
speed = 12 if (shaken and not stirred) { beverage = 'cocktail' } else if (speed == 88) { beverage = 'cola' } else { beverage = 'tea' }

You can also use &&, || and ! instead of and, or, and not, e.g.:

if (shaken && !stirred) {

for loops have three different forms:

#Fixed range
for (i in 1:10) {
	print(i)
}

#Looping through an array
numbers = [2, 3, 5, 7]
for (p in numbers) {
	print(p)
}

#Looping through an object
colors = {red: 0xff0000, green: 0x00ff00, blue: 0x0000ff}
for (color, code in colors) {
	print(color, code)
}

You can use continue and break in loops, e.g.:

primes = []
for (i in 1..100) {
	if (i < 2) {
		isPrime = false
	
	} else if (i == 2) {
		isPrime = true
		
	} else {
		isPrime = true
		stop = i - 1
		
		for (j in 2..stop) {
			if (i % j == 0) {
				isPrime = false
				break
			}
		}
	}
	
	if (!isPrime) {
		continue
	}
	primes[] = i
}