Basic Expressions

Literals

1234
integer number.
1234.567
float number.
true or yes
boolean true, numeric value is 1.
false or no
boolan false, numeric value is 0.
null or undefined
no value. Note that p44script has annotated null values, which means that in many cases, null/undefined also carries a short text describing its origin. This useful to debug as it helps understanding why a value is null/undefined.
12:30
time specification in hours and minutes. Numeric value is number of seconds since midnight.
13:30:22
time specification in hours, minutes and seconds. Numeric value is number of seconds since midnight.
2.9.
date specification in day.month. (period at end is important) format. Example means 2nd of September. Numeric value is number of days from beginning of current year.
2.Sep
date specification in day.monthname (no period at end!). Monthnames are jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec. Numeric value is number of days from beginning of current year.
wed
weekday specification. Weekday names are sun, mon, tue, wed, thu, fri, sat. Numeric value is 0 for sunday, 1..6 for monday..saturday.
'text'
String literal, use two single quotes in succession to include single quote.
"text"
String literal with C-like escapes (\n, \r, \t, \xHH and \\ can be used).
{ 'field1':42, 'field2':'hello' }
JSON object.
[ 42, 43, 'hello' ]
JSON array.

Note

JSON literals are really literal, you cannot use variables in place of field names as it is possible in JavaScript.

Named values

In expressions, named values (for example sensor values in evaluator conditions, or variables in scripts) can be used. Simple values just consist of a name, but there are also structured values that can be accessed using dot and subscript notation, for example JSON objects.

sensor1
the value of a sensor named sensor1.
sensor1.age
the time in seconds since the sensor named sensor1 has been updated from hardware.
sensor1.valid
true if the sensor named sensor1 has an actual valid value (a sensor that went into timeout or has never received data from hardware since the last restart does not have a value).
sensor1.oplevel
a value between 0 and 100 indicating the "operation level" of the sensor. 0 means out of order, 100 means optimal conditions, values between indicate non-optimal conditions such as poor radio reception or low battery level. If no information is available, a null value is returned.
jsonobj.fieldA
the subfield named fieldA in a JSON object jsonobj. So with jsonobj={ 'fieldA':42, 'fieldB':'hello' }, the result would be 42.
jsonarr[1]
the array element at the numeric index 1 in a JSON array jsonarr. So if jsonvalue=[ 42, 'world' ], the result would be 'world'.
jsonobj['fieldB']
the subfield named fieldA in a JSON object jsonobj. So with jsonobj={ 'fieldA':42, 'fieldB':'hello' }, the result would be 'hello'.
jsonobj[0]
the name of the first (index==0) subfield in a JSON object jsonobj. So with jsonobj={ 'fieldA':42, 'fieldB':'hello' }, the result would be 'fieldA'. This is how json objects containing fields with unknown names (for example as returned by APIs) can be examined. The elements() function can be used to determine the number of fields in an object.

Operators

Operator Precedence Description
! 6 (highest) logical NOT
* 5 multiplication
/ 5 division
% 5 modulo (remainder)
+ 4 numeric addition, string concatenation if left hand expression is of type string, JSON array concatenation of both expressions are arrays, JSON object merging if both expressions are JSON objects.
- 4 subtraction
== 3 test for equal
= 3 test for equal (depending on context, = can also be an assignment, use == to avoid ambiguousness)
!= or <> 3 test for not equal
< 3 test for less than
> 3 test for greater than
<= 3 test for less than or equal
>= 3 test for greater than or equal
& or && 2 logical AND
| or || 1 logical OR
:= 0 (lowest) assignment
= 0 (lowest) assignment (depending on context, = can also be a test for equal, use := to avoid ambiguousness)

General Functions

