[GO] Let's develop something close to embedded with TDD ~ Design pattern ~

Using googletest / googlemock, I am developing software that runs on embedded Linux with TDD. Since the procedure actually developed with TDD is written as it is in real time, there is a random part. I hope you enjoy the process as well. If you have any questions or mistakes, please comment. It will be encouraging.

If you would like to know more about the history so far, please see the previous articles. Try to develop something close to embedded with TDD ~ Preparation ~ Try to develop something close to embedded with TDD ~ Problem raising ~ Develop something close to embedded with TDD ~ file open ~ Try to develop something close to embedded with TDD ~ libevdev initialization / termination processing ~ Try to develop something close to embedded with TDD ~ Key input detection version ~ Developing something close to embedded with TDD-Intermediate review- Develop something close to embedded with TDD ~ Interface inheritance ~

It's finally over. Last time, I implemented the following specifications.

Key input detection and timer detection can now be used on the same interface by using the EventDetector interface as a common base class. This time, we will make it possible to use different operations on the LED with the same interface, further enhancing the extensibility of the program.

What do you want to do with LED operation

Today's LED drivers have three functions for manipulating LEDs.

led_driver.h


void TurnOnLed(LedDriver self);
void TurnOffLed(LedDriver self);
void ToggleLed(LedDriver self);

I want to use these three in the same interface and keep the main function unmistakable.

main.c


  for(int i = 0; detectors[i] != NULL; i++) {
    StartEventDetector(detectors[i]);
    //Wait until event detection
    while(CheckEvent(detectors[i]) != DETECTOR_EVENT_DETECTED) {}

    ToggleLed(caps_led);  //Call the common interface here and turn it on/I want to switch Off correctly
    //In order to meet the specifications, it must be written as follows.
    // if (i == 0) TurnOnLed(caps_led);
    // else if (i == 1) TurnOffLed(caps_led);
  }

Command pattern

Therefore, the LED operation is implemented as a Command pattern. The Command pattern is a design pattern that can express "commands" as instances.

The Command pattern is simple enough to make you laugh (although it's a mess in C).

command.h


typedef struct CommandInterfaceStruct *CommandInterface;
typedef struct CommandStruct *Command;

typedef struct CommandStruct {
  CommandInterface vtable;
} CommandStruct;

//Interface is Execute()Only
typedef struct CommandInterfaceStruct {
  void (*Execute)(Command);
} CommandInterfaceStruct;

void CommandExecute(Command cmd);

The class that inherits the Command interface need only implement Execute (). However, this simple implementation has incredible flexibility.

Implementation of LED operation Command

As always, let's start with the test.

//Test to generate and execute Command to turn on LED
TEST_F(LedOperatorTest, LedTrunOnOperationCallsIoWriteWithOne) {
  // driver_Is an instance of LedDriver
  Command command = LedOperatorFactory(driver_, OP_LED_TURN_ON);
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("1\n"), 2)).Times(1);
  CommandExecute(command);
}

We know that we need at least three LED-controlled commands, On / Off / Toggle. Therefore, I prepared a FactoryMethod pattern and decided to switch the generated instance with an argument.

First, the derived class of LED operation is as follows. Except for inheriting the Command interface, there is only an instance of LedDriver.

led_operator_factory.c


typedef struct LedOperatorStruct {
  CommandStruct base;
  LedDriver driver;
} LedOperatorStruct;

typedef struct LedOperatorStruct *LedOperator;

The implementation of Execute () that turns on the LED is as follows.

led_operator_factory.c


static void LedTurnOn(Command super) {
  LedOperator self = (LedOperator)super;
  TurnOnLed(self->driver);
}

static CommandInterfaceStruct interface = {
  .Execute = LedTurnOn
};

All you have to do now is create an instance. At this point, we are not using the argument to switch the LED operation, but we will implement it soon after this.

led_operator_factory.c


Command LedOperatorFactory(LedDriver driver, int32_t op_id) {
  if (driver == NULL) return NULL;
  LedOperator command = calloc(1, sizeof(LedOperatorStruct));
  command->base.vtable = &interface;
  command->driver = driver;

  return (Command)command;
}

If you make it so far, the test to turn on the LED will pass.

Although the explanation is a little complicated, we prepared the following tests one by one and proceeded with the implementation.

//Test to create and execute Command to turn off
TEST_F(LedOperatorTest, LedTrunOffOperationCallsIoWriteWithZero) {
  Command command = LedOperatorFactory(driver_, OP_LED_TURN_OFF);
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("0\n"), 2)).Times(1);
  CommandExecute(command);
}

