About p44script

p44script is a specialized script language embedded entirely in your P44 device, including a full-featured integrated development environment (p44script IDE) and this short reference. It has zero external dependencies and runs completely self-contained without internet access.

p44script has been designed to easily create reliable automations for lights, sensors, moving elements and much more in the context of a smart home installation, or as completely independent controllers. p44script automations can run for years unattended, but can be easily maintained whenever a change is needed - no external tools required except for a web browser. Scripts written are part of the P44 device's configuration, are backed up with it and can easily be restored to another device in case of a hardware replacememt.

p44script in general has a syntax similar to Javascript or C, but comes with a set of features tailored for automation tasks which are unique and distinct from what Javascript offers. In particular, concurrent in-line execution and on()-handlers are powerful tools specific to p44script.

This page is not a tutorial - please visit the on-line docs and the plan44 user forum for additional material.

Documentation describes the latest version of p44script

This quick reference refers to the latest available version of p44script. If something does not work as described here, it may be because the latest firmware is not installed on your device. However, the quick reference for the respective firmware is always built into the firmware itself and can be opened using the small link at the bottom right of the script fields or from the p44script IDE.


Comments

Comments are useful to add notes to source code explaining the code or important context to a human reader of the script or expression. Comments are ignored by the script engine, like empty lines and whitespace.

/* comment */
C-style comment, starts with /* and continues, possibly over multiple lines, until a */ is found
// comment
C++-style single line comment, starts with // and ends at the end of the same line

Literals

There are different ways to type numbers, text strings and some special values:

1234
integer number, in decimal
0x89ab
integer number in hexadecimal.
1234.567
floating point 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. You can create annotated null values using the null() function.
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 the beginning of the 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' } // strict JSON
{ field1:42, field2:'hello' } // JavaScript style, field names do not need quotes
objects with named fields.
[ 42, 43, 'hello' ]
array.

Named Values

In expressions, named values (for example sensor values in evaluator conditions, or variables in scripts, see below) 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 data or objects/arrays.

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.
object.fieldA
the subfield named fieldA in an object. So with object={ 'fieldA':42, 'fieldB':'hello' }, the result would be 42.
array[1]
the array element at the numeric index 1 in an array array. The index is zero-based, so if array=[ 42, 'world' ], the result would be 'world'.
object['fieldB']
the subfield named fieldB in a object object. So with object={ 'fieldA':42, 'fieldB':'hello' }, the result would be 'hello'.
object[0]
the name of the first (index==0) subfield in a object. So with object={ 'fieldA':42, 'fieldB':'hello' }, the result would be 'fieldA'. This is how 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. Another way to go though all fields of an object is the foreach statement, see below.

Operators

Operators are used to build expressions (calculations) from literals and named values (variables):

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, array concatenation if both expressions are arrays, object fields merging if both expressions are 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)
++ 0 (lowest) increments the variable on the left side (a++ is equivalent to the assignment a = a+1)
-- 0 (lowest) decrements the variable on the left side (a-- is equivalent to the assignment a = a-1)
+= 0 (lowest) add to the variable on the left side (a += 2 is equivalent to the assignment a = a+2)
-= 0 (lowest) subtract from the variable on the left side (a -= 2 is equivalent to the assignment a = a-2)
*= 0 (lowest) multiply the variable on the left side (a *= 2 is equivalent to the assignment a = a*2)
/= 0 (lowest) multiply the variable on the left side (a /= 2 is equivalent to the assignment a = a/2)

Note that p44script does not have bitwise and/or operators, but instead provides the bit(), setbit() and flipbit() functions which provide bit(field) manipulation in a more intuitive way.

Variables

Local Variables

Local variables represent values that are local and private to a context where the script runs in. 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). Variables defined in a user function are private/local to that function. Variables defined with threadvar are even local to a single execution run (thread), even when the same code is run multiple times in parallel.

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.
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, of which there might be more than one in parallel).

Automatically created thread variables

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

Global Variables

Global variables are values that can be written and read from any script in a P44 device under a common name.

Visibility of global variables

