[GO] Lassen Sie uns mit TDD ~ Design pattern ~ etwas entwickeln, das dem Integrierten nahe kommt

Mit googletest / googlemock entwickle ich Software, die unter Embedded Linux mit TDD läuft. Da die mit TDD tatsächlich entwickelte Prozedur so geschrieben ist, wie sie in Echtzeit ist, gibt es einen zufälligen Teil. Ich hoffe, Sie genießen den Prozess auch. Wenn Sie Fragen oder Fehler haben, kommentieren Sie bitte. Es wird ermutigend sein.

Wenn Sie mehr über die bisherige Geschichte erfahren möchten, lesen Sie bitte die vorherigen Artikel. Versuchen Sie, mit TDD ~ Preparation ~ etwas zu entwickeln, das nahezu eingebettet ist Entwickeln Sie mit TDD-Problem-Raising etwas in der Nähe von Embedded- Versuchen Sie, mit TDD ~ file open ~ etwas zu entwickeln, das nahezu eingebettet ist Versuchen Sie, mit TDD ~ libevdev Initialisierungs- / Beendigungsverarbeitung ~ etwas zu entwickeln, das nahezu eingebettet ist ~ Entwickeln Sie mit TDD-Key Input Detection- etwas in der Nähe des Integrierten Versuchen Sie, mit TDD-Intermediate Review- etwas zu entwickeln, das nahezu eingebettet ist Entwickeln Sie etwas, das mit TDD ~ Interface-Vererbung fast eingebettet ist ~

Es ist endlich vorbei. Beim letzten Mal habe ich die folgenden Spezifikationen implementiert.

Die Tasteneingabeerkennung und die Timererkennung können jetzt auf derselben Schnittstelle verwendet werden, indem die EventDetector-Schnittstelle als gemeinsame Basisklasse verwendet wird. Dieses Mal können verschiedene Operationen an der LED auf derselben Schnittstelle verwendet werden, was die Erweiterbarkeit des Programms weiter verbessert.

Was möchten Sie mit dem LED-Betrieb tun?

Die heutigen LED-Treiber haben drei Funktionen zum Manipulieren von LEDs.

led_driver.h


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

Ich möchte diese drei in derselben Oberfläche verwenden und die Hauptfunktion unverkennbar machen.

main.c


  for(int i = 0; detectors[i] != NULL; i++) {
    StartEventDetector(detectors[i]);
    //Warten Sie bis zur Ereigniserkennung
    while(CheckEvent(detectors[i]) != DETECTOR_EVENT_DETECTED) {}

    ToggleLed(caps_led);  //Rufen Sie hier die gemeinsame Schnittstelle auf und schalten Sie sie ein/Ich möchte richtig ausschalten
    //Um die Spezifikationen zu erfüllen, muss es wie folgt geschrieben werden.
    // if (i == 0) TurnOnLed(caps_led);
    // else if (i == 1) TurnOffLed(caps_led);
  }

Befehlsmuster

Daher wird der LED-Betrieb als Befehlsmuster implementiert. Das Befehlsmuster ist ein Entwurfsmuster, das "Befehle" als Instanzen ausdrücken kann.

Das Befehlsmuster ist einfach genug, um Sie zum Lachen zu bringen (obwohl es in C ein Chaos ist).

command.h


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

typedef struct CommandStruct {
  CommandInterface vtable;
} CommandStruct;

//Schnittstelle ist Ausführen()Nur
typedef struct CommandInterfaceStruct {
  void (*Execute)(Command);
} CommandInterfaceStruct;

void CommandExecute(Command cmd);

Die Klasse, die die Befehlsschnittstelle erbt, muss nur Execute () implementieren. Diese einfache Implementierung bietet jedoch eine unglaubliche Flexibilität.

Implementierung des LED-Betriebsbefehls

Beginnen wir wie immer mit dem Test.

//Test zum Generieren und Ausführen des Befehls zum Einschalten der LED
TEST_F(LedOperatorTest, LedTrunOnOperationCallsIoWriteWithOne) {
  // driver_Ist eine Instanz von LedDriver
  Command command = LedOperatorFactory(driver_, OP_LED_TURN_ON);
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("1\n"), 2)).Times(1);
  CommandExecute(command);
}

Wir wissen, dass wir mindestens drei LED-gesteuerte Befehle benötigen: Ein / Aus / Umschalten. Daher habe ich ein FactoryMethod-Muster vorbereitet und beschlossen, die generierte Instanz durch ein Argument zu ersetzen.

Erstens ist die abgeleitete Klasse des LED-Betriebs wie folgt. Abgesehen von der Vererbung der Befehlsschnittstelle gibt es nur eine Instanz von LedDriver.

led_operator_factory.c


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

typedef struct LedOperatorStruct *LedOperator;

Die Implementierung von Execute (), die die LED einschaltet, ist wie folgt.

led_operator_factory.c


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

static CommandInterfaceStruct interface = {
  .Execute = LedTurnOn
};

Jetzt müssen Sie nur noch eine Instanz erstellen. An dieser Stelle verwenden wir das Argument nicht, um die LED-Operation umzuschalten, aber wir werden es bald danach implementieren.

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

