Integration test for club status service

In order to test the club status service on a regular desktop Linux (or macOS or Windows) system before we embark on cross-compiling and testing on real hardware, a simple integration test was written, which uses mocks for the GPIO and I2C peripherals.

In the source code for the project covered in Chapter 3, Developing for Embedded Linux and Similar Systems, the files for these peripherals are found in the wiring folder of that project.

We start with the wiringPi.h header:

#include <Poco/Timer.h>


#define INPUT 0
#define OUTPUT 1
#define PWM_OUTPUT 2
#define GPIO_CLOCK 3
#define SOFT_PWM_OUTPUT 4
#define SOFT_TONE_OUTPUT 5
#define PWM_TONE_OUTPUT 6

We include a header from the POCO framework to allow us to easily create a timer instance later on. Then, we define all possible pin modes, just as the actual WiringPi header defines:

#define  LOW                0
#define HIGH 1

#define PUD_OFF 0
#define PUD_DOWN 1
#define PUD_UP 2

#define INT_EDGE_SETUP 0
#define INT_EDGE_FALLING 1
#define INT_EDGE_RISING 2
#define INT_EDGE_BOTH 3

These defines define further pin modes, including the digital input levels, the possible states of the pull-ups and pull-downs on the pins, and finally the possible types of interrupts, defining the trigger or triggers for an interrupt:

typedef void (*ISRCB)(void); 

This typedef defines the format for an interrupt callback function pointer.

Let's now look at the WiringTimer class:

class WiringTimer {
Poco::Timer* wiringTimer;
Poco::TimerCallback<WiringTimer>* cb;
uint8_t triggerCnt;

public:
ISRCB isrcb_0;
ISRCB isrcb_7;
bool isr_0_set;
bool isr_7_set;

WiringTimer();
~WiringTimer();
void start();
void trigger(Poco::Timer &t);
};

This class is the integral part of the GPIO-side of our mock implementation. Its main purpose is to keep track of which of the two interrupts we're interested in have been registered, and to trigger them at regular intervals using the timer, as we'll see in a moment:

int wiringPiSetup(); 
void pinMode(int pin, int mode);
void pullUpDnControl(int pin, int pud);
int digitalRead(int pin);
int wiringPiISR(int pin, int mode, void (*function)(void));

Finally, we define the standard WiringPi functions before moving on the implementation:

#include "wiringPi.h"


#include <fstream>
#include <memory>


WiringTimer::WiringTimer() {
triggerCnt = 0;
isrcb_0 = 0;
isrcb_7 = 0;
isr_0_set = false;
isr_7_set = false;

wiringTimer = new Poco::Timer(10 * 1000, 10 * 1000);
cb = new Poco::TimerCallback<WiringTimer>(*this,
&WiringTimer::trigger);
}

In the class constructor, we set the default values before creating the timer instance, configuring it to call our callback function every ten seconds, after an initial 10-second delay:

WiringTimer::~WiringTimer() {
delete wiringTimer;
delete cb;
}

In the destructor, we delete the timer callback instance:

void WiringTimer::start() {
wiringTimer->start(*cb);
}

In this function, we actually start the timer:

void WiringTimer::trigger(Poco::Timer &t) {
if (triggerCnt == 0) {
char val = 0x00;
std::ofstream PIN0VAL;
PIN0VAL.open("pin0val", std::ios_base::binary | std::ios_base::trunc);
PIN0VAL.put(val);
PIN0VAL.close();

isrcb_0();

++triggerCnt;
}
else if (triggerCnt == 1) {
char val = 0x01;
std::ofstream PIN7VAL;
PIN7VAL.open("pin7val", std::ios_base::binary | std::ios_base::trunc);
PIN7VAL.put(val);
PIN7VAL.close();

isrcb_7();

++triggerCnt;
}
else if (triggerCnt == 2) {
char val = 0x00;
std::ofstream PIN7VAL;
PIN7VAL.open("pin7val", std::ios_base::binary | std::ios_base::trunc);
PIN7VAL.put(val);
PIN7VAL.close();

isrcb_7();

++triggerCnt;
}
else if (triggerCnt == 3) {
char val = 0x01;
std::ofstream PIN0VAL;
PIN0VAL.open("pin0val", std::ios_base::binary | std::ios_base::trunc);
PIN0VAL.put(val);
PIN0VAL.close();

isrcb_0();

triggerCnt = 0;
}
}

This last function in the class is the callback for the timer. The way it functions is that it keeps track of how many times it has been triggered, with it setting the appropriate pin level in the form of a value in a file that we write to disk.