//Test to generate and execute Command to Toggle
TEST_F(LedOperatorTest, LedToggleOperationTogglesIoWrite) {
  Command command = LedOperatorFactory(driver_, OP_LED_TOGGLE);

  InSequence s;
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("1\n"), 2)).Times(1);
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("0\n"), 2)).Times(1);
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("1\n"), 2)).Times(1);

  CommandExecute(command);
  CommandExecute(command);
  CommandExecute(command);
}

//Test that Factory fails to generate Command
TEST_F(LedOperatorTest, FatoryReturnsNullIfFailed) {
  Command command = LedOperatorFactory(nullptr, OP_LED_TURN_ON);
  EXPECT_EQ(nullptr, command);

  command = LedOperatorFactory(driver_, -1);
  EXPECT_EQ(nullptr, command);

  command = LedOperatorFactory(driver_, OP_MAX_FACTORY_ID);
  EXPECT_EQ(nullptr, command);
}

The final product code looks like this:

led_operator_factory.c


static void LedTurnOn(Command super) {
  LedOperator self = (LedOperator)super;
  TurnOnLed(self->driver);
}

static void LedTurnOff(Command super) {
  LedOperator self = (LedOperator)super;
  TurnOffLed(self->driver);
}

static void LedToggle(Command super) {
  LedOperator self = (LedOperator)super;
  ToggleLed(self->driver);
}

static CommandInterfaceStruct interface[OP_MAX_FACTORY_ID] = {
  { .Execute = LedTurnOn },
  { .Execute = LedTurnOff },
  { .Execute = LedToggle }
};

Command LedOperatorFactory(LedDriver driver, int32_t op_id) {
  if (driver == NULL) return NULL;
  if (op_id <= -1 || OP_MAX_FACTORY_ID <= op_id) return NULL;

  LedOperator command = calloc(1, sizeof(LedOperatorStruct));
  command->base.vtable = &interface[op_id];
  command->driver = driver;

  return (Command)command;
}

Now you can do everything from the Command interface, whether the LED is On, Off, or Toggle.

Product code

The final product code is as follows.

main.c


int main(void) {
  struct timeval kTime = {};
  const struct input_event kPressA = {kTime, EV_KEY, KEY_A, INPUT_KEY_PRESSED};

  EventDetector detectors[NUM_OPERATION_ON_DETECTION];
  detectors[0] = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressA);
  detectors[1] = CreateTimeOutDetector(5000, TIMER_ONE_SHOT);
  
  LedDriver caps_led = CreateLedDriver();
  if (InitLedDriver(caps_led, LED_DEVICE) != LED_DRIVER_SUCCESS) {
    DEBUG_LOG("Fail to init led device\n");
    exit(1);
  }

  Command operators[NUM_OPERATION_ON_DETECTION];
  operators[0] = LedOperatorFactory(caps_led, OP_LED_TURN_ON);
  operators[1] = LedOperatorFactory(caps_led, OP_LED_TURN_OFF);

  //The specification is realized only by the EventDetector and Command interface.
  for(int i = 0; i < NUM_OPERATION_ON_DETECTION; i++) {
    StartEventDetector(detectors[i]);
    while(CheckEvent(detectors[i]) != DETECTOR_EVENT_DETECTED) {}

    CommandExecute(operators[i]);
  }
//The end process is omitted
}

If possible, the functions of detecting various types of events and performing some operations can all be implemented with the same interface.

Detects that the button has been pressed 3 times in 2 seconds

Implement the following problem (but ignore the regulation quite a bit): (Problem) Implement a function that returns true / false depending on whether the button was pressed three times in 2 seconds.

In the above problem, the button press is detected while measuring the passage of 2 seconds with a timer. The individual elements are not a big deal, but you need to do two things at the same time.

This time, I will try to solve this problem by using the ActiveObject pattern, which is a simple parallel processing design pattern.

I will omit the description of the test because the story will be mixed up, but I will continue to develop everything with TDD after that. Click here for tests on ActiveObject. active_object_test.c

ActiveObject pattern

I think this pattern is easier to understand if you look at the API and the core implementation. Roughly speaking, just add multiple Commands you want to execute and execute them all at once with Run ().

active_object_engine.h


//Add Command to engine
void FuelEngine(ActiveObjectEngine engine, Command cmd);

//Execute Command installed in engine
void EngineRuns(ActiveObjectEngine engine);

active_object_engine.c


typedef struct ActiveObjectEngineStruct {
  //Borrow a list of glib. Holds the Command list added by FuelEngine.
  GSList *commands;
} ActiveObjectEngineStruct;

void FuelEngine(ActiveObjectEngine engine, Command cmd) {
  if ((engine == NULL) || (cmd == NULL)) return;
  //Add Command to engine
  engine->commands = g_slist_append(engine->commands, (gpointer)cmd);
}