As global variables are visible from all scripts, it is recommended to use unique names that do not happen to occur by accident elsewhere as well. In general, global variables should be used sparingly and only when local variables are not sufficient.

glob g
global g
declaration of global variable g. Declaration means that the name g is made known to all scripts in the device. Values assigned to g by one script will get accessible by all scripts. A global variable's value remains intact as long as the device runs (unless it is explicitly removed via unset, see below).
glob g default 78/9
declaration of global variable g and initialisation with a default value, when the variable does not yet exist.
Note that the initialisation expression can only consist of constants or other global variables that are already defined.

Initialisation occurs once and before execution

Initialisation of global variables occurs only once, usually long before the script actually runs, when the script is first parsed by the p44script engine (at device startup or when a script is changed).

This can be confusing because when the script actually runs, all global variables are already initialized when the first script line executes, even if the initializer was written in the middle of the text. Furthermore, when multiple scripts try to initialize the same global variable with different values, only the first one encountered will actually set a default value.

To avoid confusion, it is good practice to put global variable declarations at the top of scripts, and use default initializers only when really needed. Note that in earlier versions of p44script, it was possible to use = or := for assigning a default, which even more looked like a regular assignment - that's why that syntax is no longer supported.

Assigning Values to Variables

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).
obj = {}
create an empty object
obj.c = 7+8
assign the expression's result to a field named c of the object variable obj. If the field c does not exist in obj, it will be created. Otherwise the former value of the field c will be replaced by the result of the expression.
arr = []
create an empty array
arr[] = 6
append the new element 6 to the array arr
arr[2] = 7
assign 7 as the 3rd (not the 2nd, array indices are zero based!) element of the array arr
obj = {
  a: 42, b:'a string', c:[ 1, 2, a*2], d: { n:1, sin:sin(a), ["a_is_"+a)]:13 }
}
complex object/array structures can be created and multiple fields assigned expressions in a single statement. Even field names can be created from expressions by enclosing them in square brackets (see last field in the example).

Deleting Variables

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.

Multiple Script Statements

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 (group) of multiple statements

Control Flow

Conditionals

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.

Loops

while (i>0) { i := i-1; log(5, "i is now: " + i); }
repeat (loop) statement block as long as i is greater than zero.
for (var j:=0; j<5; j++) { log(5, "j is now: " + j); }
first initializes the loop with creating j and setting it to 0 then repeats the (loop) statement block as long as j is less than 5, and increments j at the end of every iteration (_j++).
break
exits a while, for or foreach loop.
continue
starts the next iteration in a while, for or foreach loop.
foreach objectOrArray as value { do_something_with_value }
foreach objectOrArray as key,value { do_something_with_key_and_value }
iterates through each field (when objectOrArray is an object) or each array element (when objectOrArray is an array) and assigns value and optionally key accordingly.

Return

return
ends the current script or user defined function with null/undefined return value.
return value
ends the current script returning value.

Catching Errors

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.

User Defined Functions

function hello()
{
  log('Hello World');
}
Defines a globally available function named hello which logs the text 'hello world'.
global function sum(a,b)
{
  var c = a+b
  return c
}
Defines 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 most 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.

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

Basically, handlers are like functions that are automatically called when a certain condition is met (rather than getting called from other script code).

Handlers make implementing asychronous tasks - which are very common in automation, but are not so easy to implement in many standard scripting languages - quite straightforward and easy to understand.

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

Handling Timed Events

Combined with functions for timed triggering, handlers can perform tasks based on time:

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.

Handling Value Changes via Event Sources

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.
var midi = midibus('/dev/midi1')
on (midi.control(0,42)) stable 0.1 as knob42 {
  log('Control #42: %d', knob42.value)
}
Defines an event handler that responds to changes to MIDI controller #42 on channel 0.
For handlers that are triggered by event sources (like control() is one), stable has a slightly different meaning: In contrast to states - such as "a sensor value is greater than x" - events are unique and by definition not "stable", so stable defines the minimum time interval between two activations of the handler. If the events occur more quickly, they are discarded, except for the last one occurring. In the MIDI example shown, stable 0.1 means that the control changes do not trigger the handler more than once every tenth of a second.

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.

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.
concurrent passing x, y=2*a { log("x=%s, y=%s", x, y) }
the passing keyword creates threadvar_s (variables with values private to the thread being started) and passes values (_x from a outside variable named x, y by assigning the expression 2*a) into the thread. This allows for using concurrent multiple times in quick succession while ensuring each started thread gets its own set of parameters.

