External component missing .h file

I try to create a external component for a DCF77 receiver for ESPHome, which can be used by an ESP32 in esp-idf, but a missing .h file stopped me and I have no idea what I do wrong. Here is what I do:

Created files:

  • esphome
    • components
      • dcf77_2
        • init.py
        • dcf77_2_component.cpp
        • dcf77_2_component.h
        • time.py
#time.py
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import time
import esphome.const as const
from esphome import pins
from esphome.const import (
  CONF_PIN,
)

DEPENDENCIES = []

# C++ namespace
dcf77_2_ns = cg.esphome_ns.namespace("dcf77_2")
Dcf77Time_2 = dcf77_2_ns.class_("Dcf77Time_2", time.RealTimeClock, cg.Component)

CONFIG_SCHEMA = time.TIME_SCHEMA.extend({
    cv.Required(const.CONF_ID): cv.declare_id(Dcf77Time_2),
    cv.Required(CONF_PIN): pins.gpio_output_pin_schema,
})

async def to_code(config):
    pin = await cg.gpio_pin_expression(config[CONF_PIN])
    var = cg.new_Pvariable(config[const.CONF_ID], pin)
    await cg.register_component(var, config)
    await time.register_time(var, config)
//dcf77_2_component.h
#pragma once

#include "esphome/core/component.h"
#include "esphome/core/time.h"
#include "esphome/components/time/real_time_clock.h"

//#include "DCF77.h"
//#include "TimeLib.h"

namespace esphome {
namespace dcf77_2 {

class Dcf77Time_2 : public time::RealTimeClock {
public:
  explicit Dcf77Time_2(int pin) { this->pin_ = pin; }

  //-----------------------------------------------------
  // Initialize the components's hardware 
  //-----------------------------------------------------
  void setup() override;

  //-----------------------------------------------------
  // 
  //-----------------------------------------------------
  void loop() override;

  //-----------------------------------------------------
  // 
  //-----------------------------------------------------
  void update() override;

protected:
  InternalGPIOPin *pin_;
};

}  // namespace dcf77_2
}  // namespace esphome
//dcf77_2_component.cpp
#include "esphome/core/log.h"
#include "dcf77_2.h"

namespace esphome {
namespace dcf77_2 {

static const char *const TAG = "dcf77_2_time";

Dcf77Time_2::setup() {
  this->dcf_ = new ::DCF77(this->pin_, digitalPinToInterrupt(this->pin_));
  this->dcf_->Start();
  ESP_LOGI(TAG, "DCF77 Decoder gestartet (Pin %d)", this->pin_);
}

void Dcf77Time_2::loop()  {
 
}

void Dcf77Time_2::update() {
}

} // namespace dcf77_2
} // namespace esphome
#----------------------------------------------------
# wortuhr-3.yaml
#----------------------------------------------------
substitutions:
  devicename: wortuhr-3
  friendly_name: Wortuhr 3
  device_description: Wortuhr 3
  led_gpio: GPIO10
  dcf_gpio: "3"
  # for fritzbox it shall be .fritz.box, typically it shall be .local
  network_domain: .fritz.box

# https://esphome.io/components/external_components/   dcf77_idf
external_components:
  - source: 
      type: local
      path: components

# https://esphome.io/components/esphome
esphome:
  name: ${devicename}
  # do not use friendly_name bause it will be added to entities
  #friendly_name: ${friendly_name}
  comment: ${device_description}

# https://esphome.io/components/esp32
esp32:
  board: lolin_c3_mini
  variant: esp32c3
  framework:
    type: esp-idf

# https://esphome.io/components/logger
# Enable logging
logger:
  level: VERBOSE # VERY_VERBOSE # DEBUG #  INFO    

# https://esphome.io/components/time
# used for stand alone 
time:
  - platform: dcf77_2
    id: dcf_time_source
    pin: GPIO3 

The validation works without an error, but the compiler:

INFO ESPHome 2025.10.4
INFO Reading configuration /config/esphome/wortuhr-3.yaml...
INFO Detected timezone 'Europe/Berlin'
INFO Generating C++ source...
INFO Compiling app...
Processing wortuhr-3 (board: lolin_c3_mini; framework: espidf; platform: https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21-2/platform-espressif32.zip)
--------------------------------------------------------------------------------
INFO Package configuration completed successfully
INFO Package configuration completed successfully
HARDWARE: ESP32C3 160MHz, 320KB RAM, 4MB Flash
 - framework-espidf @ 3.50402.0 (5.4.2) 
 - tool-cmake @ 3.30.2 
 - tool-esp-rom-elfs @ 2024.10.11 
 - tool-esptoolpy @ 5.0.2 
 - tool-mklittlefs @ 3.2.0 
 - tool-ninja @ 1.13.1 
 - tool-scons @ 4.40801.0 (4.8.1) 
 - toolchain-riscv32-esp @ 14.2.0+20241119
Reading CMake configuration...
No dependencies
Compiling .pioenvs/wortuhr-3/src/esphome/components/dcf77_2/dcf77_2_component.cpp.o
Compiling .pioenvs/wortuhr-3/src/esphome/components/esp32/gpio.cpp.o
src/esphome/components/dcf77_2/dcf77_2_component.cpp:3:10: fatal error: dcf77_2.h: No such file or directory

***********************************************************************
* Looking for dcf77_2.h dependency? Check our library registry!
*
* CLI  > platformio lib search "header:dcf77_2.h"
* Web  > https://registry.platformio.org/search?q=header:%1B%5Bm%1B%5BKdcf77_2.h
*
***********************************************************************

    3 | #include "dcf77_2.h"
      |          ^~~~~~~~~~~
compilation terminated.
*** [.pioenvs/wortuhr-3/src/esphome/components/dcf77_2/dcf77_2_component.cpp.o] Error 1
========================== [FAILED] Took 7.08 seconds ==========================

I use a terminal to check the files by ls src/esphome/components/dcf77_2/

and got: dcf77_2_component.cpp dcf77_2_component.h

So header file seems to be there.

I tried an example from: GitHub - jesserockz/esphome-external-component-examples

components/empty_binary_sensor
And this one worked as expectet.

Would be great if someone can give me a hint.

It looks like you have somehow inserted some special characters in your file - namely ESC[m and ESC[K, which look like editing commands.

Try commenting out that line and retyping the include manually.

Don’t forget to flush your ESPHome cache to get of of any leftover corrupted files before your next recompile.

  • dcf77_2_component.cpp
  • dcf77_2_component.h

I think that a part of the problem is the cache.
Can you give me a hint where to do this ?
I use Home Assistant OS and SSH & Web Terminal add-on and disable protection mode.
At the moment I used : docker exec -it addon_5c53de3b_esphome bash
to get access to the build path.

Do I find the chache somewhere there ?

Thank you, I was blind :man_facepalming:. Now it works.

You’re welcome!