void EngineRuns(ActiveObjectEngine engine) {
  while(g_slist_length(engine->commands) > 0) {
    //Continues to execute Command while Command exists in engine.
    Command cmd = (Command)g_slist_nth_data(engine->commands, 0);
    CommandExecute(cmd);
    //Exclude commands once executed from engine
    engine->commands = g_slist_remove(engine->commands, (gpointer)cmd);
  }
}

As far as I can see, it doesn't look like anything. The real essence of ActiveObject is ** after Command started adding itself to the engine **.

detect_chain.c


//This DetectChain will be used later to detect the Konami command.
//Command that executes wakeup Command when an event is detected
Command CreateDetectChain(EventDetector detector,
                          //Takes the engine that executes itself as an argument
                          ActiveObjectEngine engine,
                          //Command to add to engine when event is detected
                          Command wakeup) {
  DetectChain self = calloc(1, sizeof(DetectChainStruct));
  self->base.vtable = &interface;
  self->detector = detector;
  self->wakeup = wakeup;
}

static void DetectChainExecute(Command super) {
  DetectChain self = (DetectChain)super;

  if (CheckEvent(self->detector) == DETECTOR_EVENT_DETECTED) {
    //Add wakeup Command to engine when event detected
    FuelEngine(self->engine, self->wakeup);
    self->detector_started = false;
    CleanupEventDetector(self->detector);
  } else {
    //Return itself to engine until an event is detected
    FuelEngine(self->engine, (Command)self);
  }
}

static CommandInterfaceStruct interface = {
  .Execute = DetectChainExecute
};

Did you see it somehow?

In other words, if you add a Command that stops the engine after 2 seconds elapsed detection and a Command that counts the button press detection to the engine, the two Commands will continue to move at the same time. When the engine is stopped, take out the value of the button press counter and you're done.

ActiveObject is a parallel processing pattern in which only one object moves at a time. Personally, it's a pretty favorite pattern, but I can't use it when I really need multithreading. It has a light footprint and should work well in poor environments.

Implementation that returns true / false depending on whether the button is pressed 3 times in 2 seconds

Immediately after executing the program, the number of times the "A" button was pressed was output within 2 seconds.

Execution result


sudo led_controller/three_times_in_two_sec
Press A key 3 times in two seconds

Below, only the important parts are excerpted.

three_times_in_two_sec.c


int main(void) {
  struct timeval kTime = {};
  const struct input_event kPressA = {kTime, EV_KEY, KEY_A, INPUT_KEY_PRESSED};

  ActiveObjectEngine engine = CreateActiveObjectEngine();

  // Create components.
  EventDetector press_a = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressA);
  EventDetector two_sec = CreateTimeOutDetector(2000, TIMER_ONE_SHOT);
  Command total = CreateCountTotal();
  Command halt = CreateHaltEngine(engine);

  TriggerActionPair one = CreateTriggerActionPair(press_a, total);
  TriggerActionPair two = CreateTriggerActionPair(two_sec, halt);

  Command cmd_one = CreateActionOnTriggerChain(one, engine, LOOP_CHAIN);
  Command cmd_two = CreateActionOnTriggerChain(two, engine, ONE_SHOT_CHAIN);
  FuelEngine(engine, cmd_one);
  FuelEngine(engine, cmd_two);

  EngineRuns(engine);

  //Compared to 3, the truth is returned
  printf("Press A key %d times in two seconds\n.", TotalIs(total));

  //End processing
  return 0;
}

action_on_trigger.c


//Command that executes the specified Command when an event is detected
typedef struct TriggerActionPairStruct {
  EventDetector detector;
  Command command;
} TriggerActionPairStruct;

typedef struct ActionOnTriggerChainStruct {
  CommandStruct base;
  TriggerActionPair chain;
  ActiveObjectEngine engine;
  int32_t loop_flag;
  bool started;
} ActionOnTriggerChainStruct;
typedef struct ActionOnTriggerChainStruct *ActionOnTriggerChain;

static void ExecuteActionOnTrigger(Command super) {
  ActionOnTriggerChain self = (ActionOnTriggerChain)super;

  if (!self->started) {
    StartEventDetector(self->chain->detector);
    self->started = true;
  }

  //When an event is detected
  if (CheckEvent(self->chain->detector) == DETECTOR_EVENT_DETECTED) {
    FuelEngine(self->engine, self->chain->command);
    CleanupEventDetector(self->chain->detector);
    self->started = false;
    if (self->loop_flag == ONE_SHOT_CHAIN)
      return;  // Finish this chain by avoiding FuelEngine().
  }

  FuelEngine(self->engine, (Command)self);
}

static CommandInterfaceStruct interface = {
  .Execute = ExecuteActionOnTrigger
};

TriggerActionPair CreateTriggerActionPair(EventDetector detector, Command command) {
  TriggerActionPair pair = calloc(1, sizeof(TriggerActionPairStruct));
  pair->detector = detector;
  pair->command = command;
  return pair;
}