Wenn Sie es bis jetzt geschafft haben, besteht der Test zum Einschalten der LED.

Obwohl die Erklärung etwas kompliziert ist, haben wir die folgenden Tests einzeln vorbereitet und mit der Implementierung fortgefahren.

//Test zum Erstellen und Ausführen des Befehls zum Ausschalten
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 zum Generieren und Ausführen des Befehls zum Umschalten
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);
}

//Testen Sie, ob Factory keinen Befehl generiert
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);
}

Der endgültige Produktcode sieht folgendermaßen aus:

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

Jetzt können Sie alles über die Befehlsoberfläche tun, egal ob LED ein, aus oder umschalten.

Produktcode

Der endgültige Produktcode lautet wie folgt.

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

  //Die Spezifikation wird nur von der EventDetector- und Command-Schnittstelle realisiert.
  for(int i = 0; i < NUM_OPERATION_ON_DETECTION; i++) {
    StartEventDetector(detectors[i]);
    while(CheckEvent(detectors[i]) != DETECTOR_EVENT_DETECTED) {}

    CommandExecute(operators[i]);
  }
//Der Endvorgang entfällt
}

Wenn möglich, können die Funktionen zum Erkennen verschiedener Arten von Ereignissen und zum Ausführen einiger Operationen alle mit derselben Schnittstelle implementiert werden.

Erkennt, dass die Taste innerhalb von 2 Sekunden dreimal gedrückt wurde

Implementieren Sie das folgende Problem (ignorieren Sie die Regelung jedoch ein wenig). (Problem) Implementieren Sie eine Funktion, die true / false zurückgibt, je nachdem, ob die Taste innerhalb von 2 Sekunden dreimal gedrückt wurde.

Bei dem obigen Problem wird der Tastendruck erkannt, während der Durchgang von 2 Sekunden mit einem Timer gemessen wird. Die einzelnen Elemente sind keine große Sache, aber Sie müssen zwei Dinge gleichzeitig tun.

Dieses Mal werde ich versuchen, dieses Problem mithilfe des ActiveObject-Musters zu lösen, bei dem es sich um ein einfaches Entwurfsmuster für die parallele Verarbeitung handelt.

Ich werde die Beschreibung des Tests weglassen, weil die Geschichte durcheinander geraten wird, aber danach werde ich alles mit TDD weiterentwickeln. Klicken Sie hier für Tests auf ActiveObject. active_object_test.c

ActiveObject-Muster

Ich denke, dieses Muster ist leichter zu verstehen, wenn Sie sich die API und die Kernimplementierung ansehen. Fügen Sie grob gesagt einfach mehrere Befehle hinzu, die Sie ausführen möchten, und führen Sie sie alle gleichzeitig mit Run () aus.

active_object_engine.h


//Befehl zur Engine hinzufügen
void FuelEngine(ActiveObjectEngine engine, Command cmd);

//Führen Sie den in der Engine installierten Befehl aus
void EngineRuns(ActiveObjectEngine engine);

active_object_engine.c


typedef struct ActiveObjectEngineStruct {
  //Leihen Sie sich eine Liste mit Glib aus. Enthält die von FuelEngine hinzugefügte Befehlsliste.
  GSList *commands;
} ActiveObjectEngineStruct;

void FuelEngine(ActiveObjectEngine engine, Command cmd) {
  if ((engine == NULL) || (cmd == NULL)) return;
  //Befehl zur Engine hinzufügen
  engine->commands = g_slist_append(engine->commands, (gpointer)cmd);
}

void EngineRuns(ActiveObjectEngine engine) {
  while(g_slist_length(engine->commands) > 0) {
    //Führt den Befehl weiter aus, solange der Befehl in der Engine vorhanden ist.
    Command cmd = (Command)g_slist_nth_data(engine->commands, 0);
    CommandExecute(cmd);
    //Schließen Sie Befehle aus, die einmal von der Engine ausgeführt wurden
    engine->commands = g_slist_remove(engine->commands, (gpointer)cmd);
  }
}

Soweit ich sehen kann, sieht es nicht nach irgendetwas aus. Die wahre Essenz von ActiveObject ist ** nachdem Command begonnen hat, sich selbst zur Engine hinzuzufügen **.

detect_chain.c


//Diese DetectChain wird später verwendet, um den Konami-Befehl zu erkennen.
//Befehl, der den Weckbefehl ausführt, wenn ein Ereignis erkannt wird
Command CreateDetectChain(EventDetector detector,
                          //Nimmt die Engine, die sich selbst ausführt, als Argument
                          ActiveObjectEngine engine,
                          //Befehle, die der Engine hinzugefügt werden sollen, wenn ein Ereignis erkannt wird
                          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) {
    //Fügen Sie der Engine einen Weckbefehl hinzu, wenn ein Ereignis erkannt wird
    FuelEngine(self->engine, self->wakeup);
    self->detector_started = false;
    CleanupEventDetector(self->detector);
  } else {
    //Kehren Sie zur Engine zurück, bis ein Ereignis erkannt wird
    FuelEngine(self->engine, (Command)self);
  }
}

