Next: , Up: Expanding the AlarmSystem   [Contents]


3.5.1 Timer and Siren integration

One integration step you haven’t seen yet is the binding of foreign code to a System function (i.e. calling foreign code from Dezyne). This step is performed when you need to integrate an out-event on a provides port or an in-event on a requires port. Recall that the provides/requires is seen from a System perspective.

images/system_6

A good starting point for this integration step is the AlarmSystem with foreign LED component that you have already fully integrated by now. By adding the Siren and Timer from the introductory section, you can learn how to perform this last type of integration. A starting point containing the relevant Dezyne models for this section can be found on https://github.com/VerumSoftwareTools/DezyneSection/tree/master/Code_Integration/Ch4_Siren_Timer.

Note that in the System component in AlarmSystem.dzn, a requires ITimer was added as opposed to using a component without behavior that provides ITimer. The reasoning behind this is that C++ already has access to a complete Timer implementation on most GNU/Linux distributions, including Raspbian which we are running on the Raspberry Pi. In such a case, it might be simpler to integrate the existing implementation as a required port like you will see in this section.

You can start off by generating code from the models again, either through the makefile or by use of the Dezyne-IDE client. Integrating the Siren should be rather simple after having integrated the LED already. We chose to stub the Siren functionalities with simple console output messages (Siren is activated, Siren is deactivated). Start off by integrating the Siren as an exercise.

Solution:

Siren.hh:

#include "skel_Siren.hh"

class Siren : public skel::Siren {
public:
  Siren(const dzn::locator& loc);
  void iSiren_turnOn();
  void iSiren_turnOff();
};

Siren.cc:

#include <iostream>

#include "Siren.hh"

Siren::Siren(const dzn::locator& loc) : skel::Siren(loc) {
  //no op
}

void Siren::iSiren_turnOn() {
  std::cout << "SIREN >>ACTIVATED<<" << std::endl;
}

void Siren::iSiren_turnOff() {
  std::cout << "SIREN >>DEACTIVATED<<" << std::endl;
}

After you have implemented and integrated the Siren, you should be able to compile the application again with the provided makefile. However, when you try to run the dznpi executable, the application will abort upon hitting the as.check_bindings(); line that is processed before entering the event loop with the following error message:

images/error_msg

As you can see, check_bindings() discovered that iTimer’s start function has not been bound. Previously we could not see the effects of the check_bindings() statement as we did not run the application yet. Now you can clearly see the difference between the integration of a component without behaviour and the integration of an unbound port; try leaving out the declaration and implementation of iSiren_turnOn in your foreign Siren implementation. You will discover that you can’t even compile the application, whereas the fact that the iTimer port on the System was not bound couldn’t be discovered until the application was already running.

Now of course, this AlarmSystem application is rather trivial and there are few implications for running the application and hitting the check_bindings() assert. However, imagine having a much more complex, expensive system that is partly through its initialisation process when hitting the assert- sure, you can implement measures to have the application shut down gracefully but it is a situation you would like to avoid. By using components without behaviour for your foreign implementations these issues can be found while compiling before the application is ever run. This does not mean you should skip calling check_bindings() before initiating events on the System, however.

But let’s continue with the integration process. Like we mentioned earlier in this section, we can access a complete Timer implementation in C++ on the Raspberry- the alarm (https://linux.die.net/man/3/alarm) library. What remains to be done is to bind the events on the iTimer port to the controls of this alarm library. The table that was provided in the Implementation of events on interfaces section earlier in the section suggests that the necessary integration step is to assign foreign code to a System function for in-events and call Dezyne functions from foreign code for out-events.

A quick recap: a System can expose ports, these ports contain structs for in-events and out-events where the in- and out-events are represented by std::function objects. For iTimer, which is a port of type ITimer, these are the generated structs:

 struct
  {
    std::function<void(long milliseconds)> start;
    std::function<void()> cancel;
  } in;

  struct
  {
    std::function<void()> timeout;

  } out;

For the iTimer port, a timer timeout must result in calling the out.timeout() function. From the manual for the alarm library, we can see that the alarm will generate a SIGALRM signal when the time has elapsed. Handling SIGALRM can be done by implementing a signal handler and binding SIGALRM to this signal handler in C++ as follows:

#include <signal.h>

void sigalrm_handler(int signal) {
  // signal handling logic
}

// in main():
signal(SIGALRM, sigalrm_handler);

The signal handler is called when SIGALRM is raised, so when the alarm times out. In this signal handler, you should not fire the timeout event into the Dezyne system immediately; the reasoning behind this has to do with how Dezyne’s execution model works based on certain assumptions. This will be covered later in Thread safety in the Dezyne execution model. For now, consider it safe to call the Dezyne function in the signal handler, but keep in mind that this is not something you can generally assume.

To be able to call the timeout function in the signal handler, you need to declare a global std::function object. The AlarmSystem you are interacting with in your event loop has its scope limited to the main() function, but the signal handler is separated from that. You can work around this by declaring a global std::function object and setting its value to the corresponding timeout event on the AlarmSystem’s iTimer port:

#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>

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

std::function<void()> timeout;

void sigalrm_handler(int signal) {
  // For now, call the global timeout() function object within this signal handler
  timeout();
}

int
main(int argc, char* argv[])
{
  dzn::locator loc;
  dzn::runtime rt;

  loc.set(rt);

  AlarmSystem as(loc);
  as.dzn_meta.name = "AlarmSystem";

  timeout = as.iTimer.out.timeout;
  signal(SIGALRM, sigalrm_handler);

  as.check_bindings();

  std::string input;
  while(std::cin >> input) {
    if(input.compare("s") == 0) {
      as.iController.in.sensorTriggered();
    }
    else if(input.compare("v") == 0) {
      as.iController.in.validPincode();
    }
  }
}

However, we still have not fully bound the iTimer port on the AlarmSystem. The in-events require an implementation in your foreign code; start(long milliseconds) and cancel() must be bound to their respective counterparts in the alarm implementation. The most efficient way to do this is by using lambda expressions. Starting and cancelling the alarm can be done by the following expressions:

To bind these implementations to their respective events on the iTimer port, a simple assignment will suffice:

#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>

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

std::function<void()> timeout;

void sigalrm_handler(int signal) {
  // For now, call the global timeout() function object within this signal handler
  timeout();
}

int
main(int argc, char* argv[])
{
  dzn::locator loc;
  dzn::runtime rt;

  loc.set(rt);

  AlarmSystem as(loc);
  as.dzn_meta.name = "AlarmSystem";

  as.iTimer.in.start = [](int ms){ alarm(ms/1000); };
  as.iTimer.in.cancel = [](){ alarm(0); };
  timeout = as.iTimer.out.timeout;
  signal(SIGALRM, sigalrm_handler);

  as.check_bindings();

  std::string input;
  while(std::cin >> input) {
    if(input.compare("s") == 0) {
      as.iController.in.sensorTriggered();
    }
    else if(input.compare("v") == 0) {
      as.iController.in.validPincode();
    }
  }
}

Next: , Up: Expanding the AlarmSystem   [Contents]