Link errors because I'm somehow compiling duplicates to different folders. Yikes!

For reasons I’m no longer sure of, I’m trying to code a custom component to support the Adafruit STEMMA Soil Sensor. After banging my head against the keyyboard for a good couple weeks, I found this code on the net that supposedly worked for the auther at some point about 3 yeara ago. Yeah, I kmow HA has changed a lot since then but please - here me out cause this gets interesting.

I’ll post the code and logs below but let me start by saying that I think the problem can be seen at the front of the log where it’s clear that there are two compiles run for several identical objects and there target locations are different. Two of the objects in question are:

Adafruit_BusIO_Register.cpp.o
Adafruit_I2CDevice.cpp.o

Am I right about this? If so, what can I do to correct it?

Andf here’s the exciting YAML file!

esphome:
  name: plant-monitor-1
  friendly_name: plant-monitor-1

  includes:
    - read_sensor.h
    
  libraries:
    - SPI
    - Wire

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "AuRssv2tp+yAg8bsmW5YhMi95ytMb9WxgDKLT8KfYbY="

ota:
  password: "0e0ed3ec33d02c4b25d3daa90c427b89"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Plant-Monitor-1 Fallback Hotspot"
    password: "UXGb5XfIYzTY"

captive_portal:

sensor:
  - platform: custom
    lambda: |-
      auto soil_sensor = new StemmaSoilSensor();
      App.register_component(soil_sensor);
      return {soil_sensor->temperature_sensor, soil_sensor->moisture_sensor};
    sensors:
      - name: "Plant Temperature"
      - name: "Plant Moisture"    

And the riveting c++ header . . .

#include "esphome.h"
#include "Wire.h"

#define SEESAW_HW_ID_CODE 0x55 ///< seesaw HW ID code

/** Module Base Addreses
 *  The module base addresses for different seesaw modules.
 */
enum
{
    SEESAW_STATUS_BASE = 0x00,
    SEESAW_TOUCH_BASE = 0x0F,
};

/** status module function address registers
 */
enum
{
    SEESAW_STATUS_HW_ID = 0x01,
    SEESAW_STATUS_VERSION = 0x02,
    SEESAW_STATUS_OPTIONS = 0x03,
    SEESAW_STATUS_TEMP = 0x04,
    SEESAW_STATUS_SWRST = 0x7F,
};

/** touch module function address registers
 */
enum {
    SEESAW_TOUCH_CHANNEL_OFFSET = 0x10,
};

class StemmaSoilSensor : public PollingComponent
{
    public:
        // Constructor
        StemmaSoilSensor() : PollingComponent (300000)
        {
            this->i2c_addr = 0x36;
        }

        Sensor * temperature_sensor = new Sensor();
        Sensor * moisture_sensor = new Sensor();

        uint8_t i2c_addr;

        bool setupFailed = true;

        // This will be called by App.setup ()
        void setup() override
        {
            // Start the seesaw.
            // Perform software reset.
            this->write8 (SEESAW_STATUS_BASE, SEESAW_STATUS_SWRST, 0xFF);
            delay (500);

            // Check for seesaw.
            uint8_t c = this->read8(SEESAW_STATUS_BASE, SEESAW_STATUS_HW_ID);
            if (c != SEESAW_HW_ID_CODE)
            {
                ESP_LOGE("soil_sensor", "Failed to connect to soil sensor.");
                // TODO: inform of failure
            }

            this->setupFailed = false;
            ESP_LOGI("soil_sensor", "Successfully reset soil sensor.");
            // TODO: inform of success

            this->temperature_sensor->set_unit_of_measurement("°F");
            this->temperature_sensor->set_accuracy_decimals(0);

            this->moisture_sensor->set_unit_of_measurement("moistcap");
            this->moisture_sensor->set_accuracy_decimals(0);
        }

        // This will be called every update_interval milliseconds.
        void update() override
        {
            if (this->setupFailed)
                return;

            float tempC = this->getTemp();
            uint16_t capread = this->touchRead(0);

            float tempF = (tempC * 1.8) + 32.0;

            this->temperature_sensor->publish_state(tempF);
            this->moisture_sensor->publish_state(capread);
        }