Built-in Functions

p44Script has many built-in functions and methods (functions that are part of a object like a device, or a view etc.). Not all of them are available in any context. Therefore, this section has subsections, and some built-in functions/methods are listed further down along with the descriptions of their context (device, hardware etc.)

General Calculation Functions

These functions are available in every expression/calculation, and are real functions in the mathematical sense (no side effects, just providing the calculation results).

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). Also see flipbit() and setbit() functions.
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().
cos(angle)
returns the cosine of angle (in degrees)
cyclic(x, a, b)
return x with wraparound into range a..b (not including b, because it means the same thing as a).
dawn([epochtime])
returns approximate time of dawn on the current day, or on the date specified by epochtime, 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([epochtime])
returns current day of month, or the day-of-month component of epochtime, but cannot directly trigger timed actions.
dusk([epochtime])
returns approximate time of dusk on the current day, or on the date specified by epochtime, 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 an 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]])
epochtime(hour, minute, second [, day [, month [, 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. Caution: Daylight saving time is not taken into account in this variant.
With hour, minute, second and optionally day, month and year, the function returns the epoch time for the corresponding point in time, using current date/month/year if not specified.
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 [, arg...])
evaluate string as p44script script. The optional arg are available as variables named arg1, arg2 ... argN within the evaluation. The evaluation takes place in the current script's main context, and can access its variables, including global variables. However variables defined in the evaluation are local to the evaluation.
exp(n)
raises e to the power of n (e being the Euler number 2.718281828459045). exp() is the inverse function of ln().
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). Also see bit() and setbit() functions.
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([epochtime])
returns current hour, or the hour component of epochtime, 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 >=1 (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.
ln(n)
returns the natural logarithm of n (logarithm to the base e = 2.718281828459045). ln() is the inverse function of exp().
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([epochtime])
returns current minute, or the minute component of epochtime, but cannot directly trigger timed actions.
month([epochtime])
returns current month (1..12), or the month component of epochtime, 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.
null(annotation)
creates an annotated null value, that is a null value carrying some text that is shown in logs and when debugging - this annotation text has no function except for documentation. p44script itself often explains why something is null using annotations, the null() function allows script writers to do so as well.
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.
replace(haystack, needle, replacement [, occurrences]])
returns haystack with occurrences of needle replaced by replacement, at most occurrences times (all occurrences will be replaced if occurrences is not specified or set to 0).
round(a [, p])
round a to optionally specified precision p (1=integer=default, 0.5=halves, 100=hundreds, etc...).
second([epochtime])
returns current second, or the second component of epochtime, 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). Also see flipbit() and bit() functions.
sin(angle)
returns the sine of angle (in degrees)
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([epochtime])
returns approximate time of sunrise on the current day, or on the date specified by epochtime, but cannot directly trigger timed actions.
sunset([epochtime])
returns approximate time of sunset on the current day, or on the date specified by epochtime, but cannot directly trigger timed actions.
timeofday([epochtime])
returns current time-of-day in seconds since midnight, or seconds since midnight of epochtime, but cannot directly trigger timed actions.
uppercase(s)
returns (simple ASCII) uppercase version of string s.
weekday([epochtime])
returns current weekday (0..6), or the weekday of epochtime, but cannot directly trigger timed actions.
year([epochtime])
returns current year, or the year component of epochtime, but cannot directly trigger timed actions.
yearday([epochtime])
returns current date, or the date represented by epochtime, as day number in the year, but cannot directly trigger timed actions.

Functions for Timed Triggering

This is a special class of functions unique to p44script. These can be used in expressions that are meant to trigger an action at a specific point in time. Please note that you must use these functions to test for time/date in evaluator or handler 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 specified 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!). Note that testlater() is available mostly for backwards compatibility with older setups, before the more general every() functions existed in p44script.

