ESPHome and class GPIOPin

I have one ESPHome device that I’ve had for a couple years now. It’s a temperature/humidity sensor using the SHT1X component. ESPHome doesn’t have support for that component natively, so I’m using a 3rd party class (GitHub - practicalarduino/SHT1x: Arduino library to support SHT1x-series (SHT10, SHT11, SHT15) temperature / humidity sensors from Sensirion) to talk to the SHT1X per another thread here. I’ve got a custom sensor class that wraps all that. The sensor has been working fine until I upgraded to HA 2021.11.5 and then I noticed I wasn’t getting readings from it anymore. I updated to the latest ESPHome and tried to recompile my sensor but ran into several errors related to the GPIOPin class.

The board I’m using is a sonoff dev board based on the ESP8266.

When I try to compile, I get the following output:

HARDWARE: ESP8266 80MHz, 80KB RAM, 1MB Flash
Dependency Graph
|-- <ESPAsyncTCP-esphome> 1.2.3
|-- <SHT1x> 0.0.0-alpha+sha.be7042c3e3
|-- <ESPAsyncWebServer-esphome> 2.1.0
|   |-- <ESPAsyncTCP-esphome> 1.2.3
|   |-- <Hash> 1.0
|   |-- <ESP8266WiFi> 1.0
|-- <DNSServer> 1.1.1
|-- <ESP8266WiFi> 1.0
|-- <ESP8266mDNS> 1.2
Compiling /data/sonoff-dev-ths1/.pioenvs/sonoff-dev-ths1/src/SHT1XSensor.cpp.o
Compiling /data/sonoff-dev-ths1/.pioenvs/sonoff-dev-ths1/src/esphome/components/api/proto.cpp.o
Compiling /data/sonoff-dev-ths1/.pioenvs/sonoff-dev-ths1/src/esphome/components/api/subscribe_state.cpp.o
Compiling /data/sonoff-dev-ths1/.pioenvs/sonoff-dev-ths1/src/esphome/components/api/user_services.cpp.o
In file included from src/SHT1XSensor.cpp:1:0:
src/SHT1XSensor.h: In constructor 'esphome::sht1xsensor::SHT1XSensor::SHT1XSensor(int, int)':
src/SHT1XSensor.h:16:50: error: cannot allocate an object of abstract type 'esphome::GPIOPin'
     this->set_data(new GPIOPin(data, INPUT, false));
                                                  ^
In file included from src/esphome/core/hal.h:4:0,
                 from src/SHT1XSensor.h:4,
                 from src/SHT1XSensor.cpp:1:
