Coder Social home page Coder Social logo

esphome-custom-component-examples's Introduction

ESPHome logoESPHome custom component examples

This repository provides examples and basic empty components that can be used as templates to quickly develop your own custom components for the splendid ESPHome ESP8266/ESP32 home automation system.

How to use

All sample components can be found in the custom_components directory. The test_empty_components.yaml file provides configuration examples for the various custom components. To use a particular component for your project, do the following:

  • Create a custom_components directory in your esphome configuration directory (the directory where your .yaml files are)

  • Copy the directory of an empty component to custom_components in its entirety, so you end up with e.g. custom_components/empty_sensor/

  • Find the configuration entry for the empty component in test_empty_components.yaml and copy it into your own .yaml file.

  • Compile with esphome your_config.yaml compile (change your_config.yaml to your own .yaml file) or compile with the dashboard.

  • No errors? Great! You can now start modifying the empty component into your own custom component.

Basic structure of a custom component

Let's start with the simplest custom component:

custom_components
├── empty_component
│   ├── __init.py__
│   ├── empty_component.cpp
│   ├── empty_component.h
│  ...

The __init.py__ file contains 2 main things:

  • configuration validation or cv
  • code generation or cg

cv handles validation of user input in the .yaml configuration file for this particular component: defining the available configuration options, whether they are required or optional, any constraints on the types and values of an option, etc.

cg takes these validated configuration options and generates the code necessary to streamline them into your c++ code, as well as registering your component properly within the ESPHome runtime.

The .cpp and .h files are the main source code files for your component. You're free to add to or modify the structure of the source code within the constraints of c++, but your code needs at least one class derived from Component that will be registered in the __init__.py.

If your component yields a specific type of component, e.g. a sensor or a switch, ESPHome will instead look for a sensor.py or switch.py. The structure of these is identical to that of __init__.py.

See for example:

custom_components
├── empty_binary_sensor
│   ├── binary_sensor.py
│   ├── empty_binary_sensor.cpp
│   ├── empty_binary_sensor.h
│  ...

There is no __init__.py here, instead we have a binary_sensor.py.

esphome-custom-component-examples's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

esphome-custom-component-examples's Issues

Empty Fan fails to compile

When I use the empty_fan component, I get the following:

