esphome / feature-requests

ESPHome Feature Request Tracker
https://esphome.io/
411 stars 26 forks source link

support for MPR121 #54

Closed mvturnho closed 5 years ago

mvturnho commented 5 years ago

Is your feature request related to a problem/use-case? Please describe.

For my controlpanel I have a MPR121 12key touch sensor. I implemented a customcomponent for this device using the Adafruit_MPR121 library.

https://github.com/adafruit/Adafruit_MPR121

From main.cpp you create the MPR121_sensorand then for each key you add a MPR121_channelto the MPR121_Sensor like this.:

auto mpr121_sensor = new MPR121_Sensor();
App.register_component(mpr121_sensor);

MPR121_Channel *mpr121_ch0 = new MPR121_Channel("MPR_CH0", 0);
MPR121_Channel *mpr121_channel0 = mpr121_sensor->add_channel(mpr121_ch0);
App.register_binary_sensor(mpr121_channel0);

MPR121_Channel *mpr121_ch4 = new MPR121_Channel("MPR_CH4", 4);
MPR121_Channel *mpr121_channel4 = mpr121_sensor->add_channel(mpr121_ch4);
App.register_binary_sensor(mpr121_channel4);

Describe the solution you'd like:

I would like to make the MPR_121 sensor part of the esphomelib_core and add support for it in the yaml file. Is there some explenation how to add a component to the yaml interpreter? And when I add the component to the core, in what namespace would you want to have this? Also I gues you want the implementation seperated in a .h and .cpp part?

I think the yaml should look something like this:

mpr121_sensor:
  address: 0x5A
  channels: 12
  id: mpr121_sensor 

binary_sensor:
  - platform: mpr121_channel
    sensor_id: mpr121_sensor
    name: "ch0"
    channel: 0
  - platform: mpr121_channel
    sensor_id: mpr121_sensor
    name: "ch1"
    channel: 1

To support the address and channels I have to alter the component a little.

Additional context:

my custom component source that I have tested and works:

#include "esphomelib.h"
#include "esphomelib/defines.h"
#include "esphomelib/component.h"
#include "esphomelib/binary_sensor/binary_sensor.h"

#include "Adafruit_MPR121.h"

using namespace esphomelib;

class MPR121_Channel : public binary_sensor::BinarySensor {
 public:
  int channel = 0;
  explicit MPR121_Channel(const std::string &name,int channel_num) : BinarySensor(name) {
    //ESP_LOGD("MPR121_Channel", "channel %d " , channel_num);
    channel = channel_num;
  };

  void process_(uint16_t *data, uint16_t *last_data) {
    if ((*data & _BV(channel)) && !(*last_data & _BV(channel))) {
      this->publish_state(true);
    } 
    if (!(*data & _BV(channel)) && (*last_data & _BV(channel)) ) {
      this->publish_state(false);
    }
  }
};

class MPR121_Sensor : public Component {
 public:
  Adafruit_MPR121 cap = Adafruit_MPR121();
  std::vector<MPR121_Channel *> channels{};
  // Keeps track of the last pins touched
  // so we know when buttons are 'released'
  uint16_t lasttouched = 0;
  uint16_t currtouched = 0;

  MPR121_Channel *add_channel(MPR121_Channel *channel) {
    this->channels.push_back(channel);
    return channel;
  };

  void process_(uint8_t *ch, uint16_t *data,uint16_t *last_data) {
    bool found_channel = false;
    for (auto *channel : this->channels) {
      if(channel->channel == *ch)
        channel->process_(data,last_data);
    }
  }

  void setup() override {
    // Initialize the device here. Usually Wire.begin() will be called in here,
    // though that call is unnecessary if you have an 'i2c:' entry in your config
    //ESP_LOGD("mpr121_setup", "initializing");

    // Default address is 0x5A, if tied to 3.3V its 0x5B
    // If tied to SDA its 0x5C and if SCL then 0x5D
    bool connected = cap.begin(0x5A);
    if (!connected) {
      ;//ESP_LOGD("mpr121_setup", "NOT connected");  
    } 

  }