After the initial delay, the first trigger will set the lock switch to false, the second the status switch to true, the third the status switch back to false, and finally the fourth trigger sets the lock switch back to true, before resetting the counter and starting over again:

namespace Wiring {
std::unique_ptr<WiringTimer> wt;
bool initialized = false;
}

We add a global namespace in which we have a unique_ptr instance for a WiringTimer class instance, along with an initialization status indicator.

int wiringPiSetup() {
char val = 0x01;
std::ofstream PIN0VAL;
std::ofstream PIN7VAL;
PIN0VAL.open("pin0val", std::ios_base::binary | std::ios_base::trunc);
PIN7VAL.open("pin7val", std::ios_base::binary | std::ios_base::trunc);
PIN0VAL.put(val);
val = 0x00;
PIN7VAL.put(val);
PIN0VAL.close();
PIN7VAL.close();

Wiring::wt = std::make_unique<WiringTimer>();
Wiring::initialized = true;

return 0;
}

The setup function is used to write the default values for the mocked GPIO pin inputs value to disk. We also create the pointer to a WiringTimer instance here:

 void pinMode(int pin, int mode) {
//

return;
}

void pullUpDnControl(int pin, int pud) {
//

return;
}

Because our mocked implementation determines the behavior of the pins, we can ignore any input on these functions. For testing purposes, we could add an assert to validate that these functions have been called at the right times with the appropriate settings:

 int digitalRead(int pin) {
if (pin == 0) {
std::ifstream PIN0VAL;
PIN0VAL.open("pin0val", std::ios_base::binary);
int val = PIN0VAL.get();
PIN0VAL.close();

return val;
}
else if (pin == 7) {
std::ifstream PIN7VAL;
PIN7VAL.open("pin7val", std::ios_base::binary);
int val = PIN7VAL.get();
PIN7VAL.close();

return val;
}

return 0;
}

When reading the value for one of the two mocked pins, we open its respective file and read out its content, which is either the 1 or 0 set by the setup function or by the callback:

//This value is then returned to the calling function.


int wiringPiISR(int pin, int mode, void (*function)(void)) {
if (!Wiring::initialized) {
return 1;
}

if (pin == 0) {
Wiring::wt->isrcb_0 = function;
Wiring::wt->isr_0_set = true;
}
else if (pin == 7) {
Wiring::wt->isrcb_7 = function;
Wiring::wt->isr_7_set = true;
}

if (Wiring::wt->isr_0_set && Wiring::wt->isr_7_set) {
Wiring::wt->start();
}

return 0;
}

This function is used to register an interrupt and its associated callback function. After an initial check that the mock has been initialized by the setup function, we then continue to register the interrupt for one of the two specified pins.

Once both pins have had an interrupt set for them, we start the timer, which will in turn start generating events for the interrupt callbacks.

Next is the I2C bus mock:

int wiringPiI2CSetup(const int devId);
int wiringPiI2CWriteReg8(int fd, int reg, int data);

We just need two functions here: the setup function and the simple one-byte register write function.

The implementation is as follows:

#include "wiringPiI2C.h"

#include "../club.h"

#include <Poco/NumberFormatter.h>

using namespace Poco;


int wiringPiI2CSetup(const int devId) {
Club::log(LOG_INFO, "wiringPiI2CSetup: setting up device ID: 0x"
+ NumberFormatter::formatHex(devId));


return 0;
}

In the setup function, we log the requested device ID (I2C bus address) and return a standard device handle. Here, we use the log() function from the Club class to make the mock integrate into the rest of the code:

int wiringPiI2CWriteReg8(int fd, int reg, int data) {
Club::log(LOG_INFO, "wiringPiI2CWriteReg8: Device handle 0x" + NumberFormatter::formatHex(fd)
+ ", Register 0x" + NumberFormatter::formatHex(reg)
+ " set to: 0x" + NumberFormatter::formatHex(data));

return 0;
}

Since the code that would call this function wouldn't be expecting a response, beyond a simple acknowledgment that the data has been received, we can just log the received data and further details here. The NumberFormatter class from POCO is used here as well for formatting the integer data as hexadecimal values like in the application, for consistency.

We now compile the project and use the following command-line command:

make TEST=1  

Running the application (under GDB, to see when new threads are created/destroyed) now gets us the following output:

 Starting ClubStatus server...
