Multiple class method definitions from library include

I am building firmware for a project at university and currently want to clean up the code in a way, that the main.cpp file is very lightweight. I thus shift most code into headers and cpp files, which I include. So far everything worked (with some minor bumps on the way) but right on the last leg I get errors telling me methods of a class from a library I use have multiple definitions…

Error Message:

.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `StateMachine::~StateMachine()':
robotStateMachine.cpp:(.text._ZN12StateMachineD2Ev+0x0): multiple definition of `StateMachine::~StateMachine()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN12StateMachineD2Ev+0x0): first defined here
c:/users/lelem/.platformio/packages/toolchain-gccarmnoneeabi/bin/../lib/gcc/arm-none-eabi/5.4.1/../../../../arm-none-eabi/bin/ld.exe: Disabling relaxation: it will not work with multiple definitions
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `StateMachine::~StateMachine()':
robotStateMachine.cpp:(.text._ZN12StateMachineD2Ev+0x0): multiple definition of `StateMachine::~StateMachine()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN12StateMachineD2Ev+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `State::State()':
robotStateMachine.cpp:(.text._ZN5StateC2Ev+0x0): multiple definition of `State::State()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN5StateC2Ev+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `State::State()':
robotStateMachine.cpp:(.text._ZN5StateC2Ev+0x0): multiple definition of `State::State()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN5StateC2Ev+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `State::~State()':
robotStateMachine.cpp:(.text._ZN5StateD2Ev+0x0): multiple definition of `State::~State()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN5StateD2Ev+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `State::~State()':
robotStateMachine.cpp:(.text._ZN5StateD2Ev+0x0): multiple definition of `State::~State()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN5StateD2Ev+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `State::addTransition(bool (*)(), State*)':    
robotStateMachine.cpp:(.text._ZN5State13addTransitionEPFbvEPS_+0x0): multiple definition of `State::addTransition(bool (*)(), State*)' 
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN5State13addTransitionEPFbvEPS_+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `State::addTransition(bool (*)(), int)':       
robotStateMachine.cpp:(.text._ZN5State13addTransitionEPFbvEi+0x0): multiple definition of `State::addTransition(bool (*)(), int)'      
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN5State13addTransitionEPFbvEi+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `State::evalTransitions()':
robotStateMachine.cpp:(.text._ZN5State15evalTransitionsEv+0x0): multiple definition of `State::evalTransitions()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN5State15evalTransitionsEv+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `State::execute()':
robotStateMachine.cpp:(.text._ZN5State7executeEv+0x0): multiple definition of `State::execute()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN5State7executeEv+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `State::setTransition(int, int)':
robotStateMachine.cpp:(.text._ZN5State13setTransitionEii+0x0): multiple definition of `State::setTransition(int, int)'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN5State13setTransitionEii+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `StateMachine::StateMachine()':
robotStateMachine.cpp:(.text._ZN12StateMachineC2Ev+0x0): multiple definition of `StateMachine::StateMachine()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN12StateMachineC2Ev+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `StateMachine::StateMachine()':
robotStateMachine.cpp:(.text._ZN12StateMachineC2Ev+0x0): multiple definition of `StateMachine::StateMachine()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN12StateMachineC2Ev+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `StateMachine::run()':
robotStateMachine.cpp:(.text._ZN12StateMachine3runEv+0x0): multiple definition of `StateMachine::run()'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN12StateMachine3runEv+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `StateMachine::addState(void (*)())':
robotStateMachine.cpp:(.text._ZN12StateMachine8addStateEPFvvE+0x0): multiple definition of `StateMachine::addState(void (*)())'        
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN12StateMachine8addStateEPFvvE+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `StateMachine::transitionTo(State*)':
robotStateMachine.cpp:(.text._ZN12StateMachine12transitionToEP5State+0x0): multiple definition of `StateMachine::transitionTo(State*)' 
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN12StateMachine12transitionToEP5State+0x0): first defined here
.pio\build\teensy41\lib3d6\librobotStateMachine.a(robotStateMachine.cpp.o): In function `StateMachine::transitionTo(int)':
robotStateMachine.cpp:(.text._ZN12StateMachine12transitionToEi+0x0): multiple definition of `StateMachine::transitionTo(int)'
.pio\build\teensy41\src\main.cpp.o:main.cpp:(.text._ZN12StateMachine12transitionToEi+0x0): first defined here

main.cpp

#include <Arduino.h>
#include <robotStateMachine.h>


void emergencyStop()
{
  lcl.recordingbutton = 0;
  robotStateMachine.transitionTo(SafeState);
  return;
}



/// @brief setup function to be run once
void setup()
{
  // Serial.begin(115200); // this seems to be done in lcl already..
  Serial.println("Setting up buttons");
  setupButtons();

  pinMode(ON_OFF_BUTTON, INPUT_PULLDOWN);
  attachInterrupt(digitalPinToInterrupt(ON_OFF_BUTTON), emergencyStop, RISING);
  SetupSerialCommands();

  robotStateMachine.transitionTo(Idle);
  
  statusLED.begin();
  statusLEDShineFullColorCode(COLOR_OFF);
  Serial.println("Setup complete");
}

void loop()
{  
  loopButtons();
  robotCommands.ReadSerial();
  robotStateMachine.run();
  // delay(10);

}

robotStateMachine.h

#ifndef STATE_MACHINE_H_GUARD
#define STATE_MACHINE_H_GUARD

#include <robotCommands.h>
#include <robotButtons.h>
#include <robotStatusLED.h>
#include <stateMachine.h>


/**
 * @brief robotStateMachine declaration
*/
extern StateMachine robotStateMachine;

/**
 * @defgroup robotStates States for the robot
 * 
 * @{
*/
extern State* Idle;
extern State* SafeState;
extern State* Recording;
extern State* ProcessRecording;
extern State* Play;
extern State* DirectControl;
extern State* SerialComm;
/// @}

/**
 * @defgroup robotStateLogic Functions to the states
 * 
 * @{
*/
void idleLogic();
void safeStateLogic();
void recordingLogic();
void processRecordingLogic();
void playLogic();
void directControlLogic();
void serialCommLogic();
/// @}

/**
 * @defgroup robotStateTransition transition functions to the states
 * 
 * @{
*/
bool transitionIdleRecord();
bool transitionIdlePlay();
bool transitionIdleDirectControl();
bool transitionPlayIdle();
bool transitionRecordProcessRecording();
bool transitionProcessRecordingIdle();
bool transitionDirectControlIdle();
bool transitionSafeStateIdle();
/// @}

void setupTransitions();

#endif

robotStateMachine.cpp

#include <robotStateMachine.h>

StateMachine robotStateMachine;


void idleLogic() {
    if(robotStateMachine.executeOnce) {
        statusLEDShineFullColorCode(COLOR_GREEN);
        Serial.println("Idle");
    }
    if ((millis() % 5000 < 5 || millis() % 5000 > 4995)) {
        
        lcl.lclServoBus.getServoPositions();
        for (int k = 0; k < lcl.lclServoBus.busServoCount; k++) {
            Serial.print("Servo ");
            Serial.print(lcl.lclServoBus.servoIDs[k]);
            Serial.print(" at position: ");
            Serial.println(lcl.lclServoBus.servoPositions[k]);
        }
    }
}

void safeStateLogic() {
    if(robotStateMachine.executeOnce) {
        statusLEDShineFullColorCode(COLOR_RED);
        Serial.println("SafeState");
    }
    lcl.lclServoBus.EnableTorque(0xFE, 0);
}

void recordingLogic(){
    unsigned long timeStartRecording = 0;

    if(robotStateMachine.executeOnce) {
        statusLEDShineFullColorCode(COLOR_ORANGE);
        lcl.lclServoBus.EnableTorque(SERVOBROADCAST, 0);
        lcl.iteration = 0;

        Serial.println("Recording");
        
        // Header for Table output over Serial
        Serial.print("Time_ms");
        for (int k = 0; k < lcl.lclServoBus.busServoCount; k++) {
          Serial.print(";Pos_Servo_");
          Serial.print(lcl.lclServoBus.servoIDs[k]);
          Serial.print(";Speed_Servo_");
          Serial.print(lcl.lclServoBus.servoIDs[k]);
        }
        Serial.print("; timeArray");
        Serial.println();
        timeStartRecording = millis();
    }
    lcl.timluethrecord(timeStartRecording);
}

void processRecordingLogic() {
    if(robotStateMachine.executeOnce) {
        Serial.println("Processing Recording");
        statusLEDShineFullColorCode(COLOR_TUM_DARKBLUE);
    }
    lcl.processPositions(); /// FIXME: actually this is the transition function as well... so we will have done the processPostions two times 
}

void playLogic() {
    if(robotStateMachine.executeOnce) {
        lcl.playIteration = 0;
        Serial.println("Play");
        Serial.print(lcl.iteration);
        Serial.println(" will be played");
    }
    lcl.playProgram();
}

void directControlLogic() {
    if (robotStateMachine.executeOnce) {
        Serial.println("Direct control");
        statusLEDShineFullColorCode(COLOR_CYAN);
    }
    for (int k = 0; k < NUMBEROFPOSITIONS; k++) {
        for (int n = 0; n < NUMBEROFSERVOS; n++) {
            lcl.lclServoBus.WritePosEx(lcl.lclServoBus.servoIDs[n], directControlPositionList[k][n], directControlVelocityList[k][n]); //directControlAccelerationList[k][n]
        }
        while (lcl.lclServoBus.isAnyMoving()) {}
    }
}
void serialCommLogic();

bool transitionIdleRecord() {
    return recordButton.isPressed();
}
bool transitionIdlePlay() {
    return playButton.isPressed();
}
bool transitionIdleDirectControl() {
    return playButton.isPressed() && recordButton.isPressed();
}
bool transitionPlayIdle() {
    return lcl.iteration == lcl.playIteration;
}
bool transitionRecordProcessRecording() {
    return lcl.recordingbutton == 0;
}
bool transitionProcessRecordingIdle() {
    return lcl.processPositions();
}
bool transitionDirectControlIdle() {
    return playButton.isPressed() || playButton.isPressed();
}
bool transitionSafeStateIdle() {
    return digitalRead(ON_OFF_BUTTON) == HIGH;
};


State* Idle = robotStateMachine.addState(&idleLogic);
State* SafeState = robotStateMachine.addState(&safeStateLogic);
State* Recording = robotStateMachine.addState(&recordingLogic);
State* ProcessRecording = robotStateMachine.addState(&processRecordingLogic);
State* Play = robotStateMachine.addState(&playLogic);
State* DirectControl = robotStateMachine.addState(&directControlLogic);



void setupTransitions() {
    Idle->addTransition(&transitionIdlePlay,Play);
    Idle->addTransition(&transitionIdleRecord,Recording);Idle->addTransition(&transitionIdleDirectControl,DirectControl);

    SafeState->addTransition(&transitionSafeStateIdle,Idle);

    Recording->addTransition(&transitionRecordProcessRecording, ProcessRecording);

    ProcessRecording->addTransition(&transitionProcessRecordingIdle, Idle);

    Play->addTransition(&transitionPlayIdle, Idle);

    DirectControl->addTransition(&transitionDirectControlIdle, Idle);
}

I am very confused, as I don’t do any definition on class methods nor even use the deconstructor of StateMachine class…

I am kind of unsure about this line StateMachine robotStateMachine; in robotStateMachine.cpp as the library “documentation” uses StateMachine robotStateMachine = StateMachine(); instead… I get the same error, however then there also is a multiple definition error for the StateMachine constructor.

Which is deadly if your function implementations instead of declarations thus get included into multiple .cpp files and thus creates multiple instances of the same code.

The error is not visible in the code you’ve shown. The problem seems to be in the file defining the State class. Are you using the latest version of GitHub - jrullan/StateMachine: State machine library for Arduino, unmodified? Per Old version available on Platformio · Issue #16 · jrullan/StateMachine · GitHub the registered version is outdated so you’d have to say

lib_deps = https://github.com/jrullan/StateMachine/archive/refs/heads/master.zip
1 Like

I’ll be damned… it was in fact the outdated version that caused the error for me. Thank you very much.

Is there a better way to structure the code, as it seems I’m wandering on a deadly path?

It’s perfectly possible to create single-header-libraries (SHL) or header-only libraries in C++ that can be included from multiple .cpp files without running into multiple definition errors, you just have to follow certain rules / techniques. See resources like

1 Like