1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-17 18:52:19 +01:00

🏗 Merge C++ into python codebase (#504)

## Description:

Move esphome-core codebase into esphome (and a bunch of other refactors). See https://github.com/esphome/feature-requests/issues/97

Yes this is a shit ton of work and no there's no way to automate it :( But it will be worth it 👍

Progress:
- Core support (file copy etc): 80%
- Base Abstractions (light, switch): ~50%
- Integrations: ~10%
- Working? Yes, (but only with ported components).

Other refactors:
- Moves all codegen related stuff into a single class: `esphome.codegen` (imported as `cg`)
- Rework coroutine syntax
- Move from `component/platform.py` to `domain/component.py` structure as with HA
- Move all defaults out of C++ and into config validation.
- Remove `make_...` helpers from Application class. Reason: Merge conflicts with every single new integration.
- Pointer Variables are stored globally instead of locally in setup(). Reason: stack size limit.

Future work:
- Rework const.py - Move all `CONF_...` into a conf class (usage `conf.UPDATE_INTERVAL` vs `CONF_UPDATE_INTERVAL`). Reason: Less convoluted import block
- Enable loading from `custom_components` folder.

**Related issue (if applicable):** https://github.com/esphome/feature-requests/issues/97

**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>

## Checklist:
  - [ ] The code change is tested and works locally.
  - [ ] Tests have been added to verify that the new code works (under `tests/` folder).

If user exposed functionality or configuration variables are added/changed:
  - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs).
This commit is contained in:
Otto Winter
2019-04-17 12:06:00 +02:00
committed by GitHub
parent 049807e3ab
commit 6682c43dfa
817 changed files with 54156 additions and 10830 deletions

View File

@@ -0,0 +1,47 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_SCAN, CONF_SCL, CONF_SDA, CONF_ADDRESS, \
CONF_I2C_ID
from esphome.core import coroutine
i2c_ns = cg.esphome_ns.namespace('i2c')
I2CComponent = i2c_ns.class_('I2CComponent', cg.Component)
I2CDevice = i2c_ns.class_('I2CDevice')
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(I2CComponent),
cv.Optional(CONF_SDA, default='SDA'): pins.input_pin,
cv.Optional(CONF_SCL, default='SCL'): pins.input_pin,
cv.Optional(CONF_FREQUENCY, default='50kHz'):
cv.All(cv.frequency, cv.Range(min=0, min_included=False)),
cv.Optional(CONF_SCAN, default=True): cv.boolean,
}).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_SDA], config[CONF_SCL],
int(config[CONF_FREQUENCY]), config[CONF_SCAN])
yield cg.register_component(var, config)
cg.add_library('Wire', None)
cg.add_global(i2c_ns.using)
def i2c_device_schema(default_address):
schema = {
cv.GenerateID(CONF_I2C_ID): cv.use_variable_id(I2CComponent),
}
if default_address is None:
schema[cv.Required(CONF_ADDRESS)] = cv.i2c_address
else:
schema[cv.Optional(CONF_ADDRESS, default=default_address)] = cv.i2c_address
return cv.Schema(schema)
@coroutine
def register_i2c_device(var, config):
parent = yield cg.get_variable(config[CONF_I2C_ID])
cg.add(var.set_i2c_parent(parent))
cg.add(var.set_i2c_address(config[CONF_ADDRESS]))

View File

