During the course of this tutorial on Dezyne code integration, you have followed the basic steps to successfully integrate code generated from a Dezyne model. In this chapter, you will find some additional materials regarding more advanced aspects of the code integration process. The following items will be covered:

Using the Dezyne locator to distribute (runtime) objects

Thread safety in the Dezyne execution model & thread-safe-shell

Making use of private thread scheduling to poll hardware

If you are interested in learning more about features in the Dezyne modeling language, you are recommended to have a look at the next tutorial, which discusses the usage of the external keyword in Dezyne. The current Controller model has a race condition with its usage of the Timer which you cannot find without the usage of external; in the next tutorial you will learn more about increasing the robustness of your Dezyne models.

Using the Dezyne locator to distribute (runtime) objects

In one of the first chapters of this tutorial, you created instances of dzn::locator and dzn::runtime to be able to construct the generated System and its components. It was mentioned that dzn::locator can be used for more than distributing the runtime; otherwise it wouldn’t make sense to have a separate wrapper, of course.

In this section we will explore some of the default objects stored in the dzn::locator that are used by all Dezyne components as well as discuss how to use the dzn::locator to distribute your own objects throughout a System.

The way the dzn::locator works is it stores all sorts of objects by type. You can use the set() function on a locator object to store an object of a certain type T in the locator, which has two possible outcomes:

  • Object of type T did not previously exist in the dzn::locator; locator now contains an object of type T

  • Object of type T did previously exist in the dzn::locator; previously stored object of type T is overwritten with new object of type T

Default runtime objects

The dzn::locator creates two objects that are used by all Dezyne components, namely an instance of std::clog (which is an object of type std::ostream) for logging purposes and a default dzn::illegal_handler:

