mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 12:35:25 +00:00
[i2c] Add basic host platform support (#14489)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
@@ -29,6 +31,7 @@ from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_DEVICE,
|
||||
CONF_FREQUENCY,
|
||||
CONF_I2C,
|
||||
CONF_I2C_ID,
|
||||
@@ -40,6 +43,7 @@ from esphome.const import (
|
||||
CONF_TIMEOUT,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_HOST,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_RP2040,
|
||||
PlatformFramework,
|
||||
@@ -56,6 +60,7 @@ InternalI2CBus = i2c_ns.class_("InternalI2CBus", I2CBus)
|
||||
ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", InternalI2CBus, cg.Component)
|
||||
IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component)
|
||||
ZephyrI2CBus = i2c_ns.class_("ZephyrI2CBus", I2CBus, cg.Component)
|
||||
HostI2CBus = i2c_ns.class_("HostI2CBus", I2CBus, cg.Component)
|
||||
I2CDevice = i2c_ns.class_("I2CDevice")
|
||||
|
||||
ESP32_I2C_CAPABILITIES = {
|
||||
@@ -83,6 +88,12 @@ CONF_SCL_PULLUP_ENABLED = "scl_pullup_enabled"
|
||||
MULTI_CONF = True
|
||||
|
||||
|
||||
def validate_device(value):
|
||||
if not re.match(r"^/(?:[^/]+/)*[^/]+$", value):
|
||||
raise cv.Invalid("Device must be an absolute device path (e.g., /dev/i2c-0)")
|
||||
return value
|
||||
|
||||
|
||||
def _bus_declare_type(value):
|
||||
if CORE.is_esp32:
|
||||
return cv.declare_id(IDFI2CBus)(value)
|
||||
@@ -90,6 +101,8 @@ def _bus_declare_type(value):
|
||||
return cv.declare_id(ArduinoI2CBus)(value)
|
||||
if CORE.using_zephyr:
|
||||
return cv.declare_id(ZephyrI2CBus)(value)
|
||||
if CORE.is_host:
|
||||
return cv.declare_id(HostI2CBus)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -121,15 +134,48 @@ def validate_config(config):
|
||||
return config
|
||||
|
||||
|
||||
def validate_host_config(config):
|
||||
if CORE.is_host:
|
||||
# Host I2C is currently only supported on Linux
|
||||
if not sys.platform.lower().startswith("linux"):
|
||||
raise cv.Invalid(
|
||||
"I2C is only supported on Linux for the host platform. "
|
||||
f"Current platform: {sys.platform}"
|
||||
)
|
||||
if CONF_SDA in config or CONF_SCL in config:
|
||||
raise cv.Invalid(
|
||||
"'sda' and 'scl' are not supported on host platform; use 'device' instead."
|
||||
)
|
||||
if CONF_SDA_PULLUP_ENABLED in config or CONF_SCL_PULLUP_ENABLED in config:
|
||||
raise cv.Invalid("Pull-up configuration is not supported on host platform.")
|
||||
if CONF_DEVICE not in config:
|
||||
raise cv.Invalid(
|
||||
"'device' is required for host platform (e.g., /dev/i2c-0)."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): _bus_declare_type,
|
||||
cv.Optional(CONF_SDA, default="SDA"): pins.internal_gpio_pin_number,
|
||||
cv.SplitDefault(
|
||||
CONF_SDA,
|
||||
esp32="SDA",
|
||||
esp8266="SDA",
|
||||
rp2040="SDA",
|
||||
nrf52="SDA",
|
||||
): pins.internal_gpio_pin_number,
|
||||
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32=True): cv.All(
|
||||
cv.only_on_esp32, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_SCL, default="SCL"): pins.internal_gpio_pin_number,
|
||||
cv.SplitDefault(
|
||||
CONF_SCL,
|
||||
esp32="SCL",
|
||||
esp8266="SCL",
|
||||
rp2040="SCL",
|
||||
nrf52="SCL",
|
||||
): pins.internal_gpio_pin_number,
|
||||
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32=True): cv.All(
|
||||
cv.only_on_esp32, cv.boolean
|
||||
),
|
||||
@@ -139,6 +185,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
esp8266="50kHz",
|
||||
rp2040="50kHz",
|
||||
nrf52="100kHz",
|
||||
host="50kHz",
|
||||
): cv.All(
|
||||
cv.frequency,
|
||||
cv.float_range(min=0, min_included=False),
|
||||
@@ -155,10 +202,22 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.boolean,
|
||||
),
|
||||
cv.Optional(CONF_DEVICE): cv.All(
|
||||
cv.only_on(PLATFORM_HOST), validate_device
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_NRF52]),
|
||||
cv.only_on(
|
||||
[
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_HOST,
|
||||
]
|
||||
),
|
||||
validate_config,
|
||||
validate_host_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -217,7 +276,13 @@ FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
async def to_code(config):
|
||||
cg.add_global(i2c_ns.using)
|
||||
cg.add_define("USE_I2C")
|
||||
if CORE.using_zephyr:
|
||||
if CORE.is_host:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_device(config[CONF_DEVICE]))
|
||||
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
|
||||
cg.add(var.set_scan(config[CONF_SCAN]))
|
||||
elif CORE.using_zephyr:
|
||||
zephyr_add_prj_conf("I2C", True)
|
||||
i2c = "i2c0"
|
||||
if zephyr_data()[KEY_BOARD] == "xiao_ble":
|
||||
@@ -244,25 +309,40 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID], MockObj(f"DEVICE_DT_GET(DT_NODELABEL({i2c}))")
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_sda_pin(config[CONF_SDA]))
|
||||
if CONF_SDA_PULLUP_ENABLED in config:
|
||||
cg.add(var.set_sda_pullup_enabled(config[CONF_SDA_PULLUP_ENABLED]))
|
||||
cg.add(var.set_scl_pin(config[CONF_SCL]))
|
||||
if CONF_SCL_PULLUP_ENABLED in config:
|
||||
cg.add(var.set_scl_pullup_enabled(config[CONF_SCL_PULLUP_ENABLED]))
|
||||
|
||||
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
|
||||
cg.add(var.set_scan(config[CONF_SCAN]))
|
||||
if CONF_TIMEOUT in config:
|
||||
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
|
||||
if CONF_LOW_POWER_MODE in config:
|
||||
cg.add(var.set_lp_mode(bool(config[CONF_LOW_POWER_MODE])))
|
||||
else:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_sda_pin(config[CONF_SDA]))
|
||||
if CONF_SDA_PULLUP_ENABLED in config:
|
||||
cg.add(var.set_sda_pullup_enabled(config[CONF_SDA_PULLUP_ENABLED]))
|
||||
cg.add(var.set_scl_pin(config[CONF_SCL]))
|
||||
if CONF_SCL_PULLUP_ENABLED in config:
|
||||
cg.add(var.set_scl_pullup_enabled(config[CONF_SCL_PULLUP_ENABLED]))
|
||||
cg.add(var.set_sda_pin(config[CONF_SDA]))
|
||||
if CONF_SDA_PULLUP_ENABLED in config:
|
||||
cg.add(var.set_sda_pullup_enabled(config[CONF_SDA_PULLUP_ENABLED]))
|
||||
cg.add(var.set_scl_pin(config[CONF_SCL]))
|
||||
if CONF_SCL_PULLUP_ENABLED in config:
|
||||
cg.add(var.set_scl_pullup_enabled(config[CONF_SCL_PULLUP_ENABLED]))
|
||||
|
||||
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
|
||||
cg.add(var.set_scan(config[CONF_SCAN]))
|
||||
if CONF_TIMEOUT in config:
|
||||
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
|
||||
if CORE.using_arduino and not CORE.is_esp32:
|
||||
cg.add_library("Wire", None)
|
||||
if CONF_LOW_POWER_MODE in config:
|
||||
cg.add(var.set_lp_mode(bool(config[CONF_LOW_POWER_MODE])))
|
||||
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
|
||||
cg.add(var.set_scan(config[CONF_SCAN]))
|
||||
if CONF_TIMEOUT in config:
|
||||
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
|
||||
if CORE.using_arduino and not CORE.is_esp32:
|
||||
cg.add_library("Wire", None)
|
||||
if CONF_LOW_POWER_MODE in config:
|
||||
cg.add(var.set_lp_mode(bool(config[CONF_LOW_POWER_MODE])))
|
||||
|
||||
|
||||
def i2c_device_schema(default_address):
|
||||
@@ -365,5 +445,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"i2c_bus_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
||||
"i2c_bus_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||
}
|
||||
)
|
||||
|
||||
297
esphome/components/i2c/i2c_bus_host.cpp
Normal file
297
esphome/components/i2c/i2c_bus_host.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
#ifdef USE_HOST
|
||||
#if defined(__linux__)
|
||||
|
||||
#include "i2c_bus_host.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/i2c-dev.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::i2c {
|
||||
|
||||
static const char *const TAG = "i2c.host";
|
||||
|
||||
HostI2CBus::~HostI2CBus() {
|
||||
if (this->file_descriptor_ != -1) {
|
||||
close(this->file_descriptor_);
|
||||
this->file_descriptor_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void HostI2CBus::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up I2C bus...");
|
||||
|
||||
// Open I2C device file
|
||||
this->file_descriptor_ = open(this->device_.c_str(), O_RDWR);
|
||||
if (this->file_descriptor_ == -1) {
|
||||
int err = errno;
|
||||
if (err == ENOENT) {
|
||||
this->update_error_("not found");
|
||||
} else if (err == EACCES) {
|
||||
this->update_error_("permission denied");
|
||||
} else {
|
||||
this->update_error_(std::string("failed to open: ") + strerror(err));
|
||||
}
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->initialized_ = true;
|
||||
ESP_LOGCONFIG(TAG, " Device: %s", this->device_.c_str());
|
||||
|
||||
// Run bus scan if enabled
|
||||
if (this->scan_) {
|
||||
this->i2c_scan_();
|
||||
}
|
||||
}
|
||||
|
||||
void HostI2CBus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
||||
ESP_LOGCONFIG(TAG, " Device: %s", this->device_.c_str());
|
||||
// Bus frequency cannot be set from userspace via i2c-dev; report it as informational only
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %u Hz (informational; not applied on host)", this->frequency_);
|
||||
|
||||
if (!this->first_error_.empty()) {
|
||||
ESP_LOGE(TAG, " Setup Error: %s", this->first_error_.c_str());
|
||||
}
|
||||
|
||||
if (this->scan_) {
|
||||
ESP_LOGI(TAG, " Scan Results:");
|
||||
for (const auto &s : this->scan_results_) {
|
||||
if (s.second) {
|
||||
ESP_LOGI(TAG, " 0x%02X: Found", s.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode HostI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
|
||||
uint8_t *read_buffer, size_t read_count) {
|
||||
if (!this->initialized_) {
|
||||
ESP_LOGE(TAG, "I2C bus not initialized");
|
||||
return ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "I2C write_readv addr=0x%02X write=%zu read=%zu", address, write_count, read_count);
|
||||
|
||||
// Handle special case: probe (no write data, no read data)
|
||||
// This is used for device detection during bus scanning
|
||||
if (write_count == 0 && read_count == 0) {
|
||||
struct i2c_msg msg;
|
||||
msg.addr = address;
|
||||
msg.flags = 0;
|
||||
msg.len = 0;
|
||||
msg.buf = nullptr;
|
||||
|
||||
struct i2c_rdwr_ioctl_data rdwr_data;
|
||||
rdwr_data.msgs = &msg;
|
||||
rdwr_data.nmsgs = 1;
|
||||
|
||||
int ret = ioctl(this->file_descriptor_, I2C_RDWR, &rdwr_data);
|
||||
if (ret < 0) {
|
||||
int err = errno;
|
||||
// If I2C_RDWR not supported, try SMBus Quick command (what i2cdetect uses)
|
||||
if (err == EOPNOTSUPP || err == ENOSYS) {
|
||||
ESP_LOGVV(TAG, "I2C_RDWR probe failed, trying SMBus Quick for addr=0x%02X", address);
|
||||
if (ioctl(this->file_descriptor_, I2C_SLAVE, address) < 0) { // NOLINT
|
||||
return this->map_errno_to_error_code_(errno);
|
||||
}
|
||||
// Use I2C_SMBUS ioctl with Quick command
|
||||
union i2c_smbus_data data;
|
||||
struct i2c_smbus_ioctl_data args;
|
||||
args.read_write = I2C_SMBUS_WRITE;
|
||||
args.command = 0;
|
||||
args.size = I2C_SMBUS_QUICK;
|
||||
args.data = &data;
|
||||
ret = ioctl(this->file_descriptor_, I2C_SMBUS, &args);
|
||||
if (ret < 0) {
|
||||
return this->map_errno_to_error_code_(errno);
|
||||
}
|
||||
return ERROR_OK;
|
||||
}
|
||||
return this->map_errno_to_error_code_(err);
|
||||
}
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
// i2c_msg.len is a 16-bit field; reject transfers that would silently truncate
|
||||
if (write_count > UINT16_MAX || read_count > UINT16_MAX) {
|
||||
ESP_LOGE(TAG, "I2C transfer too large: write=%zu read=%zu (max %u)", write_count, read_count,
|
||||
(unsigned) UINT16_MAX);
|
||||
return ERROR_TOO_LARGE;
|
||||
}
|
||||
|
||||
// Prepare messages for combined write-read transaction
|
||||
struct i2c_msg msgs[2];
|
||||
int num_msgs = 0;
|
||||
|
||||
// Add write message if write data present
|
||||
if (write_count > 0) {
|
||||
msgs[num_msgs].addr = address;
|
||||
msgs[num_msgs].flags = 0; // Write
|
||||
msgs[num_msgs].len = write_count;
|
||||
msgs[num_msgs].buf = const_cast<uint8_t *>(write_buffer);
|
||||
num_msgs++;
|
||||
}
|
||||
|
||||
// Add read message if read data requested
|
||||
if (read_count > 0) {
|
||||
msgs[num_msgs].addr = address;
|
||||
msgs[num_msgs].flags = I2C_M_RD; // Read
|
||||
msgs[num_msgs].len = read_count;
|
||||
msgs[num_msgs].buf = read_buffer;
|
||||
num_msgs++;
|
||||
}
|
||||
|
||||
// Execute I2C transaction
|
||||
struct i2c_rdwr_ioctl_data rdwr_data;
|
||||
rdwr_data.msgs = msgs;
|
||||
rdwr_data.nmsgs = num_msgs;
|
||||
|
||||
int ret = ioctl(this->file_descriptor_, I2C_RDWR, &rdwr_data);
|
||||
if (ret < 0) {
|
||||
int err = errno;
|
||||
if (err == EOPNOTSUPP || err == ENOSYS) {
|
||||
ESP_LOGV(TAG, "I2C_RDWR not supported, using I2C_SLAVE fallback for addr=0x%02X", address); // NOLINT
|
||||
if (ioctl(this->file_descriptor_, I2C_SLAVE, address) < 0) { // NOLINT
|
||||
ESP_LOGV(TAG, "I2C_SLAVE ioctl failed: %s", strerror(errno)); // NOLINT
|
||||
return this->map_errno_to_error_code_(errno);
|
||||
}
|
||||
// Perform write if needed
|
||||
if (write_count > 0) {
|
||||
ssize_t written = ::write(this->file_descriptor_, write_buffer, write_count);
|
||||
if (written != (ssize_t) write_count) {
|
||||
int write_err = errno;
|
||||
// If write() also fails with EOPNOTSUPP, try I2C_SMBUS as last resort
|
||||
if (write_err == EOPNOTSUPP || write_err == ENOSYS) {
|
||||
ESP_LOGV(TAG, "I2C_SLAVE write not supported, trying I2C_SMBUS for addr=0x%02X", address); // NOLINT
|
||||
// Use I2C_SMBUS_I2C_BLOCK_DATA for writes up to 32 bytes
|
||||
// Standard SMBus mapping: first byte is command, remaining bytes are data
|
||||
if (write_count < 1) {
|
||||
ESP_LOGE(TAG, "Write size too small for I2C_SMBUS");
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (write_count > I2C_SMBUS_BLOCK_MAX + 1) {
|
||||
ESP_LOGE(TAG, "Write size %zu exceeds I2C_SMBUS_BLOCK_MAX+1 (%d)", write_count, I2C_SMBUS_BLOCK_MAX + 1);
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
union i2c_smbus_data data;
|
||||
// Standard SMBus: first byte = command, rest = data
|
||||
uint8_t command = write_buffer[0];
|
||||
size_t data_len = write_count - 1;
|
||||
data.block[0] = data_len;
|
||||
if (data_len > 0) {
|
||||
memcpy(&data.block[1], write_buffer + 1, data_len);
|
||||
}
|
||||
|
||||
struct i2c_smbus_ioctl_data args;
|
||||
args.read_write = I2C_SMBUS_WRITE;
|
||||
args.command = command;
|
||||
args.size = I2C_SMBUS_I2C_BLOCK_DATA;
|
||||
args.data = &data;
|
||||
|
||||
ret = ioctl(this->file_descriptor_, I2C_SMBUS, &args);
|
||||
if (ret < 0) {
|
||||
ESP_LOGV(TAG, "I2C_SMBUS write failed: %s", strerror(errno));
|
||||
return this->map_errno_to_error_code_(errno);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGV(TAG, "I2C write failed: %s", strerror(write_err));
|
||||
return this->map_errno_to_error_code_(write_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Perform read if needed
|
||||
if (read_count > 0) {
|
||||
ssize_t bytes_read = ::read(this->file_descriptor_, read_buffer, read_count);
|
||||
if (bytes_read != (ssize_t) read_count) {
|
||||
int read_err = errno;
|
||||
// If read() also fails with EOPNOTSUPP, try I2C_SMBUS as last resort
|
||||
if (read_err == EOPNOTSUPP || read_err == ENOSYS) {
|
||||
ESP_LOGV(TAG, "I2C_SLAVE read not supported, trying I2C_SMBUS for addr=0x%02X", address); // NOLINT
|
||||
// Use I2C_SMBUS_I2C_BLOCK_DATA for reads up to 32 bytes
|
||||
if (read_count > I2C_SMBUS_BLOCK_MAX) {
|
||||
ESP_LOGE(TAG, "Read size %zu exceeds I2C_SMBUS_BLOCK_MAX (%d)", read_count, I2C_SMBUS_BLOCK_MAX);
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
union i2c_smbus_data data;
|
||||
data.block[0] = read_count;
|
||||
|
||||
struct i2c_smbus_ioctl_data args;
|
||||
args.read_write = I2C_SMBUS_READ;
|
||||
args.command = 0; // Start register/command
|
||||
args.size = I2C_SMBUS_I2C_BLOCK_DATA;
|
||||
args.data = &data;
|
||||
|
||||
ret = ioctl(this->file_descriptor_, I2C_SMBUS, &args);
|
||||
if (ret < 0) {
|
||||
ESP_LOGV(TAG, "I2C_SMBUS read failed: %s", strerror(errno));
|
||||
return this->map_errno_to_error_code_(errno);
|
||||
}
|
||||
// I2C_SMBUS_I2C_BLOCK_DATA returns the actual byte count in block[0];
|
||||
// a short read means we did not receive all requested bytes
|
||||
if (data.block[0] < read_count) {
|
||||
ESP_LOGV(TAG, "I2C_SMBUS short read: got %u, expected %zu", data.block[0], read_count);
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
}
|
||||
// Copy data from SMBus buffer to output buffer
|
||||
memcpy(read_buffer, &data.block[1], read_count);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "I2C read failed: %s", strerror(read_err));
|
||||
return this->map_errno_to_error_code_(read_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
ESP_LOGVV(TAG, "I2C transaction successful (I2C_SLAVE method)"); // NOLINT
|
||||
return ERROR_OK;
|
||||
}
|
||||
ESP_LOGV(TAG, "I2C transaction failed: %s", strerror(err));
|
||||
return this->map_errno_to_error_code_(err);
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "I2C transaction successful");
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
ErrorCode HostI2CBus::map_errno_to_error_code_(int err) {
|
||||
switch (err) {
|
||||
case ENXIO:
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
case ETIMEDOUT:
|
||||
return ERROR_TIMEOUT;
|
||||
case EINVAL:
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
case ENODEV:
|
||||
case ENOTTY:
|
||||
return ERROR_NOT_INITIALIZED;
|
||||
case EOPNOTSUPP:
|
||||
case ENOSYS:
|
||||
// Operation not supported - some I2C adapters don't support zero-length transactions
|
||||
ESP_LOGVV(TAG, "I2C adapter does not support this operation (likely zero-length probe)");
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
default:
|
||||
ESP_LOGV(TAG, "Unmapped error code: %d (%s)", err, strerror(err));
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void HostI2CBus::update_error_(const std::string &error) {
|
||||
if (this->first_error_.empty()) {
|
||||
this->first_error_ = error;
|
||||
}
|
||||
ESP_LOGE(TAG, "[%s] %s", this->device_.c_str(), error.c_str());
|
||||
}
|
||||
|
||||
} // namespace esphome::i2c
|
||||
|
||||
#else
|
||||
#error "HostI2CBus is only supported on Linux"
|
||||
#endif // defined(__linux__)
|
||||
#endif // USE_HOST
|
||||
41
esphome/components/i2c/i2c_bus_host.h
Normal file
41
esphome/components/i2c/i2c_bus_host.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "i2c_bus.h"
|
||||
|
||||
namespace esphome::i2c {
|
||||
|
||||
class HostI2CBus : public I2CBus, public Component {
|
||||
public:
|
||||
~HostI2CBus() override;
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
|
||||
size_t read_count) override;
|
||||
|
||||
void set_device(const std::string &device) { this->device_ = device; }
|
||||
void set_scan(bool scan) { this->scan_ = scan; }
|
||||
void set_frequency(uint32_t frequency) { this->frequency_ = frequency; }
|
||||
|
||||
const std::string &get_device() const { return this->device_; }
|
||||
|
||||
protected:
|
||||
void update_error_(const std::string &error);
|
||||
ErrorCode map_errno_to_error_code_(int err);
|
||||
|
||||
std::string device_;
|
||||
uint32_t frequency_{50000};
|
||||
int file_descriptor_{-1};
|
||||
bool initialized_{false};
|
||||
std::string first_error_;
|
||||
};
|
||||
|
||||
} // namespace esphome::i2c
|
||||
|
||||
#endif // USE_HOST
|
||||
4
tests/components/i2c/test.host.yaml
Normal file
4
tests/components/i2c/test.host.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/host.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
7
tests/test_build_components/common/i2c/host.yaml
Normal file
7
tests/test_build_components/common/i2c/host.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# Common I2C configuration for host platform tests
|
||||
|
||||
i2c:
|
||||
- id: i2c_bus
|
||||
device: /dev/i2c-0
|
||||
frequency: 100kHz
|
||||
scan: true
|
||||
Reference in New Issue
Block a user