@@ -0,0 +1,205 @@
#include "i2c.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/application.h"
namespace esphome {
namespace i2c {
static const char *TAG = "i2c";
I2CComponent::I2CComponent(uint8_t sda_pin, uint8_t scl_pin, uint32_t frequency, bool scan)
: sda_pin_(sda_pin), scl_pin_(scl_pin), frequency_(frequency), scan_(scan) {
#ifdef ARDUINO_ARCH_ESP32
if (next_i2c_bus_num_ == 0)
this->wire_ = &Wire;
else
this->wire_ = new TwoWire(next_i2c_bus_num_);
next_i2c_bus_num_++;
#else
this->wire_ = &Wire;
#endif
}
void I2CComponent::setup() {
this->wire_->begin(this->sda_pin_, this->scl_pin_);
this->wire_->setClock(this->frequency_);
}
void I2CComponent::dump_config() {
ESP_LOGCONFIG(TAG, "I2C Bus:");
ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_);
ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_);
ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_);
if (this->scan_) {
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
uint8_t found = 0;
for (uint8_t address = 8; address < 120; address++) {
this->wire_->beginTransmission(address);
uint8_t error = this->wire_->endTransmission();
if (error == 0) {
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
found++;
} else if (error == 4) {
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
}
delay(1);
}
if (found == 0) {
ESP_LOGI(TAG, "Found no i2c devices!");
}
}
}
float I2CComponent::get_setup_priority() const { return setup_priority::HARDWARE; }
void I2CComponent::raw_begin_transmission(uint8_t address) {
ESP_LOGVV(TAG, "Beginning Transmission to 0x%02X:", address);
this->wire_->beginTransmission(address);
}
bool I2CComponent::raw_end_transmission(uint8_t address) {
uint8_t status = this->wire_->endTransmission();
ESP_LOGVV(TAG, " Transmission ended. Status code: 0x%02X", status);
switch (status) {
case 0:
break;
case 1:
ESP_LOGW(TAG, "Too much data to fit in transmitter buffer for address 0x%02X", address);
break;
case 2:
ESP_LOGW(TAG, "Received NACK on transmit of address 0x%02X", address);
break;
case 3:
ESP_LOGW(TAG, "Received NACK on transmit of data for address 0x%02X", address);
break;
default:
ESP_LOGW(TAG, "Unknown transmit error %u for address 0x%02X", status, address);
break;
}
return status == 0;
}
bool I2CComponent::raw_request_from(uint8_t address, uint8_t len) {
ESP_LOGVV(TAG, "Requesting %u bytes from 0x%02X:", len, address);
uint8_t ret = this->wire_->requestFrom(address, len);
if (ret != len) {
ESP_LOGW(TAG, "Requesting %u bytes from 0x%02X failed!", len, address);
return false;
}
return true;
}
void HOT I2CComponent::raw_write(uint8_t address, const uint8_t *data, uint8_t len) {
for (size_t i = 0; i < len; i++) {
ESP_LOGVV(TAG, " Writing 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
this->wire_->write(data[i]);
App.feed_wdt();
}
}
void HOT I2CComponent::raw_write_16(uint8_t address, const uint16_t *data, uint8_t len) {
for (size_t i = 0; i < len; i++) {
ESP_LOGVV(TAG, " Writing 0b" BYTE_TO_BINARY_PATTERN BYTE_TO_BINARY_PATTERN " (0x%04X)",
BYTE_TO_BINARY(data[i] >> 8), BYTE_TO_BINARY(data[i]), data[i]);
this->wire_->write(data[i] >> 8);
this->wire_->write(data[i]);
App.feed_wdt();
}
}
bool I2CComponent::raw_receive(uint8_t address, uint8_t *data, uint8_t len) {
if (!this->raw_request_from(address, len))
return false;
for (uint8_t i = 0; i < len; i++) {
data[i] = this->wire_->read();
ESP_LOGVV(TAG, " Received 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]);
App.feed_wdt();
}
return true;
}
bool I2CComponent::raw_receive_16(uint8_t address, uint16_t *data, uint8_t len) {
if (!this->raw_request_from(address, len * 2))
return false;
auto *data_8 = reinterpret_cast<uint8_t *>(data);
for (uint8_t i = 0; i < len; i++) {
data_8[i * 2 + 1] = this->wire_->read();
data_8[i * 2] = this->wire_->read();
ESP_LOGVV(TAG, " Received 0b" BYTE_TO_BINARY_PATTERN BYTE_TO_BINARY_PATTERN " (0x%04X)",
BYTE_TO_BINARY(data_8[i * 2 + 1]), BYTE_TO_BINARY(data_8[i * 2]), data[i]);
}
return true;
}
bool I2CComponent::read_bytes(uint8_t address, uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) {
if (!this->write_bytes(address, a_register, nullptr, 0))
return false;
if (conversion > 0)
delay(conversion);
return this->raw_receive(address, data, len);
}
bool I2CComponent::read_bytes_16(uint8_t address, uint8_t a_register, uint16_t *data, uint8_t len,
uint32_t conversion) {
if (!this->write_bytes(address, a_register, nullptr, 0))
return false;
if (conversion > 0)
delay(conversion);
return this->raw_receive_16(address, data, len);
}
bool I2CComponent::read_byte(uint8_t address, uint8_t a_register, uint8_t *data, uint32_t conversion) {
return this->read_bytes(address, a_register, data, 1, conversion);
}
bool I2CComponent::read_byte_16(uint8_t address, uint8_t a_register, uint16_t *data, uint32_t conversion) {
return this->read_bytes_16(address, a_register, data, 1, conversion);
}
bool I2CComponent::write_bytes(uint8_t address, uint8_t a_register, const uint8_t *data, uint8_t len) {
this->raw_begin_transmission(address);
this->raw_write(address, &a_register, 1);
this->raw_write(address, data, len);
return this->raw_end_transmission(address);
}
bool I2CComponent::write_bytes_16(uint8_t address, uint8_t a_register, const uint16_t *data, uint8_t len) {
this->raw_begin_transmission(address);
this->raw_write(address, &a_register, 1);
this->raw_write_16(address, data, len);
return this->raw_end_transmission(address);
}
bool I2CComponent::write_byte(uint8_t address, uint8_t a_register, uint8_t data) {
return this->write_bytes(address, a_register, &data, 1);
}
bool I2CComponent::write_byte_16(uint8_t address, uint8_t a_register, uint16_t data) {
return this->write_bytes_16(address, a_register, &data, 1);
}
void I2CDevice::set_i2c_address(uint8_t address) { this->address_ = address; }
bool I2CDevice::read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) { // NOLINT
return this->parent_->read_bytes(this->address_, a_register, data, len, conversion);
}
bool I2CDevice::read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion) { // NOLINT
return this->parent_->read_byte(this->address_, a_register, data, conversion);
}
bool I2CDevice::write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { // NOLINT
return this->parent_->write_bytes(this->address_, a_register, data, len);
}
bool I2CDevice::write_byte(uint8_t a_register, uint8_t data) { // NOLINT
return this->parent_->write_byte(this->address_, a_register, data);
}
bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion) { // NOLINT
return this->parent_->read_bytes_16(this->address_, a_register, data, len, conversion);
}
bool I2CDevice::read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion) { // NOLINT
return this->parent_->read_byte_16(this->address_, a_register, data, conversion);
}
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) { // NOLINT
return this->parent_->write_bytes_16(this->address_, a_register, data, len);
}
bool I2CDevice::write_byte_16(uint8_t a_register, uint16_t data) { // NOLINT
return this->parent_->write_byte_16(this->address_, a_register, data);
}
void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; }
#ifdef ARDUINO_ARCH_ESP32
uint8_t next_i2c_bus_num_ = 0;
#endif
} // namespace i2c
} // namespace esphome

