Vermont Energy Control Systems

Practical monitoring and control for the real world

User-Defined Tasks

Sample Task

Almost every application can be handled using the rule engine that's provided with the Vesta. However, there could be situations where more complex logic is required. As an Open Source platform, the Vesta comes with complete source code and the ability to add custom code. This section describes the process of compiling a task that allows the Vesta to perform operations that can't easily be done using the standard rule engine.

Tasks are written in C and built on a standard template using library functions to access shared memory and other services. This section documents task structure using the sampleTask.c program that’s distributed with the system. We’ll go through the program section by section.

Well-Behaved Tasks

In order to be well-behaved, a task needs to have a unique ID. This ID is used to claim write access to shared memory data elements. The ID values are defined in vecs.h as follows:

    #define SERVER_ID 1
    #define CTL_ID  2
    #define WEBEDIT_ID 3
    #define LOGGER_ID 4
    #define SQLLOGGER_ID 5
    #define ONEWIRE_ID 6
    #define PID_ID 7
    #define NETIO_ID 8
    #define USR1_ID 20
    #define USR2_ID 21
    #define USR3_ID 22
    #define USR4_ID 23
        

The system also maintains a matching list of shared memory variables that tells each task how often they should run. The ‘System’ tab provides a mechanism for setting task frequency. Each task should access one of these ‘period’ variables.

The last four entries in the list above are for user-created tasks, named USR1 through USR4. This example will be written to run as USR1, so its task ID will be USR1_ID and it will run at an interval determined by usr1_period.

Task Outline

Almost all tasks have the same basic structure:

Perform startup initialization

Eternal Loop: { Perform periodic initialization if needed Read data from shared memory Do some computation Write results to shared memory Sleep until next cycle }

Task Support API

The system provides a set of functions that simplify writing a well behaved task. These functions handle interaction with shared memory as well as sleep management.

This sample task uses most of the task support API:

  • get_shm(taskid) - get link to shared memory
  • getNow() - get current timestamp
  • reloadRequired(taskid) - true if this task needs to reload data due to changes
  • claimElement(elementid,taskid) - claim write access to a shared memory data element
  • clearReloadFlag(taskid) - clear reload flag for this task
  • elementValue(elementid) - get value of data element from shared memory
  • setElement(elementid,value,diskflag) - set data element value in shared memory
  • timedSleep(processname,period,starttime) - sleep until next cycle is due

Sample Task Function

This task will perform the following functions:

  • Read two temperature values
  • Determine the difference
  • Find the higher of the two
  • Set one variable to the value of the difference
  • Set another variable equal to the higher of the two

Screenshot of control panel with sampleTask running:

Screenshot of sampleTask running

Sample Task Code

This section contains the complete code for sampleTask, with each section described in detail.

Includes and Defines

Like all C programs, you’ll need to include header files appropriate to the functions that you use. Additionally, you’ll need to include ‘nfcs.h’. Each process also has a name that’s used for status and error logging - in this case, ‘sample’.

#include <stdio.h>
    #include <fcntl.h>            // For file I/O
    #include "nfcs.h"

    // Choose task name here. Used for status and error logging.
    #define PROCESSNAME  FUNCTION_NAME(sample)
        

Variables

Like all C programs, a function named ‘main’ is required. In this case all of the code is in this single function. We’ll need a variable to keep track of timing - start_usec in this example. We also have variables to hold the temperatures from shared memory and the values that we’ll write to shared memory.

main(){

      // Variable for calculating sleep interval
      unsigned long long start_usec;

      // We need element ID values for elements that we'll use.
      // Normally will get list from disk file. In this case we'll hard code.
      int temp1_id = 2;
      int temp2_id = 3;
      int diff_id = 4;
      int max_id = 5;

      // Variables to store shared memory values. Not necessary but can improve readability.
      float temp1, temp2, diff, max;
        

Initialization

There are four initializations that could be necessary:

  1. Establish shared memory link
  2. Initialize cycle timer
  3. Claim shared memory variables that this task will write to
  4. Read task-specific data from disk file(s)

The first two happen only once, when the program is first run. The second two could happen repeatedly, and are handled in the main loop.

Initializing shared memory and cycle timer:

  // Get shared memory link.
      get_shm(USR1_ID); start_usec = getNow();
        

Controller tasks typically run forever with a timed sleep after each pass through the main loop.

  // Do forever
    while (1){
        

At the start of each cycle, check to see if there has been a change that requires us to refresh our shared memory or disk file data. For instance, the user could have changed element numbers. There’s a shared memory flag that we check to see if we need to re-initialize. This is always invoked the very first pass through. At the very least, we should claim the shared memory elements that we are going to write to. Claiming elements will generate warnings if other tasks have already claimed them, although it is legal for more than one process to write to the same element.

   
    // Each cycle, check to see if we should respond to config changes in shared memory.
    // If we read a disk file, we should do that here too. Must use valid ID here as well
    if (reloadRequired(USR1_ID)){
      // Claim our elements (the ones we'll write to)
      claimElement(diff_id,USR1_ID);
      claimElement(max_id,USR1_ID);
      // Clear our bit in the reload flag
      clearReloadFlag(USR1_ID);
    }
        

Do the Work

Now that we’re through with any initialization, we need to do the actual task. Typically this involves reading some data from shared memory, performing some computation, and writing values back into shared memory. In this example, we’ll read two temperature values as ‘temp1’ and ‘temp2’. We’ll calculate the difference and determine the larger of the two temperatures.

    // Read values from shared memory using elementValue() function
    temp1 = elementValue(temp1_id);
    temp2 = elementValue(temp2_id);

    // Calculate diff
    diff = temp1 - temp2;

    // Determine value for max
    if(temp1 > temp2){
      max = temp1;
    }else{
      max = temp2;
    }
        

Post the Results

Once the calculations are complete, the resulting values will be written back into two shared memory variables using the setElement() function. This function requires three arguments:

  1. The ID of the element to be written
  2. The value to be written
  3. A ‘disk write’ flag

The disk write flag tells the controller whether to update the elements.csv file on disk with the new value. Writing to disk means that the value will persist across system restarts. This would be appropriate for a user-defined variable such as ‘Top Floor Setpoint’, but would be pointless for this example. We’ll use a value of ‘0’ to indicate that the disk file does not need to be written.

   
    // Update shared memory with new values using setElement.
    // setElement needs element ID, value, and disk write flag.
    setElement(diff_id, diff, 0);
    setElement(max_id, max, 0);
        

Sleep

Once the work is done, we need to sleep until it’s time to run again. Our sleep interval is determined by a system configuration value - in this case, usr1_period which can be set on the controller’s ‘System’ tab.

    // Sleep until next cycle. Select appropriate period for your ID.
    timedSleep(PROCESSNAME, config->usr1_period, &start_usec);
  }
}