Script Programming Functions

Most of these functions are not functions in the mathematical sense, as they have side effects, which means they make something happen in the system such as accessing the network or device hardware, or general changes to the system's current state (such as changing the log level).

abort()
cancels all concurrently running threads in the current Script context, except the thread from which abort() is called. The aborted threads receive an error as the final result. This form of abort() is primarily intended for debugging purposes in the IDE/REPL.
abort(a [, abortresult [, self]])
specifically aborts the thread named a, i.e. the thread that was started with concurrent as a {...}. If abortresult is not set to null, it is used as the final result of the thread (which is returned by await(), for example). If self is not set, and a is the own thread (i.e. the one from which abort() was called), then it is not cancelled, because this is usually not intended and difficult 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.
breakpoint()
When a script is being debugged in the p44script IDE, the debugger will stop when encountering the breakpoint() function. Without debugging enabled, breakpoint() does nothing. Note that usually it is easier to set breakpoints in the p44script IDE by clicking into the line number bar of the editor, so this functions is rarely needed.
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.
dnssdbrowse(type [, hostname])

Searches for devices in the local network that make their services discoverable via the DNS-SD protocol (e.g. printers, but also many other devices, especially in the home automation area, including the P44-xx devices). With type a DNS-SD service type must be specified. For example, to find web interfaces, _http._tcp can be specified (see here for a complete list of service types). By specifying hostname, a specific device can be searched for by its hostname (in the form devicehostname.local). As a result, dnssdbrowse returns an array which may be empty if no service of the specified type (and hostname, if applicable) was found. Otherwise, the array contains objects describing the service with the following fields:

  • name: name of the service as a text description.
  • hostname : hostname ending in .local.
  • hostaddress : the IP address on the LAN where the service can be reached.
  • ipv6 : 1/true if hostaddress is an IPv6 address, 0/false otherwise.
  • port : port number of the service
  • interface : interface index of the network interface over which the service is reachable (only relevant for IPv6 if necessary).
  • txts : object with the TXT records of the service as fields (can be empty if the service has no TXT records).
  • url : This field a URL that can be used directly in a browser depending on the service type (composed of the above fields). For unknown service names http is assumed, which sometimes works for services with type other than http, but not always.
geturl(url [,timeout])
returns response from GET request to (http or https) url, times out after timeout seconds if specified.
If the url is prefixed with !, SSL/certificate errors are ignored. For http(s) access with more options see httprequest().
hsv(hue [, saturation [, brightness [, alpha]]])
hsv(object)
hsv(webcolor_string)
the first form converts the specified hue (0..359), saturation (0..1), brightness (0..1) and alpha (0..1) values to the hexadecimal 6 digit (or 8 digit, if alpha is specified and not equal to 1.0) web color format (#rrggbb or #aarrggbb), which can be used for setting bgcolor or color in p44lrg views, or wherever else web colors are needed. saturation, brightness and alpha default to 1 when not specified.
the second form converts an object of the form { hue:60, saturation:0.85, brightness:0.5, a:1.0 } to a web color in the same way as the first form.
the third form converts the webcolor_string into an object of the same form as described above.
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 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 object value, the data is converted to JSON and sent with Content_type: application/json on PUT and POST, otherwise with Content_type: text/html unless content type is explicitly specified in headers.
  • formdata : optional boolean value. If this option is set to true and data is a object, the fields of the 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 an 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 archieved with a ! before the url, see above). By default, the server certificate is checked against the system's root certificates.
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 [, symbols [, coloring]]])
change the application's log level to level. If deltatime is set, timestamps will also show time difference to previous log entry in mS. If symbols is set, UTF-8 symbols will be used to differentiate log levels and separate context prefix from actual log message. If coloring is set, ANSI colors are used in the log output (suitable for terminal output, not browser). Each of the arguments can also be null in order not to change the corresponding value.
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 object/array) with POST to url, returns response, times out after timeout seconds if specified. If data is an object/array value, the data is converted to JSON and sent with Content_type: application/json, otherwise with Content_type: application/x-www-form-urlencoded.
If the url is prefixed with !, SSL/certificate errors are ignored. For http(s) access with more options see httprequest().
puturl(url [,timeout][,data])
sends data (must be string or object/array) with PUT to url, returns response, times out after timeout seconds if specified. If data is an object/array value, the data is sent with Content_type: application/json, otherwise with Content_type: application/x-www-form-urlencoded.
If the url is prefixed with !, SSL/certificate errors are ignored. For http(s) access with more options see httprequest().
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 (be careful with this if you are not on site!). 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!