Initialised C++ Mosquitto library.
Created listener, entering loop...
[New Thread 0x7ffff49c9700 (LWP 35462)]
[New Thread 0x7ffff41c8700 (LWP 35463)]
[New Thread 0x7ffff39c7700 (LWP 35464)]
Initialised the HTTP server.
INFO: Club: starting up...
INFO: Club: Finished wiringPi setup.
INFO: Club: Finished configuring pins.
INFO: Club: Configured interrupts.
[New Thread 0x7ffff31c6700 (LWP 35465)]
INFO: Club: Started update thread.
Connected. Subscribing to topics...
INFO: ClubUpdater: Starting i2c relay device.
INFO: wiringPiI2CSetup: setting up device ID: 0x20
INFO: wiringPiI2CWriteReg8: Device handle 0x0, Register 0x6 set to: 0x0
INFO: wiringPiI2CWriteReg8: Device handle 0x0, Register 0x2 set to: 0x0
INFO: ClubUpdater: Finished configuring the i2c relay device's registers.

At this point, the system has been configured with all interrupts set and the I2C device configured by the application. The timer has started its initial countdown:

 INFO:       ClubUpdater: starting initial update run.
INFO: ClubUpdater: New lights, clubstatus off.
DEBUG: ClubUpdater: Power timer not active, using current power state: off
INFO: ClubUpdater: Red on.
DEBUG: ClubUpdater: Changing output register to: 0x8
INFO: wiringPiI2CWriteReg8: Device handle 0x0, Register 0x2 set to: 0x8
DEBUG: ClubUpdater: Finished writing relay outputs with: 0x8
INFO: ClubUpdater: Initial status update complete.

The initial status of the GPIO pins has been read out and both switches are found to be in the off position, so we activate the red light on the traffic light indicator by writing its position in the register:

 INFO:       ClubUpdater: Entering waiting condition.
INFO: ClubUpdater: lock status changed to unlocked
INFO: ClubUpdater: New lights, clubstatus off.
DEBUG: ClubUpdater: Power timer not active, using current power state: off
INFO: ClubUpdater: Yellow on.
DEBUG: ClubUpdater: Changing output register to: 0x4
INFO: wiringPiI2CWriteReg8: Device handle 0x0, Register 0x2 set to: 0x4
DEBUG: ClubUpdater: Finished writing relay outputs with: 0x4
INFO: ClubUpdater: status switch status changed to on
INFO: ClubUpdater: Opening club.
INFO: ClubUpdater: Started power timer...
DEBUG: ClubUpdater: Sent MQTT message.
INFO: ClubUpdater: New lights, clubstatus on.
DEBUG: ClubUpdater: Power timer active, inverting power state from: on
INFO: ClubUpdater: Green on.
DEBUG: ClubUpdater: Changing output register to: 0x2
INFO: wiringPiI2CWriteReg8: Device handle 0x0, Register 0x2 set to: 0x2
DEBUG: ClubUpdater: Finished writing relay outputs with: 0x2
INFO: ClubUpdater: status switch status changed to off
INFO: ClubUpdater: Closing club.
INFO: ClubUpdater: Started timer.
INFO: ClubUpdater: Started power timer...
DEBUG: ClubUpdater: Sent MQTT message.
INFO: ClubUpdater: New lights, clubstatus off.
DEBUG: ClubUpdater: Power timer active, inverting power state from: off
INFO: ClubUpdater: Yellow on.
DEBUG: ClubUpdater: Changing output register to: 0x5
INFO: wiringPiI2CWriteReg8: Device handle 0x0, Register 0x2 set to: 0x5
DEBUG: ClubUpdater: Finished writing relay outputs with: 0x5
INFO: ClubUpdater: setPowerState called.
DEBUG: ClubUpdater: Writing relay with: 0x4
INFO: wiringPiI2CWriteReg8: Device handle 0x0, Register 0x2 set to: 0x4
DEBUG: ClubUpdater: Finished writing relay outputs with: 0x4
DEBUG: ClubUpdater: Written relay outputs.
DEBUG: ClubUpdater: Finished setPowerState.
INFO: ClubUpdater: lock status changed to locked
INFO: ClubUpdater: New lights, clubstatus off.
DEBUG: ClubUpdater: Power timer not active, using current power state: off
INFO: ClubUpdater: Red on.
DEBUG: ClubUpdater: Changing output register to: 0x8
INFO: wiringPiI2CWriteReg8: Device handle 0x0, Register 0x2 set to: 0x8
DEBUG: ClubUpdater: Finished writing relay outputs with: 0x8

Next, the timer starts triggering the callback function repeatedly, causing it to go through its different stages. This allows us to ascertain that the basic functioning of the code is correct.

At this point, we can start implementing more complex test cases, conceivably even implementing scriptable test cases using an embedded Lua, Python runtime or similar.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.185.147