Writing Periodic Scripts¶
Periodic Scripts are useful for running tasks that need to occur on a repeated basis. Possible uses include collecting data from external sources, running reports, or performing maintenance tasks. Check out the Periodic Scripts page for more detail on their uses and configuration in BMON. The intent of this page is to provide some guidance to Developers wishing to write a custom Periodic Script that can be run by BMON.
When writing a custom Periodic Script, it is helpful to look at an example
script. The bmon/bmsapp/periodic_scripts/okofen.py
is one such example. A somewhat more complicated example is
bmon/bmsapp/periodic_scripts/ecobee.py
.
Basic Requirements of a Custom Periodic Script¶
There are a few requirements for a custom Periodic Script:
- The script must reside in a Python file located in the
bmon/bmsapp/periodic_scripts/
directory. - That Python file must have a
run()
function that, at a minimum, accepts arbitrary keyword arguments; i.e. it must have a**kwargs
parameter. - The return value from the
run()
function must be a Python dictionary, although it can be an empty dictionary if there is no need for a return value.
More details on these requirements are presented below.
Arguments Passed to the run()
Function¶
The Periodic Script resides in a Python file located in the
bmon/bmsapp/periodic_scripts/
directory. The name of the Python file,
excluding the “.py” extension is the File Name that is entered in the
Periodic Script configuration inputs. The run()
function in that
file is called at the periodic interval specified when the BMON System
Administrator configures the script.
Here is the minimum signature of the run()
function, which must allow for arbitrary keyword arguments:
def run(**kwargs):
# Script code goes here
The run()
function can also include specific keyword parameters with default values, such as:
def run(account_num='', units='metric', **kwargs):
# Script code goes here
When the run()
function is called, it is passed a number of keyword
arguments, and the arguments are generated from these sources:
The
Script Parameters in YAML form
input from the Periodic Script configuration inputs. As an example, if the Script Parameters input is:account_num: 1845236 include_occupancy: True
the
run()
function will be called with these arguments:
run(account_num=1845236, include_occupancy=True)
The Results returned from the prior run of the Script. As discussed in more detail below, the Periodic Script returns a Python dictionary. Each one of the key/value pairs in that dictionary are converted to keyword arguments and passed to the next run of the script. Continuing the example above, if the Script returned the following Python dictionary:
{'last_record': 2389, 'last_run_ts': 143234423}
the next call to the
run()
function of the Periodic Script will look like this:run(account_num=1845236, include_occupancy=True, last_record=2389, last_run_ts=143234423)
This example shows the arguments combined from the two sources mentioned so far.
There is special treatment of return values that are in the
hidden
key of the return dictionary. The purpose of thehidden
key is discussed in more detail below, but the return values in that key are processed differently than other keys. Thehidden
key should contain another dictionary of key/value pairs, and those key/value pairs are extracted from thehidden
value and passed to therun()
function as separate arguments. Continuing the above example, ifrun()
returns the following dictionary:{'last_record': 2389, 'last_run_ts': 143234423, 'hidden': {'auth_key': 'x4ab72i'}}
the next call to the
run()
function of the Periodic Script will look like this:run(account_num=1845236, include_occupancy=True, last_record=2389, last_run_ts=143234423, auth_key='x4ab72i')
If the same keyword argument appears in more than one of the above sources, the highest priority is Script Parameters in YAML form
, then visible results from the prior run of the script, and finally hidden results from the prior run of the script.
The Return Value from the run()
Function¶
There are a few different purposes for the Python dictionary that is returned from the run()
function:
- As stated before, values in that dictionary are passed as arguments
to the next call to the
run()
function. This can be useful for tracking things like the time or ID of the last record extracted from a data source, so that future calls only extract newer data. (Note that storing the same sensor reading multiple times in BMON does not cause an error.) - The values returned by
run()
are displayed in the Django Admin interface, so are useful for debugging script problems or displaying status messages. The values appear in theScript results in YAML form
field on the form used to configure the Periodic Script. The exception to this are the values that appear in the specialhidden
key in the return dictionary; they are not displayed in the configuration form, but are passed to the next call to therun()
function. This feature is useful for storing authorization keys that should not be readily viewed by the System Administrator. The feature is also useful if some of the return values from the script would be confusing or not useful if viewed in the System Admin interface. - Sensor readings acquired by the Periodic Script can be returned in
the special
readings
key in the return dictionary, and these readings will be automatically stored in the BMON sensor reading database (more detail later). - A list of Script Parameter names can be returned in the special
delete_params
key, and these parameters will automatically be deleted from theScript Parameters in YAML form
input on the Periodic Script configuration form. This can useful for deleting out authorization keys that are no longer valid or should be hidden from the System Administrator. An example use of thedelete_params
key in a return dictionary is:{'last_record': 2389, 'delete_params': ['access_token', 'refresh_token']}
. After this dictionary is returned, the script parametersaccess_token
andrefresh_token
will be deleted from theScript Parameters in YAML form
input, if they exist there. Also, thisdelete_params
key/value pair will not be passed to the next call of therun()
function and will not be displayed in the Script Results field in the Admin interface.
A common use of a Periodic Script is to collect sensor readings from an external source. A
special feature has been built into the Periodic Script framework to allow for easy
storage of those collected readings. If the Script returns the sensor readings as a list
of 3-element-tuples, and that list is stored in the readings
key of the return dictionary,
the readings will automatically be stored in BMON’s sensor reading database. Here is an example
return dictionary that contains three sensor readings that will be stored by BMON:
{ 'readings': [(1479769950, '311015614158_temp', 70.1),
(1479769950, '311015614158_heat_setpoint', 69.0),
(1479769950, '311015614158_rh', 23)]
}
Each reading is formatted in a 3-element tuple:
(Unix Timestamp of reading, Sensor ID, Reading Value)
These reading values are not displayed in the Script Results
field of the configuration
screen, but the storage message returned by the BMON sensor reading database is
displayed. Here is an example:

The reading_insert_message
indicates that on the last run of this Periodic Script,
15 readings were collected and stored in the BMON sensor reading database.
The Script Results example above also shows some other data that is added to the Script Results for display to the System Admin. The time when the script ran last is shown, and the amount of time required to run the script is shown. Had an error been raised by the script, the traceback from that error would be shown here as well.
Note that the Periodic Script can collect sensor readings and have them stored in the BMON sensor reading database. However, those readings will not be displayed in charts and reports without configuring each Sensor ID in the Sensors table using the Admin interface. This process is described in the “Adding Sensors” section of the Add Buildings and Sensors document.