Examples of custom devices implemented in p44script
An implementation of a custom device always consists of two parts:
-
the so-called init-message, which describes the type of device as it should be visible in the Smarthome system. The init-message must be entered when the device is created and cannot be changed later without deleting it and creating it again (from the point of view of the SmartHome system, it is then a new, different device).
-
The actual implementation in p44script. This can be changed again and again until the function is as desired.
Complete Documentation
For complete documentation please see the Custom Devices page with full specification of the external device API (init message and other device communication)
Create Custom-Device
A new device is created with the "+ Scripted" button:
In the appearing dialog the init-Message can be entered.
A valid init message must be entered here. A complete documentation of the init message can be found here; for the first experiments it is recommended to take one of the following examples.
Please note
- The init message is compatible with the init messages used via the external device API via TCP socket for externally implemented devices. However, for devices implemented in p44script it is not necessary to specify a 'uniqueid', and usually not recommended.
- For p44Script-implemented devices, the init message can be spread over several lines and provided with C-style comments
/* ... */
(while the init message via the external device API must always be on a single line).
Edit custom device implementation
The implementation can be customized at any time using the edit button (pencil icon)
Simple scripts can be entered directly in the appearing dialog, but for longer/more complex scripts it is recommended to use the built-in fullscreen code editor (via the link at the bottom right of the input field)
In the following examples the JSON init-Message and the script code of the Implementation are shown in separate text fields, from which the examples can be easily copied and pasted.
Ready-to-use examples
Internet watchdog
This simple example implements a switch signal device that indicates whether the internet connection is working or not.
Init message (details here):
{
'message':'init',
'inputs':[ {
'inputtype':0, 'usage':2, 'group':8, /* schwarz: Joker */
'hardwarename': 'Internet-Test',
'updateinterval': 300, 'alivesigninterval': 900
}]
}
Implementation (details of the content of the message() sent here).
// This URL must be reachable on the Internet. The Apple example works as-is,
// but you can take any other URL that is reliably reachable and returns a small
// response (for efficiency reasons)
var testurl = "http://www.apple.com/library/test/success.html"
// every 5 minutes:
on (every(0:05)) {
var msg = {'message':'input', 'index':0 }
try {
// If "testurl" returns an error free answer within 10 sec, the internet connection is working
var ans = geturl(testurl, 10)
log(6, "Internet is ok")
msg.value = 1
}
catch as e {
// Error or timeout accessing the "testurl".
log(4, "No internet access")
msg.value = 0
}
message(msg)
}
// Important: signals that it's ok that the script exits here
// Only the handler runs in the background.
// If this "return true" is missing, the implementation will be restarted 20 seconds after
// exit (assuming that an error has occurred)
return true
This creates an input device that tests the internet connection every 5 minutes, and reports the status (1=ok, 0=interrupted).
For P44-DSB-X on Raspberry Pi: CPU temperature as sensor value
On RaspberryPi devices the CPU temp is available as a number in the file /sys/class/thermal/thermal_zone0/temp
. With the following simple Custom Device a sensor device can be created from it:
Init message (details here):
{
'message':'init',
'protocol':'simple',
'group':3, /* blue/climate */
'name':'RPi CPU temp.', /* initial name */
'sensors':[
/* sensortype1 = temperature, updateinterval = 60 = read once per minute */
{'sensortype':1,'usage':1,'hardwarename':'RPiCPU','min':0,'max':100,'resolution':0.1,'updateinterval':60}
]
}
Implementation (details of the content of the message() sent here).
// Every minute:
on(every(0:01)) {
// read the value from the file, treat it as a number and scale it (file returns 1/1000 degrees)
var t = number(readfile('/sys/class/thermal/thermal_zone0/temp'))/1000;
// report as sensor value
message(format("S0=%.1f",t))
}
// important: signals that it's ok that the script ends here
// Only the handler is running in the background.
// If this "return true" is missing, the implementation will be restarted 20 seconds after
// exit (assuming that an error has occurred)
return true
This creates a sensor device that reads the current CPU temperature from the system every minute, and reports it as a sensor value to the Smarthome system. It does not differ from any other temperature sensor (e.g. EnOcean devices etc.) in the way it works.
!!! warning "userlevel 1 required".
Because this example uses readfile() with an absolute path, the device must have at least userlevel 1 for security reasons. Production devices have userlevel 0 by default. Experimental DIY devices and devices with beta versions have userlevel 1. The userlevel is defined in /flash/p44userlevel. Thus, for devices with access to the commandline (e.g. P44-DSB-X) the userlevel can simply be set to 1 with echo 1 >/flash/p44userlevel
.
myStrom switching socket: Simple HTTP API
From mystrom.ch there are switching sockets that can be connected via WiFi and switched and programmed via an app. This device is used here as an example because it has a simple API that can be controlled directly from the LAN {:target="_blank"}. Using this API, such an outlet can easily be used as a switchable output for the smarthome system (without depending on an external cloud!):
Init message (details here):
{
'message':'init',
'output':'light', /* with output of type "light"... */
'dimmable':false, /* ...but not dimmable */
'colorclass':8, /* universally usable, in digitalSTROM: black joker terminal */
}
Implementation (details of the content of the received message() m here): For the example to work, the correct IP address of the socket must be entered, see comment in code:
// the correct IP address of the socket must be entered here
var baseurl = "http://192.168.1.42"
// handle messages from the system (here: change of output value)
on (message()) as m {
// if the message concerns the channel with index 0 (=brightness)...
if (m.message=="channel" && m.index==0) {
// control mySTROM API and switch it on, if brightness > 50%, otherwise off
geturl(baseurl + format('/relay?state=%d', if(m.value>50, 1, 0)))
}
}
return true // it's ok that the script ends here
This creates a device whose output can be set directly in the P44-xx web interface (via the gear icon), and behaves in the digitalSTROM Smarthome with all functions (room assignment, scene programming, etc.) like any other on/off switch actuator.
The myStrom sockets can also measure the current drawn, and report back the current switching state (useful if this has been changed with the button on the socket). This can also be used with a slightly more advanced implementation:
Init-Message (new line sensors is added, details see here):
{
'message':'init',
'output':'light', /* with output of type "light"... */
'dimmable':false, /* ...but not dimmable */
'colorclass':8, /* universally usable, in digitalSTROM: black joker terminal */
/* definition of a power sensor */
'sensors':[{"sensortype":14, "usage":0, "hardwarename": "power", "min":0, "max":2300, "resolution":1, "updateinterval":30, "alivesigninterval":300}]
}
Implementation, which now additionally polls the status every 30 seconds, and reports back the effective state of the output (relay) as well as the power consumption (power).
function updatestate()
{
var t = geturl(baseurl + '/report');
log (7, "myCurrent reply = %s", t)
var state = json(t);
log (6, "myStrom state = %s", state)
if (isvalid(state.relay)) {
var msg = { "message": "channel", "index":0 }
msg.value = if(state.relay, 100, 0);
message(msg)
}
if (isvalid(state.power)) {
var msg = { "message": "sensor", "index":0 }
msg.value = state.power;
message(msg)
}
}
// the correct IP address of the socket must be entered here
var baseurl = "http://192.168.1.42"
on (message()) as m {
if (m.message=="channel" && m.index==0) {
geturl(baseurl + format('/relay?state=%d', if(m.value>50, 1, 0)))
}
}
// every 30 seconds
on (every(30)) {
// Read status
updatestate()
}
// read current state immediately on device startup
updatestate()
return true // it's ok that the script ends here
The new device now has an additional sensor input that shows the current power consumption.
Virtual Weather Station
The following example uses the free openweathermap.org service, which provides pretty good and detailed current weather data for a geographic location and can thus (depending on needs) complement or even replace a dedicated hardware weather station.
This scripted device application was developed, tested in use and made available for this example by Andreas Kleeb of www.redIT.ch Smart Solutions - many thanks!
The example shows that even a more complex device with many sensors can be implemented relatively easily.
Please note
To query the weather data from openweathermap.org, an account must be created there. In this account an API key can be generated, which must be inserted in the implementation script at the designated place. A "How to Start" guide is available here.
Init message (details here):
{
'message': 'init',
'protocol': 'simple',
'group': 8, /* Joker */
'name': 'OpenWeatherMap.org', /* initialer Name */
/* Definition der Wetter-Sensoren */
'sensors': [
/* S0 */ { 'sensortype': 1, 'usage': 2, 'group': 0, 'hardwarename': 'temperature', 'min': 0, 'max': 100, 'resolution': 0.1, 'updateinterval': 120 },
/* S1 */ { 'sensortype': 18, 'usage': 2, 'group': 0, 'hardwarename': 'pressure', 'min': 0, 'max': 1200, 'resolution': 1, 'updateinterval': 120 },
/* S2 */ { 'sensortype': 2, 'usage': 2, 'group': 0, 'hardwarename': 'humidity', 'min': 0, 'max': 100, 'resolution': 1, 'updateinterval': 120 },
/* S3 */ { 'sensortype': 29, 'usage': 2, 'group': 0, 'hardwarename': 'visibility', 'min': 0, 'max': 10000, 'resolution': 1, 'updateinterval': 120 },
/* S4 */ { 'sensortype': 13, 'usage': 2, 'group': 0, 'hardwarename': 'wind speed', 'min': 0, 'max': 300, 'resolution': 0.1, 'updateinterval': 120 },
/* S5 */ { 'sensortype': 19, 'usage': 2, 'group': 0, 'hardwarename': 'wind deg', 'min': 0, 'max': 360, 'resolution': 1, 'updateinterval': 120 },
/* S6 */ { 'sensortype': 23, 'usage': 2, 'group': 0, 'hardwarename': 'wind gust', 'min': 0, 'max': 300, 'resolution': 0.1, 'updateinterval': 120 },
/* S7 */ { 'sensortype': 21, 'usage': 2, 'group': 0, 'hardwarename': 'precipitation', 'min': 0, 'max': 1000, 'resolution': 0.1, 'updateinterval': 120 },
/* S8 */ { 'sensortype': 0, 'usage': 2, 'group': 0, 'hardwarename': 'clouds %', 'min': 0, 'max': 100, 'resolution': 1, 'updateinterval': 120 }
]
}
Implementation (details of the content of the received message() m here): For this example to work, the correct API key and location must be included in the code, see comments:
function updateweather()
{
try {
var t = geturl(baseurl,30)
}
catch {
return true
}
log (7, "OpenWeatherMap reply = %s", t)
var weather = json(t);
log (6, "OpenWeatherMap weather = %s", weather)
if (isvalid(weather.main.temp)) {
message(format("S0=%.1f",weather.main.temp))
}
if (isvalid(weather.main.pressure)) {
message(format("S1=%.1f",weather.main.pressure))
}
if (isvalid(weather.main.humidity)) {
message(format("S2=%.1f",weather.main.humidity))
}
if (isvalid(weather.visibility)) {
message(format("S3=%.1f",weather.visibility))
}
if (isvalid(weather.wind.speed)) {
message(format("S4=%.1f",weather.wind.speed))
}
if (isvalid(weather.wind.deg)) {
message(format("S5=%.1f",weather.wind.deg))
}
/* without gust info -> zero gust */
message(format("S6=%.1f", ifvalid(weather.wind.gust,0)))
/* Precipitation */
var precipitation = ifvalid(
weather.snow['1h'], // Snow..
ifvalid(
weather.rain['1h'], // if no snow: rain
0 // neither snow nor rain
)
)
message(format("S7=%.1f",precipitation))
/* Irradiation */
if (isvalid(weather.clouds.all)) {
message(format("S8=%.1f",weather.clouds.all))
}
}
// the OpenWeatherMap API key, city and country must be entered here
var openweathertoken = 'API_key'; // API key
var openweathercity = 'Zug'; // Location (name of town)
var openweathercountry = 'CH'; // Location (country)
var baseurl = string('https://api.openweathermap.org/data/2.5/weather?q=' + openweathercity + '%2C' + openweathercountry + '&units=metric&appid=' + openweathertoken)
// Every 120 Seconds
on (every(120)) {
// update weather
updateweather()
}
return true // it's ok that the script ends here
This creates a weather station device with 8 sensors for temperature, pressure, humidity, visibility, wind speed, wind direction, gusts, precipitation and cloud cover.
More examples in German part
Note - more examples in German version
Currently, there are some more examples in the German version of this page that are not yet translated to English (which will happen, eventually).