Added heatpumpir support (#1343)

Co-authored-by: Otto winter <otto@otto-winter.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
This commit is contained in:
Rob Deutsch 2021-10-13 05:38:19 +11:00 committed by GitHub
parent d13134135b
commit a3eb2a7ee0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 481 additions and 0 deletions

View file

@ -64,6 +64,7 @@ esphome/components/graph/* @synco
esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey

View file

@ -0,0 +1,114 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
from esphome.const import (
CONF_ID,
CONF_MAX_TEMPERATURE,
CONF_MIN_TEMPERATURE,
CONF_PROTOCOL,
CONF_VISUAL,
)
CODEOWNERS = ["@rob-deutsch"]
AUTO_LOAD = ["climate_ir"]
heatpumpir_ns = cg.esphome_ns.namespace("heatpumpir")
HeatpumpIRClimate = heatpumpir_ns.class_("HeatpumpIRClimate", climate_ir.ClimateIR)
Protocol = heatpumpir_ns.enum("Protocol")
PROTOCOLS = {
"aux": Protocol.PROTOCOL_AUX,
"ballu": Protocol.PROTOCOL_BALLU,
"carrier_mca": Protocol.PROTOCOL_CARRIER_MCA,
"carrier_nqv": Protocol.PROTOCOL_CARRIER_NQV,
"daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417,
"daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480,
"daikin": Protocol.PROTOCOL_DAIKIN,
"fuego": Protocol.PROTOCOL_FUEGO,
"fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ,
"gree": Protocol.PROTOCOL_GREE,
"greeya": Protocol.PROTOCOL_GREEYAA,
"greeyan": Protocol.PROTOCOL_GREEYAN,
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
"hitachi": Protocol.PROTOCOL_HITACHI,
"hyundai": Protocol.PROTOCOL_HYUNDAI,
"ivt": Protocol.PROTOCOL_IVT,
"midea": Protocol.PROTOCOL_MIDEA,
"mitsubishi_fa": Protocol.PROTOCOL_MITSUBISHI_FA,
"mitsubishi_fd": Protocol.PROTOCOL_MITSUBISHI_FD,
"mitsubishi_fe": Protocol.PROTOCOL_MITSUBISHI_FE,
"mitsubishi_heavy_fdtc": Protocol.PROTOCOL_MITSUBISHI_HEAVY_FDTC,
"mitsubishi_heavy_zj": Protocol.PROTOCOL_MITSUBISHI_HEAVY_ZJ,
"mitsubishi_heavy_zm": Protocol.PROTOCOL_MITSUBISHI_HEAVY_ZM,
"mitsubishi_heavy_zmp": Protocol.PROTOCOL_MITSUBISHI_HEAVY_ZMP,
"mitsubishi_heavy_kj": Protocol.PROTOCOL_MITSUBISHI_KJ,
"mitsubishi_msc": Protocol.PROTOCOL_MITSUBISHI_MSC,
"mitsubishi_msy": Protocol.PROTOCOL_MITSUBISHI_MSY,
"mitsubishi_sez": Protocol.PROTOCOL_MITSUBISHI_SEZ,
"panasonic_ckp": Protocol.PROTOCOL_PANASONIC_CKP,
"panasonic_dke": Protocol.PROTOCOL_PANASONIC_DKE,
"panasonic_jke": Protocol.PROTOCOL_PANASONIC_JKE,
"panasonic_lke": Protocol.PROTOCOL_PANASONIC_LKE,
"panasonic_nke": Protocol.PROTOCOL_PANASONIC_NKE,
"samsung_aqv": Protocol.PROTOCOL_SAMSUNG_AQV,
"samsung_fjm": Protocol.PROTOCOL_SAMSUNG_FJM,
"sharp": Protocol.PROTOCOL_SHARP,
"toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
"toshiba": Protocol.PROTOCOL_TOSHIBA,
}
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
HorizontalDirections = heatpumpir_ns.enum("HorizontalDirections")
HORIZONTAL_DIRECTIONS = {
"auto": HorizontalDirections.HORIZONTAL_DIRECTION_AUTO,
"middle": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE,
"left": HorizontalDirections.HORIZONTAL_DIRECTION_LEFT,
"mleft": HorizontalDirections.HORIZONTAL_DIRECTION_MLEFT,
"mright": HorizontalDirections.HORIZONTAL_DIRECTION_MRIGHT,
"right": HorizontalDirections.HORIZONTAL_DIRECTION_RIGHT,
}
CONF_VERTICAL_DEFAULT = "vertical_default"
VerticalDirections = heatpumpir_ns.enum("VerticalDirections")
VERTICAL_DIRECTIONS = {
"auto": VerticalDirections.VERTICAL_DIRECTION_AUTO,
"up": VerticalDirections.VERTICAL_DIRECTION_UP,
"mup": VerticalDirections.VERTICAL_DIRECTION_MUP,
"middle": VerticalDirections.VERTICAL_DIRECTION_MIDDLE,
"mdown": VerticalDirections.VERTICAL_DIRECTION_MDOWN,
"down": VerticalDirections.VERTICAL_DIRECTION_DOWN,
}
CONFIG_SCHEMA = cv.All(
climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HeatpumpIRClimate),
cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS),
cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS),
cv.Required(CONF_VERTICAL_DEFAULT): cv.enum(VERTICAL_DIRECTIONS),
cv.Required(CONF_MIN_TEMPERATURE): cv.temperature,
cv.Required(CONF_MAX_TEMPERATURE): cv.temperature,
}
),
cv.only_with_arduino,
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
if CONF_VISUAL not in config:
config[CONF_VISUAL] = {}
visual = config[CONF_VISUAL]
if CONF_MAX_TEMPERATURE not in visual:
visual[CONF_MAX_TEMPERATURE] = config[CONF_MAX_TEMPERATURE]
if CONF_MIN_TEMPERATURE not in visual:
visual[CONF_MIN_TEMPERATURE] = config[CONF_MIN_TEMPERATURE]
yield climate_ir.register_climate_ir(var, config)
cg.add(var.set_protocol(config[CONF_PROTOCOL]))
cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT]))
cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT]))
cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
cg.add_library("tonia/HeatpumpIR", "1.0.15")

View file

@ -0,0 +1,183 @@
#include "heatpumpir.h"
#ifdef USE_ARDUINO
#include <map>
#include "ir_sender_esphome.h"
#include "HeatpumpIRFactory.h"
#include "esphome/core/log.h"
namespace esphome {
namespace heatpumpir {
static const char *const TAG = "heatpumpir.climate";
const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP = {
{PROTOCOL_AUX, []() { return new AUXHeatpumpIR(); }}, // NOLINT
{PROTOCOL_BALLU, []() { return new BalluHeatpumpIR(); }}, // NOLINT
{PROTOCOL_CARRIER_MCA, []() { return new CarrierMCAHeatpumpIR(); }}, // NOLINT
{PROTOCOL_CARRIER_NQV, []() { return new CarrierNQVHeatpumpIR(); }}, // NOLINT
{PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }}, // NOLINT
{PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }}, // NOLINT
{PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }}, // NOLINT
{PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }}, // NOLINT
{PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_IVT, []() { return new IVTHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MIDEA, []() { return new MideaHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_FA, []() { return new MitsubishiFAHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_FD, []() { return new MitsubishiFDHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_FE, []() { return new MitsubishiFEHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_HEAVY_FDTC, []() { return new MitsubishiHeavyFDTCHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_HEAVY_ZJ, []() { return new MitsubishiHeavyZJHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_HEAVY_ZM, []() { return new MitsubishiHeavyZMHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_HEAVY_ZMP, []() { return new MitsubishiHeavyZMPHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_KJ, []() { return new MitsubishiKJHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_MSC, []() { return new MitsubishiMSCHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_MSY, []() { return new MitsubishiMSYHeatpumpIR(); }}, // NOLINT
{PROTOCOL_MITSUBISHI_SEZ, []() { return new MitsubishiSEZKDXXHeatpumpIR(); }}, // NOLINT
{PROTOCOL_PANASONIC_CKP, []() { return new PanasonicCKPHeatpumpIR(); }}, // NOLINT
{PROTOCOL_PANASONIC_DKE, []() { return new PanasonicDKEHeatpumpIR(); }}, // NOLINT
{PROTOCOL_PANASONIC_JKE, []() { return new PanasonicJKEHeatpumpIR(); }}, // NOLINT
{PROTOCOL_PANASONIC_LKE, []() { return new PanasonicLKEHeatpumpIR(); }}, // NOLINT
{PROTOCOL_PANASONIC_NKE, []() { return new PanasonicNKEHeatpumpIR(); }}, // NOLINT
{PROTOCOL_SAMSUNG_AQV, []() { return new SamsungAQVHeatpumpIR(); }}, // NOLINT
{PROTOCOL_SAMSUNG_FJM, []() { return new SamsungFJMHeatpumpIR(); }}, // NOLINT
{PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }}, // NOLINT
{PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT
};
void HeatpumpIRClimate::setup() {
auto protocol_constructor = PROTOCOL_CONSTRUCTOR_MAP.find(protocol_);
if (protocol_constructor == PROTOCOL_CONSTRUCTOR_MAP.end()) {
ESP_LOGE(TAG, "Invalid protocol");
return;
}
this->heatpump_ir_ = protocol_constructor->second();
climate_ir::ClimateIR::setup();
}
void HeatpumpIRClimate::transmit_state() {
uint8_t power_mode_cmd;
uint8_t operating_mode_cmd;
uint8_t temperature_cmd;
uint8_t fan_speed_cmd;
uint8_t swing_v_cmd;
switch (default_vertical_direction_) {
case VERTICAL_DIRECTION_AUTO:
swing_v_cmd = VDIR_AUTO;
break;
case VERTICAL_DIRECTION_UP:
swing_v_cmd = VDIR_UP;
break;
case VERTICAL_DIRECTION_MUP:
swing_v_cmd = VDIR_MUP;
break;
case VERTICAL_DIRECTION_MIDDLE:
swing_v_cmd = VDIR_MIDDLE;
break;
case VERTICAL_DIRECTION_MDOWN:
swing_v_cmd = VDIR_MDOWN;
break;
case VERTICAL_DIRECTION_DOWN:
swing_v_cmd = VDIR_DOWN;
break;
default:
ESP_LOGE(TAG, "Invalid default vertical direction");
return;
}
if ((this->swing_mode == climate::CLIMATE_SWING_VERTICAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH)) {
swing_v_cmd = VDIR_SWING;
}
uint8_t swing_h_cmd;
switch (default_horizontal_direction_) {
case HORIZONTAL_DIRECTION_AUTO:
swing_h_cmd = HDIR_AUTO;
break;
case HORIZONTAL_DIRECTION_MIDDLE:
swing_h_cmd = HDIR_MIDDLE;
break;
case HORIZONTAL_DIRECTION_LEFT:
swing_h_cmd = HDIR_LEFT;
break;
case HORIZONTAL_DIRECTION_MLEFT:
swing_h_cmd = HDIR_MLEFT;
break;
case HORIZONTAL_DIRECTION_MRIGHT:
swing_h_cmd = HDIR_MRIGHT;
break;
case HORIZONTAL_DIRECTION_RIGHT:
swing_h_cmd = HDIR_RIGHT;
break;
default:
ESP_LOGE(TAG, "Invalid default horizontal direction");
return;
}
if ((this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH)) {
swing_h_cmd = HDIR_SWING;
}
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)) {
case climate::CLIMATE_FAN_LOW:
fan_speed_cmd = FAN_2;
break;
case climate::CLIMATE_FAN_MEDIUM:
fan_speed_cmd = FAN_3;
break;
case climate::CLIMATE_FAN_HIGH:
fan_speed_cmd = FAN_4;
break;
case climate::CLIMATE_FAN_AUTO:
default:
fan_speed_cmd = FAN_AUTO;
break;
}
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
power_mode_cmd = POWER_ON;
operating_mode_cmd = MODE_COOL;
break;
case climate::CLIMATE_MODE_HEAT:
power_mode_cmd = POWER_ON;
operating_mode_cmd = MODE_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
power_mode_cmd = POWER_ON;
operating_mode_cmd = MODE_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
power_mode_cmd = POWER_ON;
operating_mode_cmd = MODE_FAN;
break;
case climate::CLIMATE_MODE_DRY:
power_mode_cmd = POWER_ON;
operating_mode_cmd = MODE_DRY;
break;
case climate::CLIMATE_MODE_OFF:
default:
power_mode_cmd = POWER_OFF;
operating_mode_cmd = MODE_AUTO;
break;
}
temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_);
IRSenderESPHome esp_sender(0, this->transmitter_);
heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd,
swing_h_cmd);
}
} // namespace heatpumpir
} // namespace esphome
#endif

View file

@ -0,0 +1,116 @@
#pragma once
#ifdef USE_ARDUINO
#include "esphome/components/climate_ir/climate_ir.h"
// Forward-declare HeatpumpIR class from library. We cannot include its header here because it has unnamespaced defines
// that conflict with ESPHome.
class HeatpumpIR;
namespace esphome {
namespace heatpumpir {
// Simple enum to represent protocols.
enum Protocol {
PROTOCOL_AUX,
PROTOCOL_BALLU,
PROTOCOL_CARRIER_MCA,
PROTOCOL_CARRIER_NQV,
PROTOCOL_DAIKIN_ARC417,
PROTOCOL_DAIKIN_ARC480,
PROTOCOL_DAIKIN,
PROTOCOL_FUEGO,
PROTOCOL_FUJITSU_AWYZ,
PROTOCOL_GREE,
PROTOCOL_GREEYAA,
PROTOCOL_GREEYAN,
PROTOCOL_HISENSE_AUD,
PROTOCOL_HITACHI,
PROTOCOL_HYUNDAI,
PROTOCOL_IVT,
PROTOCOL_MIDEA,
PROTOCOL_MITSUBISHI_FA,
PROTOCOL_MITSUBISHI_FD,
PROTOCOL_MITSUBISHI_FE,
PROTOCOL_MITSUBISHI_HEAVY_FDTC,
PROTOCOL_MITSUBISHI_HEAVY_ZJ,
PROTOCOL_MITSUBISHI_HEAVY_ZM,
PROTOCOL_MITSUBISHI_HEAVY_ZMP,
PROTOCOL_MITSUBISHI_KJ,
PROTOCOL_MITSUBISHI_MSC,
PROTOCOL_MITSUBISHI_MSY,
PROTOCOL_MITSUBISHI_SEZ,
PROTOCOL_PANASONIC_CKP,
PROTOCOL_PANASONIC_DKE,
PROTOCOL_PANASONIC_JKE,
PROTOCOL_PANASONIC_LKE,
PROTOCOL_PANASONIC_NKE,
PROTOCOL_SAMSUNG_AQV,
PROTOCOL_SAMSUNG_FJM,
PROTOCOL_SHARP,
PROTOCOL_TOSHIBA_DAISEIKAI,
PROTOCOL_TOSHIBA,
};
// Simple enum to represent horizontal directios
enum HorizontalDirection {
HORIZONTAL_DIRECTION_AUTO = 0,
HORIZONTAL_DIRECTION_MIDDLE = 1,
HORIZONTAL_DIRECTION_LEFT = 2,
HORIZONTAL_DIRECTION_MLEFT = 3,
HORIZONTAL_DIRECTION_MRIGHT = 4,
HORIZONTAL_DIRECTION_RIGHT = 5,
};
// Simple enum to represent vertical directions
enum VerticalDirection {
VERTICAL_DIRECTION_AUTO = 0,
VERTICAL_DIRECTION_UP = 1,
VERTICAL_DIRECTION_MUP = 2,
VERTICAL_DIRECTION_MIDDLE = 3,
VERTICAL_DIRECTION_MDOWN = 4,
VERTICAL_DIRECTION_DOWN = 5,
};
// Temperature
const float TEMP_MIN = 0; // Celsius
const float TEMP_MAX = 100; // Celsius
class HeatpumpIRClimate : public climate_ir::ClimateIR {
public:
HeatpumpIRClimate()
: climate_ir::ClimateIR(
TEMP_MIN, TEMP_MAX, 1.0f, true, true,
std::set<climate::ClimateFanMode>{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_AUTO},
std::set<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL,
climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {}
void setup() override;
void set_protocol(Protocol protocol) { this->protocol_ = protocol; }
void set_horizontal_default(HorizontalDirection horizontal_direction) {
this->default_horizontal_direction_ = horizontal_direction;
}
void set_vertical_default(VerticalDirection vertical_direction) {
this->default_vertical_direction_ = vertical_direction;
}
void set_max_temperature(float temperature) { this->max_temperature_ = temperature; }
void set_min_temperature(float temperature) { this->min_temperature_ = temperature; }
protected:
HeatpumpIR *heatpump_ir_;
/// Transmit via IR the state of this climate controller.
void transmit_state() override;
Protocol protocol_;
HorizontalDirection default_horizontal_direction_;
VerticalDirection default_vertical_direction_;
float max_temperature_;
float min_temperature_;
};
} // namespace heatpumpir
} // namespace esphome
#endif

View file

@ -0,0 +1,32 @@
#include "ir_sender_esphome.h"
#ifdef USE_ARDUINO
namespace esphome {
namespace heatpumpir {
void IRSenderESPHome::setFrequency(int frequency) { // NOLINT(readability-identifier-naming)
auto data = transmit_.get_data();
data->set_carrier_frequency(1000 * frequency);
}
// Send an IR 'mark' symbol, i.e. transmitter ON
void IRSenderESPHome::mark(int mark_length) {
auto data = transmit_.get_data();
data->mark(mark_length);
}
// Send an IR 'space' symbol, i.e. transmitter OFF
void IRSenderESPHome::space(int space_length) {
if (space_length) {
auto data = transmit_.get_data();
data->space(space_length);
} else {
transmit_.perform();
}
}
} // namespace heatpumpir
} // namespace esphome
#endif

View file

@ -0,0 +1,27 @@
#pragma once
#ifdef USE_ARDUINO
#include "esphome/components/remote_base/remote_base.h"
#include "esphome/components/remote_transmitter/remote_transmitter.h"
#include <IRSender.h> // arduino-heatpump library
namespace esphome {
namespace heatpumpir {
class IRSenderESPHome : public IRSender {
public:
IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter)
: IRSender(pin), transmit_(transmitter->transmit()){};
void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming)
void space(int space_length) override;
void mark(int mark_length) override;
protected:
remote_transmitter::RemoteTransmitterComponent::TransmitCall transmit_;
};
} // namespace heatpumpir
} // namespace esphome
#endif

View file

@ -49,6 +49,7 @@ lib_deps =
glmnet/Dsmr@0.5 ; dsmr
rweather/Crypto@0.2.0 ; dsmr
dudanov/MideaUART@1.1.8 ; midea
tonia/HeatpumpIR@^1.0.15 ; heatpumpir
build_flags =
${common.build_flags}
-DUSE_ARDUINO

View file

@ -1681,6 +1681,13 @@ climate:
name: Toshiba Climate
- platform: hitachi_ac344
name: Hitachi Climate
- platform: heatpumpir
protocol: mitsubishi_heavy_zm
horizontal_default: left
vertical_default: up
name: HeatpumpIR Climate
min_temperature: 18
max_temperature: 30
- platform: midea
id: midea_unit
uart_id: uart0