View File

@@ -0,0 +1,207 @@
#pragma once
#include <Wire.h>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace i2c {
#define LOG_I2C_DEVICE(this) ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
/** The I2CComponent is the base of ESPHome's i2c communication.
*
* It handles setting up the bus (with pins, clock frequency) and provides nice helper functions to
* make reading from the i2c bus easier (see read_bytes, write_bytes) and safe (with read timeouts).
*
* For the user, it has a few setters (see set_sda_pin, set_scl_pin, set_frequency)
* to setup some parameters for the bus. Additionally, the i2c component has a scan feature that will
* scan the entire 7-bit i2c address range for devices that respond to transmissions to make finding
* the address of an i2c device easier.
*
* On the ESP32, you can even have multiple I2C bus for communication, simply create multiple
* I2CComponents, each with different SDA and SCL pins and use `set_parent` on all I2CDevices that use
* the non-first I2C bus.
*/
class I2CComponent : public Component {
public:
I2CComponent(uint8_t sda_pin, uint8_t scl_pin, uint32_t frequency, bool scan);
/** Read len amount of bytes from a register into data. Optionally with a conversion time after
* writing the register value to the bus.
*
* @param address The address to send the request to.
* @param a_register The register number to write to the bus before reading.
* @param data An array to store len amount of 8-bit bytes into.
* @param len The amount of bytes to request and write into data.
* @param conversion The time in ms between writing the register value and reading out the value.
* @return If the operation was successful.
*/
bool read_bytes(uint8_t address, uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0);
/** Read len amount of 16-bit words (MSB first) from a register into data.
*
* @param address The address to send the request to.
* @param a_register The register number to write to the bus before reading.
* @param data An array to store len amount of 16-bit words into.
* @param len The amount of 16-bit words to request and write into data.
* @param conversion The time in ms between writing the register value and reading out the value.
* @return If the operation was successful.
*/
bool read_bytes_16(uint8_t address, uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0);
/// Read a single byte from a register into the data variable. Return true if successful.
bool read_byte(uint8_t address, uint8_t a_register, uint8_t *data, uint32_t conversion = 0);
/// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful.
bool read_byte_16(uint8_t address, uint8_t a_register, uint16_t *data, uint32_t conversion = 0);
/** Write len amount of 8-bit bytes to the specified register for address.
*
* @param address The address to use for the transmission.
* @param a_register The register to write the values to.
* @param data An array from which len bytes of data will be written to the bus.
* @param len The amount of bytes to write to the bus.
* @return If the operation was successful.
*/
bool write_bytes(uint8_t address, uint8_t a_register, const uint8_t *data, uint8_t len);
/** Write len amount of 16-bit words (MSB first) to the specified register for address.
*
* @param address The address to use for the transmission.
* @param a_register The register to write the values to.
* @param data An array from which len 16-bit words of data will be written to the bus.
* @param len The amount of bytes to write to the bus.
* @return If the operation was successful.
*/
bool write_bytes_16(uint8_t address, uint8_t a_register, const uint16_t *data, uint8_t len);
/// Write a single byte of data into the specified register of address. Return true if successful.
bool write_byte(uint8_t address, uint8_t a_register, uint8_t data);
/// Write a single 16-bit word of data into the specified register of address. Return true if successful.
bool write_byte_16(uint8_t address, uint8_t a_register, uint16_t data);
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Begin a write transmission to an address.
void raw_begin_transmission(uint8_t address);
/// End a write transmission to an address, return true if successful.
bool raw_end_transmission(uint8_t address);
/** Request data from an address with a number of (8-bit) bytes.
*
* @param address The address to request the bytes from.
* @param len The number of bytes to receive, must not be 0.
* @return True if all requested bytes were read, false otherwise.
*/
bool raw_request_from(uint8_t address, uint8_t len);
/// Write len amount of bytes from data to address. begin_transmission_ must be called before this.
void raw_write(uint8_t address, const uint8_t *data, uint8_t len);
/// Write len amount of 16-bit words from data to address. begin_transmission_ must be called before this.
void raw_write_16(uint8_t address, const uint16_t *data, uint8_t len);
/// Request len amount of bytes from address and write the result it into data. Returns true iff was successful.
bool raw_receive(uint8_t address, uint8_t *data, uint8_t len);
/// Request len amount of 16-bit words from address and write the result into data. Returns true iff was successful.
bool raw_receive_16(uint8_t address, uint16_t *data, uint8_t len);
/// Setup the i2c. bus
void setup() override;
void dump_config() override;
/// Set a very high setup priority to make sure it's loaded before all other hardware.
float get_setup_priority() const override;
protected:
TwoWire *wire_;
uint8_t sda_pin_;
uint8_t scl_pin_;
uint32_t frequency_;
bool scan_;
};
#ifdef ARDUINO_ARCH_ESP32
extern uint8_t next_i2c_bus_num_;
#endif
/** All components doing communication on the I2C bus should subclass I2CDevice.
*
* This class stores 1. the address of the i2c device and has a helper function to allow
* users to manually set the address and 2. stores a reference to the "parent" I2CComponent.
*
*
* All this class basically does is to expose all helper functions from I2CComponent.
*/
class I2CDevice {
public:
I2CDevice() = default;
I2CDevice(I2CComponent *parent, uint8_t address) : address_(address), parent_(parent) {}
/// Manually set the i2c address of this device.
void set_i2c_address(uint8_t address);
/// Manually set the parent i2c bus for this device.
void set_i2c_parent(I2CComponent *parent);
protected:
/** Read len amount of bytes from a register into data. Optionally with a conversion time after
* writing the register value to the bus.
*
* @param a_register The register number to write to the bus before reading.
* @param data An array to store len amount of 8-bit bytes into.
* @param len The amount of bytes to request and write into data.
* @param conversion The time in ms between writing the register value and reading out the value.
* @return If the operation was successful.
*/
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); // NOLINT
/** Read len amount of 16-bit words (MSB first) from a register into data.
*
* @param a_register The register number to write to the bus before reading.
* @param data An array to store len amount of 16-bit words into.
* @param len The amount of 16-bit words to request and write into data.
* @param conversion The time in ms between writing the register value and reading out the value.
* @return If the operation was successful.
*/
bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0); // NOLINT
/// Read a single byte from a register into the data variable. Return true if successful.
bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0); // NOLINT
/// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful.
bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0); // NOLINT
/** Write len amount of 8-bit bytes to the specified register.
*
* @param a_register The register to write the values to.
* @param data An array from which len bytes of data will be written to the bus.
* @param len The amount of bytes to write to the bus.
* @return If the operation was successful.
*/
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len); // NOLINT
/** Write len amount of 16-bit words (MSB first) to the specified register.
*
* @param a_register The register to write the values to.
* @param data An array from which len 16-bit words of data will be written to the bus.
* @param len The amount of bytes to write to the bus.
* @return If the operation was successful.
*/
bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len); // NOLINT
/// Write a single byte of data into the specified register. Return true if successful.
bool write_byte(uint8_t a_register, uint8_t data); // NOLINT
/// Write a single 16-bit word of data into the specified register. Return true if successful.
bool write_byte_16(uint8_t a_register, uint16_t data); // NOLINT
uint8_t address_{0x00};
I2CComponent *parent_{nullptr};
};
} // namespace i2c
} // namespace esphome