  void loop() override {

    // Get the currently touched pads
    currtouched = cap.touched();

    for (uint8_t i=0; i<12; i++) {
      this->process_(&i,&currtouched,&lasttouched);
    }

    // reset our state
    lasttouched = currtouched;

  }
};

the code to add to main.cpp

  // =========== AUTO GENERATED CODE END ============
  // ========= YOU CAN EDIT AFTER THIS LINE =========
  auto mpr121_sensor = new MPR121_Sensor();
  App.register_component(mpr121_sensor);

  MPR121_Channel *mpr121_ch0 = new MPR121_Channel("MPR_CH0", 0);
  MPR121_Channel *mpr121_channel0 = mpr121_sensor->add_channel(mpr121_ch0);
  App.register_binary_sensor(mpr121_channel0);

  MPR121_Channel *mpr121_ch4 = new MPR121_Channel("MPR_CH4", 4);
  MPR121_Channel *mpr121_channel4 = mpr121_sensor->add_channel(mpr121_ch4);
  App.register_binary_sensor(mpr121_channel4);
OttoWinter commented 5 years ago

Great!

So for adding it to the project you have to create PRs in the esphome-core repo (with C++ framework changes), esphome repo (with python changes) and esphome-docs (with docs).

I would recommend reading the contributing page on the docs first. It's not complete, but will tell you about the basic code requirements. Then for the code itself I would recommend looking at examples. There exist many examples of similar sensors (for example PN532, RDM6300, APDS-9960). I would recommend looking at those for examples.

Also ESPHome uses its own abstraction between the i2c bus and code. So it is not possible to use the adafruit library you linked to directly (partly also because Arduino libraries are often low quality).

You will have to write the MPR121 communication yourself - but fortunately usually it's not too much work because we only need a few functions. See the other i2c components for examples (usages of I2CDevice)

mvturnho commented 5 years ago

@OttoWinter Hi, I have a question, can you help me out?

I have implemented the esphome-core part of the mpr121 sensor, and started with the yaml part.

I created the top level component mpr121.py in esphome\esphome\components then I need another mpr121.py in esphome\esphome\components\binary_sensor for the bynary sensor (not done that yet)

but it is not recognized when used in the yaml file. I get the folowing error:

INFO Reading configuration...
ERROR Unable to find component mpr121
Failed config

mpr121: [source C:\Users\michi\eclipse-workspace\esphometest\test.yaml:41]

  Component not found: mpr121
  address: 90
  update_interval: 200ms

the yaml should look like this;

mpr121:
  address: 0xA5
  channels: 2
  update_interval: 500ms

binary_sensor:
  - platform: mpr121
    channel: 0
    name: "touchkey0"
  - platform: mpr121
    channel: 1
    name: "touchkey1"

my python file mpr121.py in esphome\esphome\components

import voluptuous as vol

from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID,  CONF_NAME, CONF_UPDATE_INTERVAL, CONF_CHANNELS
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, PollingComponent

DEPENDENCIES = ['i2c']
MULTI_CONF = True

CONF_MPR121_ID = 'mpr121_id'
MPR121 = sensor.sensor_ns.class_('MPR121', PollingComponent, i2c.I2CDevice)

CONFIG_SCHEMA = vol.Schema({
    cv.GenerateID(): cv.declare_variable_id(MPR121),
    vol.Optional(CONF_ADDRESS): cv.i2c_address,
    vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
}).extend(cv.COMPONENT_SCHEMA.schema)

def to_code(config):
    rhs = App.make_mpr121(config.get(CONF_UPDATE_INTERVAL))
    var = Pvariable(config[CONF_ID], rhs)

    if CONF_ADDRESS in config:
        add(var.set_address(config[CONF_ADDRESS]))

    setup_component(var, config)

BUILD_FLAGS = '-DUSE_MPR121'
OttoWinter commented 5 years ago

The path looks ok, so it is probably an issue with how you set up your python environment with esphome. I just updated the beta.esphome.io contribution docs with more info how to set things up.

This might also help

pip2 uninstall esphome
# go to some folder (should not contain a folder called esphome)
python2 -c 'import esphome; print esphome.__file__;'
# if the command above succeeds you still have esphome installed *somewhere*

# otherwise install as per dev env instructions
OttoWinter commented 5 years ago

This can be closed now