        // Get the temperature of the seesaw board in degrees Celsius
        float getTemp()
        {
            uint8_t buf[4];
            this->read(SEESAW_STATUS_BASE, SEESAW_STATUS_TEMP, buf, 4, 1000);
            int32_t ret = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) |
                ((uint32_t)buf[2] << 8) | (uint32_t)buf[3];
            return (1.0 / (1UL << 16)) * ret;
        }

        // Read the current analog value of the capacitative sensor.
        uint16_t touchRead(uint8_t pin)
        {
            uint8_t buf[2];
            uint8_t p = pin;
            uint16_t ret = 65535;
            do {
                delay(1);
                this->read(SEESAW_TOUCH_BASE, SEESAW_TOUCH_CHANNEL_OFFSET + p, buf, 2, 1000);
                ret = ((uint16_t)buf[0] << 8) | buf[1];
            } while (ret == 65535);
            return ret;
        }

        // Read a specified number of bytes into a buffer from the seesaw.
        void read(uint8_t regHigh, uint8_t regLow, uint8_t *buf, uint8_t num, uint16_t delay)
        {
            uint8_t pos = 0;

            // on arduino we need to read in 32 byte chunks
            while (pos < num) {
                uint8_t read_now = min(32, num - pos);
                Wire.beginTransmission((uint8_t)this->i2c_addr);
                Wire.write((uint8_t)regHigh);
                Wire.write((uint8_t)regLow);
                Wire.endTransmission();

                // TODO: tune this
                delayMicroseconds(delay);

                Wire.requestFrom((uint8_t)this->i2c_addr, read_now);

                for (int i = 0; i < read_now; i++) {
                    buf[pos] = Wire.read();
                    pos++;
                }
            }
        }

        // Read 1 byte from the specified seesaw register
        uint8_t read8(byte regHigh, byte regLow, uint16_t delay = 125)
        {
            uint8_t ret;
            this->read(regHigh, regLow, &ret, 1, delay);

            return ret;
        }

        // Write a specified number of bytes to the seesaw from the passed buffer.
        void write(uint8_t regHigh, uint8_t regLow, uint8_t *buf, uint8_t num)
        {
            Wire.beginTransmission((uint8_t)this->i2c_addr);
            Wire.write((uint8_t)regHigh);
            Wire.write((uint8_t)regLow);
            Wire.write((uint8_t *)buf, num);
            Wire.endTransmission();
        }

        // Write one byte to specified seesaw register.
        void write8(byte regHigh, byte regLow, byte value)
        {
            this->write(regHigh, regLow, &value, 1);
        }
};

But WAIT! We also have logs to share