abs(a)
absolute value of a.
annotation(something)
Returns the annotation of something as a string. The annotation may describe the type or origin of a value more precisely, e.g. for null\ values the annotation can often give information why a value is null.
binary(hexstring [, spacesallowed])
converts hexstring into a string (which can contain non-printable binary characters, including NUL) by exepcting 2 hex digits in hexstring for each output byte. Hex bytes might be separated by colons or dashes. If spacesallowed is set, bytes can also be separated by spaces, and bytes will also be considered complete if consisting of one hex digit only when preceeded and followed by a colon, dash or space. Also see hex().
bit(bitno, value)
bit(frombitno, tobitno, value, [, signed])
the first form extracts a single bit as indicated by bitno (least significant bit has bitno==0) from value.
The second form extracts a range of bits (between frombitno and tobitno) as a integer number. If signed is set, the bit range is treated as a signed integer (2's complement).
boolean(something)
Interprets something as a boolean. If something is an empty string, undefined, null or the number 0, the result is false or 0, otherwise true or 1.
chr(byte)
returns a string consisting of a single byte (value between 0 and 255). Note that not all values for byte create a valid (UTF8) string. However, this function is primarily indended to create binary byte strings - p44script strings can contain 0 bytes. Also see ord().
cyclic(x, a, b)
return x with wraparound into range a..b (not including b, because it means the same thing as a).
dawn()
returns approximate time of dawn on the current day, but cannot directly trigger timed actions.
describe(something)
Describes something as a string containing information on data type, internal name, annotation, etc. This function is useful for debugging purposes.
day()
returns current day of month, but cannot directly trigger timed actions.
dusk()
returns approximate time of dusk on the current day, but cannot directly trigger timed actions.
elements(_array_or_object_).
If array_or_object is an array, elements returns the number of array elements. If array_or_object is a JSON object, returns the number of fields (whose names can be obtained with array_or_object[numeric_index]). If array_or_object by type cannot have any elements accessible by index, undefined will be returned (but the number 0 if the object or array is currently empty).
epochtime()
epochtime(dayseconds [, yeardays, [, year]])
without arguments, the function returns unix epoch time (seconds since 1.1.1970 midnight UTC), but cannot directly trigger timed actions.
With dayseconds argument only, the function returns epoch time of today with the time-of-day as represented by dayseconds.
With yeardays, the function returns the epoch time represented by dayseconds/yeardays in the current year.
With year the function returns the epoch time for the point in time corresponding to dayseconds/yeardays/year.
epochdays()
returns unix epoch time (days since 1.1.1970), with time-of-day as fractional part, but cannot directly trigger timed actions.
error(anything)
creates a error value with anything as error message.
errorcode(errvalue)
returns the numeric error code from errvalue.
errordomain(errvalue)
returns the error domain string from errvalue.
errormessage(errvalue)
returns the error message string from errvalue.
eval(string)
evaluate string as expression.
find(haystack, needle [, from [, caseinsensitive]])
returns position of needle in haystack or null if not found. if from is specified, search starts at that position in haystack. if caseinsensitive is true, search is done case insensitively (simple ASCII-only case insensitivity).
flipbit(bitno, value)
flipbit(frombitno, tobitno, value)
the first form returns value modified by flipping (inverting) a single bit as indicated by bitno (least significant bit has bitno==0).
The second form returns value modified by flipping a range of bits (between frombitno and tobitno).
format(formatstring, value [, value...])
formats value_s as string according to printf-like _formatstring (but note that formatstring only supports a subset of printf. In particular, no 'h', 'l' or 'll' length specifiers are allowed (nor needed). Recognized formatting chars are d,u,x,X for integer formatting, e,E,g,G,f for floating point, and s for strings.
formattime()
formattime(time)
formattime(formatstring)
formattime(time, formatstring)
returns formatted time. Without arguments, formattime returns the current date and time like 2020-08-19 11:04:42.
With only a numeric time argument, the specified time is formatted. When time is a time-of-day (between 0 and 24*60*60 seconds), only time is shown like 11:04:42 , otherwise time is treated as absolute unix epoch timestamp (see epochtime() function) and formatted as date+time by default.
formatstring can be used to set a specific date and time format, using the formatting capabilities of the strftime() C standard function.
frac(a)
fractional value of a (with same sign as a).
hex(string [, separator])
returns the bytes contained in string (which might be a binary string containing non-printables and NUL characters) as a string of hexadecimal characters. If separator is specified, it will be used as separator between two-digit hex bytes (only first character of separator is used). Also see binary().
hour()
returns current hour, but cannot directly trigger timed actions.
if(c, a, b)
returns a if c evaluates to true, b otherwise.
ifvalid(a, b)
returns a if a is a valid value (not null, error or non-data), otherwise return b.
ifok(a, b)
returns a if accessing a does not return an error, otherwise return b.
int(a)
integer value of a.
isvalid(a)
returns true if a is a valid value (not null, error or non-data), false otherwise.
isok(a)
returns true if accessing a does not return an error, false otherwise.
json(anything [, comments_allowed]).
tries to interpret something as JSON. If something is a string, the string is treated as JSON text and parsed (rather than converted to a single JSON string). If a JSON syntax error occurs during parsing, json returns an appropriate error. If comments_allowed is set to true, /* ... */ comments are allowed in the JSON text (which does not conform to the JSON specification, but is convenient). This function is useful, for example, to make a JSON string returned by an API accessible as a structure.
jsonresource(jsonfile)
Loads JSON from jsonfile. Without absolute path specification or special prefix, jsonfile is taken to be relative to the application's resource directory (which is usually /usr/share/application name). In JSON resource files, /* ... */ comments are allowed (although this is not in accordance with the JSON specification).

The following special prefixes can be used:

  • _/tempfile : means that tempfile is to be used relative to the (volatile) temporary directory (usually /tmp).
  • +/resource : is equivalent to resource alone, so relative to the resource directory.
  • =/datafile : means that datafile is to be used relative to the (permanent) application data directory (usually /flash).

Note

To use paths or prefixes other than _/ for filename, the userlevel must be >=2 (default userlevel for production devices is 0).

lastarg(a1, a2, ... , aN)
returns aN. This can be useful to execute side effects of other arguments first, before returning aN.
limited(x, a, b)
returns min(max(x, a), b), i.e. x limited to values between and including a and b.
lowercase(s)
returns (simple ASCII) lowercase version of string s.
macaddress()
returns the MAC address of the wired network interface as a string of 12 hex digits (no separators between bytes).
maprange(x, a1, b1, a2, b2)
maps x in the range of values a1..b1 linearly to the range of values a2..b2. For all values of x coming before a1 the result is a2, for all values of x following b1 the result is b2.
max(a, b)
return the bigger value of a and b.
min(a, b)
return the smaller value of a and b.
minute()
returns current minute, but cannot directly trigger timed actions.
month()
returns current month (1..12), but cannot directly trigger timed actions.
nextversion()
returns the next installable firmware version, when known (from automatic checking or a recent manual check for firmware upgrade). Otherwise an empty string is returned.
number(anything)
try to convert anything into a number, returns 0 if conversion fails.
ord(string)
returns the byte (value between 0 and 255) stored in the beginning of string. Note that this first byte might not be a complete UTF8 character, but possibly only a part of it. However, this function is primarily indended to decode binary byte strings - p44script strings can contain 0 bytes. Also see chr().
productversion()
returns the product's firmware version.
random(a, b)
random(a, b, step)
returns a (floating point) pseudo-random value from a up to and including b. If step is specified, the result is equal to a plus an integer multiple of step. I.e. to get an integer between 1 and 7, random(1,7,1) can be used. In this case, the randomness is evenly distributed among the possible outcomes (unlike, say, int(random(1,7)), which would have a vanishingly small probability for outcome 7 compared to 1..6). step does not have to be an integer; e.g. if quarters are required, random(1,7,0.25) can be used.
round(a [, p])
round a to optionally specified precision p (1=integer=default, 0.5=halves, 100=hundreds, etc...).
second()
returns current second, but cannot directly trigger timed actions.
setbit(bitno, newbit, value)
setbit(frombitno, tobitno, newvalue, value)
the first form returns value modified by setting a single bit as indicated by bitno (least significant bit has bitno==0) to the boolean value of newbit.
The second form returns value modified by setting a range of bits (between frombitno and tobitno) to the integer number newvalue (using as many least significant bits from newvalue as is needed to fill the range).
string(anything)
returns a string representation of anything. Always returns a descriptive string, even for null/undefined and error values.
strlen(string)
returns length of string.
strrep(string, count)
returns a string consisting of count times the string, concatenated.
substr(string, from [, count])
returns string starting at from in string, limited to count characters if specified. If from is negative, start position is relative to the end of string.
sunrise()
returns approximate time of sunrise on the current day, but cannot directly trigger timed actions.
sunset()
returns approximate time of sunset on the current day, but cannot directly trigger timed actions.
timeofday()
returns current time-of-day in seconds since midnight, but cannot directly trigger timed actions.
uppercase(s)
returns (simple ASCII) uppercase version of string s.
weekday()
returns current weekday (0..6), but cannot directly trigger timed actions.
year()
returns current year, but cannot directly trigger timed actions.
yearday()
returns current date as day number in current year, but cannot directly trigger timed actions.

Functions for timed triggering

Please note that you need to use the following functions to test for time/date in evaluator or trigger conditions. Just writing something like timeofday()==12:30 will not work! General functions returning time values such as sunrise or dusk are meant to be used as arguments to is_time or after_time, or to implement additional checks at the time when the expression is triggered by one of the following functions.

is_time(time)
returns true when time of day is time (with 5 seconds tolerance) and triggers evaluation once per day when time is reached.
is_weekday(weekday1 [, weekday2, ...])
returns true if today is any of the specifieds weekdayN arguments and triggers evaluation at the beginning of the specified days.
after_time(time)
return true when time of day is time or later and triggers evaluation once per day when time is reached.
between_dates(date1, date2)
true when current date is between date1 and date2 (inclusive) and triggers evaluation at beginning of date1 and at the end of date2.
initial()
returns true if this is a initial run of a trigger expression, meaning after startup or expression changes.
every(interval [, syncoffset])
returns true once every interval (specified in seconds) and triggers evaluation.
Note: true is returned at first evaluation, or, if syncoffset is set, at next integer number of intervals calculated from beginning of the day + syncoffset.

So every(0:30,0:22) will trigger once every half hour, starting at 0:22, then 0:52, 1:22, ...

Attention - be careful with short intervals

Be careful with every() and short intervals, because running scripts very often can affect the performance of the device!

testlater(seconds, timedtest [, retrigger])
returns undefined/null when called first, but schedules a re-evaluation after given seconds and return value of test then. If retrigger is true then, the re-evaluation will repeat in seconds interval (use with care!).

Scripts

Full p44scripts (beyond single expressions as described above) can do various things using the features described below. The script language has syntax similar to JavaScript or C, but far from identical.

Comments

/* comment */
C-style comment.
// comment
C++-style comment continues until end of line.

Declarations

The first (optional) part of a script can be declarations of global variables and global functions. (handlers can also be in the declaration part if their trigger condition does not reference objects created only at runtime of the script). The first non-declaration statement in a script ends the declaration part. Declarations therefore cannot be mixed with actual script statements. Exceptions are handlers (see above) and global variable declarations; these can occur both in the declaration part as well as mixed with normal script statements.

Global Variables

glob g
global g
declaration of global variable g.
Note that a declaration of a global variable without initializing is also possible outside the declaration part of a script (in particular, in scripts that do not allow declarations, such as P44-DSB evaluator actions or scene scripts).
glob g = 78/9
declaration and initialisation of global variable g.
Note that the initialisation expression can only consist of constants or other global variables that are already defined.
Local variables and scriptcontext-dependent functions and objects are not available at declaration time.
Also note that initialisation of global variables is only possible in a script's declaration part (and not all scripts allow declarations at all, for example P44-DSB evaluator actions or scene scripts can not include declarations).

Visibility of global variables

Global variables are truly global, i.e. they are visible from all scripts and script parts (such as trigger conditions, scene scripts, triggers, etc.) throughout the system. Therefore, it is recommended to use unique names that do not happen to occur elsewhere as well. In general, global variables should also not be used if scriptcontext variables are also sufficient.

Functions

function hello()
{
  log('Hello World');
}

Declares a globally available function named hello which logs the text 'hello world'.

global function sum(a,b)
{
  var c = a+b
  return c
}

Declares a globally available function named sum with two arguments, which calculates and returns the sum in a local variable c (which exists privately for the function only during its execution). The global prefix is optional, but helps for clarity when local functions are in use as well (see below)

Visibility of variables in functions

In functions, the variables defined locally with var, the variables of the context from which the function is called (see local variables) and finally the global variables are visible. If a variable defined in the function has the same name as one of the calling context or a global variable, then the local variable is used in expressions and assignments.

local function diff(a,b)
{
  var c = a-b
  return c
}

Declares a function that is only visible/callable in the current context (such as: mainscript, device, trigger). This can be useful to avoid a function with no general use (e.g. one that is specific to a certain scripted device implementation) to be globally visible from other context, where calling it makes no sense or even would cause harm.

local functions are not allowed in the declaration part

Although local functions are a kind of "declarations", these must be defined the main script part, not in the declaration part. Vice versa, global functions can be defined only in the declaration part.

(Event) Handlers

A handler is a special construct provided by p44Script to easily react to asynchronous events of all kinds, such as the change of a sensor value, a keystroke, a web access, the termination of a thread, the reaching of a certain time of day or the expiration of an interval, and many more.

In principle, handlers are declarations (similar to functions, they are not executed immediately, but only defined for later execution). That is why they are described in this section.

However, there are situations where the trigger condition of a handler refers to an object that is only created in the course of the script execution. In such cases, the handler can only be created afterwards, and thus comes into the statement part, like this example:

var s = udpsocket("127.0.0.1", 4242, true, true);
on (s.message()) as msg
{
  log('Message on UDP port 4242: '+string(msg))
}

This first creates and opens a network socket listening for UDP packets on port 4242, and then defines a handler which triggers whenever a UDP packet arrives. (To send a UDP test packet on a unix command line, for example type: echo -n "Test" | socat stdin "udp-sendto:127.0.0.1:4242")

The following examples, on the other hand, only refer to objects that already exist when the script is started and can therefore be placed in the declaration part:

on (after_time(12:00))
{
  log('noon has passed ' + string(timeofday()-12:00) + ' seconds ago')
}

Defines a event handler (piece of code that is executed when a specified condition is met).

This handler is triggered by local time passing noon (12:00) and logs the number of seconds actually passed since noon - because when this handler is installed any time after 12:00, it will be run once immediately.

By default, handlers are run when the trigger condition changes from false to true (but not when it changes from true to false).

on (every(0:15, 0))
{
  log('another quarter of an hour has passed');
}

Defines a event handler that is run every whole quarter of an hour.

on (sensor1>20) toggling
{
  log('sensor1 is ' + sensor1);
}

Defines a event handler that triggers whenever the condition toggles from false to true or vice versa. Note that sensor1 must be a event source for this to work. Contexts that have hardware inputs such as sensors usually make these available as event sources (e.g. in P44-DSB Evaluators). For example, valuesource() or device.sensor() are event sources.

on (sensor2) changing
{
  log('sensor2 is ' + sensor2)
}

Defines a event handler that triggers whenever condition expressions re-evaluates to a different result as in the last evaluation.

on (sensor2) evaluating
{
  log('sensor2 is ' + sensor2)
}

Defines a event handler that triggers whenever the condition expression is evaluated, even if the result does not change. Note: use this with care, depending on the event sources involved this can cause the handler to be run very often, which might cause performance problems.

on (sensor1+sensor2) changing as sensorsum
{
  log('sensor1+sensor2 is ' + sensorsum)
}

Defines a event handler that triggers whenever the sum of sensor1+sensor2 changes. The as sensorsum part stores the result of the trigger expression into the variable sensorsum.

on (sensor1>22) stable 0:05 as sens
{
  log('sensor1: stable for 5 minutes at > 22, currently: ' + sens)
}

Defines a event handler that triggers when sensor1 remains over 22 for at least 5 minutes.

on (featureevent()) as event
{
  log('received event ' + event)
}

Defines a event handler that triggers whenever featureevent() triggers and stores the event in the variable event.

Note that for event sources that deliver one-time events like featureevent() does, storing the actual trigger value in a variable using as is important.

Warning

As powerful as the handlers are, it is also possible to construct trigger conditions that lead to very frequent execution of the handler and thus potentially blocking or degrading the device's normal operation.

Local Variables

var a
creates a scriptcontext local or function local (if used in a function) variable a (initially set to undefined. If a already exists, nothing happens.
var a = 7
Definition of a scriptcontext local or function local variable a and assignment of an initial value. If the variable already exists, this is like a normal assignment.

Variable contexts

Variables created with var are either completely local (if defined within a function) or local to the respective scriptcontext, and then called context variable. There are different scriptcontexts, each with its own variables. For example, in a P44-DSB or LC device, all scene scripts of a fixture run in a scriptcontext of that fixture, i.e. the scene scripts among themselves see the same local variables, but other fixtures or other devices do not have access to them (and therefore can use their own variables with the same name without conflicts).

threadvar t
threadvar t = 7
Definition of a thread-local variable t (and assignment of an initial value if necessary). Unlike var, this variable has a private value for each running execution thread (i.e. execution of an on(...) {...} or catch {...} handler).

Automatically created thread variables

Thread variables are also created automatically by on(...) as variablenname {...} or catch as variablenname {...}, since the value may be different for each (possibly parallel) run of the code. (Whereas concurrent as variablenname does not create a thread variable, but one in the context of the script that executes concurrent).

Statements

glob b
Creates a global variable b (all scripts and conditional expressions can access it). Global variables can (and usually should) also be declared before the script starts, see Declarations.
glob b = 42
Creates a global variable b and assigns it an initial value. See also Declarations.
unset b
removal of a (local or global) variable named b.
unset a.b
unset a['b']
removal of field b from object variable named a.
unset c[2]
removal of element with index 2 (= third element) from an array named c.
a := 3*4
Assignment of an expression's result to a.
a = 3*4
single equal sign is also treated as assignment.
let a = 3*4
let can be used to more clearly show this is an assignment (but is completely optional).
a = 7; b = 3; c = a*b;
multiple statements can be written on a single line separated by semicolons.
{ a = 42; b = 23; scene(_'bright'_); }
block of multiple statements.

Control flow

if (a>0) b := 7

conditional execution of the assignment b:=7 when a is greater than zero.

if (a>0) b := 7 else b := 19

conditional execution of the assignment b:=7 with else statement which is executed when condition is not true.

if (a>0) { b := 7; c := 44 } else { b := 0; c := "a string }

conditional with statement blocks.

while (i>0) { i := i-1; log(5, "i is now: " + i); }

repeat (loop) statement block as long as i is greater than zero.

break

exits a while loop.

continue

starts the next iteration in a while loop.

return

ends the current script with null/undefined return value.

return value

ends the current script returning value.

try { statements; } catch { handling_statements }

if any of statements generate a run time error, the handling_statements will be executed.

try { statements; } catch as error_var { handling_statements }

if any of statements generate a run time error, the handling_statements will be executed and can use error_var to examine the error.

return is considered successful even if it returns an error

A return statement does not itself generate an error, even if it returns an error. I.e. try { return error('error') } catch as e { ... } will not catch the 'error' with catch, but return it to the calling function.

throw(anything)

generate a run time error with anything as error message. If anything is already an error value, the error value is re-thrown as-is.

Concurrency (threads)

p44script has a very convenient construct for creating concurrently running sequences of operations, something that is often needed in automation. A concurrently running sequence of script statements is also called a thread Note that event handlers as shown above also evaluate and run concurrently with other script activity.

concurrent {
  while(true) { log('blink1'); delay(3) }
}
concurrent {
  while(true) { log('blink2'); delay(2.7) }
}
delay(0:02); abort();

run two asynchronously blinking sequences in parallel. After 2 minutes, both concurrent threads are stopped using the abort() function.

concurrent as blinker {
  while(true) { log('blink'); delay(2) }
}
delay(0:02); abort(blinker)

start a concurrent blinking sequence and save a reference to it as a variable named blinker.

After two minutes, the variable can be used as argument to abort() to specifically stop a thread.

concurrent as task1 { delay(random(2,5)); log('task1 done') };
concurrent as task2 { delay(random(2,5)); log('task2 done') };
await(task1,task2)
log('one of both tasks done')
await(task1)
await(task2)
log('both tasks are done now')

start two concurrent task with a random duration between 2 and 5 seconds.

The await() function can be used to wait for completion of one of its arguments. Using a separate await() call for each thread will complete when all tasks are done. For complicated concurrency situations, also see lock() and and signal() for synchronizing threads.

General functions within scripts

abort()
aborts all concurrently running threads in the current scriptcontext, except the thread from where abort() is called.
abort(a [, abortresult [, self]])
aborts the thread named a, i.e. the thread started with concurrent as a {...}. If abortresult is not set to null, it will be used as the final result of the thread (returned e.g. by await()). If self is not set, and a is the current thread (i.e. the one from which abort() was called), then it will not be aborted, because that is usually not intended and hard to debug - if it is, self can be set.
await(a [,b,...] [, timeout])
waits for one or multiple events to happen, for example a signal arriving (see signal()) or threads terminating, and returns the event's value. if timeout is used, await will quit waiting after the specified time and return undefined.
delay(seconds)
delays the script execution by seconds (floating point number; fractions of seconds are possible).
delayutil(dayseconds|epochtime)
delay script execution until the time dayseconds (number of seconds since midnight, see timeofday() function) or epochtime (number of seconds since 1/1/1970 midnight UTC, see epochtime() function) is reached.
Note that time literals also return seconds since midnight, so delayutil(16:00) will wait until four o'clock.
httprequest(request [, data])

This is a generalized function that can be used instead of geturl(), puturl(), _posturl() when http requests with special headers, certificate checking, client certificate etc. are needed. All parameters are to be passed a request JSON object:

  • url: the complete URL. May contain a ! as the first character to prevent certificate checking for https connections.
  • method : Optional, without specification GET will be used.
  • data : Data to send for PUT and POST, can also be passed directly as the second parameter of the function (especially necessary if it is a binary string). If data is a JSON value, the data is sent with Content_type: application/json on PUT and POST, otherwise with Content_type: text/html unless otherwise specified in headers.
  • formdata : optional boolean value. If this option is set to true and data is a JSON object, the fields of the JSON object will be sent as form fields with Content_type: application/x-www-form-urlencoded.
  • timeout : optional timeout in seconds.
  • user : optional username for http auth (can also be passed as part of the URL).
  • password : optional password for http auth (can also be passed as part of the URL).
  • basicauth : optional mode for using http basic auth. By default, basic auth is only possible on https connections, and only if the server asks for it. With basicauth=onrequest basic auth is also allowed in non-SSL connections if the server asks for it. With basicauth=immediate, basic auth is sent with the first request (which may be necessary for simple IoT web servers, but offers very poor security without SSL). With basicauth=never, basic auth is not allowed at all, not even for SSL connections.
  • headers : optional JSON object containing http headers to send as name/value pairs. If no content-type is included, this header will be determined automatically from the supplied data (html or json).
  • withmeta : optional boolean. If set to true, httprequest will return a JSON object containing a status field with the http status code (success or error), a data field with the actual response (if any) and a headers field with all received headers as name/value pairs. Note that in this mode any request that leads to a http response will not be considered an error, regardless of the http status (whereas otherwise, only status 200..203 will be considered ok).
  • clientcert : optional path to a client certificate to use.
  • servercert : optional path to a root certificate or certificate directory to be used for checking the server certificate. If an empty string is specified, the server certificate will not be checked (which can also be achieved with a ! before the url, see above). By default, the server certificate is checked against the system's root certificates.
geturl(url [,timeout])
returns response from GET request to (http or https) url, times out after timeout seconds if specified.
lock([entered])
Creates a Lock object, which can be used with its two methods .enter() and .leave() for synchronization of parallel running threads. If entered==true is specified, then the lock is already reserved for the calling thread when it is created. Normally one assigns a created lock to a variable: var l = lock(). The value of the lock is 0 if it is free, otherwise it specifies the nesting depth of the enter() calls of the thread that currently has reserved the lock. Usually enter() is used in a conditional:
      var lk = lock()

      // ...

      if (lk.enter(0:03)) {
        // Sequentially executing code
        // ...
        lk.leave()
      }
      else {
        log("3min timeout")
      }
lk.enter([timeout])
Waits until the lock lk is free. Without specifying timeout, the wait is indefinite. timeout can be 0 to only test the lock. The function returns true if the lock could be reserved for the calling thread. It returns false if the lock could not be reserved within the given timeout, or if the lock itself is deleted (for example with unset).
lk.leave()

Releases the lock lk. If other threads are waiting for the release, the thread that had called lk.enter() first gets access. leave() returns true if a release has actually been made with it. leave() from a thread that has not called enter() before returns false and does nothing.

Warning - beware of deadlocks

If locks are not used properly, it is easy to get into a situation where two threads have reserved one lock each, and are waiting for each other to release the other thread's lock, which may never happen. This is called deadlock and must be avoided. It is therefore recommended to use enter with timeout, so that a script application cannot block itself completely.

log([loglevel, ] logmessage [, value...])
writes a message to the application log file. If loglevel is omitted, 5 (LOG_NOTICE) is used. If logmessage is followed by further arguments, logmessage is treated as a format string, as in the format() function (see there).
loglevel()
returns the current log level.
loglevel(level [, deltatime])
change the application's log level to level. If deltatime is set, timestamps will also show time difference to previous log entry in mS.
logleveloffset(offset)
change the log level offset of the scriptcontext the script runs in to offset. This allows making part of an application more or less verbose in the log, selectively.
maxblocktime([time, [currentonly]])

return, or (with time argument, set) the maximum uninterrupted execution time for scripts (in seconds). The default is 0.05 seconds. When set with currentonly true, the setting only affects the current thread, otherwise this also sets the default for all future threads started from now on (but not already running threads, except for the calling thread).

High maxblocktime can severely affect device performance

setting maxblocktime too high will allow scripts to run uninterrupted for the specified time, without any other device function getting time to run, which might causing sluggish or blocked response including web interface. Recommended maxblocktime is under one second.

maxruntime([time|undefined])
return, or (with time argument, set) the overall run time limit for the current thread (in seconds). The default is infinite (represented by undefined).
posturl(url [,timeout][,data])
sends data (must be string or JSON) with POST to url, returns response, times out after timeout seconds if specified. If data is a JSON value, the data is sent with Content_type: application/json, otherwise with Content_type: application/x-www-form-urlencoded.
puturl(url [,timeout][,data])
sends data (must be string or JSON) with PUT to url, returns response, times out after timeout seconds if specified. If data is a JSON value, the data is sent with Content_type: application/json, otherwise with Content_type: application/x-www-form-urlencoded.
readfile(filename)
reads the file filename as string.

Without absolute path specification or special prefix, filename is taken to be relative to the p44script data directory (a directory in permanent flash memory). The following special prefixes can be used:

  • _/tempfile : means that tempfile is to be used relative to the (volatile) p44script temporary directory.
  • +/resource : means that resource is to be used relative to the resource directory of the application
  • =/file : is equivalent to file alone, so relative to the p44script data directory

Note

To use paths or prefixes other than _/ for filename, the userlevel must be >=1 (default userlevel for production devices is 0).

restartapp(["reboot"|"shutdown"|"upgrade"])
restarts the application (on P44-xx devices this is the vdcd). Without arguments only the application is restarted, not the whole device (this usually takes only a few seconds, but may take longer if there are many DALI devices connected). This "small" restart is especially useful when developing/testing more complex scripts.

With "reboot" as parameter the device is completely restarted, with "shutdown" it is shut down and stopped, so that it only starts again when it is disconnected from the power supply and reconnected. With "upgrade" an upgrade to the latest available firmware is triggered (which triggers a reboot in any case, even if no new firmware is available).

Caution

Using restartapp() in scripts that are executed automatically (e.g. the mainscript) may cause the device to enter a reboot loop that can only be interrupted by a factory reset. So please consider carefully whether the conditions that lead to the execution of restartapp() are correct and do not trigger again immediately after each restart!

shellquote(argument)
returns argument interpreted as string and single quote enclosed such that it safely works as a single shell argument (even if argument contains single quotes itself) and no shell variable expansion occurs.
signal()
creates a "signal" object which can be assigned to a (possibly global) variable (signal_var below). Threads can wait for the arrival of the signal with await(), or the signal variable can be used as a trigger condition in on()\ handlers. The signal object has a send() method that can be used to trigger (send) a signal, see below.
signal_var.send()
sends a signal with the signal value = true (numeric 1)
signal_var.send(signalvalue)
sends a signal with signalvalue. signalvalue can be any value, but remember that simple on(condition) {} handlers only trigger when condition evaluates to a "trueish" value. To catch all values, including null, undefined, 0, false, on(condition) evaluating {} can be used. Waiting for a signal with await() returns on any signal value, including all "falsish" ones.
system(commandline)

executes commandline using the platform's shell and returns the output as string.

Note

The system() function is only available when userlevel is >=2 (default userlevel for production devices is 0)

Warning

As the system() function can call any OS-level command, it can seriously damage the firmware up to bricking the device. That's why it is not enabled by default. If you have a DIY P44-DSB-X, you can set the userlevel to >=2 to use it, but be careful!

udpsocket(host, port, receive, nonlocal, broadcast)
returns a UDP socket configured to send packets to host:port. Use .send(string) method to send data
If receive is true, the socket can also receive UDP packets (via its .message() method, which can be used in on() statements as trigger.
If nonlocal is true, the socket can also receive UDP packets originating from other hosts than just localhost (which is the default).
If broadcast is true, the socket can send and receive broadcast packets
Usually the return value of this function is assigned to a variable: var udpsocket_var = udpsocket(...) to be able to call the methods described below.
udpsocket_var.message()
is a event source which returns UDP messages received on the socket. It can be used in on() statements as trigger condition and can also be await()-ed.
udpsocket_var.send(string)
sends string as UDP packet.
undeclare()
deletes all function and handler definitions that are not part of a stored script, but were made in an ad-hoc way, for example from a interactive script command line (a REPL = Read-Execute-Print-Loop).
Note that functions and handlers defined in a script text stored in the application (such as trigger actions, scene scripts, etc.) remain tied to these script texts, i.e. are updated (or removed) according to edits in the script text, and will not be removed by undeclare().
urlencode(text [, x-www-form-urlencoded])
Encodes the text for use in a URL or, if x-www-form-urlencoded is true, for sending form fields in POST data.
websocket(url [, protocol [,pinginterval]])
Creates a websocket connection to the given url with the protocol ("Sec-WebSocket-Protocol" header) protocol and the given pinginterval.
Usually one assigns the return value of this function to a variable: var websocket_var = websocket(...) to be able to call the methods described further below.
websocket(configobj)

In this variant all parameters are passed in configobj. This allows for more advanced options. The following fields are supported:

  • url: the websocket URL (ws: or wss:)
  • protocol: the subprotocol ("Sec-WebSocket-Protocol"-header)
  • pinginterval: the ping interval
  • headers : optional JSON object containing http headers to send as name/value pairs.

Usually the return value of this function is assigned to a variable: var websocket_var = websocket(...) to call the methods described below.

websocket_var.message()
is a event source which returns websocket messages received on the socket. It can be used in on() statements as trigger condition and can also be await()-ed.
websocket_var.send(message [, opcode])
sends message as a websocket message. If opcode is not specified, the message will be sent as "text" (opcode 1).
websocket_var.close([closecode [, reason]])
closes the websocket with the close-code "Normal" (1000), or the given closecode. If specified, reason is included in the close-message.
writefile(filename, something [, append])
converts something to a string, and writes the result as file filename. If something == null, filename is deleted. If append is set to true, something is appended to the end of the file if it already exists, otherwise an already existing file is overwritten.

Without absolute path specification or special prefix, filename is taken to be relative to the p44script data directory (a directory in permanent flash memory). The following special prefixes can be used:

  • _/tempfile : means that tempfile is to be used relative to the (volatile) p44script temporary directory.
  • +/resource : means that resource is to be used relative to the resource directory of the application
  • =/file : is equivalent to file alone, so relative to the p44script data directory

Note

To use paths or prefixes other than _/ for filename, the userlevel must be >=2 (default userlevel for production devices is 0).

Controlling device/product specific subsystems

Note - Product specific features

The features described in the following sections depend on the device type, especially the control of external hardware via GPIO connectors, which needs specially equipped device variants (e.g. the LED controller P44-LC-LED, or the automation controller P44-AC-DCM), or then DIY setups like the P44-DSB-X image on Raspberry Pi.

P44-LC: Functions for use in trigger actions or global scripts

scene(name [, transition_time])
invoke scene by name, with optional transition_time in seconds.
scene(id, zone_or_device [, transition_time])
invoke light scene by id (numeric digitalSTROM scene or generic name like "preset 1") in zone (room/zone by name), with optional transition_time in seconds.
scene(id, zone_or_device, transition_time, group)
invoke scene in group ('light', 'shadow', 'heating'...) by id (numeric digitalSTROM scene or generic name like 'preset 1') in zone_or_device (in room/zone by name or in a single device by name or dSUID), with optional transition_time in seconds.
sceneid(name)
returns the id of the scene (as a generic action description such as 'preset 1') named name , or null if no such scene exists.
sceneno(name)
returns the (internal, dS) scene number of the user named scene name or having the id (generic action description) name, or null if no such scene exists.
set(zone_or_device, value [, transition_time])
set brightness level of a room/zone or a single device (by name or dSUID) to value (0..100), with optional transition_time in seconds.
set(zone_or_device, value, transitiontime, channelid)
set channel specified by channelid ('hue', 'saturation', 'hPos'... - note: channelIDs are case sensitive!) to value in the room/zone or ingle device (by name or dSUID), with transition_time in seconds.
trigger(triggername)
execute the action of the trigger named triggername.
savescene(name)
savescene(id, zone [, group])
save scene by name or by id, zone and group (default group is 'light').

P44-DSB/P44-LC: Access to devices and API

Device Level

Note: the following functions are available in device level scripts (such as scene scripts). But basically, these functions are members of the device object or of one of its members. The device object can also be obtained from global scripts using the device function, see below. This allows statements like: device('mydevicename').output.channel('brightness',75)

output
Represents the output of a device (for devices without output, this is undefined).
output.applychannels([force [, transitionTimeOverride]])
apply changes to channels made with dimchannel and channel. If force is true, even unchanged channels will be re-applied to the hardware. If transitionTimeOverride is set, transition times of all changing channels will be overridden with the specified value. Otherwise, the transition times as loaded from a scene with loadscene or individually set with channel apply.
output.syncchannels()
force reading back actual channel values from hardware.
output.channel(channelid)
returns the current value of channelid ('brightness', 'hue', 'saturation', 'colortemp', 'hPos' - note: channelIDs are case sensitive!)
Note that channels have subfields named .valid (true if the channel is know in sync with the hardware), .age (seconds since last change), and .oplevel (a value between 0..100 indicating the overall "health" level of the device the channel belongs to, which includes radio reception, battery level etc.). A channel is an event source and can be used in on() statements as trigger condition and can also be await()-ed.
output.channel(channelid, value[, transition_time])
set the value of channelid to value, with optional transition_time in seconds.
Note that the channel value change is not automatically applied to the hardware; you need to call applychannels() for that. Also note that channelids are case sensitive!
output.dimchannel(channelid, valuechange, transition_time)
change the value of channelid by valuechange, with optional transition_time in seconds. Note that channelids are case sensitive!
output.loadscene(sceneIdOrNo [, transitionTimeOverride])
loads the channels of the output with the values stored in the scene sceneIdOrNo. The scene is determined by its number or preset name (e.g. 'bell 1' or 'preset 2'). If transitionTimeOverride is specified, the transition times of all channels are set to this value, even if the scene defines other values. Please note that the loaded new channel values are not automatically applied to the hardware; applychannels() must be called for this. loadscene() can be used e.g. to load a scene but still adjust it with channel() before applying it to the outputs.
output.runactions(sceneIdOrNo)
runs the scene actions (e.g. blinking, or starting a scene script) of scene sceneIdOrNo. The scene is determined by its number or preset name (e.g. 'bell 1' or 'preset 2').
output.stopactions()
stops all running scene actions (scene scripts, blinking, etc.).
sensor('id_or_index')
returns the current value of a sensor input. id_or_index can be either the index of the sensor (starting at 0), or the text id (a string like 'temperature' or 'humidity', depending on what the device actually provides)
Note that sensors have subfields named .valid (true if the sensor has an actual value), .age (seconds since last update), and .oplevel (a value between 0..100 indicating the overall "health" level of the sensor, which includes radio reception, battery level etc.). A sensor is an event source and can be used in on() statements as trigger condition and can also be await()-ed.
input('id_or_index')
returns the current value of a binary input. id_or_index can be either the index of the binary input (starting at 0), or the text id (a string like 'rain' or 'window_handle', depending on what the device actually provides). Input values are usually 0 or 1, but special inputs like window handles might have more states (2,3...).
Note that inputs have subfields named .valid (true if the input state is known), .age (seconds since last update), and .oplevel (a value between 0..100 indicating the overall "health" level of the input, which includes radio reception, battery level etc.). A input is an event source and can be used in on() statements as trigger condition and can also be await()-ed.
button('id_or_index')
returns the current value from a button input. id_or_index can be either the index of the button (starting at 0), or the text id (a string like 'up' or 'down', depending on the type of button). The value represents the number of clicks detected (1..4) or >4 when the button is held down.
Note that buttons have subfields named .valid (true if the button state is known), .age (seconds since button was last operated), and .oplevel (a value between 0..100 indicating the overall "health" level of the button, which includes radio reception, battery level etc.). A button is an event source and can be used in on() statements as trigger condition and can also be await()-ed.
view
P44-DSB-X, P44-LC-E+L, P44-LC-LED with WS281x ledchains only: access the root view of a ledchain device. The object returned is a P44lgrView, which has functions of its own to (re-)configure and find views in its view hierarchy.

Global Level

Note

these global functions allow accessing specific devices in the P44-DSB/P44-LC directly, or indirectly by using the vDC API. These might be needed for advanced custom applications, such as complex effect setups requiring coordinated control of multiple devices, usually from a global main script. For scripting related to a single device, using device-level scripts such as the scene script is preferable. Please be careful accessing devices and API directly.

device('devicename')
device('dSUID')
access a device by devicename or dSUID. Note that while using names is more convenient, scripts might break when users rename devices. So using dSUID is recommended for scripts that are meant to remain installed and working for a long time.
valuesource('valuesource_id')
find a value source (source of values that can trigger on(xxx) expressions or evaluator conditions) by its internal id. Note that ususally, it is better to access these values using device(x).sensor(y).
vdcapi(json_request)
issues a vDC API method call or a notification. Returns the method's response or just an empty result for a notification.
webrequest()
Is an event source which returns JSON requests sent to the scriptapi endpoint of the JSON API. webrequest can be used, for example, with on(webrequest()) as request { ... } to implement API commands in scripts. The return value of webrequest() has the method answer() to answer the request. The whole mechanism can be used when implementing custom special web pages besides the standard web interface.
request.answer(json_or_error)
Replies to a web request (the request variable in the example above) with a JSON response or error. Typically, a response is sent as part of an on() handler, with a construction such as:
on (webrequest()) as request {
  // execute request
  // ...
  request.answer({ "status": "all ok"});
}

 

webrequest must be answered!

If an on(webrequest()) as request { ... } is installed, it must be ensured that the handler code really answers the request with answer() in every case. Otherwise, the requesting web client will hang until it times out.

LED chains: p44 low resolution graphics (p44lrgraphics) access

p44lrgraphics is a graphics subsystem especially for smart LEDs. Only the p44script interface is described here. More information can be found in the technical documentation online.

lrg
returns the root view of the entire led chain arrangement currently configured, i.e. the view that contains the entire area covered by all mapped ledchain segments.
addledchain(ledchainconfigstring)
adds a LED chain to the LED chain arrangement. ledchainconfigstring has the same format as the --ledchain command line parameter:
[LED-type:[leddevicename:]]numberOfLeds:[x:dx:y:dy:firstoffs:betweenoffs][XYSA][W#whitecolor]
removeledchains()
removes all ledchains from the LED chain arrangement.
ledchaincover()
returns the rectangular area (x,y,dx,dy) currently (possibly partially) covered by the installed ledchains.
neededledpower()
returns the (approximate) electrical power im milliwatts needed for the currently displayed pixels on the LED chain arrangement. Note that this returns the power that would be needed to display all pixels without limiting (even if currently limited, see setmaxledpower()).
currentledpower()
returns the (approximate) electrical power im milliwatts actually used for the currently actually displayed pixels. This should not significantly exceed the value set by setmaxledpower().
setmaxledpower(maxpower)
sets the (approximate) maximal electrical power in milliwatts the entire LED chain arrangement may consume. If the currently requested pixel colors would exceed the limit, all pixels are dimmed as needed to stay withing the power limit.
setledrefresh(refreshinterval [, priorityinterval])
Sets the minimum refresh interval for the connected LED chains, i.e. the maximum number of times a new state is output to the LEDs. With increasing number of LEDs per chain this interval may have to be set higher, so that all LEDs can be controlled (default is 15mS). The optional priorityinterval specifies how long priority timing (e.g. scrolling) has priority over secondary timing (e.g. animated parts of a scrolled area). This can be important for smooth tickers etc. (default is 50mS).
setrootview(newrootview)
Set newrootview as new root view.
makeview(json)
makeview(resource_file)
creates a view according to the configuration from the directly specified json or from a resource_file read JSON text.
.findview(viewlabel)
searches for a subview with the specified viewlabel, returns the view or undefined.
.addview(view)
adds view as a subview (if this view suppots subviews).
.parent()
returns the parent view (or null if none).
.remove()
removes the view from its parent (if there is a parent), returns true if view could be removed.
.configure(json)
.configure(resource_file)
(re)configures the view with the directly specified json or with a resource_file read JSON text.
.set('propertyname', new_value)
set a single property in the view. This is just a more convenient way than using configure with a json object containing a single field, but technically does the same thing.
.animator('propertyname')
Get an animator for the specified property of the view. Animatable p44lrg properties are: alpha, rotation, x, y, dx, dy, content_x, content_y, rel_content_x, rel_content_y, rel_center_x, rel_center_y, content_dx, content_dy, color, bgcolor. Colors have the animatable subcomponents r,g,b,a,hue,saturation,brightness
If a valid animatable property name is passed, an animator object is returned. See animator description for details.
.stopanimations()
stop all animations running in the view.
hsv(hue [, saturation [, brightness]])
converts the specified hue (0..359), saturation (0..1) and brightness (0..1) values to the hexadecimal 6 digit web color format, which can be used for setting bgcolor or color in p44lrg views. saturation (0..1) and brightness default to 1 when not specified.

Animators

Animators generate a series of changes of a numeric value over time, with a number of parameters such as total duration, curve (function) to follow, repetition, delay etc. Animators are returned from animator(...) functions of objects that support animation, such as p44lrg views, or PWM/analog outputs.

The animator methods which just set a paramater, return the animator itself, such that calls can be chained like animator('dx').delay(2).function('easein',4).runto(50,0:01:02) Animations run concurrently with the script that starts them, but await() or on() {} handlers can be used to wait for the end of an animation.

.delay(seconds)
delays the start of the animation by the specified time.
.runafter(animation)
schedules the animation to start only after the specified animation has completed.
.repeat(cycles [, autoreverse])
Causes the animation to repeat cycles times total. If cycles is set to 0, the animation will repeat forever (until explicitly stopped with stop()). if autoreverse is true, the animation is run backwards every second cycle.
.function('animationfunction')
Sets the animation function, which can be on of: linear,easein,easeout,easeinout.
.from(startvalue)
Sets the start value for the animation. By default, the start value is the current value of the property being animated, but in some cases this is not available for reading (e.g. hue or saturation of a switched off RGB light is undefined), so from allows to set a start value explicitly.
.step(minsteptime [, stepsize]).
This can be used to influence the resolution of the animation. Here minsteptime specifies the minimum time in seconds between two changes of the animated value (0 = use default for the respective hardware) and stepsize the size of the steps on the animated value (0 = automatically according to minsteptime).
.runto(endvalue, duration)
Only this method actually starts the animation to reach endvalue within the given duration.
Normally a statement to set up an animation looks like this: animator('color.hue').delay(2).from(0).runto(360,20), where runto is the last method in the chain.
runto can also be called several times without having to specify the other animation parameters again (except runafter and delay, and from).
.reset()
Resets the animation to its default values (and stops it beforehand, if it was still running).
.stop()
Stops an animation.
.current
returns the current value of the animator.
.running
returns the time (in seconds) the animator is already running, or null if it is not running.

Digital I/O (signals and indicators)

p44script can use various hardware as digital inputs or outputs. The hardware is defined by a pinspec string as follows:

gpio.11
GPIO number 11.
led.ledname
system LED with the name ledname.
i2c0.MCP23017@22.7
pin #7 of an i2c I/O expander chip MCP23017, at i2c address 0x22, on i2c bus number 0. Supported chips are TCA9555, PCF8574, MCP23017.
spi12.MCP23S17@0.6
Pin #5 of a SPI I/O expander chip MCP23S17, at SPI bus 1.2 (Bus 1, Chip Select 2).

The pinspec for digital inputs can be prefixed with a / to invert the signal polarity, and a + or a - to enable a pullup or pulldown on an input (if supported by the hardware):

+/gpio.22
GPIO number 22 with activated pullup, and inverted signal polarity (e.g. for a limit switch connecting the input to GND when active).

Please be careful - hardware access

These functions provide access to the hardware and can therefore cause malfunctions and, depending on the connected devices, damage if used improperly. The use of these functions therefore requires at least userlevel 1.

Functions for digital inputs/outputs

var d = digitalio(pinspec, output [, initial state])
Creates an object to control a digital input (output=false) or output signal (output=true) according to pinspec. With Initial state the initial state is defined. For inputs, initial state plays a role as the expected idle state if changes to the state are to trigger an event (but not setting up the input).

The object returned by digitalio() has the following methods (d = object created with digitalio):

d.toggle()
For outputs: changes the output state to the opposite of the current state.
d.state()
Returns the current state of the signal. If an input signal is set to signal change monitoring with detectchanges(), state() is an event source and can be used as a trigger in on(...) {...} handlers.
d.state(newstate)
Sets an output signal to the value newstate.
d.detectchanges([debounce time [, poll interval]])
d.detectchanges(null)
Switches signal change monitoring on or off. On power-up, the debounce time and (for inputs whose hardware does not detect signal changes by itself) the pollinterval can be optionally set. With signal monitoring on, state() can be used in on(...) {...} handlers in the trigger condition.

Functions for indicators

An indicator is an output that is usually used for visual signaling to the user (i.e. usually an LED), and also provides some convenience functions for common use cases such as short flashing or blinking.

var i = indicator(pinspec [, initialValue])
Creates an object to control a digital output according to pinspec, which is to be used as an indicator.

The object returned by indicator() has the following methods (i = object created with indicator):

i.on()
i.on(on_duration)
turns on the indicator. If a on_duration is specified, the indicator will automatically turn off after the specified time. However, the on()\ method does not wait for the on_duration; the indicator runs in parallel in the background.
i.off()
turns the indicator off (and stops any blinking).
i.blink([period [, percentOn [, duration]])
makes the indicator blink with the period, where percentOn is the duty cycle of the blinking (e.g. 20 = 20% on and 80% off). When the period is specified, the blinking stops automatically. The blinking runs in parallel in the background.
i.stop()
stops the blinking in the currently active state.

Analog I/O

p44script can use different hardware as analog inputs or outputs. The hardware is defined by a pinspec string as follows:

pwmchip0.1
PWM signal output number 1 to PWMChip number 0, with default PWM period.
pwmchip0.2.100000

PWM signal output number 2 at PWMChip number 0, with PWM period of 100000nS (= 0.1mS, = 10kHz).

Please note: Hardware limitations

Depending on the PWM hardware the possible values for the PWM period are different, and may not be adjustable for each output individually.

i2c0.MAX1161x@33.2
Input #2 of a MAX1161x i2c A/D converter, at i2c address 0x33, on i2c bus number 0. Currently supported chips are MAX11612..617, MCP3021 as well as the LM75 temperature sensor.
spi12.MCP3008@0.6
Input #6 of a SPI-A/D converter type MCP3008 at SPI bus 1.2 (bus 1, chip select 2). Currently supported chips are MCP3008 and MCP3002.

Please be careful - hardware access

These functions provide access to the hardware and can therefore cause malfunctions and, depending on the connected devices, damage if used improperly. The use of these functions therefore requires at least userlevel 1.

Functions for analog inputs/outputs

var a = analogio(pinspec, output [, initial value])
Creates an object to control an analog input (output=false) or output signal (output=true) according to pinspec. With initial the initial value is set.

The object returned by analogio() has the following methods (a = object created with analogio):

a.value()
Reads the current Aanlog value of the input (or the current set value for outputs). If an analog input is set to poll regularly with poll(), _value() is an event source and can be used as a trigger in on(...) {...} handlers.
a.value(val)
for outputs only: sets the output value to val. The possible range of values can be read with range.
a.range()
returns the value range for the analog signal, and if known, the resolution as an object with the fields min, max and if necessary resolution.
a.poll(interval [, tolerance])
a.poll()
sets up a regular poll of an analog input with specified interval (and tolerance if necessary). With regular polling enabled, value() can be used in on(...) {...} handlers in the trigger condition. _poll() without parameter turns off regular polling.
a.filter(type, [interval [, collection_time]])
installs a filter type ("average", "simpleaverage", "min", "max") that filters the read values over the specified interval with a sliding window. The collection_time specifies the time window per data point. The filter function "average" also considers irregular time intervals between data points, "simpleaverage" does not.
a.animator()
returns an animator which can be used to animate the value of an analog output over time.

Low-Level i2c access

To directly access i2c devices which are not directly supported as analog or digital I/O pins, p44script supports low level access to i2c devices.

var i = i2cdevice(busno,devicespec)
Creates a i2c device on bus busno using devicespec to specify the driver and the device address. For low-level access, the driver being used is almost always the "generic" driver, so devicespec usually is something like generic@20 (generic i2c device at address hex 20).

The object returned by i2cdevice() has the following methods (i = object created with i2cdevice):

i.smbusread(reg [,type])
Returns value of register reg using SMBus protocol. Without setting type, the register is read as a single byte and returned as a number. Setting type to "word" reads a 16-bit word as a number, and "block" reads a block of variable length, which will be returned as a (binary) string.
i.smbuswrite(reg, value [,type])
Writes value to register reg. Without setting type, the numeric value is is written as a single byte. Setting type to "word" writes numeric value as 16-bit word, and "block" interprets value as a (binary) string and writes it as a block of variable length.
i.rawread([count])
Without count specified, reads a single byte without any register addressing (no SMBus) and returns it as a number. With count specified (even if set to 1) count bytes will be read and returned as a (binary) string.
i.rawwrite(byte)
Writes the byte to the device without any register addressing (no SMBus).

Please be careful - hardware access

I2C related functions provide access to the hardware and can therefore cause malfunctions and, depending on the connected devices, damage if used improperly. The use of these functions therefore requires at least userlevel 1.

Low-Level SPI access

To directly access SPI devices which are not directly supported as analog or digital I/O pins, p44script supports low level access to SPI devices.

var s = spidevice(busno_and_cs, devicespec)

Creates a SPI device on bus number (busno_and_cs DIV 10) using CS (chip select) pin number (busno_and_cs MOD 10) using devicespec to specify the driver and the device address, and optionally SPI mode options. For low-level access, the driver being used is almost always the "generic" driver, so devicespec usually is something like generic@15 (generic SPI device at address hex 15). If your device does not use any addressing/register semantics, the address part still needs to be specified but the actual number is irrelevant. SPI mode options are characters appended to the driver number after a dash, like generic-NP@0 for not using a CS line and inverted polarity. The following options are available:

  • H : inverted phase (compared to original microwire SPI)
  • P : inverted polarity (compared to original microwire SPI)
  • C : chip select active high (instead of active low)
  • N : no chip select
  • 3 : 3 wire mode
  • R : SPI ready indication: slave pulls low to pause
  • S : slow speed - 1/10 of normal for the bus
  • s : very slow speed - 1/100 of normal for the bus

The object returned by spidevice() has the following methods (s = object created with spidevice):

s.regread(reg [,type, [, count])
Returns value of register reg using device/register addressing similar to i2c SMBus (2-byte request header consisting of (device address<<1) and Bit0=read flag in first byte and reg in second byte). Without setting type, the register is read as a single byte and returned as a number. Setting type to "word" reads a 16-bit word as a number, and "bytes" reads as many bytes as specified by count, which will be returned as a (binary) string.
s.regwrite(reg, value [,type])
Writes to register reg using device/register addressing (see regread()). Without setting type, the register is written with value as a single byte and returned as a number. Setting type to "word" writes numeric value as a 16-bit word, and "bytes" interprets value as a (binary) string and sends all of its bytes.
s.writeread(bytes_to_write [, num_bytes_to_read [, fullduplex]])
This method represents the basic SPI transaction consisting of writing and reading some bytes either simultaneously (full duplex) or sequentially (half duplex).
The bytes to be sent must be passed as a (binary) string in bytes_to_write; if no bytes should be sent, specify an empty string. num_bytes_to_read specifies the number of bytes to read and returned as a (binary) string. If fullduplex is specified and set to true, reading is simultaneous with writing. Otherwise, reading starts after bytes contained in bytes_to_write are written.

Please be careful - hardware access

SPI related functions provide access to the hardware and can therefore cause malfunctions and, depending on the connected devices, damage if used improperly. The use of these functions therefore requires at least userlevel 1.

Modbus

Modbus support is based on the libmodbus library, and allows instantiating modbus RTU and TCP slaves and masters.

Note: needs userlevel >= 1

Modbus access requires userlevel >=1 because RTU mode needs access to the hardware (serial interface, transmitter/receiver enable lines), which can interfere with normal device operation if not properly chosen.

Creating modbus instances

var mbm = modbusmaster()
Creates a modbus master instance. It has methods common to both master and slave and some functions specific to modbus master operation, see below.
var mbs = modbusslave()
Creates a modbus slave instance. It has methods common to both master and slave and some functions specific to modbus slave operation, see below.

The objects returned are referenced below as mbm and mbs for master and slave, resp; and as mb in the description of common functions.

Functions common to both modbus master and slave

Note: In the following, mb is assumed to be a variable containing a modbus master or slave instance.

mb.connection(connectionspec [, txenablepin|RS232|RTS [, rxenablepin, [, txdisabledelay]])

Configures the connection method for the modbus instance.

  • For modbus RTU, connectionspec consists of a serial device specification of the form /dev[:commParams], where dev is a serial device and commparams can be [baud rate][,[bits][,[parity][,[stopbits][,[H]]]]], where parity can be O, E or N and H means hardware handshake enabled. The second argument can specify either an I/O pin that is used to enable the transmitter, RTS to use the RTS line of the UART for enabling the transmitter, and RS232 to not manage transmitter drivers at all (e.g. point-to-point serial connection). rxenablepin can specify a second line to enable the receiver separately. txdisabledelay specifies the delay to insert between end of transmission and disabling of transmitter drivers.
  • For modbus TCP, connectionspec specifies hostname and port as host[:port]. For slave instances, host is the local IP address to bind the TCP server to - usually 0.0.0.0 (all addresses). For master instances, host is the remote slave's hostname or address to connect to.
mb.bytetime(byte_time_in_seconds)
Configures the time needed to transmit one byte. This is relevant for RTU only, and only needed when the UART has an imprecise baud rate such that actual byte times differ significantly from the theoretical precise values derived from the baud rate.
mb.recoverymode(link, protocol)
Enables/disables recovery at the link and/or protocol level by setting link and protocol to true or false.
mb.debug(enable)
Enables libmodbus debug output (directly to stderr, bypassing the normal p44 log system) by setting enable to true.
mb.connect([autoflush])
Opens the modbus connection. Depending on RTU or TCP, and master or slave role, this opens the serial connection, or initiates a TCP connection to a slave, or starts a listening TCP socket. If autoflush is set to true, data possibly already present in a serial input buffer is flushed (discarded) before starting to listen for modbus protocol data (PDUs).
Note that explicit opening is not required when acting as master - only if the connection is to be kept open over more than a single modbus transaction. Otherwise, calling any of the master functions (see below) will open the connection, perform the operation and close the connection automatically.
mb.close()
Closes the modbus connection.

Functions for modbus master

Note: In the following, mbm is assumed to be a variable containing a modbus master instance.

mbm.slave(slaveaddress)
Sets the slaveaddress for modbus RTU. The function returns the modbus master object, such that it can be used to chain with an actuall access like mbm.slave(1).readreg(100)
mbm.readinfo()
Reads and returns the info (slave id) string from the currently addressed slave.
mbm.findslaves(idmatch, from, to)
Tries to read the info (slave id) for every slave address starting at from up to and including to, and returns an array for slave addresses. If idmatch is not an empty string, only slaves with slave id strings containing idmatch are returned.
mbm.readreg(regaddr [, input])
reads the register regaddr (set input to true to access a read-only register) and returns it as an unsigned number.
mbm.readsreg(regaddr [, input])
reads the register regaddr (set input to true to access a read-only register) and returns it as an signed number.
mbm.readbit(bitaddr [, input])
reads the bit(coil) bitaddr (set input to true to access an input-only bit) and returns its state as boolean value.
mbm.writereg(regaddr, value)
writes numeric value to the register regaddr.
mbm.writebit(bitaddr, value)
writes boolean state of value to the bit (coil) bitaddr.

Functions for modbus slave

Note: In the following, mbs is assumed to be a variable containing a modbus slave instance.

mbs.slaveaddress([slave_address])
returns the currently configured slave address of this slave, or with slave_address specified, sets the slave address. The slave address is only relevant for modbus RTU.
mbs.slaveid(slave_id_string)
sets the slave id (the info string) to slave_id_string. This is the string that can by a master using readinfo().
mbs.setmodel(registermodel_json)

Defines the register model of the slave (coils, bits, registers and inputs). registermodel_json must be a JSON object of the following form:

{ "coils" : { "first":100, "num":10 }, "bits" : { "first":100, "num":10 }, "registers" : { "first":100, "num":20 }, "inputs" : { "first":100, "num":20 } }

Of "coils", "bits", "registers", "inputs" those not actually needed can be omitted.

mbs.setreg(regaddr, value [, input])
Write numeric value to the register regaddr (or to the input register regaddr, if input is set to true).
mbs.setbit(bitaddr, state [, input])
Write boolean state to the bit (coil) bitaddr (or to the input bit bitaddr, if input is set to true).
mbs.getreg(regaddr [, input])
Get the unsigned numeric value of the register regaddr (or the input register regaddr, if input is set to true).
mbs.getsreg(regaddr [, input])
Get the signed numeric value of the register regaddr (or the input register regaddr, if input is set to true).
mbs.getbit(bitaddr [, input])
Get the boolean state of the bit (coil) bitaddr (or the input bit bitaddr, if input is set to true).
mbs.access()

This is an event source. If used in a handler like on(mbs.access()) as acc { ... }, every time a master accesses the slave, the handler will be executed with acc returning a json object with the following fields:

  • "reg" : the register address accessed or null
  • "bit" : the bit address accessed or null
  • "addr" : the bit or register address accessed
  • "input" : true if a input bit or input register was accessed
  • "write" : true if a bit or register was accessed for write

DC motors

The DC motor object can combine an analog output for the motor power (usually PWM), one or two digital outputs for controlling the direction of rotation and, with suitable hardware, also switching between driving/braking, an analog input for monitoring the motor current for switching off in case of overload, and finally 2 digital inputs for limit switches to create a convenient motor controller.

var m = dcmotor(output [, direction1 [, direction2 ]])
Creates a dcmotor object. The parameters can be either previously created I/O objects, or pinspec strings to create the corresponding signals directly. output must be an analog output, usually a PWM, which controls the motor power. For a motor with only one running direction, no other signals need to be specified. If the motor can run in both directions (via an H-bridge), then either direction1 alone specifies the direction of rotation, or - if direction2 is also specified - direction1 controls one side and direction2 the other side of the H-bridge. In this configuration, active braking and holding of the motor is then also possible (both sides of the H-bridge connected in the same way).

The object returned by dcmotor() has the following methods (m = object created with dcmotor):

m.outputparams(scaling [, offset])
If the output for the motor power does not have the value range 0..100 (which is normally the case only for PWMs), the conversion of the 0..100 power specification to the effective output value can be adjusted. By default scaling = 1 and offset = 0.
m.power(Power [, Direction [, RampTime [, RampExponent]])
Changes the motor power to Power (value range 0..100%). Direction can be 1 (default, forward), -1 (reverse) or 0 (brake). A positive RampTime specifies the time period during which the change should occur. A negative RampTime specifies the time duration for a 0..100 ramp, the effective time is determined proportionally to the change (small power change gives shorter ramp). By default, the RampTime -1 (i.e. 1 second for a 0..100 change) is used.
m.stop()
Switches the power to 0 without delay. The motor stops and coasts down. To brake, m.power(50,0) can be used instead, for example (braking with 50% power).
m.status()
Returns the status of the motor as an object with the fields power, direction and if a current sensor is configured, current. If the motor has been stopped by a limit switch or current limiter, an additional stoppedby field indicates that (contains either "overcurrent" or "endswitch"). status() is an event source and can be used as a trigger in on(...) {...} handlers. The status is triggered as an event when a ramp started with power() has reached its final value, or when the motor is stopped by limit switches or current limiting.
m.endswitches(positiveEnd, negativeEnd [, debouncetime [, pollinterval]])
Defines two digital inputs (positiveEnd, negativeEnd, either as digitalIO object or as pinspec) as limit switches for the end stops of the drive. Optionally, the debouncetime and (for inputs whose hardware does not detect signal changes by itself) the pollinterval can be set.
If a limit switch responds, the motor is stopped in any case, even if it actually runs in the direction of the other limit switch (secures the mechanics if the motor is connected the wrong way round). But to allow the motor to move away from the limit switch, power() is blocked only in the direction towards the active limit switch, but it is possible to start the motor in the other direction.
m.currentsensor(sensor [, pollinterval])
m.currentsensor(null)
Defines (or removes if sensor is null) an analog input sensor as a motor current sensor, with the specified pollinterval. If no polling interval is specified, polling occurs 3 times per second.
m.currentlimit(Limit [, startup_time [, max_startup_current]])
Sets the current limit to Limit. The value is not in A or mA, but simply the value as returned by the analog input (usually a raw A/D value, e.g. 0..4095 for a MAX11615). If this value is exceeded, the motor stops. Since a higher current may be required during startup than during travel, startup_time can be used to specify a duration after execution of a power() command in which allowed current may go up to max_startup_current.

P44features - specific functionality/hardware support

The following functions are global functions allowing access to "features" (see here for details) and their API from scripts. P44features are a library of specific functionalities usually developed for a particular exhibition/art use case by plan44 (examples see here). As some of these might be useful for other applications, some P44-XX products include them for use from p44script.

Note: requires activation on the commandline

Because the features require special hardware, which is only available in special devices, features to be used must be activated on the commandline at the start of the vdcd-daemon with appropriate options. If no feature is activated at all, then the whole feature system is not activated (also to save RAM) and the following functions do not exist.

feature(name)
access a feature by name. Each feature supports some generic methods shown below, and a set of commands and properties that are specific to the feature.
featurecall(json_request)
issues json_request as a feature API request and returns its response (or null if call has no response). This means json_request is processed the same way as when sent by a feature API client via TCP. A feature API client is an external instance accessing the feature API via a TCP socket connection, usually a system coordinating large installations consisting of many P44-xx modules.
featurecall()
Is an event source that delivers requests to the feature API that could not otherwise be processed. featurecall can be used, for example, with on(featurecall()) as call { ... } to implement additional feature API commands in scripts. The return value of featurecall() has the method answer() to answer the request.
call.answer(json_or_error)
Answers a Feature API request (the call variable in the example above) with a JSON response or error. Typically, a response is sent as part of an on() handler, with a construction such as:
on (featurecall()) as call {
  // execute request
  // ...
  call.answer({ "status": "all ok"});
}

 

featurecall must be answered!

If an on(featurecall()) as call { ... } is installed, it must be ensured that the handler code really answers the request with answer() in every case. Otherwise, the requesting client may hang.

featureevent(json_event)
sends an event message to the current feature API client (if any). Using featureevent(), a device can notify the API client (the coordinating system) of local events such as activated sensors etc.
featureevent()
returns events (in JSON) issued from active features, such as detected RFID, input changes etc. featureevent() can be passed to await() or used in on (featureevent()) as event { do something with event } handlers. Note that the events are also sent to a connected API client, if any.

The following are methods of the feature object (see feature(...) above). We assume the feature assigned to a variable f in the examples below.

f.status()
returns the status of the feature. Is false when the feature is not initialized, or when initialized, a feature-specific JSON value or object.
f.init(init_json)
initializes the feature, with feature-specific init_json parameters. Some simple features may have no parameters, for those you can pass true as init_json.
f.cmd(command [, json_param_object])
send command to the feature, with optional json_param_object containing command parameters.
f.set(property, value)
f.set(properties)
sets a single property in the feature to a new value value, or multiple properties together according to the fields in properties.