static CommandInterfaceStruct interface = {
  .Execute = DetectChainExecute
};

Hast du es irgendwie gesehen?

Mit anderen Worten, wenn Sie einen Befehl hinzufügen, der die Engine nach Ablauf von 2 Sekunden stoppt, und einen Befehl, der die Erkennung des Tastendrucks für die Engine zählt, werden die beiden Befehle gleichzeitig weiter bewegt. Wenn der Motor abgestellt ist, nehmen Sie den Wert des Knopfdruckzählers heraus und Sie sind fertig.

ActiveObject ist ein Parallelverarbeitungsmuster, bei dem sich jeweils nur ein Objekt bewegt. Persönlich ist es ein ziemlich beliebtes Muster, aber ich kann es nicht verwenden, wenn ich wirklich Multithreading brauche. Es hat einen geringen Platzbedarf und sollte in schlechten Umgebungen gut funktionieren.

Implementierung, die true / false zurückgibt, je nachdem, ob die Taste innerhalb von 2 Sekunden dreimal gedrückt wurde

Die Häufigkeit, mit der die Taste "A" gedrückt wurde, wurde innerhalb von 2 Sekunden unmittelbar nach Ausführung des Programms ausgegeben.

Ausführungsergebnis


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

Im Folgenden werden nur die wichtigen Teile extrahiert.

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

  //Im Vergleich zu 3 wird die Wahrheit zurückgegeben
  printf("Press A key %d times in two seconds\n.", TotalIs(total));

  //Verarbeitung beenden
  return 0;
}

action_on_trigger.c


//Befehl, der den angegebenen Befehl ausführt, wenn ein Ereignis erkannt wird
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;
  }

  //Wenn ein Ereignis erkannt wird
  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;
}

Siehe GitHub für den Rest der Details. Ganzer Quellcode Quellcode, in dem ** three_times_in_two_sec.c ** main enthält.

Konami-Befehl erkennen

Verwenden Sie DetectChain, das ich zuvor eingeführt habe, um Konami-Befehle zu erkennen. Wenn ein Befehl erkannt wird, leuchtet die Feststelltaste auf und der Vorgang endet. Wenn Sie unterwegs einen Fehler machen, wird das Programm beendet, ohne etwas zu tun. Da viele Ereignisse zu erkennen sind, ist die Vorbereitung etwas lang. Wenn Sie einen DetectChain-Befehl wie Matryoshka erstellen, müssen Sie lediglich die ActiveObjectEngine ausführen.

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

Unabhängig davon, ob es sich um eine dreimalige Tastendruckerkennung in 2 Sekunden oder eine Konami-Befehlserkennung handelt, wurden keine Änderungen an den bisher erstellten Tasteneingabeerkennungs-, Timer- und LED-Betriebsmodulen vorgenommen. Das ist ein großer Punkt.

Schließlich

@ mt08, vielen Dank für die (rechtzeitige) Bereitstellung eines interessanten Themas. Ich ignoriere die Regelung sehr, aber ist es eine coole Implementierung?

Dies ist das Ende des Versuchs, etwas Ähnliches wie das Einbetten in TDD zu entwickeln. Wenn die Motivation wiederhergestellt ist, werde ich sie um den Raspeltorte herum verpflanzen und bewegen. Gegen Ende konnte ich nicht alle Details erklären, also habe ich verschiedene Dinge kaputt gemacht ...

Danach wäre ich dankbar, wenn Sie Fragen oder Masakari in den Kommentaren stellen könnten.

Recommended Posts

Lassen Sie uns mit TDD ~ Design pattern ~ etwas entwickeln, das dem Integrierten nahe kommt
Lassen Sie uns mit TDD ~ Intermediate Review ~ etwas entwickeln, das dem Integrierten nahe kommt
Versuchen Sie, etwas zu entwickeln, das der Einbettung mit TDD nahe kommt ~ Problemauslösung ~
Lassen Sie uns etwas entwickeln, das mit TDD ~ file open edition ~ nahezu eingebettet ist
Lassen Sie uns mit TDD ~ Key Input Detection Version ~ etwas entwickeln, das dem Integrierten nahe kommt
Lassen Sie uns etwas entwickeln, das mit TDD ~ libevdev Initialisierungs- / Beendigungsverarbeitung ~ nahezu eingebettet ist
Verwenden wir das Entwurfsmuster wie die Sprache C mit OSS design_pattern_for_c!
Entwerfen Sie Muster für häufig verwendete Java-Bibliotheken - Fassadenmuster
Schritte zur Entwicklung von Django mit VSCode
Entwerfen Sie Muster für häufig verwendete Java-Bibliotheken - Abstract Factory-Muster
[Einführung in WordCloud] Spielen Sie mit Scraping ♬
[Einführung in Python] Verwenden wir foreach mit Python
Lernen Sie das Entwurfsmuster "Singleton" mit Python
Lassen Sie uns mit Python 1 einen Investitionsalgorithmus entwickeln
Lernen Sie das Designmuster "Facade" mit Python