INFO ESPHome 2023.9.1
INFO Reading configuration hot-water-controller.yaml...
INFO Generating C++ source...
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Python311\Scripts\esphome.exe\__main__.py", line 7, in <module>
  File "C:\Python311\Lib\site-packages\esphome\__main__.py", line 1036, in main
    return run_esphome(sys.argv)
           ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\esphome\__main__.py", line 1023, in run_esphome
    rc = POST_CONFIG_ACTIONS[args.command](args, config)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\esphome\__main__.py", line 403, in command_compile
    exit_code = write_cpp(config)
                ^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\esphome\__main__.py", line 189, in write_cpp
    generate_cpp_contents(config)
  File "C:\Python311\Lib\site-packages\esphome\__main__.py", line 201, in generate_cpp_contents
    CORE.flush_tasks()
  File "C:\Python311\Lib\site-packages\esphome\core\__init__.py", line 642, in flush_tasks
    self.event_loop.flush_tasks()
  File "C:\Python311\Lib\site-packages\esphome\coroutine.py", line 246, in flush_tasks
    next(task.iterator)
  File "C:\Python311\Lib\site-packages\esphome\__main__.py", line 181, in wrapped
    await coro(conf)
  File "C:\Python311\Lib\site-packages\esphome\coroutine.py", line 80, in coro
    ret = yield from _flatten_generator(gen)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\esphome\coroutine.py", line 132, in _flatten_generator
    to_send = yield from val.__await__()
              ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\esphome\components\fan\__init__.py", line 168, in create_fan_state
    await cg.register_component(var, config)
  File "C:\Python311\Lib\site-packages\esphome\cpp_helpers.py", line 56, in register_component
    raise ValueError(
ValueError: Component ID fan_speed was not declared to inherit from Component, or was registered twice. Please create a bug report with your configuration.
sys:1: RuntimeWarning: coroutine 'to_code' was never awaited
sys:1: RuntimeWarning: coroutine 'add_arduino_global_workaround' was never awaited
sys:1: RuntimeWarning: coroutine '_add_platformio_options' was never awaited

for

fan:
  - platform: empty_fan
    id: fan_speed
    output: fan_pwm
    name: "Fan Speed"

__init__.py is now required

I only tested the empty_text_sensor component, but I suspect the following is true for each component.

Somewhere between ESPHome versions 1.16.2 and 1.18.0, it became a requirement for the component folder to contain an __init__.py file, even if that file is empty. Otherwise, the compile fails with the following errors:

src/main.cpp:16:1: error: 'empty_text_sensor' does not name a type
 empty_text_sensor::EmptyTextSensor *empty_text_sensor_emptytextsensor;
 ^
src/main.cpp: In function 'void setup()':
src/main.cpp:148:3: error: 'empty_text_sensor_emptytextsensor' was not declared in this scope
   empty_text_sensor_emptytextsensor = new empty_text_sensor::EmptyTextSensor();
   ^
src/main.cpp:148:43: error: 'empty_text_sensor' does not name a type
   empty_text_sensor_emptytextsensor = new empty_text_sensor::EmptyTextSensor();

To fix this problem, I added an __init__.py file with nothing in it, leaving the text_sensor.py as-is.

Trying to create 74hc165 custom component

Hiii i am trying to create a 74hc165 custom component to increase my inputs in esphome and I don't know how to add the particular code in the empty binary component... It would be nice if you help 👍🏻

Trying to create a custom component that inherits pn532_i2c

First of all, I would like to say thank you for the good examples. It has been really helpful.

I'm trying to make an extension to the pn532_i2c component.
Unfortunately this is not something you cover in any of your examples, so I've tried my best to do it myself.

However for some reason I keep getting errors.

image

But I'm also not quite sure if I'm doing it right.

I've created all the folders and files as you mentioned, but done some modification to the files so that they fit my need.

I'm mostly insecure about the __init__.py file

This is what it looks like:

import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import pn532_i2c, sensor
from esphome.const import CONF_ID

DEPENDENCIES = ['pn532_i2c']

CONF_PN532_I2C_ADDR = 0x01

custom_pn532_i2c_component_ns = cg.esphome_ns.namespace('custom_pn532_i2c_component')
CustomPN532I2CComponent = custom_pn532_i2c_component_ns.class_('CustomPN532I2CComponent', pn532_i2c.PN532I2C)

CONFIG_SCHEMA = cv.Schema({
    cv.GenerateID(): cv.declare_id(CustomPN532I2CComponent)
}).extend(cv.COMPONENT_SCHEMA)


def to_code(config):
    var = cg.new_Pvariable(config[CONF_ID])
    yield cg.register_component(var, config)

And these are my C++ files:

Here is custom_pn532_i2c_component.h

#pragma once

#include "esphome/core/component.h"
#include "esphome/components/pn532_i2c/pn532_i2c.h"

namespace esphome {
namespace custom_pn532_i2c_component {

class CustomPN532I2CComponent : public pn532_i2c::PN532I2C {
 public:
  void dump_config() override;
  bool write_tag(std::vector<uint8_t> &uid, nfc::NdefMessage *message) {
    return this->write_tag_(uid, message);
  }
};

}  // namespace custom_pn532_i2c_component
}  // namespace esphome

And this is custom_pn532_i2c_component.cpp

#include "esphome/core/log.h"
#include "custom_pn532_i2c_component.h"

namespace esphome {
namespace custom_pn532_i2c_component {

static const char *TAG = "custom_pn532_i2c_component.component";


void CustomPN532I2CComponent::dump_config(){
    ESP_LOGCONFIG(TAG, "Custom PN532 I2C component");
}


}  // namespace empty_i2c_component
}  // namespace esphome

I'm getting no build errors, and I'm also able to install it, but clearly something is wrong.

It feels wrong to both have a 'pn532_i2c' and a 'custom_pn532_i2c_component' component in my ESPHome config (.yaml)

This is what it looks like:

esphome:
  name: nfc-tagreader
   # Automatically add the mac address to the name
  # so you can use a single firmware for all devices
  name_add_mac_suffix: 'true'

# If buzzer is enabled, notify on api connection success
  on_boot:
    priority: -10
    then:
    - wait_until:
        api.connected:
    - logger.log: API is connected!
    - rtttl.play: "success:d=24,o=5,b=100:c,g,b"
    - light.turn_on:
        id: led_blue
        flash_length: 500ms
    - switch.turn_on: buzzer_enabled
    - switch.turn_on: led_enabled

esp8266:
  board: d1_mini

# Enable logging
logger:


custom_pn532_i2c_component:
  id: custom_pn532_board


# Enable Home Assistant API
api:
  encryption:
    key: "C9DIQNQ07MCb2aUTp70iUx/Sluzn/S3Eple23HrC/u4="
  services:
  - service: rfidreader_tag_ok
    then:
    - rtttl.play: "beep:d=16,o=5,b=100:b"

  - service: rfidreader_tag_ko
    then:
    - rtttl.play: "beep:d=8,o=5,b=100:b"

  - service: play_rtttl
    variables:
      song_str: string
    then:
    - rtttl.play: !lambda 'return song_str;'

  - service: write_tag_id
    variables:
      tag_id: string
    then:
    - light.turn_on:
        id: led_red
        brightness: 100%
    - lambda: |-
        auto message = new nfc::NdefMessage();
        std::string uri = "https://www.home-assistant.io/tag/";
        uri += tag_id;
        message->add_uri_record(uri);
        id(custom_pn532_board).write_mode(message);
    - rtttl.play: "write:d=24,o=5,b=100:b"
    - wait_until:
      - lambda: |-
          return id(custom_pn532_board).is_writing() == false;
    - light.turn_off:
        id: led_red
    - rtttl.play: "write:d=24,o=5,b=100:b,b"

  - service: write_music_tag
    variables:
      music_url: string
      music_info: string
    then:
    - light.turn_on:
        id: led_red
        brightness: 100%
    - lambda: |-
        auto message = new nfc::NdefMessage();
        std::string uri = "";
        std::string text = "";
        uri += music_url;
        text += music_info;
        if ( music_url != "" ) {
          message->add_uri_record(uri);
        }
        if ( music_info != "" ) {
          message->add_text_record(text);
        }
        id(custom_pn532_board).write_mode(message);
    - rtttl.play: "write:d=24,o=5,b=100:b"
    - wait_until:
      - lambda: |-
          return id(custom_pn532_board).is_writing() == true;
    - light.turn_off:
        id: led_red
    - rtttl.play: "write:d=24,o=5,b=100:b,b"

ota:
  password: "a89fc07420122d2df7e1053f3e7016b5"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Nfc-Tagreader Fallback Hotspot"
    password: "uKXj4moHXSZK"

captive_portal:


dashboard_import:
  package_import_url: github://LukasMendez/tagreader_homeassistant/tag_reader.yaml

improv_serial:

substitutions:
  name: tagreader
  friendly_name: TagReader

# Define switches to control LED and buzzer from HA
switch:
- platform: template
  name: "${friendly_name} Buzzer Enabled"
  id: buzzer_enabled
  icon: mdi:volume-high
  optimistic: true
  restore_state: true
  entity_category: config
- platform: template
  name: "${friendly_name} LED enabled"
  id: led_enabled
  icon: mdi:alarm-light-outline
  optimistic: true
  restore_state: true
  entity_category: config

# Define buttons for writing tags via HA
button:
  - platform: template
    name: Paste Tag
    id: paste_tag
    # Optional variables:
    icon: "mdi:content-paste"
    on_press:
      then:
      - light.turn_on:
          id: led_blue
          brightness: 100%
      - lambda: |-
           std::vector<uint8_t> newUid = {0xAB, 0x3D, 0xBA, 0x44};
           auto message = new nfc::NdefMessage();
           ESP_LOGD("tagreader", "Writing new UID: %s", newUid);
           // auto my_custom_nfc_component = new CustomNfcToolComponent();
           // my_custom_nfc_component.write_tag(newUid, message);
           // ((esphome::custom_pn532_i2c_component::CustomPN532I2CComponent *) id(custom_pn532_board))->write_tag(newUid, message);
           id(custom_pn532_board)->write_tag(newUid, message);
      - rtttl.play: "write:d=24,o=5,b=100:b"
      - wait_until:
        - lambda: |-
            return id(custom_pn532_board).is_writing() == true;
      - light.turn_off:
          id: led_blue
      - rtttl.play: "write:d=24,o=5,b=100:b,b"

  - platform: template
    name: Write Tag Random
    id: write_tag_random
    # Optional variables:
    icon: "mdi:pencil-box"
    on_press:
      then:
      - light.turn_on:
          id: led_red
          brightness: 100%
      - light.turn_on:
          id: led_blue
          brightness: 100%
      - lambda: |-
          static const char alphanum[] = "0123456789abcdef";
          std::string uri = "https://www.home-assistant.io/tag/";
          for (int i = 0; i < 8; i++)
            uri += alphanum[random_uint32() % (sizeof(alphanum) - 1)];
          uri += "-";
          for (int j = 0; j < 3; j++) {
            for (int i = 0; i < 4; i++)
              uri += alphanum[random_uint32() % (sizeof(alphanum) - 1)];
            uri += "-";
          }
          for (int i = 0; i < 12; i++)
            uri += alphanum[random_uint32() % (sizeof(alphanum) - 1)];
          auto message = new nfc::NdefMessage();
          message->add_uri_record(uri);
          ESP_LOGD("tagreader", "Writing payload: %s", uri.c_str());
          id(custom_pn532_board).write_mode(message);
      - rtttl.play: "write:d=24,o=5,b=100:b"
      - wait_until:
        - lambda: |-
            return id(custom_pn532_board).is_writing() == true;
      - light.turn_off:
          id: led_red
      - light.turn_off:
          id: led_blue
      - rtttl.play: "write:d=24,o=5,b=100:b,b"
  - platform: template
    name: Clean Tag
    id: clean_tag
    icon: "mdi:nfc-variant-off"
    on_press:
      then:
      - light.turn_on:
          id: led_red
          brightness: 100%
      - light.turn_on:
          id: led_green
          brightness: 64.7%
      - lambda: 'id(custom_pn532_board).clean_mode();'
      - rtttl.play: "write:d=24,o=5,b=100:b"
      - wait_until:
        - lambda: |-
            return id(custom_pn532_board).is_writing() == true;
      - light.turn_off:
          id: led_red
      - light.turn_off:
          id: led_green
      - rtttl.play: "write:d=24,o=5,b=100:b,b"
  - platform: template
    name: Cancel writing
    id: cancel_writing
    icon: "mdi:pencil-off"
    on_press:
      then:
      - lambda: 'id(custom_pn532_board).read_mode();'
      - light.turn_off:
          id: led_red
      - light.turn_off:
          id: led_green
      - light.turn_off:
          id: led_blue
      - rtttl.play: "write:d=24,o=5,b=100:b,b"

  - platform: restart
    name: "${friendly_name} Restart"
    entity_category: config


i2c:
  scan: False
  frequency: 400kHz

globals:
  - id: source
    type: std::string
  - id: url
    type: std::string
  - id: info
    type: std::string
  - id: copiedData
    type: std::string
    restore_value: no  # Strings cannot be saved/restored
    initial_value: '"No value"'


pn532_i2c:
  id: pn532_board
  address: 0x01
  on_tag:
    then:
    - if:
        condition:
          switch.is_on: led_enabled
        then:
        - light.turn_on:
            id: led_green
            brightness: 100%
            flash_length: 500ms

    - delay: 0.15s #to fix slow component

    - lambda: |-
        id(source)="";
        id(url)="";
        id(info)="";
        if (tag.has_ndef_message()) {
          auto message = tag.get_ndef_message();
          auto records = message->get_records();
          for (auto &record : records) {
            std::string payload = record->get_payload();
            ESP_LOGD("tagreader", "Found payload in record: %s", payload);
            std::string type = record->get_type();
            size_t hass = payload.find("https://www.home-assistant.io/tag/");
            size_t applemusic = payload.find("https://music.apple.com");
            size_t spotify = payload.find("https://open.spotify.com");
            size_t sonos = payload.find("sonos-2://");
            if (type == "U" and hass != std::string::npos ) {
              ESP_LOGD("tagreader", "Found Home Assistant tag NDEF");
              id(source)="hass";
              id(url)=payload;
              id(info)=payload.substr(hass + 34);
            }
            else if (type == "U" and applemusic != std::string::npos ) {
              ESP_LOGD("tagreader", "Found Apple Music tag NDEF");
              id(source)="amusic";
              id(url)=payload;
            }
            else if (type == "U" and spotify != std::string::npos ) {
              ESP_LOGD("tagreader", "Found Spotify tag NDEF");
              id(source)="spotify";
              id(url)=payload;
            }
            else if (type == "U" and sonos != std::string::npos ) {
              ESP_LOGD("tagreader", "Found Sonos app tag NDEF");
              id(source)="sonos";
              id(url)=payload;
            }
            else if (type == "T" ) {
              ESP_LOGD("tagreader", "Found music info tag NDEF");
              id(info)=payload;
            }
            else if ( id(source)=="" ) {
              id(source)="uid";
            }
          }
        }
        else {
          id(source)="uid";
        }
    - if:
        condition:
          lambda: 'return ( id(source)=="uid" );'
        then:
          - homeassistant.tag_scanned: !lambda |-
              ESP_LOGD("tagreader", "No HA NDEF, using UID");
              return x;
        else:
        - if:
            condition:
              lambda: 'return ( id(source)=="hass" );'
            then:
            - homeassistant.tag_scanned: !lambda 'return id(info);'
            else:
            - homeassistant.event:
                event: esphome.music_tag
                data:
                  reader: !lambda |-
                    return App.get_name().c_str();
                  source: !lambda |-
                    return id(source);
                  url: !lambda |-
                    return id(url);
                  info: !lambda |-
                    return id(info);
    - if:
        condition:
          switch.is_on: buzzer_enabled
        then:
        - rtttl.play: "success:d=24,o=5,b=100:c,g,b"

# Define the buzzer output
output:
- platform: esp8266_pwm
  pin: D0
  id: buzzer
- platform: gpio
  pin: D8
  id: output_red
- platform: gpio
  pin: D7
  id: output_green
- platform: gpio
  pin: D6
  id: output_blue

binary_sensor:
  - platform: status
    name: "${friendly_name} Status"
    entity_category: diagnostic


text_sensor:
  - platform: version
    hide_timestamp: true
    name: "${friendly_name} ESPHome Version"
    entity_category: diagnostic
  - platform: wifi_info
    ip_address:
      name: "${friendly_name} IP Address"
      icon: mdi:wifi
      entity_category: diagnostic
    ssid:
      name: "${friendly_name} Connected SSID"
      icon: mdi:wifi-strength-2
      entity_category: diagnostic

# Define buzzer as output for RTTTL
rtttl:
  output: buzzer

# Configure LED
light:
- platform: binary
  internal: true
  id: led_red
  name: Red
  output: output_red
- platform: binary
  internal: true
  id: led_green
  name: Green
  output: output_green
  restore_mode: RESTORE_DEFAULT_OFF
- platform: binary
  internal: true
  id: led_blue
  name: Blue
  output: output_blue
  restore_mode: RESTORE_DEFAULT_OFF

The only thing I'm trying to achieve is the possibility to call a protected function which is accessible inside of pn532_i2c.h so I created a custom component that inherits the class and then exposes a public function that calls the protected member. In theory this should be easy without changing any of the other functionality, but since I'm not experienced in C++ and Python it's not as easy as I wish.

Hope you can help me.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.