rgb(red [, green [, blue [, alpha]]])
rgb(object)
rgb(webcolor_string)
the first form converts the specified red (0..1), green (0..1), blue (0..1) and alpha (0..1) values to the hexadecimal 6 digit (or 8 digit, if alpha is specified and not equal to 1.0) web color format (#rrggbb or #aarrggbb), which can be used for setting bgcolor or color in p44lrg views, or wherever else web colors are needed. green, blue and alpha default to 1 when not specified.
the second form converts an object of the form { r:0.8, g:0.4, b:0, a:1.0 } to a web color in the same way as the first form.
the third form converts the webcolor_string into an object of the same form as described above.
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 yourself 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).

Introspection - insight into variables, functions, handlers, threads

These functions are designed less for use in scripts, but are primarily useful for debugging in the console/REPL of the IDE.

globalvars()
returns an object with all global variables as fields.
contextvars()
returns an object with variables of the current scriptcontext.
localvars()
returns an object with the current local variables of the currently executed function. If no function is executed, the result is the same as for contextvars().
threadvars()
returns an object with the current thread local variables. These are the variables defined with threadvar or implicitly created with on (...) as v {...} or catch as {...}.
threads()
returns an array with information about the execution threads of the current scriptcontext. For each thread, the list contains an object with the fields id, source, status, and thread. The latter is the thread object itself (appears as null or contains the thread result if the thread has already ended).
globalhandlers()
returns an array with information about the global event handlers (global on (...) {...}).
contexthandlers()
returns an array with information about the event handlers in the current * scriptcontext*.
globalbuiltins()
returns an object whose fields contain information on the globally available built-in functions and global objects.
contextbuiltins()
returns an object whose fields contain information on the built-in functions and variables available in the current scriptcontext (in addition to those available globally).
builtins(object)
returns an object whose fields contain information on the built-in methods and fields available in the passed object.

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 Digital Strom 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 Digital Strom 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! You can also specify 0 for accessing default channel of the respective device(s)) 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! You can also specify 0 for accessing default channel of the respective device)
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! You can specify 0 for accessing default channel of the respective device.
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! You can also specify 0 for accessing default channel of the respective device.
output.movechannel(channelid, direction [, time_per_unit])
starts moving the value of channelid in the direction indicated by the sign of direction or stops movement when direction is 0. When optional time_per_unit in seconds is set, it determines speed by setting the time spent per change of 1 unit of the channel value. If time_per_unit is not specified or set to 0, the channel's default dimming speed is used instead.
Note that the channel movement is not automatically started; you need to call applychannels() for that. Also note that channelids are case sensitive! You can specify 0 for accessing default channel of the respective device.
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.) and all running channel transitions.
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 is a structured object itself with many fields and functions of its own to change properties, (re-)configure and find views in the view hierarchy. Also see ledchains.

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()
webrequest(sub_endpoint)
webrequest(sub_endpoint, peer)
Is an event source which returns JSON requests sent to the scriptapi endpoint of the JSON API (/api/json/scriptapi). 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. This mechanism can be used when implementing custom special web pages besides the standard web interface, or to provide custom API to external systems.
The first form without argument catches all requests sent to the scriptapi endpoint
The second form specifies a sub_endpoint, such as "myendpoint" in /api/json/scriptapi/myendpoint and will only receive requests sent to that very endpoint. This allows implement multiple script API endpoints completely independently from each other by just having multiple on(webrequest(…)) handlers with different sub_endpoint arguments to webrequest.
The third form additionally specifies peer, which represents the IP address of the request sender. It is also possible to specify undefined for sub_endpoint to filter only by peer.
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 construct 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.

