Through the course of this chapter of the tutorial, you will be gradually expanding the C++ environment you prepared in the previous chapter into an application where native code and Dezyne-generated code co-operate. At the end of this chapter you will be able to:

  • Identify what must be implemented natively in your Dezyne application

  • Integrate two of four kinds of event types known to Dezyne

  • Make use of class inheritance to improve the Dezyne integration process

Next step in integration: detecting native components

Let’s have a look at the situation currently at hand. If all went well, you should have a file which includes your main function, in which Dezyne runtime components were created and an instance of the Dezyne System was created. We have had a brief look at how the System controls its internal components and how to access ports on the System’s boundary.

The next step in the integration process is to implement the functionalities that could not be modeled in Dezyne such as driving hardware and manipulating data. To do this, though, you need to know two things: what needs to be implemented and where to implement it to make integration as smooth as possible.

image

The quickest way to figure out what must be implemented natively is by having a look at the System view in Dezyne. You may have already noticed there are two differently colored components in the AlarmSystem; one is a darker green, whereas the other is blue. These colors can be used to quickly assess whether a component was implemented in Dezyne. A blue component in a system indicates that the component in question was defined as a component without behavior. This should set off some alarm bells (pun very intended) because no behavior means no functionality. This leads us to a first conclusion: blue components in a System view must be implemented in native code.

The second indicator of implementation that must be done natively is the presence of ports on the boundary of a System in the System view. Such a port, as you can imagine, means that an interface is provided (or required) but there’s no entity in Dezyne that is making use of the provided interface (or implementing the functionality required by the System). This leads us to the second and last conclusion: ports on the boundary of a System must be bound in hand-written code.

So now that we’ve identified what needs to be implemented it’s time to look at where to do so. In the case of a blue component, a very specific method of implementation must be followed.

We have seen before that every .dzn file leads to the generation of a header and an implementation file in C++. For every native component the code generator creates an abstract base class in the header file with (pure) virtual functions for all events. The abstract class is put inside a namespace "skel". The implementation of the abstract class must be done in files called LED.hh and LED.cc and the struct/class that inherits from skel::LED must be named LED.

Although this may feel restrictive, this approach has considerable upsides too: the System becomes responsible for the distribution of runtime objects in the component and through the use of abstract classes and pure virtual functions you can verify that your implementation is complete compile-time instead of discovering such things run-time. We will cover the integration process of blue components in more detail later in Implementing generated ‘skeleton’ components

In case abstract or pure virtual are unfamiliar terms to you, please refer to the introduction of this tutorial where some links to additional information are given (Intended audience and prerequisites).

There is one more aspect to be aware of: Before release 2.4 one code file was generated for every component. Currently for every Dezyne file one code file is being generated. Since we want to put the handwritten code for the led in the files called LED.cc and LED.hh (named after the LED component in the dzn file) we need to avoid a name conflict with the files being generated. Hence we need to make sure that the Dezyne file is not called LED.dzn. In this tutorial we have adopted the convention to use a prefix ‘I’. The file ILED.dzn is the Dezyne interface representation of the handwritten code.

The second case we discussed was ports on System boundaries. The implementation method for this case is not as restrictive. The implementation may be done wherever you like, as long as all events on the interfaces of the unbound ports are bound eventually. This sounds a lot easier than having to deal with abstract classes and pure virtuals, and it is, but it comes with the tradeoff of bearing more responsibilities as developer.

In the next chapter, we will cover the implementation of events on unbound ports.

In general, it is recommended to encapsulate your application’s functionalities as components in a System. It increases the cohesion of the overall application and it makes it much easier to find the whereabouts of hand-written code in respect to the overall system. An added benefit is that all sorts of useful meta-information is automatically generated by Dezyne while your application is running which can help tremendously in tracking the behavior of your application.

Implementation of events on interfaces