INFO ESPHome 2024.3.0
INFO Reading configuration /config/esphome/plant-monitor-1.yaml...
INFO Generating C++ source...
INFO Compiling app...
Processing plant-monitor-1 (board: esp32dev; framework: arduino; platform: platformio/[email protected])
--------------------------------------------------------------------------------
Library Manager: Installing esphome/AsyncTCP-esphome @ 2.1.3
INFO Installing esphome/AsyncTCP-esphome @ 2.1.3
Unpacking  [####################################]  100%
Library Manager: [email protected] has been installed!
INFO [email protected] has been installed!
Library Manager: Installing esphome/ESPAsyncWebServer-esphome @ 3.1.0
INFO Installing esphome/ESPAsyncWebServer-esphome @ 3.1.0
Unpacking  [####################################]  100%
Library Manager: [email protected] has been installed!
INFO [email protected] has been installed!
Library Manager: Resolving dependencies...
INFO Resolving dependencies...
Library Manager: Installing esphome/noise-c @ 0.1.4
INFO Installing esphome/noise-c @ 0.1.4
Unpacking  [####################################]  100%
Library Manager: [email protected] has been installed!
INFO [email protected] has been installed!
Library Manager: Resolving dependencies...
INFO Resolving dependencies...
Library Manager: Installing esphome/libsodium @ 1.10018.1
INFO Installing esphome/libsodium @ 1.10018.1
Unpacking  [####################################]  100%
Library Manager: [email protected] has been installed!
INFO [email protected] has been installed!
HARDWARE: ESP32 240MHz, 320KB RAM, 4MB Flash
 - toolchain-xtensa-esp32 @ 8.4.0+2021r2-patch5
Dependency Graph
|-- AsyncTCP-esphome @ 2.1.3
|-- SPI @ 2.0.0
|-- Wire @ 2.0.0 f
|-- WiFi @ 2.0.0
|-- FS @ 2.0.0
|-- Update @ 2.0.0
|-- ESPAsyncWebServer-esphome @ 3.1.0
|-- DNSServer @ 2.0.0
|-- ESPmDNS @ 2.0.0
|-- noise-c @ 0.1.4
Compiling .pioenvs/plant-monitor-
.
.
.
1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp.o
Compiling .pioenvs/plant-monitor-1/src/Adafruit_BusIO/Adafruit_I2CDevice.cpp.o
Compiling .pioenvs/plant-monitor-1/src/Adafruit_BusIO/ f.cpp.o
Compiling .pioenvs/plant-monitor-1/src/Adafruit_BusIO_Register.cpp.o
Compiling .pioenvs/plant-monitor-1/src/Adafruit_I2CDevice.cpp.o
Compiling .pioenvs/plant-monitor-1/src/Adafruit_SPIDevice.cpp.o
.
.
.
Compiling .pioenvs/plant-monitor-1/src/esphome/components/api/api_connection.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/api/api_frame_helper.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/api/api_pb2.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/api/api_pb2_service.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/api/api_server.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/api/list_entities.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/api/proto.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/api/subscribe_state.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/api/user_services.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/captive_portal/captive_portal.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/custom/sensor/custom_sensor.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/esp32/core.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/esp32/gpio.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/esp32/preferences.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/logger/logger.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/logger/logger_esp32.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/logger/logger_esp8266.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/logger/logger_host.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/logger/logger_libretiny.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/logger/logger_rp2040.cpp.o
Compiling .pioenvs/plant-monitor-1/src/esphome/components/md5/md5.cpp.o
.
.
.
Compiling .pioenvs/plant-monitor-1/FrameworkArduino/wiring_pulse.c.o
Compiling .pioenvs/plant-monitor-1/FrameworkArduino/wiring_shift.c.o
Archiving .pioenvs/plant-monitor-1/libFrameworkArduino.a
Linking .pioenvs/plant-monitor-1/firmware.elf
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/plant-monitor-1/src/Adafruit_BusIO_Register.cpp.o: in function `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_I2CDevice*, unsigned short, unsigned char, unsigned char, unsigned char)':
/data/build/plant-monitor-1/src/Adafruit_BusIO_Register.cpp:18: multiple definition of `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_I2CDevice*, unsigned short, unsigned char, unsigned char, unsigned char)'; .pioenvs/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp.o:/data/build/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp:18: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/plant-monitor-1/src/Adafruit_BusIO_Register.cpp.o: in function `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_I2CDevice*, unsigned short, unsigned char, unsigned char, unsigned char)':
/data/build/plant-monitor-1/src/Adafruit_BusIO_Register.cpp:18: multiple definition of `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_I2CDevice*, unsigned short, unsigned char, unsigned char, unsigned char)'; .pioenvs/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp.o:/data/build/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp:18: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/plant-monitor-1/src/Adafruit_BusIO_Register.cpp.o: in function `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_SPIDevice*, unsigned short, _Adafruit_BusIO_SPIRegType, unsigned char, unsigned char, unsigned char)':
/data/build/plant-monitor-1/src/Adafruit_BusIO_Register.cpp:45: multiple definition of `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_SPIDevice*, unsigned short, _Adafruit_BusIO_SPIRegType, unsigned char, unsigned char, unsigned char)'; .pioenvs/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp.o:/data/build/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp:45: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/plant-monitor-1/src/Adafruit_BusIO_Register.cpp.o: in function `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_SPIDevice*, unsigned short, _Adafruit_BusIO_SPIRegType, unsigned char, unsigned char, unsigned char)':
/data/build/plant-monitor-1/src/Adafruit_BusIO_Register.cpp:45: multiple definition of `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_SPIDevice*, unsigned short, _Adafruit_BusIO_SPIRegType, unsigned char, unsigned char, unsigned char)'; .pioenvs/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp.o:/data/build/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp:45: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/plant-monitor-1/src/Adafruit_BusIO_Register.cpp.o: in function `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_I2CDevice*, Adafruit_SPIDevice*, _Adafruit_BusIO_SPIRegType, unsigned short, unsigned char, unsigned char, unsigned char)':
/data/build/plant-monitor-1/src/Adafruit_BusIO_Register.cpp:78: multiple definition of `Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_I2CDevice*, Adafruit_SPIDevice*, _Adafruit_BusIO_SPIRegType, unsigned short, unsigned char, unsigned char, unsigned char)'; .pioenvs/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp.o:/data/build/plant-monitor-1/src/Adafruit_BusIO/Adafruit_BusIO_Register.cpp:78: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/plant-monitor-1/src/Adafruit_BusIO_Register.cpp.o: in function .
.
.
.
`Adafruit_SPIDevice::write_and_read(unsigned char*, unsigned int)'; .pioenvs/plant-monitor-1/src/Adafruit_BusIO/Adafruit_SPIDevice.cpp.o:/data/build/plant-monitor-1/src/Adafruit_BusIO/Adafruit_SPIDevice.cpp:502: first defined here
collect2: error: ld returned 1 exit status
*** [.pioenvs/plant-monitor-1/firmware.elf] Error 1
========================= [FAILED] Took 137.58 seconds =========================

1 Like

I resolved this by backing up my yaml file, then deleting the card for this entity from Esphome in my HA. Then I added it back again, reloaded relevant parts of my yaml to the new yaml file (i.e., I kept the new encryption key). This solved the link problem but, of course I still have some execution-time errors to figure out. :thinking:

I think the original problem was the result of my switching boards. I started with a Adafruit ESP32 feather V2 but I was having some problems getting the I2C comm to work so I changed to a ESP-WROOM-32. I think the builds I did with the Adafruit board left behind some libraries that were also getting pulled in to the builds with the new board, causing the duplication.

Anyway, that’s my best guess.