src/esphome/core/gpio.h:50:7: note:   because the following virtual functions are pure within 'esphome::GPIOPin':
 class GPIOPin {
       ^
src/esphome/core/gpio.h:52:16: note: 	virtual void esphome::GPIOPin::setup()
   virtual void setup() = 0;
                ^
src/esphome/core/gpio.h:54:16: note: 	virtual void esphome::GPIOPin::pin_mode(esphome::gpio::Flags)
   virtual void pin_mode(gpio::Flags flags) = 0;
                ^
src/esphome/core/gpio.h:56:16: note: 	virtual bool esphome::GPIOPin::digital_read()
   virtual bool digital_read() = 0;
                ^
src/esphome/core/gpio.h:58:16: note: 	virtual void esphome::GPIOPin::digital_write(bool)
   virtual void digital_write(bool value) = 0;
                ^
src/esphome/core/gpio.h:60:23: note: 	virtual std::string esphome::GPIOPin::dump_summary() const
   virtual std::string dump_summary() const = 0;
                       ^
In file included from src/SHT1XSensor.cpp:1:0:
src/SHT1XSensor.h:17:52: error: cannot allocate an object of abstract type 'esphome::GPIOPin'
     this->set_clock(new GPIOPin(clock, INPUT, false));
                                                    ^
In file included from src/esphome/core/hal.h:4:0,
                 from src/SHT1XSensor.h:4,
                 from src/SHT1XSensor.cpp:1:
src/esphome/core/gpio.h:50:7: note:   since type 'esphome::GPIOPin' has pure virtual functions
 class GPIOPin {
       ^
src/SHT1XSensor.cpp: In member function 'virtual void esphome::sht1xsensor::SHT1XSensor::setup()':
src/SHT1XSensor.cpp:15:40: error: 'class esphome::GPIOPin' has no member named 'get_pin'
   this->sht1x_ = new SHT1x(this->data->get_pin(), this->clock->get_pin());
                                        ^
src/SHT1XSensor.cpp:15:64: error: 'class esphome::GPIOPin' has no member named 'get_pin'
   this->sht1x_ = new SHT1x(this->data->get_pin(), this->clock->get_pin());
                                                                ^
*** [/data/sonoff-dev-ths1/.pioenvs/sonoff-dev-ths1/src/SHT1XSensor.cpp.o] Error 1
========================== [FAILED] Took 5.18 seconds ==========================

I asked about this on the discord channel, and it was suggested I look at other components in the github repo to see how they did things. Well, the components are all over the place. Some seem to be doing what I’m doing, others are doing things completely different. So, I didn’t really find any answers there. I tried to mimic what some others were doing, but just got different errors. Below I’ll post the code for my custom sensor.

SHT1XSensor.h

#pragma once

#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#include "SHT1x.h"

namespace esphome {
namespace sht1xsensor {

/// This class implements support for 
class SHT1XSensor : public sensor::Sensor, public PollingComponent {
 public:
  SHT1XSensor() : PollingComponent(15000) { }
  SHT1XSensor(int data, int clock) : PollingComponent(15000) { 
    this->set_data(new GPIOPin(data, INPUT, false));
    this->set_clock(new GPIOPin(clock, INPUT, false));
}

  Sensor *temperature_sensor = new Sensor();
  Sensor *humidity_sensor = new Sensor();
  // ========== INTERNAL METHODS ==========
  // (In most use cases you won't need these)
  void setup() override;
  void update() override;
  void set_data(GPIOPin *pin) { data = pin; }
  void set_clock(GPIOPin *pin) { clock = pin; }

 protected:
  GPIOPin *data;
  GPIOPin *clock;

  SHT1x *sht1x_;
};

}  // namespace sht1xsensor
}  // namespace esphome

SHT1XSensor.cpp

#include "SHT1XSensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace sht1xsensor {

#define MIN_TEMP -40
#define MAX_TEMP 254

static const char *TAG = "sht1xsensor.sensor";

void SHT1XSensor::setup() {
  ESP_LOGCONFIG(TAG, "Setting up SHT1X '%s'...", this->name_.c_str());
  ESP_LOGCONFIG(TAG, "Data pin is '%d'...", this->data->get_pin());
  ESP_LOGCONFIG(TAG, "Clock pin is '%d'...", this->clock->get_pin());
  this->sht1x_ = new SHT1x(this->data->get_pin(), this->clock->get_pin());
  ESP_LOGCONFIG(TAG, "Done setting up SHT1X '%s'...", this->name_.c_str());
}

void SHT1XSensor::update() {
  float temp = this->sht1x_->readTemperatureF();
  if (temp >= MIN_TEMP && temp <= MAX_TEMP) {
    this->temperature_sensor->publish_state(temp);
  }
  float humidity = this->sht1x_->readHumidity();
  if (humidity >= 0 && humidity <= 100) {
    this->humidity_sensor->publish_state(humidity);
  }
}
}  // namespace sht1xsensor
}  // namespace esphome

I did one attempt where I did away with using the GPIOPin class altogether and just tried to hard code the pins. That compiles and installs OTA, but I don’t get any response from the sensor, I’m guessing because the pin numbers are not translated properly as they most likely are when using the GPIOPin class.

Have you considered using InternalGPIOPin instead? (see here)

I’ve had the same experiences when looking through source code for different components. I just chalk it up to “everyone codes a little differently”. You might consider checking out the hlw8012 sensor component. Shouldn’t be too difficult to create an external_component using that as a springboard.

All components in the official ESPHome tree should be handling GPIO pins similarly. None of them should create instances of GPIOPin (or derived classes) directly, instead they get one passed from the configuration as an argument through a setter method. If some component does something different, let me know, I’d be happy to fix that.

I’m not sure how you’re building your component into the firmware. In any case, you should drop the instantiation of the GPIOPin objects. If you want to build a “proper” component that can be configured from YAML, the Python part of your component should call the setters with a GPIOPin argument created through cg.gpio_pin_expression, but then you should also change the SHT1x class to control the pins using the GPIOPin class instead of relying on the pin number as was previously returned by get_pin().

If you just want to get it working, I would drop all usage of the GPIOPin class and hardcode the pin numbers in the call to the constructor of SHT1x.