// Should give ActiveObjectEngine.
Command CreateActionOnTriggerChain(TriggerActionPair chain,
                                   ActiveObjectEngine engine,
                                   int32_t loop_flag) {
  ActionOnTriggerChain self = calloc(1, sizeof(ActionOnTriggerChainStruct));
  self->base.vtable = &interface;
  self->chain = chain;
  self->engine = engine;
  self->loop_flag = loop_flag;
  self->started = false;

  return (Command)self;
}

See GitHub for the rest of the details. Overall source code Source code where ** three_times_in_two_sec.c ** contains main.

Detect Konami command

Use the DetectChain introduced earlier to detect the Konami command. When a command is detected, the caps lock LED lights up and exits. If you make a mistake on the way, the program will end without doing anything. Since there are many events to detect, the preparation is a little long. If you create a DetectChain Command like Matryoshka, all you have to do is run the ActiveObjectEngine.

konami_command.c


int main(void) {
  struct timeval kTime = {};
  const struct input_event kPressUp = {kTime, EV_KEY, KEY_UP, INPUT_KEY_PRESSED};
  const struct input_event kPressDown = {kTime, EV_KEY, KEY_DOWN, INPUT_KEY_PRESSED};
  const struct input_event kPressLeft = {kTime, EV_KEY, KEY_LEFT, INPUT_KEY_PRESSED};
  const struct input_event kPressRight = {kTime, EV_KEY, KEY_RIGHT, INPUT_KEY_PRESSED};
  const struct input_event kPressA = {kTime, EV_KEY, KEY_A, INPUT_KEY_PRESSED};
  const struct input_event kPressB = {kTime, EV_KEY, KEY_B, INPUT_KEY_PRESSED};

  LedDriver caps_led = CreateLedDriver();
  if (InitLedDriver(caps_led, LED_DEVICE) != LED_DRIVER_SUCCESS) {
    DEBUG_LOG("Fail to init led device\n");
    exit(1);
  }

  // Create components.
  EventDetector press_up = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressUp);
  EventDetector press_down = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressDown);
  EventDetector press_left = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressLeft);
  EventDetector press_right = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressRight);
  EventDetector press_a = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressA);
  EventDetector press_b = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressB);
  Command caps_on = LedOperatorFactory(caps_led, OP_LED_TURN_ON);

  ActiveObjectEngine engine = CreateActiveObjectEngine();
  Command acceptted = CreateDetectChain(press_a, engine, caps_on);
  Command uuddlrlrb = CreateDetectChain(press_b, engine, acceptted);
  Command uuddlrlr = CreateDetectChain(press_right, engine, uuddlrlrb);
  Command uuddlrl = CreateDetectChain(press_left, engine, uuddlrlr);
  Command uuddlr = CreateDetectChain(press_right, engine, uuddlrl);
  Command uuddl = CreateDetectChain(press_left, engine, uuddlr);
  Command uudd = CreateDetectChain(press_down, engine, uuddl);
  Command uud = CreateDetectChain(press_down, engine, uudd);
  Command uu = CreateDetectChain(press_up, engine, uud);
  Command start = CreateDetectChain(press_up, engine, uu);

  FuelEngine(engine, start);

  EngineRuns(engine);

  return 0;
}

Whether it is button press detection 3 times in 2 seconds or Konami command detection, no changes have been made to the key input detection, timer, and LED operation modules that have been created so far. This is a big point.

Finally

@ mt08, thank you for providing (timely) an interesting issue. I'm ignoring the regulations a lot, but is it a cool implementation?

This is the end of trying to develop something similar to embedded with TDD. When motivation is restored, I will transplant it around Raspberry Pi and move it. Towards the end, I couldn't explain all the details, so I broke various things ...

After that, I would be grateful if you could ask questions or Masakari in the comments.

Recommended Posts

Let's develop something close to embedded with TDD ~ Design pattern ~
Let's develop something close to embedded with TDD ~ Intermediate review ~
Let's develop something close to embedded with TDD ~ Problem raising ~
Let's develop something close to embedded with TDD ~ file open edition ~
Let's develop something close to embedded with TDD ~ Key input detection version ~
Let's develop something close to embedded with TDD ~ libevdev initialization / termination processing ~
Let's utilize the design pattern like C language with OSS design_pattern_for_c!
Design patterns to enjoy with frequently used Java libraries --Facade pattern
Steps to develop Django with VSCode
Design patterns to enjoy with frequently used Java libraries --Abstract Factory pattern
[Introduction to WordCloud] Let's play with scraping ♬
[Introduction to Python] Let's use foreach with Python
Learn the design pattern "Singleton" with Python
Let's develop an investment algorithm with Python 1
Learn the design pattern "Facade" with Python