It’s time to put the information we gathered from the System view in the previous chapter to use. To get a feel for interacting with the generated System, let’s start with the ‘easy’ method of integrating handwritten code: binding unbound ports. The port that is currently not bound on the AlarmSystem is of type IController. From the perspective of the System, this is a provides port. The IController interface consists of two in-events, namely validPincode and sensorTriggered. If you have a look at the generated IController.hh file, you can see how this translates to generated code:

struct IController
{
  struct
  {
   std::function<void()> validPincode;
   std::function<void()> sensorTriggered;
  } in;

  struct
  {

  } out;
  // Dezyne meta information
}

For every interface in a Dezyne System, such a file is generated. As you can imagine, in-events on an interface can be found in the in struct and out-events in the out struct. These structs contain std::function objects, which can be invoked or assigned to.

In the learning goals, four event types were mentioned. These four types are the cross product of in- and out-events on provides and requires ports. There are two distinct integration steps that can be performed to cover the integration of the four event types. The following table serves as an overview of when to apply what integration step:

Port type Event type User action

Provides interface (on top of a component)

In

Call Dezyne function from handwritten code

**

Out

Assign handwritten code to system function

Requires interface (on the bottom of a component)

In

Assign handwritten code to system function

**

Out

Call Dezyne function from handwritten code

In case of the IController port, a provides port, both of its events are in-events. Therefore, we must call the Dezyne functions from handwritten code. You may recall that earlier in the tutorial we examined the generated AlarmSystem and noted that its IController port can be accessed on the AlarmSystem object (Inspecting the generated system). Then, on the IController port, we can access events from the in and out structs. The calling of events should take place in the event loop which you created.

Let’s make the event loop a bit smarter, so we can use it to process some user input. If we then map the user input to triggers for the validPincode and sensorTriggered events, we can control the AlarmSystem. The following code snippet is sufficient for processing simple user input:

std::string input;
while(std::cin >> input) { // Loop is executed upon every newline from user input
  if(input.compare("s") == 0) {
    // s == sensorTriggered
  }
  else if(input.compare("v") == 0) {
    // v == validPincode
  }
}

If you replace the comments with the corresponding function calls on the AlarmSystem object, the integration steps for the IController port are complete.

Exercise: Replace the comments with the help of the information in this chapter. The solution is hidden below the code sample.


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

  AlarmSystem as(loc);

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


At this stage, the AlarmSystem has no unbound ports that contain provides/out or requires/in events. Later in the tutorial, when we add timer functionality to the AlarmSystem, you will see how this integration step is performed (Timer and Siren integration). First, let’s have a look at implementing the LED component.

Implementing generated ‘skeleton’ components

To start implementing the LED component’s behavior, create the LED.cc and LED.hh files. The AlarmSystem you generated from the Dezyne models already includes the LED.hh file by name, which is why you are restricted in naming it. In this LED.hh file you are also required to define a struct (or class, they are the same in C++) LED that inherits from skel::LED. The LED class definition in LED.hh will look like this:

class LED : public skel::LED {
}

By inheriting from skel::LED, the LED class is forced to implement all pure virtual functions defined in the base class. The pure virtual functions that are defined in the base class are all events that would normally be handled by the respective component’s behavior. Since this file is included in the generated code for the skel::LED we do not have to include anything in this file. Let’s take a look at the generated skel::LED class:

namespace skel {
  struct LED
  {
   // Dezyne meta information
   ILED iLed;
   LED(const dzn::locator&);
   virtual ~LED();
   // Dezyne meta informaton
   private:
   virtual void iLed_setGreen() = 0;
   virtual void iLed_setYellow() = 0;
   virtual void iLed_setRed() = 0;
   virtual void iLed_turnOff() = 0;
  };
}

As expected, the events we promised to implement in the ILED interface (setGreen, setYellow, setRed and turnOff) are pure virtual functions in the generated base class.

Exercise: Add these function declarations to the LED class definition in LED.hh.


