mirror of
https://github.com/esphome/esphome.git
synced 2024-12-21 21:14:52 +01:00
UART component support added for host platform (#6912)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Pavlo Dudnytskyi <pdudnytskyi@astrata.eu>
This commit is contained in:
parent
2873c6bbaf
commit
aa8c963c50
4 changed files with 410 additions and 2 deletions
|
@ -1,5 +1,5 @@
|
|||
from typing import Optional
|
||||
|
||||
import re
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
|
@ -11,6 +11,7 @@ from esphome.const import (
|
|||
CONF_NUMBER,
|
||||
CONF_RX_PIN,
|
||||
CONF_TX_PIN,
|
||||
CONF_PORT,
|
||||
CONF_UART_ID,
|
||||
CONF_DATA,
|
||||
CONF_RX_BUFFER_SIZE,
|
||||
|
@ -27,6 +28,7 @@ from esphome.const import (
|
|||
CONF_DUMMY_RECEIVER,
|
||||
CONF_DUMMY_RECEIVER_ID,
|
||||
CONF_LAMBDA,
|
||||
PLATFORM_HOST,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
|
@ -45,6 +47,7 @@ RP2040UartComponent = uart_ns.class_("RP2040UartComponent", UARTComponent, cg.Co
|
|||
LibreTinyUARTComponent = uart_ns.class_(
|
||||
"LibreTinyUARTComponent", UARTComponent, cg.Component
|
||||
)
|
||||
HostUartComponent = uart_ns.class_("HostUartComponent", UARTComponent, cg.Component)
|
||||
|
||||
NATIVE_UART_CLASSES = (
|
||||
str(IDFUARTComponent),
|
||||
|
@ -54,6 +57,39 @@ NATIVE_UART_CLASSES = (
|
|||
str(LibreTinyUARTComponent),
|
||||
)
|
||||
|
||||
HOST_BAUD_RATES = [
|
||||
50,
|
||||
75,
|
||||
110,
|
||||
134,
|
||||
150,
|
||||
200,
|
||||
300,
|
||||
600,
|
||||
1200,
|
||||
1800,
|
||||
2400,
|
||||
4800,
|
||||
9600,
|
||||
19200,
|
||||
38400,
|
||||
57600,
|
||||
115200,
|
||||
230400,
|
||||
460800,
|
||||
500000,
|
||||
576000,
|
||||
921600,
|
||||
1000000,
|
||||
1152000,
|
||||
1500000,
|
||||
2000000,
|
||||
2500000,
|
||||
3000000,
|
||||
3500000,
|
||||
4000000,
|
||||
]
|
||||
|
||||
UARTDevice = uart_ns.class_("UARTDevice")
|
||||
UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
|
||||
UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action)
|
||||
|
@ -95,6 +131,20 @@ def validate_invert_esp32(config):
|
|||
return config
|
||||
|
||||
|
||||
def validate_host_config(config):
|
||||
if CORE.is_host:
|
||||
if CONF_TX_PIN in config or CONF_RX_PIN in config:
|
||||
raise cv.Invalid(
|
||||
"TX and RX pins are not supported for UART on host platform."
|
||||
)
|
||||
if config[CONF_BAUD_RATE] not in HOST_BAUD_RATES:
|
||||
raise cv.Invalid(
|
||||
f"Host platform doesn't support baud rate {config[CONF_BAUD_RATE]}",
|
||||
path=[CONF_BAUD_RATE],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def _uart_declare_type(value):
|
||||
if CORE.is_esp8266:
|
||||
return cv.declare_id(ESP8266UartComponent)(value)
|
||||
|
@ -107,6 +157,8 @@ def _uart_declare_type(value):
|
|||
return cv.declare_id(RP2040UartComponent)(value)
|
||||
if CORE.is_libretiny:
|
||||
return cv.declare_id(LibreTinyUARTComponent)(value)
|
||||
if CORE.is_host:
|
||||
return cv.declare_id(HostUartComponent)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
@ -149,6 +201,12 @@ def maybe_empty_debug(value):
|
|||
return DEBUG_SCHEMA(value)
|
||||
|
||||
|
||||
def validate_port(value):
|
||||
if not re.match(r"^/(?:[^/]+/)[^/]+$", value):
|
||||
raise cv.Invalid("Port must be a valid device path")
|
||||
return value
|
||||
|
||||
|
||||
DEBUG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
|
||||
|
@ -181,6 +239,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Required(CONF_BAUD_RATE): cv.int_range(min=1),
|
||||
cv.Optional(CONF_TX_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_RX_PIN): validate_rx_pin,
|
||||
cv.Optional(CONF_PORT): cv.All(validate_port, cv.only_on(PLATFORM_HOST)),
|
||||
cv.Optional(CONF_RX_BUFFER_SIZE, default=256): cv.validate_bytes,
|
||||
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
|
||||
cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8),
|
||||
|
@ -193,8 +252,9 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_DEBUG): maybe_empty_debug,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN),
|
||||
cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN, CONF_PORT),
|
||||
validate_invert_esp32,
|
||||
validate_host_config,
|
||||
)
|
||||
|
||||
|
||||
|
@ -236,6 +296,8 @@ async def to_code(config):
|
|||
if CONF_RX_PIN in config:
|
||||
rx_pin = await cg.gpio_pin_expression(config[CONF_RX_PIN])
|
||||
cg.add(var.set_rx_pin(rx_pin))
|
||||
if CONF_PORT in config:
|
||||
cg.add(var.set_name(config[CONF_PORT]))
|
||||
cg.add(var.set_rx_buffer_size(config[CONF_RX_BUFFER_SIZE]))
|
||||
cg.add(var.set_stop_bits(config[CONF_STOP_BITS]))
|
||||
cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
|
||||
|
|
295
esphome/components/uart/uart_component_host.cpp
Normal file
295
esphome/components/uart/uart_component_host.cpp
Normal file
|
@ -0,0 +1,295 @@
|
|||
#ifdef USE_HOST
|
||||
#include "uart_component_host.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifndef __linux__
|
||||
#error This HostUartComponent implementation is only for Linux
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
speed_t get_baud(int baud) {
|
||||
switch (baud) {
|
||||
case 50:
|
||||
return B50;
|
||||
case 75:
|
||||
return B75;
|
||||
case 110:
|
||||
return B110;
|
||||
case 134:
|
||||
return B134;
|
||||
case 150:
|
||||
return B150;
|
||||
case 200:
|
||||
return B200;
|
||||
case 300:
|
||||
return B300;
|
||||
case 600:
|
||||
return B600;
|
||||
case 1200:
|
||||
return B1200;
|
||||
case 1800:
|
||||
return B1800;
|
||||
case 2400:
|
||||
return B2400;
|
||||
case 4800:
|
||||
return B4800;
|
||||
case 9600:
|
||||
return B9600;
|
||||
case 19200:
|
||||
return B19200;
|
||||
case 38400:
|
||||
return B38400;
|
||||
case 57600:
|
||||
return B57600;
|
||||
case 115200:
|
||||
return B115200;
|
||||
case 230400:
|
||||
return B230400;
|
||||
case 460800:
|
||||
return B460800;
|
||||
case 500000:
|
||||
return B500000;
|
||||
case 576000:
|
||||
return B576000;
|
||||
case 921600:
|
||||
return B921600;
|
||||
case 1000000:
|
||||
return B1000000;
|
||||
case 1152000:
|
||||
return B1152000;
|
||||
case 1500000:
|
||||
return B1500000;
|
||||
case 2000000:
|
||||
return B2000000;
|
||||
case 2500000:
|
||||
return B2500000;
|
||||
case 3000000:
|
||||
return B3000000;
|
||||
case 3500000:
|
||||
return B3500000;
|
||||
case 4000000:
|
||||
return B4000000;
|
||||
default:
|
||||
return B0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace esphome {
|
||||
namespace uart {
|
||||
|
||||
static const char *const TAG = "uart.host";
|
||||
|
||||
HostUartComponent::~HostUartComponent() {
|
||||
if (this->file_descriptor_ != -1) {
|
||||
close(this->file_descriptor_);
|
||||
this->file_descriptor_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void HostUartComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Opening UART port...");
|
||||
speed_t baud = get_baud(this->baud_rate_);
|
||||
if (baud == B0) {
|
||||
ESP_LOGE(TAG, "Unsupported baud rate: %d", this->baud_rate_);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->file_descriptor_ = ::open(this->port_name_.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
|
||||
if (this->file_descriptor_ == -1) {
|
||||
this->update_error_(strerror(errno));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
fcntl(this->file_descriptor_, F_SETFL, 0);
|
||||
struct termios options;
|
||||
tcgetattr(this->file_descriptor_, &options);
|
||||
options.c_cflag &= ~CRTSCTS;
|
||||
options.c_cflag |= CREAD | CLOCAL;
|
||||
options.c_lflag &= ~ICANON;
|
||||
options.c_lflag &= ~ECHO;
|
||||
options.c_lflag &= ~ECHOE;
|
||||
options.c_lflag &= ~ECHONL;
|
||||
options.c_lflag &= ~ISIG;
|
||||
options.c_iflag &= ~(IXON | IXOFF | IXANY);
|
||||
options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
|
||||
options.c_oflag &= ~OPOST;
|
||||
options.c_oflag &= ~ONLCR;
|
||||
// Set data bits
|
||||
options.c_cflag &= ~CSIZE; // Mask the character size bits
|
||||
switch (this->data_bits_) {
|
||||
case 5:
|
||||
options.c_cflag |= CS5;
|
||||
break;
|
||||
case 6:
|
||||
options.c_cflag |= CS6;
|
||||
break;
|
||||
case 7:
|
||||
options.c_cflag |= CS7;
|
||||
break;
|
||||
case 8:
|
||||
default:
|
||||
options.c_cflag |= CS8;
|
||||
break;
|
||||
}
|
||||
// Set parity
|
||||
switch (this->parity_) {
|
||||
case UART_CONFIG_PARITY_NONE:
|
||||
options.c_cflag &= ~PARENB;
|
||||
break;
|
||||
case UART_CONFIG_PARITY_EVEN:
|
||||
options.c_cflag |= PARENB;
|
||||
options.c_cflag &= ~PARODD;
|
||||
break;
|
||||
case UART_CONFIG_PARITY_ODD:
|
||||
options.c_cflag |= PARENB;
|
||||
options.c_cflag |= PARODD;
|
||||
break;
|
||||
};
|
||||
// Set stop bits
|
||||
if (this->stop_bits_ == 2) {
|
||||
options.c_cflag |= CSTOPB;
|
||||
} else {
|
||||
options.c_cflag &= ~CSTOPB;
|
||||
}
|
||||
cfsetispeed(&options, baud);
|
||||
cfsetospeed(&options, baud);
|
||||
tcsetattr(this->file_descriptor_, TCSANOW, &options);
|
||||
}
|
||||
|
||||
void HostUartComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "UART:");
|
||||
ESP_LOGCONFIG(TAG, " Port: %s", this->port_name_.c_str());
|
||||
if (this->file_descriptor_ == -1) {
|
||||
ESP_LOGCONFIG(TAG, " Port status: Not opened");
|
||||
if (!this->first_error_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Error: %s", this->first_error_.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Port status: opened");
|
||||
ESP_LOGCONFIG(TAG, " Baud Rate: %d", this->baud_rate_);
|
||||
ESP_LOGCONFIG(TAG, " Data Bits: %d", this->data_bits_);
|
||||
ESP_LOGCONFIG(TAG, " Parity: %s",
|
||||
this->parity_ == UART_CONFIG_PARITY_NONE ? "None"
|
||||
: this->parity_ == UART_CONFIG_PARITY_EVEN ? "Even"
|
||||
: "Odd");
|
||||
ESP_LOGCONFIG(TAG, " Stop Bits: %d", this->stop_bits_);
|
||||
this->check_logger_conflict();
|
||||
}
|
||||
|
||||
void HostUartComponent::write_array(const uint8_t *data, size_t len) {
|
||||
if (this->file_descriptor_ == -1) {
|
||||
return;
|
||||
}
|
||||
size_t written = ::write(this->file_descriptor_, data, len);
|
||||
if (written != len) {
|
||||
this->update_error_(strerror(errno));
|
||||
return;
|
||||
}
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
bool HostUartComponent::peek_byte(uint8_t *data) {
|
||||
if (this->file_descriptor_ == -1) {
|
||||
return false;
|
||||
}
|
||||
if (!this->has_peek_) {
|
||||
if (!this->check_read_timeout_()) {
|
||||
return false;
|
||||
}
|
||||
if (::read(this->file_descriptor_, &this->peek_byte_, 1) != 1) {
|
||||
this->update_error_(strerror(errno));
|
||||
return false;
|
||||
}
|
||||
this->has_peek_ = true;
|
||||
}
|
||||
*data = this->peek_byte_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostUartComponent::read_array(uint8_t *data, size_t len) {
|
||||
if ((this->file_descriptor_ == -1) || (len == 0)) {
|
||||
return false;
|
||||
}
|
||||
if (!this->check_read_timeout_(len))
|
||||
return false;
|
||||
uint8_t *data_ptr = data;
|
||||
size_t length_to_read = len;
|
||||
if (this->has_peek_) {
|
||||
length_to_read--;
|
||||
*data_ptr = this->peek_byte_;
|
||||
data_ptr++;
|
||||
this->has_peek_ = false;
|
||||
}
|
||||
if (length_to_read > 0) {
|
||||
int sz = ::read(this->file_descriptor_, data_ptr, length_to_read);
|
||||
if (sz == -1) {
|
||||
this->update_error_(strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
int HostUartComponent::available() {
|
||||
if (this->file_descriptor_ == -1) {
|
||||
return 0;
|
||||
}
|
||||
int available;
|
||||
int res = ioctl(this->file_descriptor_, FIONREAD, &available);
|
||||
if (res == -1) {
|
||||
this->update_error_(strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
if (this->has_peek_)
|
||||
available++;
|
||||
return available;
|
||||
};
|
||||
|
||||
void HostUartComponent::flush() {
|
||||
if (this->file_descriptor_ == -1) {
|
||||
return;
|
||||
}
|
||||
tcflush(this->file_descriptor_, TCIOFLUSH);
|
||||
ESP_LOGV(TAG, " Flushing...");
|
||||
}
|
||||
|
||||
void HostUartComponent::update_error_(const std::string &error) {
|
||||
if (this->first_error_.empty()) {
|
||||
this->first_error_ = error;
|
||||
}
|
||||
ESP_LOGE(TAG, "Port error: %s", error.c_str());
|
||||
}
|
||||
|
||||
} // namespace uart
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
38
esphome/components/uart/uart_component_host.h
Normal file
38
esphome/components/uart/uart_component_host.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "uart_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uart {
|
||||
|
||||
class HostUartComponent : public UARTComponent, public Component {
|
||||
public:
|
||||
virtual ~HostUartComponent();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
void write_array(const uint8_t *data, size_t len) override;
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
int available() override;
|
||||
void flush() override;
|
||||
void set_name(std::string port_name) { port_name_ = port_name; };
|
||||
|
||||
protected:
|
||||
void update_error_(const std::string &error);
|
||||
void check_logger_conflict() override {}
|
||||
std::string port_name_;
|
||||
std::string first_error_{""};
|
||||
int file_descriptor_ = -1;
|
||||
bool has_peek_{false};
|
||||
uint8_t peek_byte_;
|
||||
};
|
||||
|
||||
} // namespace uart
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
13
tests/components/uart/test.host.yaml
Normal file
13
tests/components/uart/test.host.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- uart.write: 'Hello World'
|
||||
- uart.write: [0x00, 0x20, 0x42]
|
||||
|
||||
uart:
|
||||
- id: uart_uart
|
||||
port: "/dev/ttyS0"
|
||||
baud_rate: 9600
|
||||
data_bits: 8
|
||||
parity: EVEN
|
||||
stop_bits: 2
|
Loading…
Reference in a new issue