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
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.
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()
returns unix epoch time (seconds since 1.1.1970 midnight UTC), but cannot directly trigger timed actions.
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(value)
returns the error message string from errvalue
eval(string)
evaluate string as expression
find(haystack, needle [, from])
returns position of needle in haystack optionally starting at from, or null if not found.
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 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)
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 or error), otherwise return b
int(a)
integer value of a
isvalid(a)
returns true if a is a valid value (not null or 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(path)
Loads JSON from path. If path is not absolute, it is interpreted relative to the application's resource path (which is usually /usr/share/application name). In JSON resource files, /* ... */ comments are allowed (although this is not in accordance with the JSON specification).
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
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)
returns a (floating point) pseudo-random value from a up to and including b
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.
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
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.
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 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 context-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 context variables are also sufficient.

Functions

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

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

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

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).

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.

(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 unique 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 script context 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 script context 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 Script context, and then called context variable. There are different contexts, each with its own variables. For example, in a P44-DSB or LC device, all scene scripts of a fixture run in a Script context 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.

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 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)
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).
  • 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).
  • 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).
  • 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 context the script runs in to offset. This allows making part of an application more or less verbose in the log, selectively.
posturl(url [,timeout][,data])
sends data (must be string) with POST to url, returns response, times out after timeout seconds if specified.
puturl(url [,timeout][,data])
sends data (must be string) with PUT to url, returns response, times out after timeout seconds if specified.
readfile(filename)
reads the file filename as string.

Note

To use paths for filename, the user level must be >=1 (default user level for production devices is 0). Otherwise files can only be read from the p44script data directory, or if the path starts with "_/", from the p44script temporary directory.

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. 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) the signal.
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 user level 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()
returns the last UDP message received on the socket. The result is an event source and 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. If All parameters are to be passed a request JSON object:
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()
Returns the last websocket message received on the socket. The result is an event source and can be used as a trigger condition in on() statements 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.

Note

To use paths for filename, the user level must be >=2 (default user level for production devices is 0). Otherwise files can only be written to the p44script data directory, or if the path starts with "_/", to the p44script temporary directory.

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 named name (as a generic preset name such as 'preset 1'), 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', 'ct', '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()
Returns the last JSON request to the scriptapi endpoint of the JSON API. webrequest is an event source and 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"});
}

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() 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

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.

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 control.

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 so that the motor can be moved 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 [, MaxStartup 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 MaxStartup 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.

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.
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 (f.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)
set a single property in the feature to a new value.