The following are methods, i.e. they belong to a view and must be invoked via it. lrg is the root view and is always available, other views can be found with findview() and saved in variables if necessary. For the following, let v be a variable to which a view has been assigned (for example: var v = lrg.findview('LIGHT')):

v.findview(viewlabel)
searches for a subview with the specified viewlabel, returns the view or undefined.
v.addview(view)
adds view as a subview (if this view supports subviews, e.g. stack).
v.parent()
returns the parent view (or null if none exists).
v.remove()
removes the view from its parent view (if there is a parent view), returns true if the view could be removed.
v.configure(json)
v.configure(resource_file)
(re)configures the view with the directly specified json or with a resource_file read JSON text.
v.set('propertyname', new_value)
Sets a single property of the view. This is simply a more convenient way than using configure with a json object containing a single field, but technically does the same thing.
v.animator('propertyname')
Creates an animator for the specified property of the view. Animatable p44lrg properties are: alpha, x, y, dx, dy, content_x, content_y, rel_content_x, rel_content_y, rel_center_x, rel_center_y, content_dx, content_dy, rotation, scroll_x, scroll_y, zoom_x, zoom_y, colour, bgcolor. Colours 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.
v.stopanimations()
Stops all animations running in the view.
v.reset()
Resets all transformations (zoom=1, scroll=0, rotation=0)
v.clear()
Deletes the content - depending on the view type, this means removing all subviews or deleting the text, etc.

These are the basic methods, views have further methods and properties that are described in detail in the technical documentation online.

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.

For the following, let a be a variable to which an animator has been assigned, such as var a = lrg.animator('rotation').

a.delay(seconds)
delays the start of the animation by the specified time.
a.runafter(animation)
schedules the animation to start only after the specified animation has completed.
a.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.
a.function('animationfunction')
Sets the animation function, which can be on of: linear,easein,easeout,easeinout.
a.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.
a.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).
a.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).
a.reset()
Resets the animation to its default values (and stops it beforehand, if it was still running).
a.stop()
Stops an animation.
a.current
returns the current value of the animator.
a.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 [, pollinterval]])
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 Digital Bus

A digital bus can be formed from two up to 32 digital lines, which can then be used to input or output a value consisting of several bits.

var bus = digitalbus(pinspec-list, output [, initial-pin-state])
Creates an object for a digital input (output=false) or output bus (output=true), formed by the pins as specified in pinspec-list.
pinspec-list is a comma separated list of digital pin specs, listing the digital inputs that form the bus starting with the most significant bit (MSB). To simplify listing multiple pins of the same type, a common prefix can be specified in paranthesis in front of the first pin.
For example, a 3-bit bus consisting of GPIO22..24 could be specified as gpio.22,gpio.23,gpio.24 or using the common prefix notation: (gpio.)22,23,24.
initial-pin-state determines the initial state of all pins after creating the bus (all 0 or all 1)

The object returned by digitalbus() has the following methods (bus = object created with digitalbus):

bus.value()
Reads the current value from an input bus (or the current set value for outputs).
bus.value(val)

for outputs only: sets the output value to val.

Glitches during value transitions!

As output lines do not change synchronously, but one after another in short succession, the bus will show transitional values other than previous value or val for a short time (a "Glitch"). This must be taken into consideration in the design of the circuitry that gets connected to the bus.

bus.buswidth()
Returns the number of bits the bus consists of.
bus.maxvalue()
Returns the highest number the bus can represent.

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

Serial interfaces

Note: requires userlevel >= 1

Access to serial interfaces requires userlevel >=1, as access to the hardware is required to create a serial object, which can interfere with normal device operation if the connectionspec is not set correctly.

var s = serial(connectionspec [,delimiter])
Creates an object that can operate a serial interface.
connectionspec has the form /xxx/serialdevice:commparams or host:port. serialdevice must be a serial device (e.g. /dev/ttyS1) or another serial channel. The form with host and port is used to address a serial interface via the network, e.g. one that is controlled with serialfwd.
With commparams the interface parameters can be set in the form: [baud rate][,[bits][,[parity][,[stop bits][,[H]]]]], where the parity can be O, E or N and H means that hardware handshake should be activated.
delimiter can be a string with a character that separates the received data into messages. If delimiter has the value "\n" (linefeed, 0x0A), then preceding "\r" characters are also removed. delimiter = true has the same effect as delimiter = "\n".

