We’re here to help you

Menu

Expanding the AlarmSystem

In the previous chapter on code integration, we did not cover all possible integration steps. In this chapter, we will gradually expand the AlarmSystem into the final version that can be found in the introductory tutorial, including an added functionality that lets us explore how data parameters are handled in generated and native code.

After this chapter, you will be able to:

  • Integrate all four kinds of event types known to Dezyne

  • Understand the role of data within the generated Dezyne framework

Timer and Siren integration

One integration step you haven’t seen yet is the binding of native code to a System function (i.e. calling native 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.

A good starting point for this integration step is the AlarmSystem with native LED component that you have already fully integrated by now. By adding the Siren and Timer from the introductory tutorial, you can learn how to perform this last type of integration. A starting point containing the relevant Dezyne models for this chapter can be found here.

image

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 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 chapter.

You can start off by generating code from the models again, either through the makefile or by use of the Eclipse 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.


Siren.hh:

#include "ISiren.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:

image

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 native 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 native 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 chapter, we can access a complete Timer implementation in C++ on the Raspberry- the 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 chapter earlier in the tutorial suggests that the necessary integration step is to assign native code to a System function for in-events and call Dezyne functions from native 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 notsomething 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 native 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:

  • start(long milliseconds) –> [](int ms){ alarm(ms/1000); }

  • cancel() –> [](){ alarm(0); }

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();
   }
  }
}

Handling data: password entry

The Alarm System you created in the introductory tutorial assumed that the keypad the user entered their password with could assert whether the password was correct. This sounds like an awfully smart component, providing not completely related functionalities in capturing input and assessing the correctness of input. An easy way to split that up would be to separate these two actions; on the IController interface, change the validPassword event to a passwordEntered event with a string parameter and create a component that can be used to assess the validity of a given password.

The new IController interface would look like this:

interface IController {
  in void passwordEntered(String pw);
  in void sensorTriggered();

  behaviour {
    on passwordEntered: {}
    on sensorTriggered: {}
   }
}

The interface for a password management component would look like this:

extern String $std::string$;
interface IPWManager {
  in bool verifyPassword(String pw);

  behaviour {
    on verifyPassword: reply(true);
    on verifyPassword: reply(false);
  }
}

As checking the validity of a password cannot be done in Dezyne, create a component without behavior that provides the IPWManager interface so this can be implemented in native code. Additionally, extend the behavior of the Controller component to make use of this new PWManager component by replacing the on iController.validPincode(): event statements with on iController.passwordEntered(pw): statements. The parameter to this function, pw, can then be verified with the new IPWManager port on the Controller component as follows:

[state.Unarmed] {
  on iController.passwordEntered(pw): {
    bool valid = iPWManager.verifyPassword(pw);
    if(valid) {
      state = State.Armed;
      iLed.setYellow();
    }
  }
  on iController.sensorTriggered(): {}
  on iTimer.timeout(): illegal;
}

Implement these changes for the other two state behavior blocks as well (Armed, Alarming). Lastly, update the System component to reflect the new interfaces and components. Re-generate code from the updated Dezyne models and create the necessary files for the native implementation of the password manager.

As you were previously capturing user input to trigger events on the AlarmSystem, some changes will need to be made to how the user input was being processed. Instead of mapping ‘v’ to the validPassword event like we did earlier, the simplest way to accommodate for the change is to treat every user input that is not an event trigger as a password entry, like such:

std::string input;
while(std::cin >> input) {
  if(input.compare("s") == 0) {
   as.iController.in.sensorTriggered();
  }
  else /*user input counts as password*/ {
   as.iController.in.passwordEntered(input);
  }
}

Note that the function prototype in C++ for the passwordEntered event accepts std::string as parameter, just like declared in the Dezyne model by using extern String $std::string$;. By calling the passwordEntered event with the input string, the data object is sent to the AlarmSystem. Within the System, the data object is passed to another component; the PWManager component in this case. Although this data object has no meaning for Dezyne model code, Dezyne can transport such data objects across components. Of course, this is a trivially easy example but it should give you an idea of how data gathered from one component can be handled in another component.

The last thing that needs to be done is the native implementation of the PWManager component’s behaviour. As an exercise, define and implement the PWManager. The correct password should be “Dezyne” and should only be accessible to the PWManager class itself.

Hint: in C++, you can use string::compare or the operator== to compare strings with one another.


PWManager.hh:

#include <string>
#include "IPWManager.hh"
class PWManager : public skel::PWManager {
  const std::string password = "Dezyne";
  bool iPWManager_verifyPassword(std::string pw);
public:
  PWManager(const dzn::locator& loc);
};

PWManager.cc:

#include "PWManager.hh"
PWManager::PWManager (const dzn::locator& loc){
  //no op
}
bool PWManager::iPWManager_verifyPassword(std::string pw){
  return pw.compare(this->password == 0);
}


If you have questions that weren’t answered by this Guide,
let our support team help you out.

Enjoy this article? Don't forget to share.
google-site-verification:google656c7703ab521151.html