Linking error: Multiple definitions in same line error
I am rather new to proper C++ and PlatformIO development, so bear with me.
I am developing an ESP32-DEVKIT-V1 system on the Arduino framework which would benefit from private libraries in order to separate funcionalities (IR reading, sensor reading, etc.). As such, I have developed some classes and put them on the lib/ folder. However, I get an error during linking that affects not only my libraries, but other libdeps.
Error example
This is an example of the many linking errors that occur.
C:\...\PlatformIO\Projects\MyProject/.pio/libdeps/esp32doit-devkit-v1/IRremote/src/ir_Pronto.hpp:329: multiple definition of `IRrecv::compensateAndStorePronto(String*, unsigned short)'; .pio\build\esp32doit-devkit-v1\src\main.cpp.o:C:\...\PlatformIO\Projects\MyProject/.pio/libdeps/esp32doit-devkit-v1/IRremote/src/ir_Pronto.hpp:329: first defined here
Project structure
The project structure is as follows:
.
└── Codigo_Raimundao/
├── src/
│ └── main.cpp
└── lib/
├── detector (abstract class definition)/
│ ├── src/
│ │ └── detector.hpp
│ └── library.json
├── enemy_detector/
│ ├── src/
│ │ ├── enemy_detector.cpp
│ │ └── enemy_detector.hpp
│ └── library.json
└── ...
Code examples
I will provide code examples of both the infrared signal lib and main code, in order.
ir_control
Headerfile
#ifndef INCLUDE_IR_CONTROL_H_
#define INCLUDE_IR_CONTROL_H_
#define DECODE_SONY
#include <enumerators.hpp>
#include <Arduino.h>
#include <IRremote.hpp>
static constexpr uint8_t kIrPin = 17;
class IRControl{
private:
RobotState *state;
Strategy *strat;
public:
IRControl(RobotState *p_state, Strategy *p_strat);
void ExpectIRSignal();
void DecodeIRSignal();
};
#endif
Implementation (.cpp file)
#include <ir_control.hpp>
static constexpr bool kDebug = true;
static constexpr char* TAG = "IRControl";
IRControl::IRControl(RobotState *p_state, Strategy *p_strat){
state = p_state;
strat = p_strat;
if (kDebug){
ESP_LOGI(TAG, "Started IRControl with state %d and strategy %d\n", *state, *strat);
ESP_LOGI(TAG, "Pointer for state: %p\nPointer for strategy: %p\n", state, strat);
}
pinMode(LED_BUILTIN, OUTPUT);
IrReceiver.begin(kIrPin, true, LED_BUILTIN); // LED_BUILTIN como feedback de resposta.
IrReceiver.enableIRIn(); // Linha possivelmente desnecessária.
}
void IRControl::DecodeIRSignal(){
IrReceiver.resume();
if (kDebug){
Serial.print('Received command: ');
Serial.println(IrReceiver.decodedIRData.command);
}
if (*state != RobotState::kStop && *state != RobotState::kRunning
&& IrReceiver.decodedIRData.command == RobotState::kReady)
*state = RobotState::kReady;
// Isso deveria ser um switch case?
if (*state == RobotState::kReady){
switch (IrReceiver.decodedIRData.command){
case RobotState::kRunning:
*state = RobotState::kRunning;
break;
case Strategy::kRadarEsq:
*strat = Strategy::kRadarEsq;
break;
case Strategy::kRadarDir:
*strat = Strategy::kRadarDir;
break;
case Strategy::kCurvaAbertaEsq:
*strat = Strategy::kCurvaAbertaEsq;
break;
case Strategy::kCurvaAbertaDir:
*strat = Strategy::kCurvaAbertaDir;
break;
case Strategy::kFollowOnly:
*strat = Strategy::kFollowOnly;
break;
case Strategy::kWoodPecker:
*strat = Strategy::kWoodPecker;
default:
break;
}
}
if (IrReceiver.decodedIRData.command == RobotState::kStop)
*state = RobotState::kStop;
if (kDebug)
ESP_LOGI(TAG, "Current state: %d\nCurrent strat: %d\n", *state, *strat);
}
void IRControl::ExpectIRSignal(){
if (IrReceiver.decode())
DecodeIRSignal();
}
Main code
#include <Arduino.h>
#include <esp_ipc.h>
#include <QTRSensors.h>
#include <freertos/task.h>
#include <freertos/FreeRTOS.h>
#include <motor_control.hpp>
#include <enemy_detector.hpp>
#include <line_detector.hpp>
#include <ir_control.hpp>
// Pinos dos LEDs
constexpr uint8_t kLed1 = 18;
constexpr uint8_t kLed2 = 19;
constexpr bool kDebug = true;
constexpr char* TAG = "Main";
// Protótipos de função
void CoreTaskOne(void *pvParameters);
void CoreTaskZero(void *pvParameters);
void RunStrategy();
void Radar(Direction d);
void CurvaAberta(Direction d);
void Woodpecker();
void Follow();
void LineDetectedProtocol(Direction direction);
// Globais
RobotState state = RobotState::kReady;
Strategy strat = Strategy::kRadarEsq;
uint32_t time_1;
QTRSensorsAnalog qtra((unsigned char[]){kQtr1, kQtr2}, NUM_SENSORS, NUM_SAMPLES_PER_SENSOR);
uint32_t sensor_values[NUM_SENSORS];
EventGroupHandle_t sensor_events;
EventBits_t enemy_bits, line_bit;
MotorControl motor_control = MotorControl();
IRControl ir_control = IRControl(&state, &strat);
EnemyDetector enemy_detector = EnemyDetector(&state, &sensor_events);
LineDetector line_detector = LineDetector(&state, &sensor_events, sensor_values, &qtra);
TaskHandle_t core_0;
TaskHandle_t core_1;
void setup(){
analogReadResolution(10); // Necessário para o bom funcionamento do QTR.
// Desligamento de watchdogs para funcionamento das tasks em loop.
disableCore1WDT();
disableCore0WDT();
if (kDebug){
Serial.begin(115200);
while (!Serial){;}
ESP_LOGI(TAG, "Serial Initialized.");
ESP_LOGI(TAG, "Starting state: %d\nStarting strat: %d\n", state, strat);
}
sensor_events = xEventGroupCreate();
pinMode(kLed1, OUTPUT);
pinMode(kLed2, OUTPUT);
line_detector.Calibrate();
xTaskCreatePinnedToCore(
CoreTaskOne,
"CoreTaskOne",
20000,
NULL,
1,
&core_1,
1
);
xTaskCreatePinnedToCore(
CoreTaskZero,
"CoreTaskZero",
20000,
NULL,
1,
&core_0,
0
);
}
void loop(){}
void CoreTaskOne(void *pvParameters){
for(;;){
if (state != RobotState::kStop)
RunStrategy();
else{
motor_control.StopMotors();
vTaskDelete(core_1);
}
}
}
void CoreTaskZero(void *pvParameters){
for(;;){
ir_control.ExpectIRSignal();
enemy_detector.Detect();
line_detector.Detect();
}
}
void RunStrategy(){
switch (strat){
case Strategy::kRadarEsq:
Radar(Direction::kLeft);
break;
case Strategy::kRadarDir:
Radar(Direction::kRight);
break;
case Strategy::kCurvaAbertaEsq:
CurvaAberta(Direction::kLeft);
break;
case Strategy::kCurvaAbertaDir:
CurvaAberta(Direction::kRight);
break;
case Strategy::kFollowOnly:
Follow();
break;
case Strategy::kWoodPecker:
Woodpecker();
break;
default:
break;
}
}
void Radar(Direction d){
if (state == RobotState::kRunning){
enemy_bits = enemy_detector.GetSensorEvents(30);
if (!(enemy_bits & EVENT_SENSOR1) && !(enemy_bits & EVENT_SENSOR2) && !(enemy_bits & EVENT_SENSOR3) && !(enemy_bits & EVENT_SENSOR4)){
motor_control.Turn(d);
}
else{
while(state == RobotState::kRunning)
Follow();
}
}
}
void CurvaAberta(Direction d){
Direction opposite_d = d == Direction::kRight ? Direction::kLeft : Direction::kRight;
if (state == RobotState::kRunning){
time_1 = millis();
while(millis() - time_1 <= 400)
motor_control.Turn(d);
motor_control.Accelerate(Direction::kForward);
time_1 = millis();
while(millis() - time_1 <= 1000){
motor_control.Accelerate(Direction::kForward);
enemy_bits = enemy_detector.GetSensorEvents(15);
line_bit = line_detector.GetSensorEvents(15);
if (line_bit & EVENT_QRE)
LineDetectedProtocol(opposite_d);
}
motor_control.Turn(opposite_d); // precisa de um valor menor + precisa ir pra frente depois. Isso está errado. Grosso, Rude.
vTaskDelay(400);
Follow();
}
}
/**
* @todo Implementar a estratégia Woodpecker, que consiste em pequenas acelerações ("bicadinhas")
* antes de uma eventual aceleração.
*/
void Woodpecker(){
if (state == RobotState::kRunning){
for (;;){}
}
}
/// @brief Estratégia que segue o inimigo tentando centralizá-lo para acelerar em sua direção.
void Follow(){
if (state == RobotState::kRunning){
enemy_bits = enemy_detector.GetSensorEvents(15);
line_bit = line_detector.GetSensorEvents(15);
if (line_bit & EVENT_QRE){
if (enemy_bits & EVENT_SENSOR1)
LineDetectedProtocol(Direction::kLeft);
else if (enemy_bits & EVENT_SENSOR4)
LineDetectedProtocol(Direction::kRight);
else
LineDetectedProtocol(Direction::kLeft);
}
if (enemy_bits & EVENT_SENSOR1 ||
((enemy_bits & EVENT_SENSOR1) && (enemy_bits & EVENT_SENSOR2)) ||
((enemy_bits & EVENT_SENSOR1) && (enemy_bits & EVENT_SENSOR2) && (enemy_bits & EVENT_SENSOR3))){
motor_control.Turn(Direction::kLeft);
}
else if (enemy_bits & EVENT_SENSOR4 ||
((enemy_bits & EVENT_SENSOR4) && (enemy_bits & EVENT_SENSOR3)) ||
((enemy_bits & EVENT_SENSOR4) && (enemy_bits & EVENT_SENSOR2) && (enemy_bits & EVENT_SENSOR3))){
motor_control.Turn(Direction::kRight);
}
else if ((enemy_bits & EVENT_SENSOR2) || (enemy_bits & EVENT_SENSOR3) || ((enemy_bits & EVENT_SENSOR2) && (enemy_bits & EVENT_SENSOR3))){
motor_control.Accelerate(Direction::kForward);
}
}
}
void LineDetectedProtocol(Direction direction){
motor_control.Accelerate(Direction::kBackward);
vTaskDelay(pdMS_TO_TICKS(300));
motor_control.Turn(direction);
vTaskDelay(pdMS_TO_TICKS(300));
}
The full code (all libraries and the rest of it) is available at this repo.
platformio.ini
I will also provide the platformio.ini file.
[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
lib_deps =
z3t0/IRremote@^4.5.0
https://github.com/pololu/qtr-sensors-arduino.git#3.1.0
monitor_speed = 115200
Closing thoughts
I have tried different solutions listed in different topics to no avail. I am sorry if this is repetitious, ignorant or both, but thanks in advance for taking the time to read and and help, if possible.