Note: In the following, it is assumed that s is a variable that contains a serial instance.

s.send(something)
Sends something converted to a string on the serial interface.
s.received()
This is an event source. If it is used in a handler such as on(s.received()) as data { ... }, then the handler is executed each time something is received on the serial port, or - if delimiter was set when serial() was called - each time a complete message terminated with delimiter has arrived.
s.dtr(active)
Activates or deactivates the DTR signal (if supported on the interface) according to active.
s.rts(active)
Activates or deactivates the RTS signal (if supported on the interface) according to active.
s.sendbreak()
Sends a break (long signal), if supported by the interface.

MIDI

With simple MIDI support, MIDI input devices, in particular controllers with analogue knobs and sliders, some of which can be bought very cheaply, can be connected to parameters of a lighting or other automation system and thus be conveniently controlled. Conversely, MIDI commands can also be sent to control other MIDI devices, for example to create sound effects for an installation.

Note: requires user level >= 1

MIDI access requires userlevel >=1, as access to the hardware (serial interfaces /dev/ttySx or ALSA USB-MIDI devices such as /dev/midix) is required to create a midibus object, which can interfere with normal device operation if the connection is not selected correctly.

var midi = midibus(connectionspec)
Creates a midibus instance that can operate a MIDI interface.
connectionspec has the form /xxx/mididevice or host:port. mididevice must be a serial device (e.g. /dev/midi1) or another serial channel that transmits MIDI data. The form with host and port is used to address a MIDI interface via the network, e.g. one that is controlled with serialfwd.

Note: In the following, it is assumed that midi is a variable that contains a midibus instance. Basic MIDI knowledge is assumed, details can be found in the MIDI standard.

midi.send(command, value)
midi.send(command, key, value)
midi.send(sysex_string)
Sends a MIDI message. command is the status byte of the MIDI message (e.g. 0x90 for note on on channel 0, or 0xB2 for control change on channel 2). The first form with two parameters is for MIDI commands that only have one parameter value (e.g. pitch bend or program change). The second form is for commands with two parameters. For note commands, key is the note number and value is the velocity. For control change, key is the control number and value is the setting value. In the third form, the (binary) string sysex is sent as a system_exclusive message. The EOX is automatically inserted at the end and should not be contained in sysex.
For 14-bit controls (numbers 0..31), 2 commands may be sent to transmit the entire 14 bits (depending on how the value has changed). For 14-bit values that fit into one command (such as pitch bend), value is automatically split correctly into the two MIDI data bytes.
midi.message()
midi.message(commandfilter, channelfilter, keyfilter)

This is an event source. If it is used in a handler such as on(midi.message()) as m { ... }, then the handler is executed every time a midi message arrives and returns an object with the following fields:

  • "command" : the MIDI command (status), whereby the channel bits are always 0 for channel voice messages (as they are available separately in channel)
  • "channel" : is only available for channel voice messages and specifies the channel.
  • "key" : note number or control number, is 0 for commands without this selector value.
  • "value" : the data value such as velocity or control value, can be 14-bit depending on the command (e.g. for control numbers 0..63).

The filter values can be used to determine which commands, channels and keys (notes, controls) are to be returned. For example, message(0x90,2,60) will only return note on from channel 2 with the note 60 ("middle C"). Each of the filter values can be set to undefined if the corresponding filter is not required.

midi.note()
midi.note(channelfilter, notefilter, on_only)
Like message, but only returns note messages (on and off if on_only is not set to true or false).
midi.control()
midi.control(channelfilter, controlnumberfilter)
Like message, but only returns control change messages.
midi.pitchbend()
midi.pitchbend(channelfilter)
Like message, but only returns pitch bend messages.
midi.program()
midi.program(channelfilter)
Like message, but only returns program change messages.

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