-
-
Save glmnet/49ca3d6a9742fc3649f4fbdeaa4cdf5d to your computer and use it in GitHub Desktop.
// Must disable logging if using logging in main.cpp or in other custom components for the | |
// __c causes a section type conflict with __c thingy | |
// you can enable logging and use it if you enable this in logger: | |
/* | |
logger: | |
level: DEBUG | |
esp8266_store_log_strings_in_flash: False | |
*/ | |
//#define APE_LOGGING | |
// take advantage of LOG_ defines to decide which code to include | |
#ifdef LOG_BINARY_OUTPUT | |
#define APE_BINARY_OUTPUT | |
#endif | |
#ifdef LOG_BINARY_SENSOR | |
#define APE_BINARY_SENSOR | |
#endif | |
#ifdef LOG_SENSOR | |
#define APE_SENSOR | |
#endif | |
static const char *TAGape = "ape"; | |
#define APE_CMD_DIGITAL_READ 0 | |
#define APE_CMD_WRITE_ANALOG 2 | |
#define APE_CMD_WRITE_DIGITAL_HIGH 3 | |
#define APE_CMD_WRITE_DIGITAL_LOW 4 | |
#define APE_CMD_SETUP_PIN_OUTPUT 5 | |
#define APE_CMD_SETUP_PIN_INPUT_PULLUP 6 | |
#define APE_CMD_SETUP_PIN_INPUT 7 | |
// 8 analog registers.. A0 to A7 | |
// A4 and A5 not supported due to I2C | |
#define CMD_ANALOG_READ_A0 0b1000 // 0x8 | |
// .... | |
#define CMD_ANALOG_READ_A7 0b1111 // 0xF | |
#define CMD_SETUP_ANALOG_INTERNAL 0x10 | |
#define CMD_SETUP_ANALOG_DEFAULT 0x11 | |
#define get_ape(constructor) static_cast<ArduinoPortExpander *>(constructor.get_component(0)) | |
#define ape_binary_output(ape, pin) get_ape(ape)->get_binary_output(pin) | |
#define ape_binary_sensor(ape, pin) get_ape(ape)->get_binary_sensor(pin) | |
#define ape_analog_input(ape, pin) get_ape(ape)->get_analog_input(pin) | |
class ArduinoPortExpander; | |
using namespace esphome; | |
#ifdef APE_BINARY_OUTPUT | |
class ApeBinaryOutput : public output::BinaryOutput | |
{ | |
public: | |
ApeBinaryOutput(ArduinoPortExpander *parent, uint8_t pin) | |
{ | |
this->parent_ = parent; | |
this->pin_ = pin; | |
} | |
void write_state(bool state) override; | |
uint8_t get_pin() { return this->pin_; } | |
protected: | |
ArduinoPortExpander *parent_; | |
uint8_t pin_; | |
// Pins are setup as output after the state is written, Arduino has no open drain outputs, after setting an output it will either sink or source thus activating outputs writen to false during a flick. | |
bool setup_{true}; | |
bool state_{false}; | |
friend class ArduinoPortExpander; | |
}; | |
#endif | |
#ifdef APE_BINARY_SENSOR | |
class ApeBinarySensor : public binary_sensor::BinarySensor | |
{ | |
public: | |
ApeBinarySensor(ArduinoPortExpander *parent, uint8_t pin) | |
{ | |
this->pin_ = pin; | |
} | |
uint8_t get_pin() { return this->pin_; } | |
protected: | |
uint8_t pin_; | |
}; | |
#endif | |
#ifdef APE_SENSOR | |
class ApeAnalogInput : public sensor::Sensor | |
{ | |
public: | |
ApeAnalogInput(ArduinoPortExpander *parent, uint8_t pin) | |
{ | |
this->pin_ = pin; | |
} | |
uint8_t get_pin() { return this->pin_; } | |
protected: | |
uint8_t pin_; | |
}; | |
#endif | |
class ArduinoPortExpander : public Component, public I2CDevice | |
{ | |
public: | |
ArduinoPortExpander(I2CBus *bus, uint8_t address, bool vref_default = false) | |
{ | |
set_i2c_address(address); | |
set_i2c_bus(bus); | |
this->vref_default_ = vref_default; | |
} | |
void setup() override | |
{ | |
#ifdef APE_LOGGING | |
ESP_LOGCONFIG(TAGape, "Setting up ArduinoPortExpander at %#02x ...", address_); | |
#endif | |
/* We cannot setup as usual as arduino boots later than esp8266 | |
Poll i2c bus for our Arduino for a n seconds instead of failing fast, | |
also this is important as pin setup (INPUT_PULLUP, OUTPUT it's done once) | |
*/ | |
this->configure_timeout_ = millis() + 5000; | |
} | |
void loop() override | |
{ | |
if (millis() < this->configure_timeout_) | |
{ | |
bool try_configure = millis() % 100 > 50; | |
if (try_configure == this->configure_) | |
return; | |
this->configure_ = try_configure; | |
if (ERROR_OK == this->read_register(APE_CMD_DIGITAL_READ, const_cast<uint8_t *>(this->read_buffer_), 3)) | |
{ | |
#ifdef APE_LOGGING | |
ESP_LOGCONFIG(TAGape, "ArduinoPortExpander found at %#02x", address_); | |
#endif | |
delay(10); | |
if (this->vref_default_) | |
{ | |
this->write_register(CMD_SETUP_ANALOG_DEFAULT, nullptr, 0); // 0: unused | |
} | |
// Config success | |
this->configure_timeout_ = 0; | |
this->status_clear_error(); | |
#ifdef APE_BINARY_SENSOR | |
for (ApeBinarySensor *pin : this->input_pins_) | |
{ | |
App.feed_wdt(); | |
uint8_t pinNo = pin->get_pin(); | |
#ifdef APE_LOGGING | |
ESP_LOGCONFIG(TAGape, "Setup input pin %d", pinNo); | |
#endif | |
this->write_register(APE_CMD_SETUP_PIN_INPUT_PULLUP, &pinNo, 1); | |
delay(20); | |
} | |
#endif | |
#ifdef APE_BINARY_OUTPUT | |
for (ApeBinaryOutput *output : this->output_pins_) | |
{ | |
if (!output->setup_) | |
{ // this output has a valid value already | |
this->write_state(output->pin_, output->state_, true); | |
App.feed_wdt(); | |
delay(20); | |
} | |
} | |
#endif | |
#ifdef APE_SENSOR | |
for (ApeAnalogInput *sensor : this->analog_pins_) | |
{ | |
App.feed_wdt(); | |
uint8_t pinNo = sensor->get_pin(); | |
#ifdef APE_LOGGING | |
ESP_LOGCONFIG(TAGape, "Setup analog input pin %d", pinNo); | |
#endif | |
this->write_register(APE_CMD_SETUP_PIN_INPUT, &pinNo, 1); | |
delay(20); | |
} | |
#endif | |
return; | |
} | |
// Still not answering | |
return; | |
} | |
if (this->configure_timeout_ != 0 && millis() > this->configure_timeout_) | |
{ | |
#ifdef APE_LOGGING | |
ESP_LOGE(TAGape, "ArduinoPortExpander NOT found at %#02x", address_); | |
#endif | |
this->mark_failed(); | |
return; | |
} | |
#ifdef APE_BINARY_SENSOR | |
if (ERROR_OK != this->read_register(APE_CMD_DIGITAL_READ, const_cast<uint8_t *>(this->read_buffer_), 3)) | |
{ | |
#ifdef APE_LOGGING | |
ESP_LOGE(TAGape, "Error reading. Reconfiguring pending."); | |
#endif | |
this->status_set_error(); | |
this->configure_timeout_ = millis() + 5000; | |
return; | |
} | |
for (ApeBinarySensor *pin : this->input_pins_) | |
{ | |
uint8_t pinNo = pin->get_pin(); | |
uint8_t bit = pinNo % 8; | |
uint8_t value = pinNo < 8 ? this->read_buffer_[0] : pinNo < 16 ? this->read_buffer_[1] : this->read_buffer_[2]; | |
bool ret = value & (1 << bit); | |
if (this->initial_state_) | |
pin->publish_initial_state(ret); | |
else | |
pin->publish_state(ret); | |
} | |
#endif | |
#ifdef APE_SENSOR | |
for (ApeAnalogInput *pin : this->analog_pins_) | |
{ | |
uint8_t pinNo = pin->get_pin(); | |
pin->publish_state(analogRead(pinNo)); | |
} | |
#endif | |
this->initial_state_ = false; | |
} | |
#ifdef APE_SENSOR | |
uint16_t analogRead(uint8_t pin) | |
{ | |
bool ok = (ERROR_OK == this->read_register((uint8_t)(CMD_ANALOG_READ_A0 + pin), const_cast<uint8_t *>(this->read_buffer_), 2)); | |
#ifdef APE_LOGGING | |
ESP_LOGVV(TAGape, "analog read pin: %d ok: %d byte0: %d byte1: %d", pin, ok, this->read_buffer_[0], this->read_buffer_[1]); | |
#endif | |
uint16_t value = this->read_buffer_[0] | ((uint16_t)this->read_buffer_[1] << 8); | |
return value; | |
} | |
#endif | |
#ifdef APE_BINARY_OUTPUT | |
output::BinaryOutput *get_binary_output(uint8_t pin) | |
{ | |
ApeBinaryOutput *output = new ApeBinaryOutput(this, pin); | |
output_pins_.push_back(output); | |
return output; | |
} | |
#endif | |
#ifdef APE_BINARY_SENSOR | |
binary_sensor::BinarySensor *get_binary_sensor(uint8_t pin) | |
{ | |
ApeBinarySensor *binarySensor = new ApeBinarySensor(this, pin); | |
input_pins_.push_back(binarySensor); | |
return binarySensor; | |
} | |
#endif | |
#ifdef APE_SENSOR | |
sensor::Sensor *get_analog_input(uint8_t pin) | |
{ | |
ApeAnalogInput *input = new ApeAnalogInput(this, pin); | |
analog_pins_.push_back(input); | |
return input; | |
} | |
#endif | |
void write_state(uint8_t pin, bool state, bool setup = false) | |
{ | |
if (this->configure_timeout_ != 0) | |
return; | |
#ifdef APE_LOGGING | |
ESP_LOGD(TAGape, "Writing %d to pin %d", state, pin); | |
#endif | |
this->write_register(state ? APE_CMD_WRITE_DIGITAL_HIGH : APE_CMD_WRITE_DIGITAL_LOW, &pin, 1); | |
if (setup) | |
{ | |
App.feed_wdt(); | |
delay(20); | |
#ifdef APE_LOGGING | |
ESP_LOGI(TAGape, "Setup output pin %d", pin); | |
#endif | |
this->write_register(APE_CMD_SETUP_PIN_OUTPUT, &pin, 1); | |
} | |
} | |
protected: | |
bool configure_{true}; | |
bool initial_state_{true}; | |
uint8_t read_buffer_[3]{0, 0, 0}; | |
unsigned long configure_timeout_{5000}; | |
bool vref_default_{false}; | |
#ifdef APE_BINARY_OUTPUT | |
std::vector<ApeBinaryOutput *> output_pins_; | |
#endif | |
#ifdef APE_BINARY_SENSOR | |
std::vector<ApeBinarySensor *> input_pins_; | |
#endif | |
#ifdef APE_SENSOR | |
std::vector<ApeAnalogInput *> analog_pins_; | |
#endif | |
}; | |
#ifdef APE_BINARY_OUTPUT | |
void ApeBinaryOutput::write_state(bool state) | |
{ | |
this->state_ = state; | |
this->parent_->write_state(this->pin_, state, this->setup_); | |
this->setup_ = false; | |
} | |
#endif |
/* | |
Ports: | |
0 0 .. 13 13 | |
A0: 14, A1: 15, A2: 16, A3: 17: A4: 18: A5: 19: A6: 20, A7: 21 | |
port bits: 5 ... 0..32 | |
0: 0: 00000 | |
1: 1: 00001 | |
A7: 21: 10101 | |
*/ | |
#include <Arduino.h> | |
#include <Wire.h> | |
//#define DEBUG // remove debug so pin 0 and 1 can be used for IO | |
#define I2C_ADDRESS 8 | |
void onRequest(); | |
void onReceive(int); | |
void setup() | |
{ | |
#ifdef DEBUG | |
Serial.begin(115200); | |
Serial.println(F("Init ")); | |
#endif | |
analogReference(INTERNAL); | |
Wire.begin(I2C_ADDRESS); | |
Wire.onRequest(onRequest); | |
Wire.onReceive(onReceive); | |
#ifdef DEBUG | |
Serial.println(F("Wire ok")); | |
#endif | |
} | |
void loop() | |
{ | |
//int temp = analogRead(A1); | |
//Serial.println(temp); | |
} | |
volatile byte buffer[3]; | |
volatile byte len = 1; | |
#define DIGITAL_READ(b, pin, mask) \ | |
if (digitalRead(pin)) \ | |
buffer[b] |= mask; | |
void readDigital() | |
{ | |
len = 3; | |
buffer[0] = 0; | |
DIGITAL_READ(0, 0, 1); | |
DIGITAL_READ(0, 1, 2); | |
DIGITAL_READ(0, 2, 4); | |
DIGITAL_READ(0, 3, 8); | |
DIGITAL_READ(0, 4, 16); | |
DIGITAL_READ(0, 5, 32); | |
DIGITAL_READ(0, 6, 64); | |
DIGITAL_READ(0, 7, 128); | |
buffer[1] = 0; | |
DIGITAL_READ(1, 8, 1); | |
DIGITAL_READ(1, 9, 2); | |
DIGITAL_READ(1, 10, 4); | |
DIGITAL_READ(1, 11, 8); | |
DIGITAL_READ(1, 12, 16); | |
DIGITAL_READ(1, 13, 32); | |
DIGITAL_READ(1, A0, 64); | |
DIGITAL_READ(1, A1, 128); | |
buffer[2] = 0; | |
DIGITAL_READ(2, A2, 1); | |
DIGITAL_READ(2, A3, 2); | |
// I2C | |
//DIGITAL_READ(2, A4, 4); | |
//DIGITAL_READ(2, A5, 8); | |
// DIGITAL READ not supports on A3 .. A7 | |
#ifdef DEBUG_READ | |
Serial.print(F("Read 3 bytes: ")); | |
Serial.print(buffer[0]); | |
Serial.print(' '); | |
Serial.print(buffer[1]); | |
Serial.print(' '); | |
Serial.println(buffer[2]); | |
#endif | |
} | |
void readAnalog(int pin) | |
{ | |
int val = analogRead(A0 + pin); | |
len = 2; | |
buffer[0] = val & 0xFF; | |
buffer[1] = (val >> 8) & 0b11; | |
#ifdef DEBUG_READ | |
Serial.print(F("Read analog pin ")); | |
Serial.println(pin); | |
#endif | |
} | |
void onRequest() | |
{ | |
Wire.write(const_cast<uint8_t *>(buffer), len); | |
} | |
#define CMD_DIGITAL_READ 0x0 | |
#define CMD_WRITE_ANALOG 0x2 | |
#define CMD_WRITE_DIGITAL_HIGH 0x3 | |
#define CMD_WRITE_DIGITAL_LOW 0x4 | |
#define CMD_SETUP_PIN_OUTPUT 0x5 | |
#define CMD_SETUP_PIN_INPUT_PULLUP 0x6 | |
#define CMD_SETUP_PIN_INPUT 0x7 | |
// 8 analog registers.. A0 to A7 | |
// A4 and A5 not supported due to I2C | |
#define CMD_ANALOG_READ_A0 0b1000 // 0x8 | |
// .... | |
#define CMD_ANALOG_READ_A7 0b1111 // 0xF | |
#define CMD_SETUP_ANALOG_INTERNAL 0x10 | |
#define CMD_SETUP_ANALOG_DEFAULT 0x11 | |
void onReceive(int numBytes) | |
{ | |
#ifdef DEBUG_READ | |
Serial.print("Received bytes: "); | |
Serial.println(numBytes); | |
#endif | |
int cmd = Wire.read(); | |
switch (cmd) | |
{ | |
case CMD_DIGITAL_READ: | |
readDigital(); | |
break; | |
} | |
if (cmd >= CMD_ANALOG_READ_A0 && cmd <= CMD_ANALOG_READ_A7) | |
{ | |
readAnalog(cmd & 0b111); | |
return; | |
} | |
int pin = Wire.read(); | |
switch (cmd) | |
{ | |
case CMD_WRITE_DIGITAL_HIGH: | |
case CMD_WRITE_DIGITAL_LOW: | |
{ | |
bool output = cmd == CMD_WRITE_DIGITAL_HIGH; | |
digitalWrite(pin, output); | |
#ifdef DEBUG | |
Serial.print(F("Pin ")); | |
Serial.print(pin); | |
Serial.println(output ? F(" HIGH") : F(" LOW")); | |
#endif | |
break; | |
} | |
case CMD_WRITE_ANALOG: | |
{ | |
int val = Wire.read() & (Wire.read() << 8); | |
analogWrite(pin, val); | |
#ifdef DEBUG | |
Serial.print(F("Pin ")); | |
Serial.print(pin); | |
Serial.print(F(" Analog write ")); | |
Serial.println(val); | |
#endif | |
break; | |
} | |
case CMD_SETUP_PIN_OUTPUT: | |
pinMode(pin, OUTPUT); | |
#ifdef DEBUG | |
Serial.print(F("Pin ")); | |
Serial.print(pin); | |
Serial.println(F(" OUTPUT")); | |
#endif | |
break; | |
case CMD_SETUP_PIN_INPUT: | |
pinMode(pin, INPUT); | |
#ifdef DEBUG | |
Serial.print(F("Pin ")); | |
Serial.print(pin); | |
Serial.println(F("INPUT")); | |
#endif | |
break; | |
case CMD_SETUP_PIN_INPUT_PULLUP: | |
pinMode(pin, INPUT_PULLUP); | |
#ifdef DEBUG | |
Serial.print(F("Pin ")); | |
Serial.print(pin); | |
Serial.println(F("INPUT PULLUP")); | |
#endif | |
break; | |
case CMD_SETUP_ANALOG_INTERNAL: | |
analogReference(INTERNAL); | |
#ifdef DEBUG | |
Serial.println(F("Analog reference INTERNAL")); | |
#endif | |
break; | |
case CMD_SETUP_ANALOG_DEFAULT: | |
analogReference(DEFAULT); | |
#ifdef DEBUG | |
Serial.println(F("Analog reference DEFAULT")); | |
#endif | |
break; | |
} | |
} |
I forgot to mention here I created a new repo for this. See here
@fran6120 thats a nice story but the end was predicted. If you’re short on pins you can try to use the expander for simple io pins, eg relays switches and save internal gpio for sensors which requiere fast io pins. (And also switching pin mode from input to output quickly)
Hi, do you know about the Problem, that neighborhood Analog Inuts have wrong values.
I want to read some of those Sensors: https://de.aliexpress.com/item/1005004819365767.html
Reading a single Sensor has no problems (eg A0). But if I add a second one to A1, the voltage is much higher as its really is.
I have no really solution.
On some Formthreads, they write, that you have to read the ANalog Pin twice and only use the second result:
https://forum.arduino.cc/t/analog-input-affected-by-its-neighbor/88758
or
https://forum.arduino.cc/t/cross-voltages-from-neighbor-analog-input/178708
That’s interesting. Didn’t know
you can try using update interval: none on the sensor s so you disable automatic polling. Then write an interval script which will call component update on the sensors, eg call it twice in A0 and the pick the value on the second call then call twice A1
Do you have a small example for me? I will test it shortly.
This does disables polling and calls to update explicitly
https://github.com/glmnet/esphome_devices/blob/master/my_home/eh-energia.yaml
You might want to create another template sensor which you’ll set the value you read the second time
Thank you,
I think I got it.
I added a dummy analogRead() to the arduino Script before the reading for publishing.
Also I found a thread: firmata/arduino#334 (comment)
I also have added a interval Script to my yaml, to read the Analog Sensors in the correct sequence
- interval: 60s
then:
- component.update: analog_input_3_humidity
- delay: 1s
- component.update: analog_input_3_voltage
- delay: 10s
- component.update: analog_input_4_humidity
- delay: 1s
- component.update: analog_input_4_voltage
- delay: 10s
- component.update: analog_input_5_humidity
- delay: 1s
- component.update: analog_input_5_voltage
- delay: 10s
- component.update: analog_input_6_humidity
- delay: 1s
- component.update: analog_input_6_voltage
- delay: 10s
- component.update: analog_input_1_humidity
- delay: 1s
- component.update: analog_input_1_voltage
- delay: 10s
- component.update: analog_input_2_humidity
- delay: 1s
- component.update: analog_input_2_voltage```
Now I will test it for a couple of Days :-)
Hola buen dia. Existe la manera de usar el sensor dht11 conectado en arduino. Soy nuevo en esto y no he encontrado la manera.
Hello i am trying to use Arduino Port Expander in my home project where reliability is really important .
As a test, temporary I braked the i2c line between the port expander and the esp device. I got a following error immediately (which is good) :
[E][component:112]: Component was marked as failed.
And never recovered (that is not good).
I wonder how can this made more robust? eg. after the transient is gone should recover by them self.
Thank you
Hello everyone,
I have a little dumb question, I have it all working (the first version was working for me here),
but .... if the i2c bus hangs for some reason, I know the command to reset the ESP8266 ---> "- platform: restart",
but not the NANO (in my case) ... (so the i2c bus won't reset), is there a easy way to reset the NANO too in that same command ?
GreetingZzz
lol ... I see it now, @balazs111 has the same question :-)
Hello,
here is the solution: https://community.home-assistant.io/t/arduino-port-expander-i2c-connection-not-recover-after-failure/745984
In the end decided to use modbus because i needed to breach a longer distance what i2c was not able to handle
I am embarking on a project in which I need to use
H711X
, which is natively supported by esphome but I do not have more pins for it.I have quite a few sensors connected via I2C and when I saw this component
arduino_port_expander
that allows you to expand I/O ports via I2C I thought it would be a very good option.I had a pro mini lying around the house so I decided to upload the sketch and connect it to the esp32c3.
I currently do not have the
H711X
, since I am considering the possibility of using it via I2C, but I have aDHT11
at home and I decided to test if thearduino_port_expander
component allows me to control it.First I did tests by configuring an output associated with pin 13 of the Arduino and I was able to control the LED from home assistant perfectly.
Once I verified that
arduino_port_expander
was working correctly I decided to try theDHT11
. First I had to modify theDHT11
component so that it allowed thearduino_port_expander
pins to be configured and not just the internal ones of the esp32 running esphome. ChangeInternalGPIOPin
toGPIOPin
in yaml:
Unfortunately, although it compiles perfectly, on boot it causes bootloop (I imagine due to wdt)
It seems that the sensors where communication is time critical do not work through I/O I2C expanders.
Still,
arduino_port_expander
is a cool component.Thank you!