c++esp32home-assistantesphome

Multiple definitions error in custom ESPHome component


I'm trying to make a WiFi enabled egg-cooker. I have a working Arduino sketch and am now trying to port it to ESPHome so that it can interface with Home Assistant. ESPHome uses a yaml file (eggcooker.yaml) to generate a main.cpp file, and I can add custom components to the yaml which link to C++ files that I can program myself, and which are then used by that main.cpp. My problem seems to occur in one of my C++ files. Here's a link to the code: https://github.com/Amanoo/Smart-Egg-Cooker/tree/feature-esphome/eggslice

In this folder, run these commands:

python3 -m venv venv 
source venv/bin/activate
pip3 install esphome
pip3 install tornado esptool
esphome run eggcooker.yaml

This will compile the whole thing. However, it will eventually throw an error:

/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:105: multiple definition of `m_asFont'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:105: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:106: multiple definition of `m_asPage'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:106: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:104: multiple definition of `m_drv'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:104: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:103: multiple definition of `m_gui'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:103: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:110: multiple definition of `m_asPage1ElemRef'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:110: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:109: multiple definition of `m_asPage1Elem'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:109: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:112: multiple definition of `m_asPage2ElemRef'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:112: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:111: multiple definition of `m_asPage2Elem'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:111: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:114: multiple definition of `m_asPage3ElemRef'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:114: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:113: multiple definition of `m_asPage3Elem'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:113: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:116: multiple definition of `m_asKeypadAlphaElemRef'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:116: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:115: multiple definition of `m_asKeypadAlphaElem'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:115: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:118: multiple definition of `m_sListbox2'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:118: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:120: multiple definition of `m_acListboxBuf2'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:120: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:121: multiple definition of `m_sListScroll2'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:121: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:117: multiple definition of `m_sKeyPadAlpha'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:117: first defined here
/home/marco/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/eggcooker/src/main.cpp.o: in function `InitGUIslice_gen()':
/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:169: multiple definition of `InitGUIslice_gen()'; .pioenvs/eggcooker/src/eggslice.cpp.o:/home/marco/Desktop/eggcookeresphome/.esphome/build/eggcooker/src/eggslice_GSLC.h:169: first defined here
collect2: error: ld returned 1 exit status
*** [.pioenvs/eggcooker/firmware.elf] Error 1

I'm not sure why there are "multiple definitions". I just have a file that I import. It didn't throw this error when the same file was in my Arduino sketch. I checked my include guards, and they're there. I checked m_asFont for occurences in my files, but it only exists in eggslice_GSLC.h where it's defined once and used once. Definitely not multiple definitions. As best I can tell, there are no multiple definitions, it shouldn't even be possible.


Solution

  • In your header file eggslice_GSLC.h you have quite a few variables that you define rather than declare.

    gslc_tsGui                      m_gui;
    gslc_tsDriver                   m_drv;
    gslc_tsFont                     m_asFont[MAX_FONT];
    gslc_tsPage                     m_asPage[MAX_PAGE];
    
    //<GUI_Extra_Elements !Start!>
    gslc_tsElem                     m_asPage1Elem[MAX_ELEM_PG_MAIN_RAM];
    gslc_tsElemRef                  m_asPage1ElemRef[MAX_ELEM_PG_MAIN];
    gslc_tsElem                     m_asPage2Elem[MAX_ELEM_PG_WIFI_RAM];
    gslc_tsElemRef                  m_asPage2ElemRef[MAX_ELEM_PG_WIFI];
    gslc_tsElem                     m_asPage3Elem[MAX_ELEM_PG_PASSWD_RAM];
    gslc_tsElemRef                  m_asPage3ElemRef[MAX_ELEM_PG_PASSWD];
    gslc_tsElem                     m_asKeypadAlphaElem[1];
    gslc_tsElemRef                  m_asKeypadAlphaElemRef[1];
    gslc_tsXKeyPad                  m_sKeyPadAlpha;
    gslc_tsXListbox                 m_sListbox2;
    // - Note that XLISTBOX_BUF_OH_R is extra required per item
    char                            m_acListboxBuf2[94 + XLISTBOX_BUF_OH_R];
    gslc_tsXSlider                  m_sListScroll2;
    

    Not coincidentally, these are the variables that the linker is complaining are multiply defined.

    All that has to happen to cause that is for two .cpp files to include this file. Header file guards can't protect against this; all they protect against is a header file being included multiple times in a single source file.

    The fix is easy; copy all of these definitions into a single .cpp file and change all of these definitions in the .h file to declarations by prefixing them with extern.

    When you build esphome, the build script generates a main.cpp file which includes all the header files you listed in eggcooker.yaml. That includes eggslice_GSLC.h so now it's included in multiple source files leading to the variables being multiply defined.

    // Auto generated code by esphome
    // ========== AUTO GENERATED INCLUDE BLOCK BEGIN ===========
    #include "esphome.h"
    using namespace esphome;
    using std::isnan;
    using std::min;
    using std::max;
    using namespace text_sensor;
    logger::Logger *logger_logger;
    web_server_base::WebServerBase *web_server_base_webserverbase;
    captive_portal::CaptivePortal *captive_portal_captiveportal;
    wifi::WiFiComponent *wifi_wificomponent;
    mdns::MDNSComponent *mdns_mdnscomponent;
    ota::OTAComponent *ota_otacomponent;
    api::APIServer *api_apiserver;
    using namespace api;
    web_server::WebServer *web_server_webserver;
    using namespace sensor;
    using namespace json;
    preferences::IntervalSyncer *preferences_intervalsyncer;
    template_::TemplateSensor *secs;
    template_::TemplateTextSensor *state;
    #define yield() esphome::yield()
    #define millis() esphome::millis()
    #define micros() esphome::micros()
    #define delay(x) esphome::delay(x)
    #define delayMicroseconds(x) esphome::delayMicroseconds(x)
    #include "eggslice.h"
    #include "eggslice_GSLC.h"
    #include "FreeSans14pt7b.h"
    #include "NotoMono24pt7b.h"
    #include "NotoSansBold14pt7b.h"
    #include "dosis_book12pt7b.h"
    #include "dosis_book16pt7b.h"
    #include "pijlGlyph.h"
    // ========== AUTO GENERATED INCLUDE BLOCK END ==========="
    

    You should only ever declare variables in a .h file, using extern. When you define them in a header file you open yourself up to problems like this.