struct locator
{
public:
  locator()
  {
    static illegal_handler ih;
    set(std::clog).set(ih);
  }
Custom dzn::illegal_handler

If you wish to not use the default dzn::illegal_handler, you can implement your own dzn::illegal_handler and store it in the locator. The default dzn::illegal_handler will then be overwritten and if an illegal assert occurs during the runtime of your application, the customized handler will be called. This is especially useful if your application consists of potentially dangerous hardware; if you can no longer guarantee the correctness of runtime software, you will want to shut down such hardware gracefully.

An alternative to using the illegal-handler to establish a safe pre-condition to terminating the application would be to write armour components to ensure that native code can never trigger an illegal. Armouring is a technique in Dezyne that can be used to guard control logic from spurious behaviour in native components; see Robert Howe’s paper “Software Archaeology in Practice” for more information on this subject.

Alternative logging methods

Trace logging by all Dezyne components is hardwired to use the std::ostream object in the dzn::locator. If you wish to use your own logging rules, for instance if you would like to log to a file instead of logging straight to std::clog, you can do so by overwriting the std::clog object in the dzn::locator.

To do this, you need to create an std::ostream object that uses a file as output buffer; say we want to log to “log.txt”. std::ostream can not directly write to files, it can only write to buffers; so, you need to create a buffer that writes to “log.txt”. This can be done using std::ofstream as follows:

std::ofstream logfile("log.txt");
std::ostream outstream(nullptr);
outstream.rdbuf(logfile.rdbuf());

With std::ofstream, a filestream to “log.txt” is created. The buffer for this filestream can be accessed by calling the rdbuf() member function of logfile. The buffer that the std::ostream writes to can be set using the same rdbuf() function, on the std::ostream object this time. In the end, you will have an std::ostream object that uses a filestream as its output buffer.

If you then use set() with the custom std::ostream object, your Dezyne application will send all of the trace logging to “log.txt” instead of displaying it in the console output. This could be useful for logging remote applications where you don’t have access to console output, for example.

Distributing your own data

As mentioned in the introduction of this chapter, you can store any object in the dzn::locator, with the limitation that there can only be one object for any given type T in the locator at any point in time. Aside from overwriting runtime objects to provide a custom implementation, you can also use the locator to distribute data objects through the System, for example to be used in native components. Recall that native components are constructed by the System and their constructors require a dzn::locator by reference:

Siren(const dzn::locator& loc);
Sensor(const dzn::locator& loc);
LED(const dzn::locator& loc);
PWManager(const dzn::locator& loc);

Within the implementation of the constructor, you can retrieve objects from the locator that is passed as parameter. This can be useful for initializing hardware. In the example implementations, we used hard-coded values for the GPIO pins. With the dzn::locator, you can distribute an object containing hardware configurations gathered from command line arguments to make the application more dynamic. This would be done by using the set() function to inject a configuration object into the locator, followed by using try_get() in the constructor of a native component. try_get() will return a pointer to the object of type T if it is found, or null if it is not found.

Say we have the following configuration struct:

struct HWConfig {
int GPIO_Red, GPIO_Green, GPIO_Blue;
};

In your main containing the event loop, you could initialize this struct with the use of command line parameters. If you then use set() on your locator with the initialized HWConfig struct, it will be stored in the locator. In the implementation of the LED constructor, you can do the following:

HWConfig *config = loc.try_get(HWConfig);
if(config) {
  this->PIN_RED = config->GPIO_Red;
  this->PIN_GREEN = config->GPIO_Green;
  this->PIN_BLUE = config->GPIO_Blue;
}

With this addition, the default GPIO pin definitions will be overwritten if you supplied a HWConfig struct in the dzn::locator. If you didn’t add the struct, try_get will not be able to find a HWConfig struct and will return 0, so the if-statement will not be entered. If this is the case, you can simply default to preconfigured values.

Thread safety in the Dezyne execution model

The Dezyne language semantics assumes that at most one thread is active in a component at any time. If you are working in a purely single-threaded environment where no exceptions in the thread execution model of your application exist, this condition is easily met. However, most applications will probably end up containing some thread concurrency. At this point, you need to implement a thread safety mechanism to ensure that data remains valid and no concurrent (thread) access takes place while Dezyne logic is being executed.

The easiest way to do this in Dezyne is to generate a thread-safe-shell for your System component. This thread-safe-shell will wrap its internal components in such a way that every function call is placed in an event queue. This event queue is generated with the thread-safe-shell and ensures that events are handled one-by-one in a private thread. Native code can interact with the System on its ports like normal, but interactions are placed in the event queue and processed by the private thread.

The generation of a thread-safe-shell can be done using the command-line client and the following command:

dzn code -l c++ -s SYSTEM FILE

The above command will generate a thread-safe-shell for the System named SYSTEM that is defined in FILE. For the System, *.cc and *.hh files will be generated with some key differences to their counterparts that were generated without the –s option. In *.hh, the System struct now contains a dzn::pump object. dzn::pump is the object that represents the private thread and the associated event queue. In *.cc, the constructor for the System now also implements wrapper functions so that incoming events are handled by the dzn::pump thread instead of the calling thread. (Incoming events should be seen from the perspective of the System, so in-events on a provides port or out-events on a requires port)

Earlier in the tutorial you were told that calling the timeout() function of the iTimer port is not safe by default (Timer and Siren integration). Calling an event from a signal handler is not safe by default. Signal handlers are similar to interrupt-service routines in the sense that they can be called while the process is involved in another function call.

With the default method of generating code from your System component, you run the risk of multiple active threads in the System. By generating a thread-safe-shell variant of the System, however, invoking the timeout event on the iTimer port will place the event in the event queue where it will be handled by the Dezyne private thread. With this change, thread safety is ensured without requiring any changes other than the code generation method. The easiest way to make the change is to add the following rule to the ‘generate’ make recipe:

generate: $(wildcard *.dzn)
          for f in $^; do dzn -v code --version=$(VERSION) -l c++ -o $(SRC) $$f; done
          dzn -v code --version=$(VERSION) -l c++ -o $(SRC) -s $(SYSTEMNAME) $(SYSTEMFILE)
          touch $@

SYSTEMNAME and SYSTEMFILE are variables that should be defined in the makefile.

Making use of private thread scheduling to poll hardware

A native component is able to by-pass the dzn::pump when sending events into the System. Consequently, this means it is no longer possible to guarantee the single-thread-active convention within the System. This can be avoided by interacting with the dzn::pump within the System.

To demonstrate how you can interact with the dzn::pump in a native component, the final component of the AlarmSystem will be implemented and integrated: the sensor. Again, a snapshot of the Dezyne models for this stage of the application can be found on GitHub.

For the Sensor, a hardware button was used that needs to be debounced for reliable input readings. The debouncing algorithm provided in the official Arduino tutorial works on a polling basis, which the event loop for the AlarmSystem does not support. On top of that, polling the native Sensor component outside of the safe Dezyne thread can lead to undesirable effects. Both of these challenges can be solved by making use of the dzn::pump event queue in the native Sensor implementation. If you store the logic that requires polling in a separate poll() function and place the poll() event in the queue while the Sensor is active, the private Dezyne thread will handle the polling and thread safety remains intact.

Below you can find a native Sensor implementation that implements the Arduino example debouncing algorithm by the use of dzn::pump to ensure thread safety.

Sensor.hh:

#include "ISensor.hh"
#include <dzn/pump.hh>

class Sensor : public skel::Sensor {
private:
  dzn::pump& pump;
  bool sensor_value;
  bool last_sensor_value;
  unsigned long last_dbnc_time;
  bool polling;
public:
Sensor(const dzn::locator& loc);
  void iSensor_turnOn();
  void iSensor_turnOff();
  void poll();
};

Sensor.cc:

#include <dzn/locator.hh>
#include <dzn/runtime.hh>

#include <wiringPi.h>
#include "Sensor.hh"

const int DEBOUNCE_TIME = 50;
const int PIN_SENSOR = 16;

Sensor::Sensor(const dzn::locator& loc) : skel::Sensor(loc), pump(loc.get<dzn::pump>()) {
wiringPiSetup();
pinMode(PIN_SENSOR, INPUT);

pullUpDnControl(PIN_SENSOR, PUD_UP);
  this->sensor_value = false;
  this->last_sensor_value = false;
  this->last_dbnc_time = 0;
  this->polling = false;
}

void Sensor::iSensor_turnOn() {
  this->polling = true;
  this->pump( [&] { this->poll(); } );
}

void Sensor::iSensor_turnOff() {
  this->polling = false;
}

void Sensor::poll() {
  int new_sensor_value = !digitalRead(PIN_SENSOR);

  if (new_sensor_value != this->last_sensor_value) {
    this->last_dbnc_time = millis();
  }

  if((millis() - last_dbnc_time) > DEBOUNCE_TIME) {

    if(new_sensor_value != this->sensor_value) {
      this->sensor_value = new_sensor_value;
      if(this->sensor_value) {
        this->pump( [&] { this->iSensor.out.triggered(); } );
      }
    }
  }
  this->last_sensor_value = new_sensor_value;
  if(this->polling) this->pump( [&] { this->poll(); } );
}

The interactions with the dzn::pump in the System are highlighted; the Sensor class has its own reference to the dzn::pump. This reference is set in the constructor by getting the System dzn::pump from the provided dzn::locator.

The usage of dzn::pump involves providing std::function objects of events that need to be handled. The easy way to do this is by wrapping functions in lambda expressions as can be seen in the above code snippet. Finally, a way to ensure that polling is continued while the Sensor is turned on is to have the poll() method store itself in the event queue recursively.