#include "ILED.hh"
class LED : public skel::LED {
  void iLed_setGreen();
  void iLed_setYellow();
  void iLed_setRed();
  void iLed_turnOff();
}


Lastly, as the LED class you’ll be creating will be constructed like any other generated Dezyne class, you will need to define a constructor for the LED that accepts a dzn::locator reference as parameter. The final version of the native LED class definition, from the Dezyne perspective, will look like this:

#include "ILED.hh"
class LED : public skel::LED {
  void iLed_setGreen();
  void iLed_setYellow();
  void iLed_setRed();
  void iLed_turnOff();


public:
  LED(const dzn::locator& loc);
}

Note that the constructor must be public; this is because the generated AlarmSystem directly calls the constructor of the LED class. The functions it implements do not have to be public; those functions are called through the LED class’ ILED port. The file where the_skel_LED is defined already includes the necessary files from the Dezyne runtime to be able to refer to dzn::locator, so that’s taken care of as well.

With the definition out of the way, what’s left to be done is the implementation of the functions of the LED class. Driving an LED on the Raspberry Pi can be done with the WiringPi library. To make use of WiringPi, its setup function needs to be called and the GPIO pins connected to the LED must be initialized. This is hardware-specific setup that should be performed in the implementation of the LED’s constructor; it will be performed when the constructor is called by the generated AlarmSystem.

Exercise: Write an implementation for the constructor using wiringPiSetup() and pinMode() for your hardware setup.


#include <wiringPi.h>
#include "LED.hh"
LED::LED(const dzn::locator& loc) ...
{
  wiringPiSetup();
  //Pin numbers are declared in LED.hh as PIN_RED, PIN_GREEN and PIN_BLUE for readability
  pinMode(PIN_RED, OUTPUT);
  pinMode(PIN_GREEN, OUTPUT);
  pinMode(PIN_BLUE, OUTPUT);
}


The solution above takes care of all the hardware setup, but that is not all we want to do with the native implementation of a Dezyne component. To fully integrate a native component in the AlarmSystem, you should also call the constructor of the component’s base class to handle Dezyne related initialization. In C++, this is easily done by modifying the constructor to the following:

LED::LED(const dzn::locator& loc) : skel::LED(loc) {

This modification will call the constructor of LED’s base class skel::LED as well as perform the constructor you implemented in your native component. In skel::LED all of the Dezyne meta information and port binding is handled, so all you need to worry about in the native LED class is the functional behavior of the component.

To implement the setGreen, setYellow, setRed and turnOff functions you can do that like you would any other function. WiringPi provides a digitalWrite() function you can use to turn output on a GPIO pin on or off. All the colors we wish to display on the RGB LED can be created by a mixture of on and off on the three respective pins.

Exercise: Try implementing the remaining functions yourself.


void LED::iLed_setGreen() {
  digitalWrite(PIN_RED, LOW);
  digitalWrite(PIN_GREEN, HIGH);
  digitalWrite(PIN_BLUE, LOW);
}
void LED::iLed_setYellow() {
  digitalWrite(PIN_RED, HIGH);
  digitalWrite(PIN_GREEN, HIGH);
  digitalWrite(PIN_BLUE, LOW);
}
void LED::iLed_setRed() {
  digitalWrite(PIN_RED, HIGH);
  digitalWrite(PIN_GREEN, LOW);
  digitalWrite(PIN_BLUE, LOW);
}
void LED::iLed_turnOff() {
  digitalWrite(PIN_RED, LOW);
  digitalWrite(PIN_GREEN, LOW);
  digitalWrite(PIN_BLUE, LOW);
}


In summary: you now have a small but functionally complete burglar alarm program which accepts ‘s’ and ‘v’ commands from the user to denote a sensor trigger and valid password entry. Based on sequences of events, the program will display different colors on an RGB LED denoting whether the system is Unarmed, Armed or Alarming.

In the next chapter, you will find instructions on how to compile this application on a Raspberry Pi target so you can see it function in the real world!