mirror of
https://github.com/esphome/esphome.git
synced 2024-12-18 03:24:54 +01:00
Merge branch 'dev' into dev
This commit is contained in:
commit
a83aa991ec
150 changed files with 8052 additions and 971 deletions
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
|||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4.0.1
|
||||
- uses: dessant/lock-threads@v5.0.1
|
||||
with:
|
||||
pr-inactive-days: "1"
|
||||
pr-lock-reason: ""
|
||||
|
|
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
|||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8.0.0
|
||||
- uses: actions/stale@v9.0.0
|
||||
with:
|
||||
days-before-pr-stale: 90
|
||||
days-before-pr-close: 7
|
||||
|
@ -38,7 +38,7 @@ jobs:
|
|||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8.0.0
|
||||
- uses: actions/stale@v9.0.0
|
||||
with:
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
|
|
2
.github/workflows/yaml-lint.yml
vendored
2
.github/workflows/yaml-lint.yml
vendored
|
@ -19,4 +19,4 @@ jobs:
|
|||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Run yamllint
|
||||
uses: frenck/action-yamllint@v1.4.1
|
||||
uses: frenck/action-yamllint@v1.4.2
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.11.0
|
||||
rev: 23.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
|
|
15
CODEOWNERS
15
CODEOWNERS
|
@ -12,6 +12,7 @@ esphome/core/* @esphome/core
|
|||
|
||||
# Integrations
|
||||
esphome/components/a01nyub/* @MrSuicideParrot
|
||||
esphome/components/a02yyuw/* @TH-Braemer
|
||||
esphome/components/absolute_humidity/* @DAVe3283
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
|
@ -88,7 +89,7 @@ esphome/components/ds1307/* @badbadc0ffee
|
|||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/duty_time/* @dudanov
|
||||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/ektf2232/touchscreen/* @jesserockz
|
||||
esphome/components/emc2101/* @ellull
|
||||
esphome/components/ens160/* @vincentscode
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
|
@ -110,12 +111,15 @@ esphome/components/fastled_base/* @OttoWinter
|
|||
esphome/components/feedback/* @ianchi
|
||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/fs3000/* @kahrendt
|
||||
esphome/components/ft5x06/* @clydebarrow
|
||||
esphome/components/ft63x6/* @gpambrozio
|
||||
esphome/components/gcja5/* @gcormier
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gp8403/* @jesserockz
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/graph/* @synco
|
||||
esphome/components/graphical_display_menu/* @MrMDavidson
|
||||
esphome/components/gree/* @orestismers
|
||||
esphome/components/grove_tb6612fng/* @max246
|
||||
esphome/components/growatt_solar/* @leeuwte
|
||||
|
@ -237,6 +241,11 @@ esphome/components/pmwcs3/* @SeByDocKy
|
|||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/pn7150/* @jesserockz @kbx81
|
||||
esphome/components/pn7150_i2c/* @jesserockz @kbx81
|
||||
esphome/components/pn7160/* @jesserockz @kbx81
|
||||
esphome/components/pn7160_i2c/* @jesserockz @kbx81
|
||||
esphome/components/pn7160_spi/* @jesserockz @kbx81
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/preferences/* @esphome/core
|
||||
esphome/components/psram/* @esphome/core
|
||||
|
@ -331,7 +340,7 @@ esphome/components/tmp1075/* @sybrenstuvel
|
|||
esphome/components/tmp117/* @Azimath
|
||||
esphome/components/tof10120/* @wstrzalka
|
||||
esphome/components/toshiba/* @kbx81
|
||||
esphome/components/touchscreen/* @jesserockz
|
||||
esphome/components/touchscreen/* @jesserockz @nielsnl68
|
||||
esphome/components/tsl2591/* @wjcarpenter
|
||||
esphome/components/tt21100/* @kroimon
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
|
@ -364,6 +373,6 @@ esphome/components/xiaomi_mhoc303/* @drug123
|
|||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
||||
esphome/components/xl9535/* @mreditor97
|
||||
esphome/components/xpt2046/* @nielsnl68 @numo68
|
||||
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
|
||||
esphome/components/zhlt01/* @cfeenstra1024
|
||||
esphome/components/zio_ultrasonic/* @kahrendt
|
||||
|
|
|
@ -10,5 +10,3 @@ Things to note when contributing:
|
|||
for more information.
|
||||
- Please also update the tests in the `tests/` folder. You can do so by just adding a line in one of the YAML files
|
||||
which checks if your new feature compiles correctly.
|
||||
- Sometimes I will let pull requests linger because I'm not 100% sure about them. Please feel free to ping
|
||||
me after some time.
|
||||
|
|
1
esphome/components/a02yyuw/__init__.py
Normal file
1
esphome/components/a02yyuw/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@TH-Braemer"]
|
43
esphome/components/a02yyuw/a02yyuw.cpp
Normal file
43
esphome/components/a02yyuw/a02yyuw.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Datasheet https://wiki.dfrobot.com/_A02YYUW_Waterproof_Ultrasonic_Sensor_SKU_SEN0311
|
||||
|
||||
#include "a02yyuw.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace a02yyuw {
|
||||
|
||||
static const char *const TAG = "a02yyuw.sensor";
|
||||
|
||||
void A02yyuwComponent::loop() {
|
||||
uint8_t data;
|
||||
while (this->available() > 0) {
|
||||
this->read_byte(&data);
|
||||
if (this->buffer_.empty() && (data != 0xff))
|
||||
continue;
|
||||
buffer_.push_back(data);
|
||||
if (this->buffer_.size() == 4)
|
||||
this->check_buffer_();
|
||||
}
|
||||
}
|
||||
|
||||
void A02yyuwComponent::check_buffer_() {
|
||||
uint8_t checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2];
|
||||
if (this->buffer_[3] == checksum) {
|
||||
float distance = (this->buffer_[1] << 8) + this->buffer_[2];
|
||||
if (distance > 30) {
|
||||
ESP_LOGV(TAG, "Distance from sensor: %f mm", distance);
|
||||
this->publish_state(distance);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);
|
||||
}
|
||||
this->buffer_.clear();
|
||||
}
|
||||
|
||||
void A02yyuwComponent::dump_config() { LOG_SENSOR("", "A02yyuw Sensor", this); }
|
||||
|
||||
} // namespace a02yyuw
|
||||
} // namespace esphome
|
27
esphome/components/a02yyuw/a02yyuw.h
Normal file
27
esphome/components/a02yyuw/a02yyuw.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace a02yyuw {
|
||||
|
||||
class A02yyuwComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
|
||||
public:
|
||||
// Nothing really public.
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void check_buffer_();
|
||||
|
||||
std::vector<uint8_t> buffer_;
|
||||
};
|
||||
|
||||
} // namespace a02yyuw
|
||||
} // namespace esphome
|
41
esphome/components/a02yyuw/sensor.py
Normal file
41
esphome/components/a02yyuw/sensor.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
ICON_ARROW_EXPAND_VERTICAL,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@TH-Braemer"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
UNIT_MILLIMETERS = "mm"
|
||||
|
||||
a02yyuw_ns = cg.esphome_ns.namespace("a02yyuw")
|
||||
A02yyuwComponent = a02yyuw_ns.class_(
|
||||
"A02yyuwComponent", sensor.Sensor, cg.Component, uart.UARTDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
A02yyuwComponent,
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
icon=ICON_ARROW_EXPAND_VERTICAL,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"a02yyuw",
|
||||
baud_rate=9600,
|
||||
require_tx=False,
|
||||
require_rx=True,
|
||||
data_bits=8,
|
||||
parity=None,
|
||||
stop_bits=1,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
|
@ -21,36 +21,49 @@ namespace esphome {
|
|||
namespace aht10 {
|
||||
|
||||
static const char *const TAG = "aht10";
|
||||
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
|
||||
static const size_t SIZE_CALIBRATE_CMD = 3;
|
||||
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1, 0x08, 0x00};
|
||||
static const uint8_t AHT20_CALIBRATE_CMD[] = {0xBE, 0x08, 0x00};
|
||||
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement
|
||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||
static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms
|
||||
static const uint8_t AHT10_CAL_ATTEMPTS = 10;
|
||||
static const uint8_t AHT10_STATUS_BUSY = 0x80;
|
||||
|
||||
void AHT10Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT10...");
|
||||
const uint8_t *calibrate_cmd;
|
||||
switch (this->variant_) {
|
||||
case AHT10Variant::AHT20:
|
||||
calibrate_cmd = AHT20_CALIBRATE_CMD;
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT20");
|
||||
break;
|
||||
case AHT10Variant::AHT10:
|
||||
default:
|
||||
calibrate_cmd = AHT10_CALIBRATE_CMD;
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT10");
|
||||
}
|
||||
|
||||
if (!this->write_bytes(0, AHT10_CALIBRATE_CMD, sizeof(AHT10_CALIBRATE_CMD))) {
|
||||
if (this->write(calibrate_cmd, SIZE_CALIBRATE_CMD) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint8_t data = 0;
|
||||
if (this->write(&data, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(AHT10_DEFAULT_DELAY);
|
||||
if (this->read(&data, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (this->read(&data, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
uint8_t data = AHT10_STATUS_BUSY;
|
||||
int cal_attempts = 0;
|
||||
while (data & AHT10_STATUS_BUSY) {
|
||||
delay(AHT10_DEFAULT_DELAY);
|
||||
if (this->read(&data, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
++cal_attempts;
|
||||
if (cal_attempts > AHT10_CAL_ATTEMPTS) {
|
||||
ESP_LOGE(TAG, "AHT10 calibration timed out!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
|
||||
ESP_LOGE(TAG, "AHT10 calibration failed!");
|
||||
|
@ -62,7 +75,7 @@ void AHT10Component::setup() {
|
|||
}
|
||||
|
||||
void AHT10Component::update() {
|
||||
if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) {
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
|
@ -89,7 +102,7 @@ void AHT10Component::update() {
|
|||
break;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
|
||||
if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) {
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
@ -7,12 +9,15 @@
|
|||
namespace esphome {
|
||||
namespace aht10 {
|
||||
|
||||
enum AHT10Variant { AHT10, AHT20 };
|
||||
|
||||
class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void set_variant(AHT10Variant variant) { this->variant_ = variant; }
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
@ -20,6 +25,7 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
|||
protected:
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
AHT10Variant variant_{};
|
||||
};
|
||||
|
||||
} // namespace aht10
|
||||
|
|
|
@ -10,6 +10,7 @@ from esphome.const import (
|
|||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
CONF_VARIANT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
@ -17,6 +18,12 @@ DEPENDENCIES = ["i2c"]
|
|||
aht10_ns = cg.esphome_ns.namespace("aht10")
|
||||
AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
AHT10Variant = aht10_ns.enum("AHT10Variant")
|
||||
AHT10_VARIANTS = {
|
||||
"AHT10": AHT10Variant.AHT10,
|
||||
"AHT20": AHT10Variant.AHT20,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
|
@ -33,6 +40,9 @@ CONFIG_SCHEMA = (
|
|||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_VARIANT, default="AHT10"): cv.enum(
|
||||
AHT10_VARIANTS, upper=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
|
@ -44,6 +54,7 @@ async def to_code(config):
|
|||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
cg.add(var.set_variant(config[CONF_VARIANT]))
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
|
|
|
@ -365,6 +365,7 @@ message ListEntitiesFanResponse {
|
|||
bool disabled_by_default = 9;
|
||||
string icon = 10;
|
||||
EntityCategory entity_category = 11;
|
||||
repeated string supported_preset_modes = 12;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
|
@ -387,6 +388,7 @@ message FanStateResponse {
|
|||
FanSpeed speed = 4 [deprecated = true];
|
||||
FanDirection direction = 5;
|
||||
int32 speed_level = 6;
|
||||
string preset_mode = 7;
|
||||
}
|
||||
message FanCommandRequest {
|
||||
option (id) = 31;
|
||||
|
@ -405,6 +407,8 @@ message FanCommandRequest {
|
|||
FanDirection direction = 9;
|
||||
bool has_speed_level = 10;
|
||||
int32 speed_level = 11;
|
||||
bool has_preset_mode = 12;
|
||||
string preset_mode = 13;
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
|
@ -855,6 +859,10 @@ message ListEntitiesClimateResponse {
|
|||
string icon = 19;
|
||||
EntityCategory entity_category = 20;
|
||||
float visual_current_temperature_step = 21;
|
||||
bool supports_current_humidity = 22;
|
||||
bool supports_target_humidity = 23;
|
||||
float visual_min_humidity = 24;
|
||||
float visual_max_humidity = 25;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
|
@ -875,6 +883,8 @@ message ClimateStateResponse {
|
|||
string custom_fan_mode = 11;
|
||||
ClimatePreset preset = 12;
|
||||
string custom_preset = 13;
|
||||
float current_humidity = 14;
|
||||
float target_humidity = 15;
|
||||
}
|
||||
message ClimateCommandRequest {
|
||||
option (id) = 48;
|
||||
|
@ -903,6 +913,8 @@ message ClimateCommandRequest {
|
|||
ClimatePreset preset = 19;
|
||||
bool has_custom_preset = 20;
|
||||
string custom_preset = 21;
|
||||
bool has_target_humidity = 22;
|
||||
float target_humidity = 23;
|
||||
}
|
||||
|
||||
// ==================== NUMBER ====================
|
||||
|
|
|
@ -293,6 +293,8 @@ bool APIConnection::send_fan_state(fan::Fan *fan) {
|
|||
}
|
||||
if (traits.supports_direction())
|
||||
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
if (traits.supports_preset_modes())
|
||||
resp.preset_mode = fan->preset_mode;
|
||||
return this->send_fan_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_fan_info(fan::Fan *fan) {
|
||||
|
@ -307,6 +309,8 @@ bool APIConnection::send_fan_info(fan::Fan *fan) {
|
|||
msg.supports_speed = traits.supports_speed();
|
||||
msg.supports_direction = traits.supports_direction();
|
||||
msg.supported_speed_count = traits.supported_speed_count();
|
||||
for (auto const &preset : traits.supported_preset_modes())
|
||||
msg.supported_preset_modes.push_back(preset);
|
||||
msg.disabled_by_default = fan->is_disabled_by_default();
|
||||
msg.icon = fan->get_icon();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category());
|
||||
|
@ -328,6 +332,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
|||
}
|
||||
if (msg.has_direction)
|
||||
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||
if (msg.has_preset_mode)
|
||||
call.set_preset_mode(msg.preset_mode);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
@ -554,6 +560,10 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
|||
resp.custom_preset = climate->custom_preset.value();
|
||||
if (traits.get_supports_swing_modes())
|
||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||
if (traits.get_supports_current_humidity())
|
||||
resp.current_humidity = climate->current_humidity;
|
||||
if (traits.get_supports_target_humidity())
|
||||
resp.target_humidity = climate->target_humidity;
|
||||
return this->send_climate_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
|
@ -570,7 +580,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
|||
msg.entity_category = static_cast<enums::EntityCategory>(climate->get_entity_category());
|
||||
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_current_humidity = traits.get_supports_current_humidity();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
msg.supports_target_humidity = traits.get_supports_target_humidity();
|
||||
|
||||
for (auto mode : traits.get_supported_modes())
|
||||
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
||||
|
@ -579,6 +591,8 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
|||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
|
||||
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
||||
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
||||
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
||||
|
||||
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
|
||||
msg.supports_action = traits.get_supports_action();
|
||||
|
@ -609,6 +623,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
|||
call.set_target_temperature_low(msg.target_temperature_low);
|
||||
if (msg.has_target_temperature_high)
|
||||
call.set_target_temperature_high(msg.target_temperature_high);
|
||||
if (msg.has_target_humidity)
|
||||
call.set_target_humidity(msg.target_humidity);
|
||||
if (msg.has_fan_mode)
|
||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||
if (msg.has_custom_fan_mode)
|
||||
|
|
|
@ -1375,6 +1375,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi
|
|||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->supported_preset_modes.push_back(value.as_string());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -1401,6 +1405,9 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_bool(9, this->disabled_by_default);
|
||||
buffer.encode_string(10, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
|
||||
for (auto &it : this->supported_preset_modes) {
|
||||
buffer.encode_string(12, it, true);
|
||||
}
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
|
@ -1451,6 +1458,12 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
|||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->supported_preset_modes) {
|
||||
out.append(" supported_preset_modes: ");
|
||||
out.append("'").append(it).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -1480,6 +1493,16 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
bool FanStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 7: {
|
||||
this->preset_mode = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool FanStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
|
@ -1497,6 +1520,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
||||
buffer.encode_enum<enums::FanDirection>(5, this->direction);
|
||||
buffer.encode_int32(6, this->speed_level);
|
||||
buffer.encode_string(7, this->preset_mode);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void FanStateResponse::dump_to(std::string &out) const {
|
||||
|
@ -1527,6 +1551,10 @@ void FanStateResponse::dump_to(std::string &out) const {
|
|||
sprintf(buffer, "%" PRId32, this->speed_level);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" preset_mode: ");
|
||||
out.append("'").append(this->preset_mode).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -1572,6 +1600,20 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||
this->speed_level = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->has_preset_mode = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 13: {
|
||||
this->preset_mode = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -1598,6 +1640,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_enum<enums::FanDirection>(9, this->direction);
|
||||
buffer.encode_bool(10, this->has_speed_level);
|
||||
buffer.encode_int32(11, this->speed_level);
|
||||
buffer.encode_bool(12, this->has_preset_mode);
|
||||
buffer.encode_string(13, this->preset_mode);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void FanCommandRequest::dump_to(std::string &out) const {
|
||||
|
@ -1648,6 +1692,14 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
|||
sprintf(buffer, "%" PRId32, this->speed_level);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_preset_mode: ");
|
||||
out.append(YESNO(this->has_preset_mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" preset_mode: ");
|
||||
out.append("'").append(this->preset_mode).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -3559,6 +3611,14 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
|||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 22: {
|
||||
this->supports_current_humidity = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 23: {
|
||||
this->supports_target_humidity = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -3615,6 +3675,14 @@ bool ListEntitiesClimateResponse::decode_32bit(uint32_t field_id, Proto32Bit val
|
|||
this->visual_current_temperature_step = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 24: {
|
||||
this->visual_min_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 25: {
|
||||
this->visual_max_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -3653,6 +3721,10 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_string(19, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
|
||||
buffer.encode_float(21, this->visual_current_temperature_step);
|
||||
buffer.encode_bool(22, this->supports_current_humidity);
|
||||
buffer.encode_bool(23, this->supports_target_humidity);
|
||||
buffer.encode_float(24, this->visual_min_humidity);
|
||||
buffer.encode_float(25, this->visual_max_humidity);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
|
@ -3758,7 +3830,24 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
|||
sprintf(buffer, "%g", this->visual_current_temperature_step);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
|
||||
out.append(" supports_current_humidity: ");
|
||||
out.append(YESNO(this->supports_current_humidity));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_target_humidity: ");
|
||||
out.append(YESNO(this->supports_target_humidity));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" visual_min_humidity: ");
|
||||
sprintf(buffer, "%g", this->visual_min_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" visual_max_humidity: ");
|
||||
sprintf(buffer, "%g", this->visual_max_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
#endif
|
||||
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
@ -3827,6 +3916,14 @@ bool ClimateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
|||
this->target_temperature_high = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->current_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 15: {
|
||||
this->target_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -3845,6 +3942,8 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_string(11, this->custom_fan_mode);
|
||||
buffer.encode_enum<enums::ClimatePreset>(12, this->preset);
|
||||
buffer.encode_string(13, this->custom_preset);
|
||||
buffer.encode_float(14, this->current_humidity);
|
||||
buffer.encode_float(15, this->target_humidity);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ClimateStateResponse::dump_to(std::string &out) const {
|
||||
|
@ -3906,7 +4005,16 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
|||
out.append(" custom_preset: ");
|
||||
out.append("'").append(this->custom_preset).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
|
||||
out.append(" current_humidity: ");
|
||||
sprintf(buffer, "%g", this->current_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" target_humidity: ");
|
||||
sprintf(buffer, "%g", this->target_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
#endif
|
||||
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
@ -3971,6 +4079,10 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
|||
this->has_custom_preset = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 22: {
|
||||
this->has_target_humidity = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -4007,6 +4119,10 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
|||
this->target_temperature_high = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 23: {
|
||||
this->target_humidity = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -4033,6 +4149,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_enum<enums::ClimatePreset>(19, this->preset);
|
||||
buffer.encode_bool(20, this->has_custom_preset);
|
||||
buffer.encode_string(21, this->custom_preset);
|
||||
buffer.encode_bool(22, this->has_target_humidity);
|
||||
buffer.encode_float(23, this->target_humidity);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
|
@ -4125,6 +4243,15 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
|||
out.append(" custom_preset: ");
|
||||
out.append("'").append(this->custom_preset).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_target_humidity: ");
|
||||
out.append(YESNO(this->has_target_humidity));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" target_humidity: ");
|
||||
sprintf(buffer, "%g", this->target_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -472,6 +472,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
|||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::vector<std::string> supported_preset_modes{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -490,6 +491,7 @@ class FanStateResponse : public ProtoMessage {
|
|||
enums::FanSpeed speed{};
|
||||
enums::FanDirection direction{};
|
||||
int32_t speed_level{0};
|
||||
std::string preset_mode{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -497,6 +499,7 @@ class FanStateResponse : public ProtoMessage {
|
|||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class FanCommandRequest : public ProtoMessage {
|
||||
|
@ -512,6 +515,8 @@ class FanCommandRequest : public ProtoMessage {
|
|||
enums::FanDirection direction{};
|
||||
bool has_speed_level{false};
|
||||
int32_t speed_level{0};
|
||||
bool has_preset_mode{false};
|
||||
std::string preset_mode{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -519,6 +524,7 @@ class FanCommandRequest : public ProtoMessage {
|
|||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesLightResponse : public ProtoMessage {
|
||||
|
@ -979,6 +985,10 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
|||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
float visual_current_temperature_step{0.0f};
|
||||
bool supports_current_humidity{false};
|
||||
bool supports_target_humidity{false};
|
||||
float visual_min_humidity{0.0f};
|
||||
float visual_max_humidity{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -1004,6 +1014,8 @@ class ClimateStateResponse : public ProtoMessage {
|
|||
std::string custom_fan_mode{};
|
||||
enums::ClimatePreset preset{};
|
||||
std::string custom_preset{};
|
||||
float current_humidity{0.0f};
|
||||
float target_humidity{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -1037,6 +1049,8 @@ class ClimateCommandRequest : public ProtoMessage {
|
|||
enums::ClimatePreset preset{};
|
||||
bool has_custom_preset{false};
|
||||
std::string custom_preset{};
|
||||
bool has_target_humidity{false};
|
||||
float target_humidity{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
|
|
@ -15,6 +15,16 @@ void BangBangClimate::setup() {
|
|||
this->publish_state();
|
||||
});
|
||||
this->current_temperature = this->sensor_->state;
|
||||
|
||||
// register for humidity values and get initial state
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
this->humidity_sensor_->add_on_state_callback([this](float state) {
|
||||
this->current_humidity = state;
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_humidity = this->humidity_sensor_->state;
|
||||
}
|
||||
|
||||
// restore set points
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
|
@ -47,6 +57,8 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
|
|||
climate::ClimateTraits BangBangClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(true);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
traits.set_supports_current_humidity(true);
|
||||
traits.set_supported_modes({
|
||||
climate::CLIMATE_MODE_OFF,
|
||||
});
|
||||
|
@ -171,6 +183,7 @@ void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &awa
|
|||
BangBangClimate::BangBangClimate()
|
||||
: idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {}
|
||||
void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; }
|
||||
Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; }
|
||||
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
|
|
|
@ -24,6 +24,7 @@ class BangBangClimate : public climate::Climate, public Component {
|
|||
void dump_config() override;
|
||||
|
||||
void set_sensor(sensor::Sensor *sensor);
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor);
|
||||
Trigger<> *get_idle_trigger() const;
|
||||
Trigger<> *get_cool_trigger() const;
|
||||
void set_supports_cool(bool supports_cool);
|
||||
|
@ -48,6 +49,9 @@ class BangBangClimate : public climate::Climate, public Component {
|
|||
|
||||
/// The sensor used for getting the current temperature
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
/// The sensor used for getting the current humidity
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
|
||||
/** The trigger to call when the controller should switch to idle mode.
|
||||
*
|
||||
* In idle mode, the controller is assumed to have both heating and cooling disabled.
|
||||
|
|
|
@ -8,6 +8,7 @@ from esphome.const import (
|
|||
CONF_DEFAULT_TARGET_TEMPERATURE_HIGH,
|
||||
CONF_DEFAULT_TARGET_TEMPERATURE_LOW,
|
||||
CONF_HEAT_ACTION,
|
||||
CONF_HUMIDITY_SENSOR,
|
||||
CONF_ID,
|
||||
CONF_IDLE_ACTION,
|
||||
CONF_SENSOR,
|
||||
|
@ -22,6 +23,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
{
|
||||
cv.GenerateID(): cv.declare_id(BangBangClimate),
|
||||
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
||||
cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True),
|
||||
|
@ -47,6 +49,10 @@ async def to_code(config):
|
|||
sens = await cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY_SENSOR in config:
|
||||
sens = await cg.get_variable(config[CONF_HUMIDITY_SENSOR])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
|
||||
normal_config = BangBangClimateTargetTempConfig(
|
||||
config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
|
||||
config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
|
||||
|
|
|
@ -90,40 +90,41 @@ void BP1658CJ::set_channel_value_(uint8_t channel, uint16_t value) {
|
|||
|
||||
void BP1658CJ::write_bit_(bool value) {
|
||||
this->data_pin_->digital_write(value);
|
||||
this->clock_pin_->digital_write(true);
|
||||
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
|
||||
this->clock_pin_->digital_write(true);
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
this->clock_pin_->digital_write(false);
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
}
|
||||
|
||||
void BP1658CJ::write_byte_(uint8_t data) {
|
||||
for (uint8_t mask = 0x80; mask; mask >>= 1) {
|
||||
this->write_bit_(data & mask);
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
}
|
||||
|
||||
// ack bit
|
||||
this->data_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->clock_pin_->digital_write(true);
|
||||
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
|
||||
this->clock_pin_->digital_write(false);
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
this->data_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
}
|
||||
|
||||
void BP1658CJ::write_buffer_(uint8_t *buffer, uint8_t size) {
|
||||
this->data_pin_->digital_write(false);
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
this->clock_pin_->digital_write(false);
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
this->write_byte_(buffer[i]);
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
}
|
||||
|
||||
this->clock_pin_->digital_write(true);
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
this->data_pin_->digital_write(true);
|
||||
delayMicroseconds(BP1658CJ_DELAY);
|
||||
}
|
||||
|
||||
} // namespace bp1658cj
|
||||
|
|
|
@ -8,6 +8,7 @@ from esphome.const import (
|
|||
CONF_AWAY,
|
||||
CONF_AWAY_COMMAND_TOPIC,
|
||||
CONF_AWAY_STATE_TOPIC,
|
||||
CONF_CURRENT_HUMIDITY_STATE_TOPIC,
|
||||
CONF_CURRENT_TEMPERATURE_STATE_TOPIC,
|
||||
CONF_CUSTOM_FAN_MODE,
|
||||
CONF_CUSTOM_PRESET,
|
||||
|
@ -28,6 +29,8 @@ from esphome.const import (
|
|||
CONF_SWING_MODE,
|
||||
CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
CONF_SWING_MODE_STATE_TOPIC,
|
||||
CONF_TARGET_HUMIDITY_COMMAND_TOPIC,
|
||||
CONF_TARGET_HUMIDITY_STATE_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE,
|
||||
CONF_TARGET_TEMPERATURE_COMMAND_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_STATE_TOPIC,
|
||||
|
@ -106,6 +109,9 @@ CLIMATE_SWING_MODES = {
|
|||
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
||||
|
||||
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
||||
CONF_MIN_HUMIDITY = "min_humidity"
|
||||
CONF_MAX_HUMIDITY = "max_humidity"
|
||||
CONF_TARGET_HUMIDITY = "target_humidity"
|
||||
|
||||
visual_temperature = cv.float_with_unit(
|
||||
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
|
||||
|
@ -153,6 +159,8 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
|||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
|
||||
cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
|
||||
cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
||||
|
@ -167,6 +175,9 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
|||
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
|
@ -209,6 +220,12 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
|||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
|
||||
|
@ -238,6 +255,10 @@ async def setup_climate_core_(var, config):
|
|||
visual[CONF_TEMPERATURE_STEP][CONF_CURRENT_TEMPERATURE],
|
||||
)
|
||||
)
|
||||
if CONF_MIN_HUMIDITY in visual:
|
||||
cg.add(var.set_visual_min_humidity_override(visual[CONF_MIN_HUMIDITY]))
|
||||
if CONF_MAX_HUMIDITY in visual:
|
||||
cg.add(var.set_visual_max_humidity_override(visual[CONF_MAX_HUMIDITY]))
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
|
@ -255,6 +276,12 @@ async def setup_climate_core_(var, config):
|
|||
config[CONF_CURRENT_TEMPERATURE_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_CURRENT_HUMIDITY_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_current_humidity_state_topic(
|
||||
config[CONF_CURRENT_HUMIDITY_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_FAN_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_fan_mode_command_topic(
|
||||
|
@ -323,6 +350,18 @@ async def setup_climate_core_(var, config):
|
|||
config[CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_HUMIDITY_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_humidity_command_topic(
|
||||
config[CONF_TARGET_HUMIDITY_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_HUMIDITY_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_humidity_state_topic(
|
||||
config[CONF_TARGET_HUMIDITY_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
@ -351,6 +390,7 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
|||
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY): cv.templatable(cv.percentage_int),
|
||||
cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"),
|
||||
cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable(
|
||||
validate_climate_fan_mode
|
||||
|
@ -387,6 +427,9 @@ async def climate_control_to_code(config, action_id, template_arg, args):
|
|||
config[CONF_TARGET_TEMPERATURE_HIGH], args, float
|
||||
)
|
||||
cg.add(var.set_target_temperature_high(template_))
|
||||
if CONF_TARGET_HUMIDITY in config:
|
||||
template_ = await cg.templatable(config[CONF_TARGET_HUMIDITY], args, float)
|
||||
cg.add(var.set_target_humidity(template_))
|
||||
if CONF_FAN_MODE in config:
|
||||
template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
|
||||
cg.add(var.set_fan_mode(template_))
|
||||
|
|
|
@ -14,6 +14,7 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
|||
TEMPLATABLE_VALUE(float, target_temperature)
|
||||
TEMPLATABLE_VALUE(float, target_temperature_low)
|
||||
TEMPLATABLE_VALUE(float, target_temperature_high)
|
||||
TEMPLATABLE_VALUE(float, target_humidity)
|
||||
TEMPLATABLE_VALUE(bool, away)
|
||||
TEMPLATABLE_VALUE(ClimateFanMode, fan_mode)
|
||||
TEMPLATABLE_VALUE(std::string, custom_fan_mode)
|
||||
|
@ -27,6 +28,7 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
|||
call.set_target_temperature(this->target_temperature_.optional_value(x...));
|
||||
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
|
||||
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
|
||||
call.set_target_humidity(this->target_humidity_.optional_value(x...));
|
||||
if (away_.has_value()) {
|
||||
call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,9 @@ void ClimateCall::perform() {
|
|||
if (this->target_temperature_high_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
|
||||
}
|
||||
if (this->target_humidity_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Humidity: %.0f", *this->target_humidity_);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
void ClimateCall::validate_() {
|
||||
|
@ -262,10 +265,16 @@ ClimateCall &ClimateCall::set_target_temperature_high(float target_temperature_h
|
|||
this->target_temperature_high_ = target_temperature_high;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_humidity(float target_humidity) {
|
||||
this->target_humidity_ = target_humidity;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
|
||||
const optional<float> &ClimateCall::get_target_humidity() const { return this->target_humidity_; }
|
||||
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
|
||||
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
||||
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
|
||||
|
@ -283,6 +292,10 @@ ClimateCall &ClimateCall::set_target_temperature(optional<float> target_temperat
|
|||
this->target_temperature_ = target_temperature;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_humidity(optional<float> target_humidity) {
|
||||
this->target_humidity_ = target_humidity;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) {
|
||||
this->mode_ = mode;
|
||||
return *this;
|
||||
|
@ -343,6 +356,9 @@ void Climate::save_state_() {
|
|||
} else {
|
||||
state.target_temperature = this->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
state.target_humidity = this->target_humidity;
|
||||
}
|
||||
if (traits.get_supports_fan_modes() && fan_mode.has_value()) {
|
||||
state.uses_custom_fan_mode = false;
|
||||
state.fan_mode = this->fan_mode.value();
|
||||
|
@ -408,6 +424,12 @@ void Climate::publish_state() {
|
|||
} else {
|
||||
ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature);
|
||||
}
|
||||
if (traits.get_supports_current_humidity()) {
|
||||
ESP_LOGD(TAG, " Current Humidity: %.0f%%", this->current_humidity);
|
||||
}
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
ESP_LOGD(TAG, " Target Humidity: %.0f%%", this->target_humidity);
|
||||
}
|
||||
|
||||
// Send state to frontend
|
||||
this->state_callback_.call(*this);
|
||||
|
@ -427,6 +449,12 @@ ClimateTraits Climate::get_traits() {
|
|||
traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_);
|
||||
traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_);
|
||||
}
|
||||
if (this->visual_min_humidity_override_.has_value()) {
|
||||
traits.set_visual_min_humidity(*this->visual_min_humidity_override_);
|
||||
}
|
||||
if (this->visual_max_humidity_override_.has_value()) {
|
||||
traits.set_visual_max_humidity(*this->visual_max_humidity_override_);
|
||||
}
|
||||
|
||||
return traits;
|
||||
}
|
||||
|
@ -441,6 +469,12 @@ void Climate::set_visual_temperature_step_override(float target, float current)
|
|||
this->visual_target_temperature_step_override_ = target;
|
||||
this->visual_current_temperature_step_override_ = current;
|
||||
}
|
||||
void Climate::set_visual_min_humidity_override(float visual_min_humidity_override) {
|
||||
this->visual_min_humidity_override_ = visual_min_humidity_override;
|
||||
}
|
||||
void Climate::set_visual_max_humidity_override(float visual_max_humidity_override) {
|
||||
this->visual_max_humidity_override_ = visual_max_humidity_override;
|
||||
}
|
||||
|
||||
ClimateCall Climate::make_call() { return ClimateCall(this); }
|
||||
|
||||
|
@ -454,6 +488,9 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
|||
} else {
|
||||
call.set_target_temperature(this->target_temperature);
|
||||
}
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
call.set_target_humidity(this->target_humidity);
|
||||
}
|
||||
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||
call.set_fan_mode(this->fan_mode);
|
||||
}
|
||||
|
@ -474,6 +511,9 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
|
|||
} else {
|
||||
climate->target_temperature = this->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
climate->target_humidity = this->target_humidity;
|
||||
}
|
||||
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
|
||||
climate->fan_mode = this->fan_mode;
|
||||
}
|
||||
|
@ -530,17 +570,25 @@ void Climate::dump_traits_(const char *tag) {
|
|||
auto traits = this->get_traits();
|
||||
ESP_LOGCONFIG(tag, "ClimateTraits:");
|
||||
ESP_LOGCONFIG(tag, " [x] Visual settings:");
|
||||
ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature());
|
||||
ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature());
|
||||
ESP_LOGCONFIG(tag, " - Step:");
|
||||
ESP_LOGCONFIG(tag, " - Min temperature: %.1f", traits.get_visual_min_temperature());
|
||||
ESP_LOGCONFIG(tag, " - Max temperature: %.1f", traits.get_visual_max_temperature());
|
||||
ESP_LOGCONFIG(tag, " - Temperature step:");
|
||||
ESP_LOGCONFIG(tag, " Target: %.1f", traits.get_visual_target_temperature_step());
|
||||
ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
|
||||
ESP_LOGCONFIG(tag, " - Min humidity: %.0f", traits.get_visual_min_humidity());
|
||||
ESP_LOGCONFIG(tag, " - Max humidity: %.0f", traits.get_visual_max_humidity());
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
|
||||
}
|
||||
if (traits.get_supports_current_humidity()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports current humidity");
|
||||
}
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature");
|
||||
}
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports target humidity");
|
||||
}
|
||||
if (traits.get_supports_action()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports action");
|
||||
}
|
||||
|
|
|
@ -64,6 +64,10 @@ class ClimateCall {
|
|||
* For climate devices with two point target temperature control
|
||||
*/
|
||||
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
|
||||
/// Set the target humidity of the climate device.
|
||||
ClimateCall &set_target_humidity(float target_humidity);
|
||||
/// Set the target humidity of the climate device.
|
||||
ClimateCall &set_target_humidity(optional<float> target_humidity);
|
||||
/// Set the fan mode of the climate device.
|
||||
ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
|
||||
/// Set the fan mode of the climate device.
|
||||
|
@ -93,6 +97,7 @@ class ClimateCall {
|
|||
const optional<float> &get_target_temperature() const;
|
||||
const optional<float> &get_target_temperature_low() const;
|
||||
const optional<float> &get_target_temperature_high() const;
|
||||
const optional<float> &get_target_humidity() const;
|
||||
const optional<ClimateFanMode> &get_fan_mode() const;
|
||||
const optional<ClimateSwingMode> &get_swing_mode() const;
|
||||
const optional<std::string> &get_custom_fan_mode() const;
|
||||
|
@ -107,6 +112,7 @@ class ClimateCall {
|
|||
optional<float> target_temperature_;
|
||||
optional<float> target_temperature_low_;
|
||||
optional<float> target_temperature_high_;
|
||||
optional<float> target_humidity_;
|
||||
optional<ClimateFanMode> fan_mode_;
|
||||
optional<ClimateSwingMode> swing_mode_;
|
||||
optional<std::string> custom_fan_mode_;
|
||||
|
@ -136,6 +142,7 @@ struct ClimateDeviceRestoreState {
|
|||
float target_temperature_high;
|
||||
};
|
||||
};
|
||||
float target_humidity;
|
||||
|
||||
/// Convert this struct to a climate call that can be performed.
|
||||
ClimateCall to_call(Climate *climate);
|
||||
|
@ -160,24 +167,34 @@ struct ClimateDeviceRestoreState {
|
|||
*/
|
||||
class Climate : public EntityBase {
|
||||
public:
|
||||
Climate() {}
|
||||
|
||||
/// The active mode of the climate device.
|
||||
ClimateMode mode{CLIMATE_MODE_OFF};
|
||||
|
||||
/// The active state of the climate device.
|
||||
ClimateAction action{CLIMATE_ACTION_OFF};
|
||||
|
||||
/// The current temperature of the climate device, as reported from the integration.
|
||||
float current_temperature{NAN};
|
||||
|
||||
/// The current humidity of the climate device, as reported from the integration.
|
||||
float current_humidity{NAN};
|
||||
|
||||
union {
|
||||
/// The target temperature of the climate device.
|
||||
float target_temperature;
|
||||
struct {
|
||||
/// The minimum target temperature of the climate device, for climate devices with split target temperature.
|
||||
float target_temperature_low;
|
||||
float target_temperature_low{NAN};
|
||||
/// The maximum target temperature of the climate device, for climate devices with split target temperature.
|
||||
float target_temperature_high;
|
||||
float target_temperature_high{NAN};
|
||||
};
|
||||
};
|
||||
|
||||
/// The target humidity of the climate device.
|
||||
float target_humidity;
|
||||
|
||||
/// The active fan mode of the climate device.
|
||||
optional<ClimateFanMode> fan_mode;
|
||||
|
||||
|
@ -231,6 +248,8 @@ class Climate : public EntityBase {
|
|||
void set_visual_min_temperature_override(float visual_min_temperature_override);
|
||||
void set_visual_max_temperature_override(float visual_max_temperature_override);
|
||||
void set_visual_temperature_step_override(float target, float current);
|
||||
void set_visual_min_humidity_override(float visual_min_humidity_override);
|
||||
void set_visual_max_humidity_override(float visual_max_humidity_override);
|
||||
|
||||
protected:
|
||||
friend ClimateCall;
|
||||
|
@ -280,6 +299,8 @@ class Climate : public EntityBase {
|
|||
optional<float> visual_max_temperature_override_{};
|
||||
optional<float> visual_target_temperature_step_override_{};
|
||||
optional<float> visual_current_temperature_step_override_{};
|
||||
optional<float> visual_min_humidity_override_{};
|
||||
optional<float> visual_max_humidity_override_{};
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
|
|
|
@ -44,10 +44,18 @@ class ClimateTraits {
|
|||
void set_supports_current_temperature(bool supports_current_temperature) {
|
||||
supports_current_temperature_ = supports_current_temperature;
|
||||
}
|
||||
bool get_supports_current_humidity() const { return supports_current_humidity_; }
|
||||
void set_supports_current_humidity(bool supports_current_humidity) {
|
||||
supports_current_humidity_ = supports_current_humidity;
|
||||
}
|
||||
bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
|
||||
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
|
||||
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
|
||||
}
|
||||
bool get_supports_target_humidity() const { return supports_target_humidity_; }
|
||||
void set_supports_target_humidity(bool supports_target_humidity) {
|
||||
supports_target_humidity_ = supports_target_humidity;
|
||||
}
|
||||
void set_supported_modes(std::set<ClimateMode> modes) { supported_modes_ = std::move(modes); }
|
||||
void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
|
@ -153,6 +161,11 @@ class ClimateTraits {
|
|||
int8_t get_target_temperature_accuracy_decimals() const;
|
||||
int8_t get_current_temperature_accuracy_decimals() const;
|
||||
|
||||
float get_visual_min_humidity() const { return visual_min_humidity_; }
|
||||
void set_visual_min_humidity(float visual_min_humidity) { visual_min_humidity_ = visual_min_humidity; }
|
||||
float get_visual_max_humidity() const { return visual_max_humidity_; }
|
||||
void set_visual_max_humidity(float visual_max_humidity) { visual_max_humidity_ = visual_max_humidity; }
|
||||
|
||||
protected:
|
||||
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
||||
if (supported) {
|
||||
|
@ -177,7 +190,9 @@ class ClimateTraits {
|
|||
}
|
||||
|
||||
bool supports_current_temperature_{false};
|
||||
bool supports_current_humidity_{false};
|
||||
bool supports_two_point_target_temperature_{false};
|
||||
bool supports_target_humidity_{false};
|
||||
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF};
|
||||
bool supports_action_{false};
|
||||
std::set<climate::ClimateFanMode> supported_fan_modes_;
|
||||
|
@ -190,6 +205,8 @@ class ClimateTraits {
|
|||
float visual_max_temperature_{30};
|
||||
float visual_target_temperature_step_{0.1};
|
||||
float visual_current_temperature_step_{0.1};
|
||||
float visual_min_humidity_{30};
|
||||
float visual_max_humidity_{99};
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
|
|
|
@ -12,6 +12,7 @@ void CopyFan::setup() {
|
|||
this->oscillating = source_->oscillating;
|
||||
this->speed = source_->speed;
|
||||
this->direction = source_->direction;
|
||||
this->preset_mode = source_->preset_mode;
|
||||
this->publish_state();
|
||||
});
|
||||
|
||||
|
@ -19,6 +20,7 @@ void CopyFan::setup() {
|
|||
this->oscillating = source_->oscillating;
|
||||
this->speed = source_->speed;
|
||||
this->direction = source_->direction;
|
||||
this->preset_mode = source_->preset_mode;
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
|
@ -33,6 +35,7 @@ fan::FanTraits CopyFan::get_traits() {
|
|||
traits.set_speed(base.supports_speed());
|
||||
traits.set_supported_speed_count(base.supported_speed_count());
|
||||
traits.set_direction(base.supports_direction());
|
||||
traits.set_supported_preset_modes(base.supported_preset_modes());
|
||||
return traits;
|
||||
}
|
||||
|
||||
|
@ -46,6 +49,8 @@ void CopyFan::control(const fan::FanCall &call) {
|
|||
call2.set_speed(*call.get_speed());
|
||||
if (call.get_direction().has_value())
|
||||
call2.set_direction(*call.get_direction());
|
||||
if (!call.get_preset_mode().empty())
|
||||
call2.set_preset_mode(call.get_preset_mode());
|
||||
call2.perform();
|
||||
}
|
||||
|
||||
|
|
|
@ -166,6 +166,13 @@ void Display::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, in
|
|||
}
|
||||
#endif // USE_QR_CODE
|
||||
|
||||
#ifdef USE_GRAPHICAL_DISPLAY_MENU
|
||||
void Display::menu(int x, int y, graphical_display_menu::GraphicalDisplayMenu *menu, int width, int height) {
|
||||
Rect rect(x, y, width, height);
|
||||
menu->draw(this, &rect);
|
||||
}
|
||||
#endif // USE_GRAPHICAL_DISPLAY_MENU
|
||||
|
||||
void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1,
|
||||
int *width, int *height) {
|
||||
int x_offset, baseline;
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
#include "esphome/components/qr_code/qr_code.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_GRAPHICAL_DISPLAY_MENU
|
||||
#include "esphome/components/graphical_display_menu/graphical_display_menu.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
|
||||
|
@ -392,6 +396,17 @@ class Display : public PollingComponent {
|
|||
void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
|
||||
#endif
|
||||
|
||||
#ifdef USE_GRAPHICAL_DISPLAY_MENU
|
||||
/**
|
||||
* @param x The x coordinate of the upper left corner
|
||||
* @param y The y coordinate of the upper left corner
|
||||
* @param menu The GraphicalDisplayMenu to draw
|
||||
* @param width Width of the menu
|
||||
* @param height Height of the menu
|
||||
*/
|
||||
void menu(int x, int y, graphical_display_menu::GraphicalDisplayMenu *menu, int width, int height);
|
||||
#endif // USE_GRAPHICAL_DISPLAY_MENU
|
||||
|
||||
/** Get the text bounds of the given string.
|
||||
*
|
||||
* @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
|
||||
|
|
|
@ -172,6 +172,8 @@ void DisplayMenuComponent::show_main() {
|
|||
|
||||
this->process_initial_();
|
||||
|
||||
this->on_before_show();
|
||||
|
||||
if (this->active_ && this->editing_)
|
||||
this->finish_editing_();
|
||||
|
||||
|
@ -188,6 +190,8 @@ void DisplayMenuComponent::show_main() {
|
|||
}
|
||||
|
||||
this->draw_and_update();
|
||||
|
||||
this->on_after_show();
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::show() {
|
||||
|
@ -196,18 +200,26 @@ void DisplayMenuComponent::show() {
|
|||
|
||||
this->process_initial_();
|
||||
|
||||
this->on_before_show();
|
||||
|
||||
if (!this->active_) {
|
||||
this->active_ = true;
|
||||
this->draw_and_update();
|
||||
}
|
||||
|
||||
this->on_after_show();
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::hide() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
this->on_before_hide();
|
||||
|
||||
if (this->editing_)
|
||||
this->finish_editing_();
|
||||
this->active_ = false;
|
||||
this->update();
|
||||
|
||||
this->on_after_hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,11 @@ class DisplayMenuComponent : public Component {
|
|||
update();
|
||||
}
|
||||
|
||||
virtual void on_before_show(){};
|
||||
virtual void on_after_show(){};
|
||||
virtual void on_before_hide(){};
|
||||
virtual void on_after_hide(){};
|
||||
|
||||
uint8_t rows_;
|
||||
bool active_;
|
||||
MenuMode mode_;
|
||||
|
|
|
@ -5,6 +5,29 @@
|
|||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
const LogString *menu_item_type_to_string(MenuItemType type) {
|
||||
switch (type) {
|
||||
case MenuItemType::MENU_ITEM_LABEL:
|
||||
return LOG_STR("MENU_ITEM_LABEL");
|
||||
case MenuItemType::MENU_ITEM_MENU:
|
||||
return LOG_STR("MENU_ITEM_MENU");
|
||||
case MenuItemType::MENU_ITEM_BACK:
|
||||
return LOG_STR("MENU_ITEM_BACK");
|
||||
case MenuItemType::MENU_ITEM_SELECT:
|
||||
return LOG_STR("MENU_ITEM_SELECT");
|
||||
case MenuItemType::MENU_ITEM_NUMBER:
|
||||
return LOG_STR("MENU_ITEM_NUMBER");
|
||||
case MenuItemType::MENU_ITEM_SWITCH:
|
||||
return LOG_STR("MENU_ITEM_SWITCH");
|
||||
case MenuItemType::MENU_ITEM_COMMAND:
|
||||
return LOG_STR("MENU_ITEM_COMMAND");
|
||||
case MenuItemType::MENU_ITEM_CUSTOM:
|
||||
return LOG_STR("MENU_ITEM_CUSTOM");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
void MenuItem::on_enter() { this->on_enter_callbacks_.call(); }
|
||||
|
||||
void MenuItem::on_leave() { this->on_leave_callbacks_.call(); }
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#endif
|
||||
|
||||
#include <vector>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
@ -29,6 +30,9 @@ enum MenuItemType {
|
|||
MENU_ITEM_CUSTOM,
|
||||
};
|
||||
|
||||
/// @brief Returns a string representation of a menu item type suitable for logging
|
||||
const LogString *menu_item_type_to_string(MenuItemType type);
|
||||
|
||||
class MenuItem;
|
||||
class MenuItemMenu;
|
||||
using value_getter_t = std::function<std::string(const MenuItem *)>;
|
||||
|
|
|
@ -12,7 +12,6 @@ ektf2232_ns = cg.esphome_ns.namespace("ektf2232")
|
|||
EKTF2232Touchscreen = ektf2232_ns.class_(
|
||||
"EKTF2232Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
cg.Component,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
|
@ -28,17 +27,14 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
|||
),
|
||||
cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x15))
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
).extend(i2c.i2c_device_schema(0x15))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
|
@ -15,16 +15,12 @@ static const uint8_t GET_X_RES[4] = {0x53, 0x60, 0x00, 0x00};
|
|||
static const uint8_t GET_Y_RES[4] = {0x53, 0x63, 0x00, 0x00};
|
||||
static const uint8_t GET_POWER_STATE_CMD[4] = {0x53, 0x50, 0x00, 0x01};
|
||||
|
||||
void EKTF2232TouchscreenStore::gpio_intr(EKTF2232TouchscreenStore *store) { store->touch = true; }
|
||||
|
||||
void EKTF2232Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen...");
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
|
||||
this->store_.pin = this->interrupt_pin_->to_isr();
|
||||
this->interrupt_pin_->attach_interrupt(EKTF2232TouchscreenStore::gpio_intr, &this->store_,
|
||||
gpio::INTERRUPT_FALLING_EDGE);
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
this->rts_pin_->setup();
|
||||
|
||||
|
@ -45,7 +41,7 @@ void EKTF2232Touchscreen::setup() {
|
|||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->x_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
|
||||
this->write(GET_Y_RES, 4);
|
||||
if (this->read(received, 4)) {
|
||||
|
@ -54,19 +50,14 @@ void EKTF2232Touchscreen::setup() {
|
|||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->y_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
this->store_.touch = false;
|
||||
this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
|
||||
this->set_power_state(true);
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::loop() {
|
||||
if (!this->store_.touch)
|
||||
return;
|
||||
this->store_.touch = false;
|
||||
|
||||
void EKTF2232Touchscreen::update_touches() {
|
||||
uint8_t touch_count = 0;
|
||||
std::vector<TouchPoint> touches;
|
||||
int16_t x_raw, y_raw;
|
||||
|
||||
uint8_t raw[8];
|
||||
this->read(raw, 8);
|
||||
|
@ -75,45 +66,15 @@ void EKTF2232Touchscreen::loop() {
|
|||
touch_count++;
|
||||
}
|
||||
|
||||
if (touch_count == 0) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
}
|
||||
|
||||
touch_count = std::min<uint8_t>(touch_count, 2);
|
||||
|
||||
ESP_LOGV(TAG, "Touch count: %d", touch_count);
|
||||
|
||||
for (int i = 0; i < touch_count; i++) {
|
||||
uint8_t *d = raw + 1 + (i * 3);
|
||||
uint32_t raw_x = (d[0] & 0xF0) << 4 | d[1];
|
||||
uint32_t raw_y = (d[0] & 0x0F) << 8 | d[2];
|
||||
|
||||
raw_x = raw_x * this->display_height_ - 1;
|
||||
raw_y = raw_y * this->display_width_ - 1;
|
||||
|
||||
TouchPoint tp;
|
||||
switch (this->rotation_) {
|
||||
case ROTATE_0_DEGREES:
|
||||
tp.y = raw_x / this->x_resolution_;
|
||||
tp.x = this->display_width_ - 1 - (raw_y / this->y_resolution_);
|
||||
break;
|
||||
case ROTATE_90_DEGREES:
|
||||
tp.x = raw_x / this->x_resolution_;
|
||||
tp.y = raw_y / this->y_resolution_;
|
||||
break;
|
||||
case ROTATE_180_DEGREES:
|
||||
tp.y = this->display_height_ - 1 - (raw_x / this->x_resolution_);
|
||||
tp.x = raw_y / this->y_resolution_;
|
||||
break;
|
||||
case ROTATE_270_DEGREES:
|
||||
tp.x = this->display_height_ - 1 - (raw_x / this->x_resolution_);
|
||||
tp.y = this->display_width_ - 1 - (raw_y / this->y_resolution_);
|
||||
break;
|
||||
}
|
||||
|
||||
this->defer([this, tp]() { this->send_touch_(tp); });
|
||||
x_raw = (d[0] & 0xF0) << 4 | d[1];
|
||||
y_raw = (d[0] & 0x0F) << 8 | d[2];
|
||||
this->set_raw_touch_position_(i, x_raw, y_raw);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,7 +87,7 @@ void EKTF2232Touchscreen::set_power_state(bool enable) {
|
|||
bool EKTF2232Touchscreen::get_power_state() {
|
||||
uint8_t received[4];
|
||||
this->write(GET_POWER_STATE_CMD, 4);
|
||||
this->store_.touch = false;
|
||||
this->store_.touched = false;
|
||||
this->read(received, 4);
|
||||
return (received[1] >> 3) & 1;
|
||||
}
|
||||
|
@ -145,14 +106,14 @@ bool EKTF2232Touchscreen::soft_reset_() {
|
|||
|
||||
uint8_t received[4];
|
||||
uint16_t timeout = 1000;
|
||||
while (!this->store_.touch && timeout > 0) {
|
||||
while (!this->store_.touched && timeout > 0) {
|
||||
delay(1);
|
||||
timeout--;
|
||||
}
|
||||
if (timeout > 0)
|
||||
this->store_.touch = true;
|
||||
this->store_.touched = true;
|
||||
this->read(received, 4);
|
||||
this->store_.touch = false;
|
||||
this->store_.touched = false;
|
||||
|
||||
return !memcmp(received, HELLO, 4);
|
||||
}
|
|
@ -9,19 +9,11 @@
|
|||
namespace esphome {
|
||||
namespace ektf2232 {
|
||||
|
||||
struct EKTF2232TouchscreenStore {
|
||||
volatile bool touch;
|
||||
ISRInternalGPIOPin pin;
|
||||
|
||||
static void gpio_intr(EKTF2232TouchscreenStore *store);
|
||||
};
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
|
||||
class EKTF2232Touchscreen : public Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
|
@ -33,12 +25,10 @@ class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2
|
|||
protected:
|
||||
void hard_reset_();
|
||||
bool soft_reset_();
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
GPIOPin *rts_pin_;
|
||||
EKTF2232TouchscreenStore store_;
|
||||
uint16_t x_resolution_;
|
||||
uint16_t y_resolution_;
|
||||
};
|
||||
|
||||
} // namespace ektf2232
|
|
@ -462,7 +462,7 @@ async def to_code(config):
|
|||
|
||||
add_extra_script(
|
||||
"post",
|
||||
"post_build2.py",
|
||||
"post_build.py",
|
||||
os.path.join(os.path.dirname(__file__), "post_build.py.script"),
|
||||
)
|
||||
|
||||
|
@ -497,10 +497,11 @@ async def to_code(config):
|
|||
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
if CONF_PARTITIONS in config:
|
||||
cg.add_platformio_option("board_build.partitions", config[CONF_PARTITIONS])
|
||||
else:
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
add_extra_build_file(
|
||||
"partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS])
|
||||
)
|
||||
|
||||
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||
|
@ -639,20 +640,22 @@ def _write_sdkconfig():
|
|||
# Called by writer.py
|
||||
def copy_files():
|
||||
if CORE.using_arduino:
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
get_arduino_partition_csv(
|
||||
CORE.platformio_options.get("board_upload.flash_size")
|
||||
),
|
||||
)
|
||||
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
get_arduino_partition_csv(
|
||||
CORE.platformio_options.get("board_upload.flash_size")
|
||||
),
|
||||
)
|
||||
if CORE.using_esp_idf:
|
||||
_write_sdkconfig()
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
get_idf_partition_csv(
|
||||
CORE.platformio_options.get("board_upload.flash_size")
|
||||
),
|
||||
)
|
||||
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
get_idf_partition_csv(
|
||||
CORE.platformio_options.get("board_upload.flash_size")
|
||||
),
|
||||
)
|
||||
# IDF build scripts look for version string to put in the build.
|
||||
# However, if the build path does not have an initialized git repo,
|
||||
# and no version.txt file exists, the CMake script fails for some setups.
|
||||
|
|
|
@ -25,6 +25,11 @@ AUTO_LOAD = ["psram"]
|
|||
|
||||
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
||||
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
|
||||
ESP32CameraImageData = esp32_camera_ns.struct("CameraImageData")
|
||||
# Triggers
|
||||
ESP32CameraImageTrigger = esp32_camera_ns.class_(
|
||||
"ESP32CameraImageTrigger", automation.Trigger.template()
|
||||
)
|
||||
ESP32CameraStreamStartTrigger = esp32_camera_ns.class_(
|
||||
"ESP32CameraStreamStartTrigger",
|
||||
automation.Trigger.template(),
|
||||
|
@ -139,6 +144,7 @@ CONF_IDLE_FRAMERATE = "idle_framerate"
|
|||
# stream trigger
|
||||
CONF_ON_STREAM_START = "on_stream_start"
|
||||
CONF_ON_STREAM_STOP = "on_stream_stop"
|
||||
CONF_ON_IMAGE = "on_image"
|
||||
|
||||
camera_range_param = cv.int_range(min=-2, max=2)
|
||||
|
||||
|
@ -221,6 +227,11 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
|||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_IMAGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32CameraImageTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
@ -289,3 +300,9 @@ async def to_code(config):
|
|||
for conf in config.get(CONF_ON_STREAM_STOP, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_IMAGE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(ESP32CameraImageData, "image")], conf
|
||||
)
|
||||
|
|
|
@ -335,8 +335,8 @@ void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) {
|
|||
}
|
||||
|
||||
/* ---------------- public API (specific) ---------------- */
|
||||
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f) {
|
||||
this->new_image_callback_.add(std::move(f));
|
||||
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) {
|
||||
this->new_image_callback_.add(std::move(callback));
|
||||
}
|
||||
void ESP32Camera::add_stream_start_callback(std::function<void()> &&callback) {
|
||||
this->stream_start_callback_.add(std::move(callback));
|
||||
|
|
|
@ -86,6 +86,11 @@ class CameraImage {
|
|||
uint8_t requesters_;
|
||||
};
|
||||
|
||||
struct CameraImageData {
|
||||
uint8_t *data;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
/* ---------------- CameraImageReader class ---------------- */
|
||||
class CameraImageReader {
|
||||
public:
|
||||
|
@ -147,12 +152,12 @@ class ESP32Camera : public Component, public EntityBase {
|
|||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
/* public API (specific) */
|
||||
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f);
|
||||
void start_stream(CameraRequester requester);
|
||||
void stop_stream(CameraRequester requester);
|
||||
void request_image(CameraRequester requester);
|
||||
void update_camera_parameters();
|
||||
|
||||
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback);
|
||||
void add_stream_start_callback(std::function<void()> &&callback);
|
||||
void add_stream_stop_callback(std::function<void()> &&callback);
|
||||
|
||||
|
@ -196,7 +201,7 @@ class ESP32Camera : public Component, public EntityBase {
|
|||
uint8_t stream_requesters_{0};
|
||||
QueueHandle_t framebuffer_get_queue_;
|
||||
QueueHandle_t framebuffer_return_queue_;
|
||||
CallbackManager<void(std::shared_ptr<CameraImage>)> new_image_callback_;
|
||||
CallbackManager<void(std::shared_ptr<CameraImage>)> new_image_callback_{};
|
||||
CallbackManager<void()> stream_start_callback_{};
|
||||
CallbackManager<void()> stream_stop_callback_{};
|
||||
|
||||
|
@ -207,6 +212,18 @@ class ESP32Camera : public Component, public EntityBase {
|
|||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern ESP32Camera *global_esp32_camera;
|
||||
|
||||
class ESP32CameraImageTrigger : public Trigger<CameraImageData> {
|
||||
public:
|
||||
explicit ESP32CameraImageTrigger(ESP32Camera *parent) {
|
||||
parent->add_image_callback([this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
|
||||
CameraImageData camera_image_data{};
|
||||
camera_image_data.length = image->get_data_length();
|
||||
camera_image_data.data = image->get_data_buffer();
|
||||
this->trigger(camera_image_data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class ESP32CameraStreamStartTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) {
|
||||
|
|
|
@ -18,6 +18,7 @@ from esphome.const import (
|
|||
CONF_ON_SPEED_SET,
|
||||
CONF_ON_TURN_OFF,
|
||||
CONF_ON_TURN_ON,
|
||||
CONF_ON_PRESET_SET,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_DIRECTION,
|
||||
CONF_RESTORE_MODE,
|
||||
|
@ -57,6 +58,9 @@ CycleSpeedAction = fan_ns.class_("CycleSpeedAction", automation.Action)
|
|||
FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template())
|
||||
FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template())
|
||||
FanSpeedSetTrigger = fan_ns.class_("FanSpeedSetTrigger", automation.Trigger.template())
|
||||
FanPresetSetTrigger = fan_ns.class_(
|
||||
"FanPresetSetTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
|
||||
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
|
||||
|
@ -101,9 +105,46 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte
|
|||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
_PRESET_MODES_SCHEMA = cv.All(
|
||||
cv.ensure_list(cv.string_strict),
|
||||
cv.Length(min=1),
|
||||
)
|
||||
|
||||
|
||||
def validate_preset_modes(value):
|
||||
# Check against defined schema
|
||||
value = _PRESET_MODES_SCHEMA(value)
|
||||
|
||||
# Ensure preset names are unique
|
||||
errors = []
|
||||
presets = set()
|
||||
for i, preset in enumerate(value):
|
||||
# If name does not exist yet add it
|
||||
if preset not in presets:
|
||||
presets.add(preset)
|
||||
continue
|
||||
|
||||
# Otherwise it's an error
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
f"Found duplicate preset name '{preset}'. Presets must have unique names.",
|
||||
[i],
|
||||
)
|
||||
)
|
||||
|
||||
if errors:
|
||||
raise cv.MultipleInvalid(errors)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
async def setup_fan_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
|
@ -154,6 +195,9 @@ async def setup_fan_core_(var, config):
|
|||
for conf in config.get(CONF_ON_SPEED_SET, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_PRESET_SET, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
async def register_fan(var, config):
|
||||
|
|
|
@ -165,5 +165,23 @@ class FanSpeedSetTrigger : public Trigger<> {
|
|||
int last_speed_;
|
||||
};
|
||||
|
||||
class FanPresetSetTrigger : public Trigger<> {
|
||||
public:
|
||||
FanPresetSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto preset_mode = state->preset_mode;
|
||||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||
this->last_preset_mode_ = preset_mode;
|
||||
if (should_trigger) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
this->last_preset_mode_ = state->preset_mode;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string last_preset_mode_;
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
} // namespace esphome
|
||||
|
|
|
@ -32,9 +32,12 @@ void FanCall::perform() {
|
|||
if (this->direction_.has_value()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
|
||||
}
|
||||
|
||||
if (!this->preset_mode_.empty()) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_.c_str());
|
||||
}
|
||||
this->parent_.control(*this);
|
||||
}
|
||||
|
||||
void FanCall::validate_() {
|
||||
auto traits = this->parent_.get_traits();
|
||||
|
||||
|
@ -62,6 +65,15 @@ void FanCall::validate_() {
|
|||
ESP_LOGW(TAG, "'%s' - This fan does not support directions!", this->parent_.get_name().c_str());
|
||||
this->direction_.reset();
|
||||
}
|
||||
|
||||
if (!this->preset_mode_.empty()) {
|
||||
const auto &preset_modes = traits.supported_preset_modes();
|
||||
if (preset_modes.find(this->preset_mode_) == preset_modes.end()) {
|
||||
ESP_LOGW(TAG, "'%s' - This fan does not support preset mode '%s'!", this->parent_.get_name().c_str(),
|
||||
this->preset_mode_.c_str());
|
||||
this->preset_mode_.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FanCall FanRestoreState::to_call(Fan &fan) {
|
||||
|
@ -70,6 +82,14 @@ FanCall FanRestoreState::to_call(Fan &fan) {
|
|||
call.set_oscillating(this->oscillating);
|
||||
call.set_speed(this->speed);
|
||||
call.set_direction(this->direction);
|
||||
|
||||
if (fan.get_traits().supports_preset_modes()) {
|
||||
// Use stored preset index to get preset name
|
||||
const auto &preset_modes = fan.get_traits().supported_preset_modes();
|
||||
if (this->preset_mode < preset_modes.size()) {
|
||||
call.set_preset_mode(*std::next(preset_modes.begin(), this->preset_mode));
|
||||
}
|
||||
}
|
||||
return call;
|
||||
}
|
||||
void FanRestoreState::apply(Fan &fan) {
|
||||
|
@ -77,6 +97,14 @@ void FanRestoreState::apply(Fan &fan) {
|
|||
fan.oscillating = this->oscillating;
|
||||
fan.speed = this->speed;
|
||||
fan.direction = this->direction;
|
||||
|
||||
if (fan.get_traits().supports_preset_modes()) {
|
||||
// Use stored preset index to get preset name
|
||||
const auto &preset_modes = fan.get_traits().supported_preset_modes();
|
||||
if (this->preset_mode < preset_modes.size()) {
|
||||
fan.preset_mode = *std::next(preset_modes.begin(), this->preset_mode);
|
||||
}
|
||||
}
|
||||
fan.publish_state();
|
||||
}
|
||||
|
||||
|
@ -100,7 +128,9 @@ void Fan::publish_state() {
|
|||
if (traits.supports_direction()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
|
||||
}
|
||||
|
||||
if (traits.supports_preset_modes() && !this->preset_mode.empty()) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode.c_str());
|
||||
}
|
||||
this->state_callback_.call();
|
||||
this->save_state_();
|
||||
}
|
||||
|
@ -143,20 +173,36 @@ void Fan::save_state_() {
|
|||
state.oscillating = this->oscillating;
|
||||
state.speed = this->speed;
|
||||
state.direction = this->direction;
|
||||
|
||||
if (this->get_traits().supports_preset_modes() && !this->preset_mode.empty()) {
|
||||
const auto &preset_modes = this->get_traits().supported_preset_modes();
|
||||
// Store index of current preset mode
|
||||
auto preset_iterator = preset_modes.find(this->preset_mode);
|
||||
if (preset_iterator != preset_modes.end())
|
||||
state.preset_mode = std::distance(preset_modes.begin(), preset_iterator);
|
||||
}
|
||||
|
||||
this->rtc_.save(&state);
|
||||
}
|
||||
|
||||
void Fan::dump_traits_(const char *tag, const char *prefix) {
|
||||
if (this->get_traits().supports_speed()) {
|
||||
auto traits = this->get_traits();
|
||||
|
||||
if (traits.supports_speed()) {
|
||||
ESP_LOGCONFIG(tag, "%s Speed: YES", prefix);
|
||||
ESP_LOGCONFIG(tag, "%s Speed count: %d", prefix, this->get_traits().supported_speed_count());
|
||||
ESP_LOGCONFIG(tag, "%s Speed count: %d", prefix, traits.supported_speed_count());
|
||||
}
|
||||
if (this->get_traits().supports_oscillation()) {
|
||||
if (traits.supports_oscillation()) {
|
||||
ESP_LOGCONFIG(tag, "%s Oscillation: YES", prefix);
|
||||
}
|
||||
if (this->get_traits().supports_direction()) {
|
||||
if (traits.supports_direction()) {
|
||||
ESP_LOGCONFIG(tag, "%s Direction: YES", prefix);
|
||||
}
|
||||
if (traits.supports_preset_modes()) {
|
||||
ESP_LOGCONFIG(tag, "%s Supported presets:", prefix);
|
||||
for (const std::string &s : traits.supported_preset_modes())
|
||||
ESP_LOGCONFIG(tag, "%s - %s", prefix, s.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fan
|
||||
|
|
|
@ -72,6 +72,11 @@ class FanCall {
|
|||
return *this;
|
||||
}
|
||||
optional<FanDirection> get_direction() const { return this->direction_; }
|
||||
FanCall &set_preset_mode(const std::string &preset_mode) {
|
||||
this->preset_mode_ = preset_mode;
|
||||
return *this;
|
||||
}
|
||||
std::string get_preset_mode() const { return this->preset_mode_; }
|
||||
|
||||
void perform();
|
||||
|
||||
|
@ -83,6 +88,7 @@ class FanCall {
|
|||
optional<bool> oscillating_;
|
||||
optional<int> speed_;
|
||||
optional<FanDirection> direction_{};
|
||||
std::string preset_mode_{};
|
||||
};
|
||||
|
||||
struct FanRestoreState {
|
||||
|
@ -90,6 +96,7 @@ struct FanRestoreState {
|
|||
int speed;
|
||||
bool oscillating;
|
||||
FanDirection direction;
|
||||
uint8_t preset_mode;
|
||||
|
||||
/// Convert this struct to a fan call that can be performed.
|
||||
FanCall to_call(Fan &fan);
|
||||
|
@ -107,6 +114,8 @@ class Fan : public EntityBase {
|
|||
int speed{0};
|
||||
/// The current direction of the fan
|
||||
FanDirection direction{FanDirection::FORWARD};
|
||||
// The current preset mode of the fan
|
||||
std::string preset_mode{};
|
||||
|
||||
FanCall turn_on();
|
||||
FanCall turn_off();
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
|
@ -25,12 +28,19 @@ class FanTraits {
|
|||
bool supports_direction() const { return this->direction_; }
|
||||
/// Set whether this fan supports changing direction
|
||||
void set_direction(bool direction) { this->direction_ = direction; }
|
||||
/// Return the preset modes supported by the fan.
|
||||
std::set<std::string> supported_preset_modes() const { return this->preset_modes_; }
|
||||
/// Set the preset modes supported by the fan.
|
||||
void set_supported_preset_modes(const std::set<std::string> &preset_modes) { this->preset_modes_ = preset_modes; }
|
||||
/// Return if preset modes are supported
|
||||
bool supports_preset_modes() const { return !this->preset_modes_.empty(); }
|
||||
|
||||
protected:
|
||||
bool oscillation_{false};
|
||||
bool speed_{false};
|
||||
bool direction_{false};
|
||||
int speed_count_{};
|
||||
std::set<std::string> preset_modes_{};
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
|
|
6
esphome/components/ft5x06/__init__.py
Normal file
6
esphome/components/ft5x06/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ft5x06_ns = cg.esphome_ns.namespace("ft5x06")
|
26
esphome/components/ft5x06/touchscreen/__init__.py
Normal file
26
esphome/components/ft5x06/touchscreen/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome.components import i2c, touchscreen
|
||||
from esphome.const import CONF_ID
|
||||
from .. import ft5x06_ns
|
||||
|
||||
FT5x06ButtonListener = ft5x06_ns.class_("FT5x06ButtonListener")
|
||||
FT5x06Touchscreen = ft5x06_ns.class_(
|
||||
"FT5x06Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
cg.Component,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(FT5x06Touchscreen),
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x48))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await i2c.register_i2c_device(var, config)
|
||||
await touchscreen.register_touchscreen(var, config)
|
124
esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h
Normal file
124
esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h
Normal file
|
@ -0,0 +1,124 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ft5x06 {
|
||||
|
||||
static const char *const TAG = "ft5x06.touchscreen";
|
||||
|
||||
enum VendorId {
|
||||
FT5X06_ID_UNKNOWN = 0,
|
||||
FT5X06_ID_1 = 0x51,
|
||||
FT5X06_ID_2 = 0x11,
|
||||
FT5X06_ID_3 = 0xCD,
|
||||
};
|
||||
|
||||
enum FTCmd : uint8_t {
|
||||
FT5X06_MODE_REG = 0x00,
|
||||
FT5X06_ORIGIN_REG = 0x08,
|
||||
FT5X06_RESOLUTION_REG = 0x0C,
|
||||
FT5X06_VENDOR_ID_REG = 0xA8,
|
||||
FT5X06_TD_STATUS = 0x02,
|
||||
FT5X06_TOUCH_DATA = 0x03,
|
||||
FT5X06_I_MODE = 0xA4,
|
||||
FT5X06_TOUCH_MAX = 0x4C,
|
||||
};
|
||||
|
||||
enum FTMode : uint8_t {
|
||||
FT5X06_OP_MODE = 0,
|
||||
FT5X06_SYSINFO_MODE = 0x10,
|
||||
FT5X06_TEST_MODE = 0x40,
|
||||
};
|
||||
|
||||
static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
|
||||
|
||||
class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override {
|
||||
esph_log_config(TAG, "Setting up FT5x06 Touchscreen...");
|
||||
// wait 200ms after reset.
|
||||
this->set_timeout(200, [this] { this->continue_setup_(); });
|
||||
}
|
||||
|
||||
void continue_setup_(void) {
|
||||
uint8_t data[4];
|
||||
if (!this->set_mode_(FT5X06_OP_MODE))
|
||||
return;
|
||||
|
||||
if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID"))
|
||||
return;
|
||||
switch (data[0]) {
|
||||
case FT5X06_ID_1:
|
||||
case FT5X06_ID_2:
|
||||
case FT5X06_ID_3:
|
||||
this->vendor_id_ = (VendorId) data[0];
|
||||
esph_log_d(TAG, "Read vendor ID 0x%X", data[0]);
|
||||
break;
|
||||
|
||||
default:
|
||||
esph_log_e(TAG, "Unknown vendor ID 0x%X", data[0]);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// reading the chip registers to get max x/y does not seem to work.
|
||||
this->x_raw_max_ = this->display_->get_width();
|
||||
this->y_raw_max_ = this->display_->get_height();
|
||||
esph_log_config(TAG, "FT5x06 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
void update_touches() override {
|
||||
uint8_t touch_cnt;
|
||||
uint8_t data[MAX_TOUCHES][6];
|
||||
|
||||
if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) {
|
||||
esph_log_w(TAG, "Failed to read status");
|
||||
return;
|
||||
}
|
||||
if (touch_cnt == 0)
|
||||
return;
|
||||
|
||||
if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) {
|
||||
esph_log_w(TAG, "Failed to read touch data");
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i != touch_cnt; i++) {
|
||||
uint8_t status = data[i][0] >> 6;
|
||||
uint8_t id = data[i][2] >> 3;
|
||||
uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]);
|
||||
uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]);
|
||||
|
||||
esph_log_d(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
|
||||
if (status == 0 || status == 2) {
|
||||
this->set_raw_touch_position_(id, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dump_config() override {
|
||||
esph_log_config(TAG, "FT5x06 Touchscreen:");
|
||||
esph_log_config(TAG, " Address: 0x%02X", this->address_);
|
||||
esph_log_config(TAG, " Vendor ID: 0x%X", (int) this->vendor_id_);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool err_check_(i2c::ErrorCode err, const char *msg) {
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
esph_log_e(TAG, "%s failed - err 0x%X", msg, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool set_mode_(FTMode mode) {
|
||||
return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode");
|
||||
}
|
||||
VendorId vendor_id_{FT5X06_ID_UNKNOWN};
|
||||
};
|
||||
|
||||
} // namespace ft5x06
|
||||
} // namespace esphome
|
1
esphome/components/ft63x6/__init__.py
Normal file
1
esphome/components/ft63x6/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@gpambrozio"]
|
99
esphome/components/ft63x6/ft63x6.cpp
Normal file
99
esphome/components/ft63x6/ft63x6.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
/**************************************************************************/
|
||||
/*!
|
||||
Author: Gustavo Ambrozio
|
||||
Based on work by: Atsushi Sasaki (https://github.com/aselectroworks/Arduino-FT6336U)
|
||||
*/
|
||||
/**************************************************************************/
|
||||
|
||||
#include "ft63x6.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// Registers
|
||||
// Reference: https://focuslcds.com/content/FT6236.pdf
|
||||
namespace esphome {
|
||||
namespace ft63x6 {
|
||||
|
||||
static const uint8_t FT63X6_ADDR_TOUCH_COUNT = 0x02;
|
||||
|
||||
static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05;
|
||||
|
||||
static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH2_X = 0x09;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH2_Y = 0x0B;
|
||||
|
||||
static const char *const TAG = "FT63X6Touchscreen";
|
||||
|
||||
void FT63X6Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up FT63X6Touchscreen Touchscreen...");
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
}
|
||||
|
||||
this->hard_reset_();
|
||||
|
||||
// Get touch resolution
|
||||
this->x_raw_max_ = 320;
|
||||
this->y_raw_max_ = 480;
|
||||
}
|
||||
|
||||
void FT63X6Touchscreen::update_touches() {
|
||||
int touch_count = this->read_touch_count_();
|
||||
if (touch_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH1_ID); // id1 = 0 or 1
|
||||
int16_t x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_X);
|
||||
int16_t y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_Y);
|
||||
this->set_raw_touch_position_(touch_id, x, y);
|
||||
|
||||
if (touch_count >= 2) {
|
||||
touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH2_ID); // id2 = 0 or 1(~id1 & 0x01)
|
||||
x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_X);
|
||||
y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_Y);
|
||||
this->set_raw_touch_position_(touch_id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void FT63X6Touchscreen::hard_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FT63X6Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "FT63X6 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
}
|
||||
|
||||
uint8_t FT63X6Touchscreen::read_touch_count_() { return this->read_byte_(FT63X6_ADDR_TOUCH_COUNT); }
|
||||
|
||||
// Touch functions
|
||||
uint16_t FT63X6Touchscreen::read_touch_coordinate_(uint8_t coordinate) {
|
||||
uint8_t read_buf[2];
|
||||
read_buf[0] = this->read_byte_(coordinate);
|
||||
read_buf[1] = this->read_byte_(coordinate + 1);
|
||||
return ((read_buf[0] & 0x0f) << 8) | read_buf[1];
|
||||
}
|
||||
uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t id_address) { return this->read_byte_(id_address) >> 4; }
|
||||
|
||||
uint8_t FT63X6Touchscreen::read_byte_(uint8_t addr) {
|
||||
uint8_t byte = 0;
|
||||
this->read_byte(addr, &byte);
|
||||
return byte;
|
||||
}
|
||||
|
||||
} // namespace ft63x6
|
||||
} // namespace esphome
|
41
esphome/components/ft63x6/ft63x6.h
Normal file
41
esphome/components/ft63x6/ft63x6.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**************************************************************************/
|
||||
/*!
|
||||
Author: Gustavo Ambrozio
|
||||
Based on work by: Atsushi Sasaki (https://github.com/aselectroworks/Arduino-FT6336U)
|
||||
*/
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ft63x6 {
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||
|
||||
protected:
|
||||
void hard_reset_();
|
||||
uint8_t read_byte_(uint8_t addr);
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{nullptr};
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
|
||||
uint8_t read_touch_count_();
|
||||
uint16_t read_touch_coordinate_(uint8_t coordinate);
|
||||
uint8_t read_touch_id_(uint8_t id_address);
|
||||
};
|
||||
|
||||
} // namespace ft63x6
|
||||
} // namespace esphome
|
44
esphome/components/ft63x6/touchscreen.py
Normal file
44
esphome/components/ft63x6/touchscreen.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import i2c, touchscreen
|
||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
|
||||
|
||||
CODEOWNERS = ["@gpambrozio"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ft6336u_ns = cg.esphome_ns.namespace("ft63x6")
|
||||
FT63X6Touchscreen = ft6336u_ns.class_(
|
||||
"FT63X6Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CONF_FT63X6_ID = "ft63x6_id"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(FT63X6Touchscreen),
|
||||
cv.Optional(CONF_INTERRUPT_PIN): cv.All(
|
||||
pins.internal_gpio_input_pin_schema
|
||||
),
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x38))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if interrupt_pin_config := config.get(CONF_INTERRUPT_PIN):
|
||||
interrupt_pin = await cg.gpio_pin_expression(interrupt_pin_config)
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
||||
if reset_pin_config := config.get(CONF_RESET_PIN):
|
||||
reset_pin = await cg.gpio_pin_expression(reset_pin_config)
|
||||
cg.add(var.set_reset_pin(reset_pin))
|
96
esphome/components/graphical_display_menu/__init__.py
Normal file
96
esphome/components/graphical_display_menu/__init__.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import display, font, color
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID
|
||||
from esphome import automation, core
|
||||
|
||||
from esphome.components.display_menu_base import (
|
||||
DISPLAY_MENU_BASE_SCHEMA,
|
||||
DisplayMenuComponent,
|
||||
display_menu_to_code,
|
||||
)
|
||||
|
||||
CONF_DISPLAY = "display"
|
||||
CONF_FONT = "font"
|
||||
CONF_MENU_ITEM_VALUE = "menu_item_value"
|
||||
CONF_FOREGROUND_COLOR = "foreground_color"
|
||||
CONF_BACKGROUND_COLOR = "background_color"
|
||||
CONF_ON_REDRAW = "on_redraw"
|
||||
|
||||
graphical_display_menu_ns = cg.esphome_ns.namespace("graphical_display_menu")
|
||||
GraphicalDisplayMenu = graphical_display_menu_ns.class_(
|
||||
"GraphicalDisplayMenu", DisplayMenuComponent
|
||||
)
|
||||
GraphicalDisplayMenuConstPtr = GraphicalDisplayMenu.operator("ptr").operator("const")
|
||||
MenuItemValueArguments = graphical_display_menu_ns.struct("MenuItemValueArguments")
|
||||
MenuItemValueArgumentsConstPtr = MenuItemValueArguments.operator("ptr").operator(
|
||||
"const"
|
||||
)
|
||||
GraphicalDisplayMenuOnRedrawTrigger = graphical_display_menu_ns.class_(
|
||||
"GraphicalDisplayMenuOnRedrawTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@MrMDavidson"]
|
||||
|
||||
AUTO_LOAD = ["display_menu_base"]
|
||||
|
||||
CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GraphicalDisplayMenu),
|
||||
cv.Optional(CONF_DISPLAY): cv.use_id(display.DisplayBuffer),
|
||||
cv.Required(CONF_FONT): cv.use_id(font.Font),
|
||||
cv.Optional(CONF_MENU_ITEM_VALUE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color.ColorStruct),
|
||||
cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color.ColorStruct),
|
||||
cv.Optional(CONF_ON_REDRAW): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
GraphicalDisplayMenuOnRedrawTrigger
|
||||
)
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if display_config := config.get(CONF_DISPLAY):
|
||||
drawing_display = await cg.get_variable(display_config)
|
||||
cg.add(var.set_display(drawing_display))
|
||||
|
||||
menu_font = await cg.get_variable(config[CONF_FONT])
|
||||
cg.add(var.set_font(menu_font))
|
||||
|
||||
if (menu_item_value_config := config.get(CONF_MENU_ITEM_VALUE, None)) is not None:
|
||||
if isinstance(menu_item_value_config, core.Lambda):
|
||||
template_ = await cg.templatable(
|
||||
menu_item_value_config,
|
||||
[(MenuItemValueArgumentsConstPtr, "it")],
|
||||
cg.std_string,
|
||||
)
|
||||
cg.add(var.set_menu_item_value(template_))
|
||||
else:
|
||||
cg.add(var.set_menu_item_value(menu_item_value_config))
|
||||
|
||||
if foreground_color_config := config.get(CONF_FOREGROUND_COLOR):
|
||||
foreground_color = await cg.get_variable(foreground_color_config)
|
||||
cg.add(var.set_foreground_color(foreground_color))
|
||||
|
||||
if background_color_config := config.get(CONF_BACKGROUND_COLOR):
|
||||
background_color = await cg.get_variable(background_color_config)
|
||||
cg.add(var.set_background_color(background_color))
|
||||
|
||||
for conf in config.get(CONF_ON_REDRAW, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(GraphicalDisplayMenuConstPtr, "it")], conf
|
||||
)
|
||||
|
||||
await display_menu_to_code(var, config)
|
||||
|
||||
cg.add_define("USE_GRAPHICAL_DISPLAY_MENU")
|
|
@ -0,0 +1,243 @@
|
|||
#include "graphical_display_menu.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cstdlib>
|
||||
#include "esphome/components/display/display.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace graphical_display_menu {
|
||||
|
||||
static const char *const TAG = "graphical_display_menu";
|
||||
|
||||
void GraphicalDisplayMenu::setup() {
|
||||
if (this->display_ != nullptr) {
|
||||
display::display_writer_t writer = [this](display::Display &it) { this->draw_menu(); };
|
||||
this->display_page_ = make_unique<display::DisplayPage>(writer);
|
||||
}
|
||||
|
||||
if (!this->menu_item_value_.has_value()) {
|
||||
this->menu_item_value_ = [](const MenuItemValueArguments *it) {
|
||||
std::string label = " ";
|
||||
if (it->is_item_selected && it->is_menu_editing) {
|
||||
label.append(">");
|
||||
label.append(it->item->get_value_text());
|
||||
label.append("<");
|
||||
} else {
|
||||
label.append("(");
|
||||
label.append(it->item->get_value_text());
|
||||
label.append(")");
|
||||
}
|
||||
return label;
|
||||
};
|
||||
}
|
||||
|
||||
display_menu_base::DisplayMenuComponent::setup();
|
||||
}
|
||||
|
||||
void GraphicalDisplayMenu::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Graphical Display Menu");
|
||||
ESP_LOGCONFIG(TAG, "Has Display: %s", YESNO(this->display_ != nullptr));
|
||||
ESP_LOGCONFIG(TAG, "Popup Mode: %s", YESNO(this->display_ != nullptr));
|
||||
ESP_LOGCONFIG(TAG, "Advanced Drawing Mode: %s", YESNO(this->display_ == nullptr));
|
||||
ESP_LOGCONFIG(TAG, "Has Font: %s", YESNO(this->font_ != nullptr));
|
||||
ESP_LOGCONFIG(TAG, "Mode: %s", this->mode_ == display_menu_base::MENU_MODE_ROTARY ? "Rotary" : "Joystick");
|
||||
ESP_LOGCONFIG(TAG, "Active: %s", YESNO(this->active_));
|
||||
ESP_LOGCONFIG(TAG, "Menu items:");
|
||||
for (size_t i = 0; i < this->displayed_item_->items_size(); i++) {
|
||||
auto *item = this->displayed_item_->get_item(i);
|
||||
ESP_LOGCONFIG(TAG, " %i: %s (Type: %s, Immediate Edit: %s)", i, item->get_text().c_str(),
|
||||
LOG_STR_ARG(display_menu_base::menu_item_type_to_string(item->get_type())),
|
||||
YESNO(item->get_immediate_edit()));
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicalDisplayMenu::set_display(display::Display *display) { this->display_ = display; }
|
||||
|
||||
void GraphicalDisplayMenu::set_font(display::BaseFont *font) { this->font_ = font; }
|
||||
|
||||
void GraphicalDisplayMenu::set_foreground_color(Color foreground_color) { this->foreground_color_ = foreground_color; }
|
||||
void GraphicalDisplayMenu::set_background_color(Color background_color) { this->background_color_ = background_color; }
|
||||
|
||||
void GraphicalDisplayMenu::on_before_show() {
|
||||
if (this->display_ != nullptr) {
|
||||
this->previous_display_page_ = this->display_->get_active_page();
|
||||
this->display_->show_page(this->display_page_.get());
|
||||
this->display_->clear();
|
||||
} else {
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicalDisplayMenu::on_before_hide() {
|
||||
if (this->previous_display_page_ != nullptr) {
|
||||
this->display_->show_page((display::DisplayPage *) this->previous_display_page_);
|
||||
this->display_->clear();
|
||||
this->update();
|
||||
this->previous_display_page_ = nullptr;
|
||||
} else {
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicalDisplayMenu::draw_and_update() {
|
||||
this->update();
|
||||
|
||||
// If we're in advanced drawing mode we won't have a display and will instead require the update callback to do
|
||||
// our drawing
|
||||
if (this->display_ != nullptr) {
|
||||
draw_menu();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicalDisplayMenu::draw_menu() {
|
||||
if (this->display_ == nullptr) {
|
||||
ESP_LOGE(TAG, "draw_menu() called without a display_. This is only available when using the menu in pop up mode");
|
||||
return;
|
||||
}
|
||||
display::Rect bounds(0, 0, this->display_->get_width(), this->display_->get_height());
|
||||
this->draw_menu_internal_(this->display_, &bounds);
|
||||
}
|
||||
|
||||
void GraphicalDisplayMenu::draw(display::Display *display, const display::Rect *bounds) {
|
||||
this->draw_menu_internal_(display, bounds);
|
||||
}
|
||||
|
||||
void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const display::Rect *bounds) {
|
||||
int total_height = 0;
|
||||
int y_padding = 2;
|
||||
bool scroll_menu_items = false;
|
||||
std::vector<display::Rect> menu_dimensions;
|
||||
int number_items_fit_to_screen = 0;
|
||||
const int max_item_index = this->displayed_item_->items_size() - 1;
|
||||
|
||||
for (size_t i = 0; i <= max_item_index; i++) {
|
||||
const auto *item = this->displayed_item_->get_item(i);
|
||||
const bool selected = i == this->cursor_index_;
|
||||
const display::Rect item_dimensions = this->measure_item(display, item, bounds, selected);
|
||||
|
||||
menu_dimensions.push_back(item_dimensions);
|
||||
total_height += item_dimensions.h + (i == 0 ? 0 : y_padding);
|
||||
|
||||
if (total_height <= bounds->h) {
|
||||
number_items_fit_to_screen++;
|
||||
} else {
|
||||
// Scroll the display if the selected item or the item immediately after it overflows
|
||||
if ((selected) || (i == this->cursor_index_ + 1)) {
|
||||
scroll_menu_items = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine what items to draw
|
||||
int first_item_index = 0;
|
||||
int last_item_index = max_item_index;
|
||||
|
||||
if (number_items_fit_to_screen <= 1) {
|
||||
// If only one item can fit to the bounds draw the current cursor item
|
||||
last_item_index = std::min(last_item_index, this->cursor_index_ + 1);
|
||||
first_item_index = this->cursor_index_;
|
||||
} else {
|
||||
if (scroll_menu_items) {
|
||||
// Attempt to draw the item after the current item (+1 for equality check in the draw loop)
|
||||
last_item_index = std::min(last_item_index, this->cursor_index_ + 1);
|
||||
|
||||
// Go back through the measurements to determine how many prior items we can fit
|
||||
int height_left_to_use = bounds->h;
|
||||
for (int i = last_item_index; i >= 0; i--) {
|
||||
const display::Rect item_dimensions = menu_dimensions[i];
|
||||
height_left_to_use -= (item_dimensions.h + y_padding);
|
||||
|
||||
if (height_left_to_use <= 0) {
|
||||
// Ran out of space - this is our first item to draw
|
||||
first_item_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const int items_to_draw = last_item_index - first_item_index;
|
||||
// Dont't draw last item partially if it is the selected item
|
||||
if ((this->cursor_index_ == last_item_index) && (number_items_fit_to_screen <= items_to_draw) &&
|
||||
(first_item_index < max_item_index)) {
|
||||
first_item_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render the items into the view port
|
||||
display->start_clipping(*bounds);
|
||||
|
||||
int y_offset = bounds->y;
|
||||
for (size_t i = first_item_index; i <= last_item_index; i++) {
|
||||
const auto *item = this->displayed_item_->get_item(i);
|
||||
const bool selected = i == this->cursor_index_;
|
||||
display::Rect dimensions = menu_dimensions[i];
|
||||
|
||||
dimensions.y = y_offset;
|
||||
dimensions.x = bounds->x;
|
||||
this->draw_item(display, item, &dimensions, selected);
|
||||
|
||||
y_offset = dimensions.y + dimensions.h + y_padding;
|
||||
}
|
||||
|
||||
display->end_clipping();
|
||||
}
|
||||
|
||||
display::Rect GraphicalDisplayMenu::measure_item(display::Display *display, const display_menu_base::MenuItem *item,
|
||||
const display::Rect *bounds, const bool selected) {
|
||||
display::Rect dimensions(0, 0, 0, 0);
|
||||
|
||||
if (selected) {
|
||||
// TODO: Support selection glyph
|
||||
dimensions.w += 0;
|
||||
dimensions.h += 0;
|
||||
}
|
||||
|
||||
std::string label = item->get_text();
|
||||
if (item->has_value()) {
|
||||
// Append to label
|
||||
MenuItemValueArguments args(item, selected, this->editing_);
|
||||
label.append(this->menu_item_value_.value(&args));
|
||||
}
|
||||
|
||||
int x1;
|
||||
int y1;
|
||||
int width;
|
||||
int height;
|
||||
display->get_text_bounds(0, 0, label.c_str(), this->font_, display::TextAlign::TOP_LEFT, &x1, &y1, &width, &height);
|
||||
|
||||
dimensions.w = std::min((int16_t) width, bounds->w);
|
||||
dimensions.h = std::min((int16_t) height, bounds->h);
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
inline void GraphicalDisplayMenu::draw_item(display::Display *display, const display_menu_base::MenuItem *item,
|
||||
const display::Rect *bounds, const bool selected) {
|
||||
const auto background_color = selected ? this->foreground_color_ : this->background_color_;
|
||||
const auto foreground_color = selected ? this->background_color_ : this->foreground_color_;
|
||||
|
||||
// int background_width = std::max(bounds->width, available_width);
|
||||
int background_width = bounds->w;
|
||||
|
||||
if (selected) {
|
||||
display->filled_rectangle(bounds->x, bounds->y, background_width, bounds->h, background_color);
|
||||
}
|
||||
|
||||
std::string label = item->get_text();
|
||||
if (item->has_value()) {
|
||||
MenuItemValueArguments args(item, selected, this->editing_);
|
||||
label.append(this->menu_item_value_.value(&args));
|
||||
}
|
||||
|
||||
display->print(bounds->x, bounds->y, this->font_, foreground_color, display::TextAlign::TOP_LEFT, label.c_str());
|
||||
}
|
||||
|
||||
void GraphicalDisplayMenu::draw_item(const display_menu_base::MenuItem *item, const uint8_t row, const bool selected) {
|
||||
ESP_LOGE(TAG, "draw_item(MenuItem *item, uint8_t row, bool selected) called. The graphical_display_menu specific "
|
||||
"draw_item should be called.");
|
||||
}
|
||||
|
||||
void GraphicalDisplayMenu::update() { this->on_redraw_callbacks_.call(); }
|
||||
|
||||
} // namespace graphical_display_menu
|
||||
} // namespace esphome
|
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/components/display_menu_base/display_menu_base.h"
|
||||
#include "esphome/components/display_menu_base/menu_item.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include <cstdlib>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
// forward declare from display namespace
|
||||
namespace display {
|
||||
class Display;
|
||||
class DisplayPage;
|
||||
class BaseFont;
|
||||
class Rect;
|
||||
} // namespace display
|
||||
|
||||
namespace graphical_display_menu {
|
||||
|
||||
const Color COLOR_ON(255, 255, 255, 255);
|
||||
const Color COLOR_OFF(0, 0, 0, 0);
|
||||
|
||||
struct MenuItemValueArguments {
|
||||
MenuItemValueArguments(const display_menu_base::MenuItem *item, bool is_item_selected, bool is_menu_editing) {
|
||||
this->item = item;
|
||||
this->is_item_selected = is_item_selected;
|
||||
this->is_menu_editing = is_menu_editing;
|
||||
}
|
||||
|
||||
const display_menu_base::MenuItem *item;
|
||||
bool is_item_selected;
|
||||
bool is_menu_editing;
|
||||
};
|
||||
|
||||
class GraphicalDisplayMenu : public display_menu_base::DisplayMenuComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_display(display::Display *display);
|
||||
void set_font(display::BaseFont *font);
|
||||
template<typename V> void set_menu_item_value(V menu_item_value) { this->menu_item_value_ = menu_item_value; }
|
||||
void set_foreground_color(Color foreground_color);
|
||||
void set_background_color(Color background_color);
|
||||
|
||||
void add_on_redraw_callback(std::function<void()> &&cb) { this->on_redraw_callbacks_.add(std::move(cb)); }
|
||||
|
||||
void draw(display::Display *display, const display::Rect *bounds);
|
||||
|
||||
protected:
|
||||
void draw_and_update() override;
|
||||
void draw_menu() override;
|
||||
void draw_menu_internal_(display::Display *display, const display::Rect *bounds);
|
||||
void draw_item(const display_menu_base::MenuItem *item, uint8_t row, bool selected) override;
|
||||
virtual display::Rect measure_item(display::Display *display, const display_menu_base::MenuItem *item,
|
||||
const display::Rect *bounds, bool selected);
|
||||
virtual void draw_item(display::Display *display, const display_menu_base::MenuItem *item,
|
||||
const display::Rect *bounds, bool selected);
|
||||
void update() override;
|
||||
|
||||
void on_before_show() override;
|
||||
void on_before_hide() override;
|
||||
|
||||
std::unique_ptr<display::DisplayPage> display_page_{nullptr};
|
||||
const display::DisplayPage *previous_display_page_{nullptr};
|
||||
display::Display *display_{nullptr};
|
||||
display::BaseFont *font_{nullptr};
|
||||
TemplatableValue<std::string, const MenuItemValueArguments *> menu_item_value_;
|
||||
Color foreground_color_{COLOR_ON};
|
||||
Color background_color_{COLOR_OFF};
|
||||
|
||||
CallbackManager<void()> on_redraw_callbacks_{};
|
||||
};
|
||||
|
||||
class GraphicalDisplayMenuOnRedrawTrigger : public Trigger<const GraphicalDisplayMenu *> {
|
||||
public:
|
||||
explicit GraphicalDisplayMenuOnRedrawTrigger(GraphicalDisplayMenu *parent) {
|
||||
parent->add_on_redraw_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace graphical_display_menu
|
||||
} // namespace esphome
|
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
|||
|
||||
from esphome import pins
|
||||
from esphome.components import i2c, touchscreen
|
||||
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_ROTATION
|
||||
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID
|
||||
from .. import gt911_ns
|
||||
|
||||
|
||||
|
@ -11,36 +11,21 @@ GT911ButtonListener = gt911_ns.class_("GT911ButtonListener")
|
|||
GT911Touchscreen = gt911_ns.class_(
|
||||
"GT911Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
cg.Component,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
ROTATIONS = {
|
||||
0: touchscreen.TouchRotation.ROTATE_0_DEGREES,
|
||||
90: touchscreen.TouchRotation.ROTATE_90_DEGREES,
|
||||
180: touchscreen.TouchRotation.ROTATE_180_DEGREES,
|
||||
270: touchscreen.TouchRotation.ROTATE_270_DEGREES,
|
||||
}
|
||||
CONFIG_SCHEMA = (
|
||||
touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GT911Touchscreen),
|
||||
cv.Optional(CONF_ROTATION): cv.enum(ROTATIONS),
|
||||
cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x5D))
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GT911Touchscreen),
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x5D))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
||||
if CONF_ROTATION in config:
|
||||
cg.add(var.set_rotation(ROTATIONS[config[CONF_ROTATION]]))
|
||||
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
||||
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
||||
|
|
|
@ -12,6 +12,7 @@ static const uint8_t GET_TOUCH_STATE[2] = {0x81, 0x4E};
|
|||
static const uint8_t CLEAR_TOUCH_STATE[3] = {0x81, 0x4E, 0x00};
|
||||
static const uint8_t GET_TOUCHES[2] = {0x81, 0x4F};
|
||||
static const uint8_t GET_SWITCHES[2] = {0x80, 0x4D};
|
||||
static const uint8_t GET_MAX_VALUES[2] = {0x80, 0x48};
|
||||
static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
|
||||
|
||||
#define ERROR_CHECK(err) \
|
||||
|
@ -21,24 +22,35 @@ static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
|
|||
return; \
|
||||
}
|
||||
|
||||
void IRAM_ATTR HOT Store::gpio_intr(Store *store) { store->available = true; }
|
||||
|
||||
void GT911Touchscreen::setup() {
|
||||
i2c::ErrorCode err;
|
||||
ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen...");
|
||||
// datasheet says NOT to use pullup/down on the int line.
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->interrupt_pin_->setup();
|
||||
|
||||
// check the configuration of the int line.
|
||||
uint8_t data;
|
||||
uint8_t data[4];
|
||||
err = this->write(GET_SWITCHES, 2);
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->read(&data, 1);
|
||||
err = this->read(data, 1);
|
||||
if (err == i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Read from switches: 0x%02X", data);
|
||||
this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_,
|
||||
(data & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE);
|
||||
ESP_LOGD(TAG, "Read from switches: 0x%02X", data[0]);
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
// datasheet says NOT to use pullup/down on the int line.
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_,
|
||||
(data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->write(GET_MAX_VALUES, 2);
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->read(data, sizeof(data));
|
||||
if (err == i2c::ERROR_OK) {
|
||||
this->x_raw_max_ = encode_uint16(data[1], data[0]);
|
||||
this->y_raw_max_ = encode_uint16(data[3], data[2]);
|
||||
esph_log_d(TAG, "Read max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err != i2c::ERROR_OK) {
|
||||
|
@ -46,31 +58,28 @@ void GT911Touchscreen::setup() {
|
|||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "GT911 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
void GT911Touchscreen::loop() {
|
||||
void GT911Touchscreen::update_touches() {
|
||||
i2c::ErrorCode err;
|
||||
touchscreen::TouchPoint tp;
|
||||
uint8_t touch_state = 0;
|
||||
uint8_t data[MAX_TOUCHES + 1][8]; // 8 bytes each for each point, plus extra space for the key byte
|
||||
|
||||
if (!this->store_.available)
|
||||
return;
|
||||
this->store_.available = false;
|
||||
|
||||
err = this->write(GET_TOUCH_STATE, sizeof(GET_TOUCH_STATE), false);
|
||||
ERROR_CHECK(err);
|
||||
err = this->read(&touch_state, 1);
|
||||
ERROR_CHECK(err);
|
||||
this->write(CLEAR_TOUCH_STATE, sizeof(CLEAR_TOUCH_STATE));
|
||||
|
||||
if ((touch_state & 0x80) == 0)
|
||||
return;
|
||||
uint8_t num_of_touches = touch_state & 0x07;
|
||||
|
||||
if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) {
|
||||
this->skip_update_ = true; // skip send touch events, touchscreen is not ready yet.
|
||||
return;
|
||||
}
|
||||
|
||||
if (num_of_touches == 0)
|
||||
this->send_release_();
|
||||
if (num_of_touches > MAX_TOUCHES) // should never happen
|
||||
return;
|
||||
|
||||
err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false);
|
||||
|
@ -80,29 +89,10 @@ void GT911Touchscreen::loop() {
|
|||
ERROR_CHECK(err);
|
||||
|
||||
for (uint8_t i = 0; i != num_of_touches; i++) {
|
||||
tp.id = data[i][0];
|
||||
uint16_t id = data[i][0];
|
||||
uint16_t x = encode_uint16(data[i][2], data[i][1]);
|
||||
uint16_t y = encode_uint16(data[i][4], data[i][3]);
|
||||
|
||||
switch (this->rotation_) {
|
||||
case touchscreen::ROTATE_0_DEGREES:
|
||||
tp.x = x;
|
||||
tp.y = y;
|
||||
break;
|
||||
case touchscreen::ROTATE_90_DEGREES:
|
||||
tp.x = y;
|
||||
tp.y = this->display_width_ - x;
|
||||
break;
|
||||
case touchscreen::ROTATE_180_DEGREES:
|
||||
tp.x = this->display_width_ - x;
|
||||
tp.y = this->display_height_ - y;
|
||||
break;
|
||||
case touchscreen::ROTATE_270_DEGREES:
|
||||
tp.x = this->display_height_ - y;
|
||||
tp.y = x;
|
||||
break;
|
||||
}
|
||||
this->defer([this, tp]() { this->send_touch_(tp); });
|
||||
this->set_raw_touch_position_(id, x, y);
|
||||
}
|
||||
auto keys = data[num_of_touches][0];
|
||||
for (size_t i = 0; i != 4; i++) {
|
||||
|
@ -115,7 +105,6 @@ void GT911Touchscreen::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, "GT911 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Rotation: %d", (int) this->rotation_);
|
||||
}
|
||||
|
||||
} // namespace gt911
|
||||
|
|
|
@ -8,30 +8,23 @@
|
|||
namespace esphome {
|
||||
namespace gt911 {
|
||||
|
||||
struct Store {
|
||||
volatile bool available;
|
||||
|
||||
static void gpio_intr(Store *store);
|
||||
};
|
||||
|
||||
class GT911ButtonListener {
|
||||
public:
|
||||
virtual void update_button(uint8_t index, bool state) = 0;
|
||||
};
|
||||
|
||||
class GT911Touchscreen : public touchscreen::Touchscreen, public Component, public i2c::I2CDevice {
|
||||
class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void set_rotation(touchscreen::TouchRotation rotation) { this->rotation_ = rotation; }
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
void register_button_listener(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); }
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
Store store_;
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{};
|
||||
std::vector<GT911ButtonListener *> button_listeners_;
|
||||
};
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import esphome.config_validation as cv
|
|||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.components import fan, output
|
||||
from esphome.components.fan import validate_preset_modes
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_DECAY_MODE,
|
||||
|
@ -10,6 +11,7 @@ from esphome.const import (
|
|||
CONF_PIN_A,
|
||||
CONF_PIN_B,
|
||||
CONF_ENABLE_PIN,
|
||||
CONF_PRESET_MODES,
|
||||
)
|
||||
from .. import hbridge_ns
|
||||
|
||||
|
@ -28,7 +30,6 @@ DECAY_MODE_OPTIONS = {
|
|||
# Actions
|
||||
BrakeAction = hbridge_ns.class_("BrakeAction", automation.Action)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(HBridgeFan),
|
||||
|
@ -39,6 +40,7 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
|||
),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
|
||||
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
|
||||
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
@ -69,3 +71,6 @@ async def to_code(config):
|
|||
if CONF_ENABLE_PIN in config:
|
||||
enable_pin = await cg.get_variable(config[CONF_ENABLE_PIN])
|
||||
cg.add(var.set_enable_pin(enable_pin))
|
||||
|
||||
if CONF_PRESET_MODES in config:
|
||||
cg.add(var.set_preset_modes(config[CONF_PRESET_MODES]))
|
||||
|
|
|
@ -33,7 +33,12 @@ void HBridgeFan::setup() {
|
|||
restore->apply(*this);
|
||||
this->write_state_();
|
||||
}
|
||||
|
||||
// Construct traits
|
||||
this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
|
||||
this->traits_.set_supported_preset_modes(this->preset_modes_);
|
||||
}
|
||||
|
||||
void HBridgeFan::dump_config() {
|
||||
LOG_FAN("", "H-Bridge Fan", this);
|
||||
if (this->decay_mode_ == DECAY_MODE_SLOW) {
|
||||
|
@ -42,9 +47,7 @@ void HBridgeFan::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Decay Mode: Fast");
|
||||
}
|
||||
}
|
||||
fan::FanTraits HBridgeFan::get_traits() {
|
||||
return fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
|
||||
}
|
||||
|
||||
void HBridgeFan::control(const fan::FanCall &call) {
|
||||
if (call.get_state().has_value())
|
||||
this->state = *call.get_state();
|
||||
|
@ -54,10 +57,12 @@ void HBridgeFan::control(const fan::FanCall &call) {
|
|||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
this->preset_mode = call.get_preset_mode();
|
||||
|
||||
this->write_state_();
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void HBridgeFan::write_state_() {
|
||||
float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f;
|
||||
if (speed == 0.0f) { // off means idle
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
@ -20,10 +22,11 @@ class HBridgeFan : public Component, public fan::Fan {
|
|||
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
|
||||
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
|
||||
void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; }
|
||||
void set_preset_modes(const std::set<std::string> &presets) { preset_modes_ = presets; }
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
fan::FanTraits get_traits() override;
|
||||
fan::FanTraits get_traits() override { return this->traits_; }
|
||||
|
||||
fan::FanCall brake();
|
||||
|
||||
|
@ -34,6 +37,8 @@ class HBridgeFan : public Component, public fan::Fan {
|
|||
output::BinaryOutput *oscillating_{nullptr};
|
||||
int speed_count_{};
|
||||
DecayMode decay_mode_{DECAY_MODE_SLOW};
|
||||
fan::FanTraits traits_;
|
||||
std::set<std::string> preset_modes_{};
|
||||
|
||||
void control(const fan::FanCall &call) override;
|
||||
void write_state_();
|
||||
|
|
|
@ -32,7 +32,11 @@ CODEOWNERS = ["@nielsnl68", "@clydebarrow"]
|
|||
|
||||
ili9xxx_ns = cg.esphome_ns.namespace("ili9xxx")
|
||||
ILI9XXXDisplay = ili9xxx_ns.class_(
|
||||
"ILI9XXXDisplay", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
|
||||
"ILI9XXXDisplay",
|
||||
cg.PollingComponent,
|
||||
spi.SPIDevice,
|
||||
display.Display,
|
||||
display.DisplayBuffer,
|
||||
)
|
||||
|
||||
ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode")
|
||||
|
|
|
@ -30,13 +30,24 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
const uint8_t *addr = init_sequence;
|
||||
while ((cmd = *addr++) != 0) {
|
||||
num_args = *addr++ & 0x7F;
|
||||
if (cmd == ILI9XXX_MADCTL) {
|
||||
bits = *addr;
|
||||
this->swap_xy_ = (bits & MADCTL_MV) != 0;
|
||||
this->mirror_x_ = (bits & MADCTL_MX) != 0;
|
||||
this->mirror_y_ = (bits & MADCTL_MY) != 0;
|
||||
this->color_order_ = (bits & MADCTL_BGR) ? display::COLOR_ORDER_BGR : display::COLOR_ORDER_RGB;
|
||||
break;
|
||||
bits = *addr;
|
||||
switch (cmd) {
|
||||
case ILI9XXX_MADCTL: {
|
||||
this->swap_xy_ = (bits & MADCTL_MV) != 0;
|
||||
this->mirror_x_ = (bits & MADCTL_MX) != 0;
|
||||
this->mirror_y_ = (bits & MADCTL_MY) != 0;
|
||||
this->color_order_ = (bits & MADCTL_BGR) ? display::COLOR_ORDER_BGR : display::COLOR_ORDER_RGB;
|
||||
break;
|
||||
}
|
||||
|
||||
case ILI9XXX_PIXFMT: {
|
||||
if ((bits & 0xF) == 6)
|
||||
this->is_18bitdisplay_ = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
addr += num_args;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,11 @@ CONF_VCOM_PIN = "vcom_pin"
|
|||
|
||||
inkplate6_ns = cg.esphome_ns.namespace("inkplate6")
|
||||
Inkplate6 = inkplate6_ns.class_(
|
||||
"Inkplate6", cg.PollingComponent, i2c.I2CDevice, display.DisplayBuffer
|
||||
"Inkplate6",
|
||||
cg.PollingComponent,
|
||||
i2c.I2CDevice,
|
||||
display.Display,
|
||||
display.DisplayBuffer,
|
||||
)
|
||||
|
||||
InkplateModel = inkplate6_ns.enum("InkplateModel")
|
||||
|
|
|
@ -57,7 +57,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect {
|
|||
void start() override { this->initial_run_ = true; }
|
||||
void apply(AddressableLight &it, const Color ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_run_ >= this->update_interval_) {
|
||||
if (now - this->last_run_ >= this->update_interval_ || this->initial_run_) {
|
||||
this->last_run_ = now;
|
||||
this->f_(it, current_color, this->initial_run_);
|
||||
this->initial_run_ = false;
|
||||
|
|
|
@ -118,7 +118,7 @@ class LambdaLightEffect : public LightEffect {
|
|||
void start() override { this->initial_run_ = true; }
|
||||
void apply() override {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_run_ >= this->update_interval_) {
|
||||
if (now - this->last_run_ >= this->update_interval_ || this->initial_run_) {
|
||||
this->last_run_ = now;
|
||||
this->f_(this->initial_run_);
|
||||
this->initial_run_ = false;
|
||||
|
|
|
@ -13,7 +13,6 @@ DEPENDENCIES = ["i2c"]
|
|||
LilygoT547Touchscreen = lilygo_t5_47_ns.class_(
|
||||
"LilygoT547Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
cg.Component,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
|
@ -27,17 +26,14 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
|||
pins.internal_gpio_input_pin_schema
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x5A))
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
).extend(i2c.i2c_device_schema(0x5A))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
||||
|
|
|
@ -23,15 +23,12 @@ static const uint8_t READ_TOUCH[1] = {0x07};
|
|||
return; \
|
||||
}
|
||||
|
||||
void Store::gpio_intr(Store *store) { store->touch = true; }
|
||||
|
||||
void LilygoT547Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen...");
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
|
||||
this->store_.pin = this->interrupt_pin_->to_isr();
|
||||
this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
if (this->write(nullptr, 0) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to communicate!");
|
||||
|
@ -41,19 +38,14 @@ void LilygoT547Touchscreen::setup() {
|
|||
}
|
||||
|
||||
this->write_register(POWER_REGISTER, WAKEUP_CMD, 1);
|
||||
|
||||
this->x_raw_max_ = this->get_width_();
|
||||
this->y_raw_max_ = this->get_height_();
|
||||
}
|
||||
|
||||
void LilygoT547Touchscreen::loop() {
|
||||
if (!this->store_.touch) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
}
|
||||
this->store_.touch = false;
|
||||
|
||||
void LilygoT547Touchscreen::update_touches() {
|
||||
uint8_t point = 0;
|
||||
uint8_t buffer[40] = {0};
|
||||
uint32_t sum_l = 0, sum_h = 0;
|
||||
|
||||
i2c::ErrorCode err;
|
||||
err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1);
|
||||
|
@ -69,102 +61,30 @@ void LilygoT547Touchscreen::loop() {
|
|||
|
||||
point = buffer[5] & 0xF;
|
||||
|
||||
if (point == 0) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
} else if (point == 1) {
|
||||
if (point == 1) {
|
||||
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
|
||||
ERROR_CHECK(err);
|
||||
err = this->read(&buffer[5], 2);
|
||||
ERROR_CHECK(err);
|
||||
|
||||
sum_l = buffer[5] << 8 | buffer[6];
|
||||
} else if (point > 1) {
|
||||
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
|
||||
ERROR_CHECK(err);
|
||||
err = this->read(&buffer[5], 5 * (point - 1) + 3);
|
||||
ERROR_CHECK(err);
|
||||
|
||||
sum_l = buffer[5 * point + 1] << 8 | buffer[5 * point + 2];
|
||||
}
|
||||
|
||||
this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2);
|
||||
|
||||
for (int i = 0; i < 5 * point; i++)
|
||||
sum_h += buffer[i];
|
||||
if (point == 0)
|
||||
point = 1;
|
||||
|
||||
if (sum_l != sum_h)
|
||||
point = 0;
|
||||
|
||||
if (point) {
|
||||
uint8_t offset;
|
||||
for (int i = 0; i < point; i++) {
|
||||
if (i == 0) {
|
||||
offset = 0;
|
||||
} else {
|
||||
offset = 4;
|
||||
}
|
||||
|
||||
TouchPoint tp;
|
||||
|
||||
tp.id = (buffer[i * 5 + offset] >> 4) & 0x0F;
|
||||
tp.state = buffer[i * 5 + offset] & 0x0F;
|
||||
if (tp.state == 0x06)
|
||||
tp.state = 0x07;
|
||||
|
||||
uint16_t y = (uint16_t) ((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F));
|
||||
uint16_t x = (uint16_t) ((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F));
|
||||
|
||||
switch (this->rotation_) {
|
||||
case ROTATE_0_DEGREES:
|
||||
tp.y = this->display_height_ - y;
|
||||
tp.x = x;
|
||||
break;
|
||||
case ROTATE_90_DEGREES:
|
||||
tp.x = this->display_height_ - y;
|
||||
tp.y = this->display_width_ - x;
|
||||
break;
|
||||
case ROTATE_180_DEGREES:
|
||||
tp.y = y;
|
||||
tp.x = this->display_width_ - x;
|
||||
break;
|
||||
case ROTATE_270_DEGREES:
|
||||
tp.x = y;
|
||||
tp.y = x;
|
||||
break;
|
||||
}
|
||||
|
||||
this->defer([this, tp]() { this->send_touch_(tp); });
|
||||
}
|
||||
} else {
|
||||
TouchPoint tp;
|
||||
tp.id = (buffer[0] >> 4) & 0x0F;
|
||||
tp.state = 0x06;
|
||||
|
||||
uint16_t y = (uint16_t) ((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F));
|
||||
uint16_t x = (uint16_t) ((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F));
|
||||
|
||||
switch (this->rotation_) {
|
||||
case ROTATE_0_DEGREES:
|
||||
tp.y = this->display_height_ - y;
|
||||
tp.x = x;
|
||||
break;
|
||||
case ROTATE_90_DEGREES:
|
||||
tp.x = this->display_height_ - y;
|
||||
tp.y = this->display_width_ - x;
|
||||
break;
|
||||
case ROTATE_180_DEGREES:
|
||||
tp.y = y;
|
||||
tp.x = this->display_width_ - x;
|
||||
break;
|
||||
case ROTATE_270_DEGREES:
|
||||
tp.x = y;
|
||||
tp.y = x;
|
||||
break;
|
||||
}
|
||||
|
||||
this->defer([this, tp]() { this->send_touch_(tp); });
|
||||
uint16_t id, x_raw, y_raw;
|
||||
for (uint8_t i = 0; i < point; i++) {
|
||||
id = (buffer[i * 5] >> 4) & 0x0F;
|
||||
y_raw = (uint16_t) ((buffer[i * 5 + 1] << 4) | ((buffer[i * 5 + 3] >> 4) & 0x0F));
|
||||
x_raw = (uint16_t) ((buffer[i * 5 + 2] << 4) | (buffer[i * 5 + 3] & 0x0F));
|
||||
this->set_raw_touch_position_(id, x_raw, y_raw);
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
|
|
|
@ -6,29 +6,25 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace lilygo_t5_47 {
|
||||
|
||||
struct Store {
|
||||
volatile bool touch;
|
||||
ISRInternalGPIOPin pin;
|
||||
|
||||
static void gpio_intr(Store *store);
|
||||
};
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
class LilygoT547Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
|
||||
class LilygoT547Touchscreen : public Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
|
||||
protected:
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
Store store_;
|
||||
};
|
||||
|
||||
} // namespace lilygo_t5_47
|
||||
|
|
|
@ -97,7 +97,7 @@ UART_SELECTION_LIBRETINY = {
|
|||
COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2],
|
||||
}
|
||||
|
||||
ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG]
|
||||
ESP_ARDUINO_UNSUPPORTED_USB_UARTS = [USB_SERIAL_JTAG]
|
||||
|
||||
UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
|
||||
|
||||
|
@ -124,8 +124,8 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
|||
|
||||
def uart_selection(value):
|
||||
if CORE.is_esp32:
|
||||
if value.upper() in ESP_IDF_UARTS and not CORE.using_esp_idf:
|
||||
raise cv.Invalid(f"Only esp-idf framework supports {value}.")
|
||||
if CORE.using_arduino and value.upper() in ESP_ARDUINO_UNSUPPORTED_USB_UARTS:
|
||||
raise cv.Invalid(f"Arduino framework does not support {value}.")
|
||||
variant = get_esp32_variant()
|
||||
if variant in UART_SELECTION_ESP32:
|
||||
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
|
||||
|
@ -171,6 +171,8 @@ CONFIG_SCHEMA = cv.All(
|
|||
CONF_HARDWARE_UART,
|
||||
esp8266=UART0,
|
||||
esp32=UART0,
|
||||
esp32_s2=USB_CDC,
|
||||
esp32_s3=USB_CDC,
|
||||
rp2040=USB_CDC,
|
||||
bk72xx=DEFAULT,
|
||||
rtl87xx=DEFAULT,
|
||||
|
@ -258,6 +260,10 @@ async def to_code(config):
|
|||
if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH):
|
||||
cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH")
|
||||
|
||||
if CORE.using_arduino:
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1")
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
|
||||
|
|
|
@ -236,8 +236,13 @@ void Logger::pre_setup() {
|
|||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
#else
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
|
@ -265,12 +270,35 @@ void Logger::pre_setup() {
|
|||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32) && \
|
||||
(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3))
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_USB_CDC:
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
||||
#ifdef USE_ESP32_VARIANT_ESP32C3
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#endif // ARDUINO_USB_CDC_ON_BOOT
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||
break;
|
||||
#endif // USE_ESP32 && (USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3)
|
||||
#ifdef USE_RP2040
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#endif // USE_RP2040
|
||||
}
|
||||
#endif // USE_ARDUINO
|
||||
#ifdef USE_ESP_IDF
|
||||
|
@ -393,14 +421,12 @@ const char *const UART_SELECTIONS[] = {
|
|||
"UART2",
|
||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARINT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
|
||||
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
|
||||
#if defined(USE_ESP_IDF)
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
"USB_CDC",
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
"USB_SERIAL_JTAG",
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
||||
#endif // USE_ESP_IDF
|
||||
};
|
||||
#endif // USE_ESP32
|
||||
#ifdef USE_ESP8266
|
||||
|
|
|
@ -45,7 +45,6 @@ enum UARTSelection {
|
|||
UART_SELECTION_UART2,
|
||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
|
||||
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
|
||||
#ifdef USE_ESP_IDF
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
UART_SELECTION_USB_CDC,
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||
|
@ -54,7 +53,6 @@ enum UARTSelection {
|
|||
UART_SELECTION_USB_SERIAL_JTAG,
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 ||
|
||||
// USE_ESP32_VARIANT_ESP32H2
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
||||
#ifdef USE_ESP8266
|
||||
UART_SELECTION_UART0_SWAP,
|
||||
|
|
|
@ -17,9 +17,12 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
|
|||
auto traits = this->device_->get_traits();
|
||||
// current_temperature_topic
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
// current_temperature_topic
|
||||
root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic();
|
||||
}
|
||||
// current_humidity_topic
|
||||
if (traits.get_supports_current_humidity()) {
|
||||
root[MQTT_CURRENT_HUMIDITY_TOPIC] = this->get_current_humidity_state_topic();
|
||||
}
|
||||
// mode_command_topic
|
||||
root[MQTT_MODE_COMMAND_TOPIC] = this->get_mode_command_topic();
|
||||
// mode_state_topic
|
||||
|
@ -57,6 +60,13 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
|
|||
root[MQTT_TEMPERATURE_STATE_TOPIC] = this->get_target_temperature_state_topic();
|
||||
}
|
||||
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
// target_humidity_command_topic
|
||||
root[MQTT_TARGET_HUMIDITY_COMMAND_TOPIC] = this->get_target_humidity_command_topic();
|
||||
// target_humidity_state_topic
|
||||
root[MQTT_TARGET_HUMIDITY_STATE_TOPIC] = this->get_target_humidity_state_topic();
|
||||
}
|
||||
|
||||
// min_temp
|
||||
root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature();
|
||||
// max_temp
|
||||
|
@ -66,6 +76,11 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
|
|||
// temperature units are always coerced to Celsius internally
|
||||
root[MQTT_TEMPERATURE_UNIT] = "C";
|
||||
|
||||
// min_humidity
|
||||
root[MQTT_MIN_HUMIDITY] = traits.get_visual_min_humidity();
|
||||
// max_humidity
|
||||
root[MQTT_MAX_HUMIDITY] = traits.get_visual_max_humidity();
|
||||
|
||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||
// preset_mode_command_topic
|
||||
root[MQTT_PRESET_MODE_COMMAND_TOPIC] = this->get_preset_command_topic();
|
||||
|
@ -192,6 +207,20 @@ void MQTTClimateComponent::setup() {
|
|||
});
|
||||
}
|
||||
|
||||
if (traits.get_supports_target_humidity()) {
|
||||
this->subscribe(this->get_target_humidity_command_topic(),
|
||||
[this](const std::string &topic, const std::string &payload) {
|
||||
auto val = parse_number<float>(payload);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
|
||||
return;
|
||||
}
|
||||
auto call = this->device_->make_call();
|
||||
call.set_target_humidity(*val);
|
||||
call.perform();
|
||||
});
|
||||
}
|
||||
|
||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||
this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) {
|
||||
auto call = this->device_->make_call();
|
||||
|
@ -273,6 +302,17 @@ bool MQTTClimateComponent::publish_state_() {
|
|||
success = false;
|
||||
}
|
||||
|
||||
if (traits.get_supports_current_humidity() && !std::isnan(this->device_->current_humidity)) {
|
||||
std::string payload = value_accuracy_to_string(this->device_->current_humidity, 0);
|
||||
if (!this->publish(this->get_current_humidity_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
if (traits.get_supports_target_humidity() && !std::isnan(this->device_->target_humidity)) {
|
||||
std::string payload = value_accuracy_to_string(this->device_->target_humidity, 0);
|
||||
if (!this->publish(this->get_target_humidity_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||
std::string payload;
|
||||
if (this->device_->preset.has_value()) {
|
||||
|
|
|
@ -20,6 +20,7 @@ class MQTTClimateComponent : public mqtt::MQTTComponent {
|
|||
void setup() override;
|
||||
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(current_temperature, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(current_humidity, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(mode, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(mode, command)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(target_temperature, state)
|
||||
|
@ -28,6 +29,8 @@ class MQTTClimateComponent : public mqtt::MQTTComponent {
|
|||
MQTT_COMPONENT_CUSTOM_TOPIC(target_temperature_low, command)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(target_temperature_high, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(target_temperature_high, command)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(target_humidity, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(target_humidity, command)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(away, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(away, command)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(action, state)
|
||||
|
|
|
@ -51,6 +51,8 @@ constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req";
|
|||
constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req";
|
||||
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t";
|
||||
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl";
|
||||
constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "curr_hum_t";
|
||||
constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "curr_hum_tpl";
|
||||
constexpr const char *const MQTT_DEVICE = "dev";
|
||||
constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla";
|
||||
constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t";
|
||||
|
@ -305,6 +307,8 @@ constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required";
|
|||
constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required";
|
||||
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic";
|
||||
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template";
|
||||
constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "current_humidity_topic";
|
||||
constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "current_humidity_template";
|
||||
constexpr const char *const MQTT_DEVICE = "device";
|
||||
constexpr const char *const MQTT_DEVICE_CLASS = "device_class";
|
||||
constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic";
|
||||
|
|
144
esphome/components/nfc/nci_core.h
Normal file
144
esphome/components/nfc/nci_core.h
Normal file
|
@ -0,0 +1,144 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace nfc {
|
||||
|
||||
// Header info
|
||||
static const uint8_t NCI_PKT_HEADER_SIZE = 3; // NCI packet (pkt) headers are always three bytes
|
||||
static const uint8_t NCI_PKT_MT_GID_OFFSET = 0; // NCI packet (pkt) MT and GID offsets
|
||||
static const uint8_t NCI_PKT_OID_OFFSET = 1; // NCI packet (pkt) OID offset
|
||||
static const uint8_t NCI_PKT_LENGTH_OFFSET = 2; // NCI packet (pkt) message length (size) offset
|
||||
static const uint8_t NCI_PKT_PAYLOAD_OFFSET = 3; // NCI packet (pkt) payload offset
|
||||
// Important masks
|
||||
static const uint8_t NCI_PKT_MT_MASK = 0xE0; // NCI packet (pkt) message type mask
|
||||
static const uint8_t NCI_PKT_PBF_MASK = 0x10; // packet boundary flag bit
|
||||
static const uint8_t NCI_PKT_GID_MASK = 0x0F;
|
||||
static const uint8_t NCI_PKT_OID_MASK = 0x3F;
|
||||
// Message types
|
||||
static const uint8_t NCI_PKT_MT_DATA = 0x00; // For sending commands to NFC endpoint (card/tag)
|
||||
static const uint8_t NCI_PKT_MT_CTRL_COMMAND = 0x20; // For sending commands to NFCC
|
||||
static const uint8_t NCI_PKT_MT_CTRL_RESPONSE = 0x40; // Response from NFCC to commands
|
||||
static const uint8_t NCI_PKT_MT_CTRL_NOTIFICATION = 0x60; // Notification from NFCC
|
||||
// GIDs
|
||||
static const uint8_t NCI_CORE_GID = 0x0;
|
||||
static const uint8_t RF_GID = 0x1;
|
||||
static const uint8_t NFCEE_GID = 0x1;
|
||||
static const uint8_t NCI_PROPRIETARY_GID = 0xF;
|
||||
// OIDs
|
||||
static const uint8_t NCI_CORE_RESET_OID = 0x00;
|
||||
static const uint8_t NCI_CORE_INIT_OID = 0x01;
|
||||
static const uint8_t NCI_CORE_SET_CONFIG_OID = 0x02;
|
||||
static const uint8_t NCI_CORE_GET_CONFIG_OID = 0x03;
|
||||
static const uint8_t NCI_CORE_CONN_CREATE_OID = 0x04;
|
||||
static const uint8_t NCI_CORE_CONN_CLOSE_OID = 0x05;
|
||||
static const uint8_t NCI_CORE_CONN_CREDITS_OID = 0x06;
|
||||
static const uint8_t NCI_CORE_GENERIC_ERROR_OID = 0x07;
|
||||
static const uint8_t NCI_CORE_INTERFACE_ERROR_OID = 0x08;
|
||||
|
||||
static const uint8_t RF_DISCOVER_MAP_OID = 0x00;
|
||||
static const uint8_t RF_SET_LISTEN_MODE_ROUTING_OID = 0x01;
|
||||
static const uint8_t RF_GET_LISTEN_MODE_ROUTING_OID = 0x02;
|
||||
static const uint8_t RF_DISCOVER_OID = 0x03;
|
||||
static const uint8_t RF_DISCOVER_SELECT_OID = 0x04;
|
||||
static const uint8_t RF_INTF_ACTIVATED_OID = 0x05;
|
||||
static const uint8_t RF_DEACTIVATE_OID = 0x06;
|
||||
static const uint8_t RF_FIELD_INFO_OID = 0x07;
|
||||
static const uint8_t RF_T3T_POLLING_OID = 0x08;
|
||||
static const uint8_t RF_NFCEE_ACTION_OID = 0x09;
|
||||
static const uint8_t RF_NFCEE_DISCOVERY_REQ_OID = 0x0A;
|
||||
static const uint8_t RF_PARAMETER_UPDATE_OID = 0x0B;
|
||||
|
||||
static const uint8_t NFCEE_DISCOVER_OID = 0x00;
|
||||
static const uint8_t NFCEE_MODE_SET_OID = 0x01;
|
||||
// Interfaces
|
||||
static const uint8_t INTF_NFCEE_DIRECT = 0x00;
|
||||
static const uint8_t INTF_FRAME = 0x01;
|
||||
static const uint8_t INTF_ISODEP = 0x02;
|
||||
static const uint8_t INTF_NFCDEP = 0x03;
|
||||
static const uint8_t INTF_TAGCMD = 0x80; // NXP proprietary
|
||||
// Bit rates
|
||||
static const uint8_t NFC_BIT_RATE_106 = 0x00;
|
||||
static const uint8_t NFC_BIT_RATE_212 = 0x01;
|
||||
static const uint8_t NFC_BIT_RATE_424 = 0x02;
|
||||
static const uint8_t NFC_BIT_RATE_848 = 0x03;
|
||||
static const uint8_t NFC_BIT_RATE_1695 = 0x04;
|
||||
static const uint8_t NFC_BIT_RATE_3390 = 0x05;
|
||||
static const uint8_t NFC_BIT_RATE_6780 = 0x06;
|
||||
// Protocols
|
||||
static const uint8_t PROT_UNDETERMINED = 0x00;
|
||||
static const uint8_t PROT_T1T = 0x01;
|
||||
static const uint8_t PROT_T2T = 0x02;
|
||||
static const uint8_t PROT_T3T = 0x03;
|
||||
static const uint8_t PROT_ISODEP = 0x04;
|
||||
static const uint8_t PROT_NFCDEP = 0x05;
|
||||
static const uint8_t PROT_T5T = 0x06;
|
||||
static const uint8_t PROT_MIFARE = 0x80;
|
||||
// RF Technologies
|
||||
static const uint8_t NFC_RF_TECH_A = 0x00;
|
||||
static const uint8_t NFC_RF_TECH_B = 0x01;
|
||||
static const uint8_t NFC_RF_TECH_F = 0x02;
|
||||
static const uint8_t NFC_RF_TECH_15693 = 0x03;
|
||||
// RF Technology & Modes
|
||||
static const uint8_t MODE_MASK = 0xF0;
|
||||
static const uint8_t MODE_LISTEN_MASK = 0x80;
|
||||
static const uint8_t MODE_POLL = 0x00;
|
||||
|
||||
static const uint8_t TECH_PASSIVE_NFCA = 0x00;
|
||||
static const uint8_t TECH_PASSIVE_NFCB = 0x01;
|
||||
static const uint8_t TECH_PASSIVE_NFCF = 0x02;
|
||||
static const uint8_t TECH_ACTIVE_NFCA = 0x03;
|
||||
static const uint8_t TECH_ACTIVE_NFCF = 0x05;
|
||||
static const uint8_t TECH_PASSIVE_15693 = 0x06;
|
||||
// Status codes
|
||||
static const uint8_t STATUS_OK = 0x00;
|
||||
static const uint8_t STATUS_REJECTED = 0x01;
|
||||
static const uint8_t STATUS_RF_FRAME_CORRUPTED = 0x02;
|
||||
static const uint8_t STATUS_FAILED = 0x03;
|
||||
static const uint8_t STATUS_NOT_INITIALIZED = 0x04;
|
||||
static const uint8_t STATUS_SYNTAX_ERROR = 0x05;
|
||||
static const uint8_t STATUS_SEMANTIC_ERROR = 0x06;
|
||||
static const uint8_t STATUS_INVALID_PARAM = 0x09;
|
||||
static const uint8_t STATUS_MESSAGE_SIZE_EXCEEDED = 0x0A;
|
||||
static const uint8_t DISCOVERY_ALREADY_STARTED = 0xA0;
|
||||
static const uint8_t DISCOVERY_TARGET_ACTIVATION_FAILED = 0xA1;
|
||||
static const uint8_t DISCOVERY_TEAR_DOWN = 0xA2;
|
||||
static const uint8_t RF_TRANSMISSION_ERROR = 0xB0;
|
||||
static const uint8_t RF_PROTOCOL_ERROR = 0xB1;
|
||||
static const uint8_t RF_TIMEOUT_ERROR = 0xB2;
|
||||
static const uint8_t NFCEE_INTERFACE_ACTIVATION_FAILED = 0xC0;
|
||||
static const uint8_t NFCEE_TRANSMISSION_ERROR = 0xC1;
|
||||
static const uint8_t NFCEE_PROTOCOL_ERROR = 0xC2;
|
||||
static const uint8_t NFCEE_TIMEOUT_ERROR = 0xC3;
|
||||
// Deactivation types/reasons
|
||||
static const uint8_t DEACTIVATION_TYPE_IDLE = 0x00;
|
||||
static const uint8_t DEACTIVATION_TYPE_SLEEP = 0x01;
|
||||
static const uint8_t DEACTIVATION_TYPE_SLEEP_AF = 0x02;
|
||||
static const uint8_t DEACTIVATION_TYPE_DISCOVERY = 0x03;
|
||||
// RF discover map modes
|
||||
static const uint8_t RF_DISCOVER_MAP_MODE_POLL = 0x1;
|
||||
static const uint8_t RF_DISCOVER_MAP_MODE_LISTEN = 0x2;
|
||||
// RF discover notification types
|
||||
static const uint8_t RF_DISCOVER_NTF_NT_LAST = 0x00;
|
||||
static const uint8_t RF_DISCOVER_NTF_NT_LAST_RL = 0x01;
|
||||
static const uint8_t RF_DISCOVER_NTF_NT_MORE = 0x02;
|
||||
// Important message offsets
|
||||
static const uint8_t RF_DISCOVER_NTF_DISCOVERY_ID = 0 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_DISCOVER_NTF_PROTOCOL = 1 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_DISCOVER_NTF_MODE_TECH = 2 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_DISCOVER_NTF_RF_TECH_LENGTH = 3 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_DISCOVER_NTF_RF_TECH_PARAMS = 4 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_INTF_ACTIVATED_NTF_DISCOVERY_ID = 0 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_INTF_ACTIVATED_NTF_INTERFACE = 1 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_INTF_ACTIVATED_NTF_PROTOCOL = 2 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_INTF_ACTIVATED_NTF_MODE_TECH = 3 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_INTF_ACTIVATED_NTF_MAX_SIZE = 4 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_INTF_ACTIVATED_NTF_INIT_CRED = 5 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_INTF_ACTIVATED_NTF_RF_TECH_LENGTH = 6 + NCI_PKT_HEADER_SIZE;
|
||||
static const uint8_t RF_INTF_ACTIVATED_NTF_RF_TECH_PARAMS = 7 + NCI_PKT_HEADER_SIZE;
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace esphome
|
166
esphome/components/nfc/nci_message.cpp
Normal file
166
esphome/components/nfc/nci_message.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include "nci_core.h"
|
||||
#include "nci_message.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace nfc {
|
||||
|
||||
static const char *const TAG = "NciMessage";
|
||||
|
||||
NciMessage::NciMessage(const uint8_t message_type, const std::vector<uint8_t> &payload) {
|
||||
this->set_message(message_type, payload);
|
||||
}
|
||||
|
||||
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid) {
|
||||
this->set_header(message_type, gid, oid);
|
||||
}
|
||||
|
||||
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
|
||||
const std::vector<uint8_t> &payload) {
|
||||
this->set_message(message_type, gid, oid, payload);
|
||||
}
|
||||
|
||||
NciMessage::NciMessage(const std::vector<uint8_t> &raw_packet) { this->nci_message_ = raw_packet; };
|
||||
|
||||
std::vector<uint8_t> NciMessage::encode() {
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
|
||||
std::vector<uint8_t> message = this->nci_message_;
|
||||
return message;
|
||||
}
|
||||
|
||||
void NciMessage::reset() { this->nci_message_ = {0, 0, 0}; }
|
||||
|
||||
uint8_t NciMessage::get_message_type() const {
|
||||
return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK;
|
||||
}
|
||||
|
||||
uint8_t NciMessage::get_gid() const { return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK; }
|
||||
|
||||
uint8_t NciMessage::get_oid() const { return this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK; }
|
||||
|
||||
uint8_t NciMessage::get_payload_size(const bool recompute) {
|
||||
if (!this->nci_message_.empty()) {
|
||||
if (recompute) {
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
|
||||
}
|
||||
return this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t NciMessage::get_simple_status_response() const {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) {
|
||||
return this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET];
|
||||
}
|
||||
return STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t NciMessage::get_message_byte(const uint8_t offset) const {
|
||||
if (this->nci_message_.size() > offset) {
|
||||
return this->nci_message_[offset];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> &NciMessage::get_message() { return this->nci_message_; }
|
||||
|
||||
bool NciMessage::has_payload() const { return this->nci_message_.size() > nfc::NCI_PKT_HEADER_SIZE; }
|
||||
|
||||
bool NciMessage::message_type_is(const uint8_t message_type) const {
|
||||
if (!this->nci_message_.empty()) {
|
||||
return message_type == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NciMessage::message_length_is(const uint8_t message_length, const bool recompute) {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_LENGTH_OFFSET) {
|
||||
if (recompute) {
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
|
||||
}
|
||||
return message_length == this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NciMessage::gid_is(const uint8_t gid) const {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_MT_GID_OFFSET) {
|
||||
return gid == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NciMessage::oid_is(const uint8_t oid) const {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_OID_OFFSET) {
|
||||
return oid == (this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NciMessage::simple_status_response_is(const uint8_t response) const {
|
||||
if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) {
|
||||
return response == this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NciMessage::set_header(const uint8_t message_type, const uint8_t gid, const uint8_t oid) {
|
||||
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
}
|
||||
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] =
|
||||
(message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK);
|
||||
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
|
||||
}
|
||||
|
||||
void NciMessage::set_message(const uint8_t message_type, const std::vector<uint8_t> &payload) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
|
||||
this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end());
|
||||
}
|
||||
|
||||
void NciMessage::set_message(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
|
||||
const std::vector<uint8_t> &payload) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] =
|
||||
(message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK);
|
||||
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
|
||||
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
|
||||
this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end());
|
||||
}
|
||||
|
||||
void NciMessage::set_message_type(const uint8_t message_type) {
|
||||
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
}
|
||||
auto mt_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_MT_MASK;
|
||||
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = mt_masked | (message_type & nfc::NCI_PKT_MT_MASK);
|
||||
}
|
||||
|
||||
void NciMessage::set_gid(const uint8_t gid) {
|
||||
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
}
|
||||
auto gid_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_GID_MASK;
|
||||
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = gid_masked | (gid & nfc::NCI_PKT_GID_MASK);
|
||||
}
|
||||
|
||||
void NciMessage::set_oid(const uint8_t oid) {
|
||||
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
}
|
||||
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
|
||||
}
|
||||
|
||||
void NciMessage::set_payload(const std::vector<uint8_t> &payload) {
|
||||
std::vector<uint8_t> message(this->nci_message_.begin(), this->nci_message_.begin() + nfc::NCI_PKT_HEADER_SIZE);
|
||||
|
||||
message.insert(message.end(), payload.begin(), payload.end());
|
||||
message[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
|
||||
this->nci_message_ = message;
|
||||
}
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace esphome
|
50
esphome/components/nfc/nci_message.h
Normal file
50
esphome/components/nfc/nci_message.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace nfc {
|
||||
|
||||
class NciMessage {
|
||||
public:
|
||||
NciMessage() {}
|
||||
NciMessage(uint8_t message_type, const std::vector<uint8_t> &payload);
|
||||
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid);
|
||||
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector<uint8_t> &payload);
|
||||
NciMessage(const std::vector<uint8_t> &raw_packet);
|
||||
|
||||
std::vector<uint8_t> encode();
|
||||
void reset();
|
||||
|
||||
uint8_t get_message_type() const;
|
||||
uint8_t get_gid() const;
|
||||
uint8_t get_oid() const;
|
||||
uint8_t get_payload_size(bool recompute = false);
|
||||
uint8_t get_simple_status_response() const;
|
||||
uint8_t get_message_byte(uint8_t offset) const;
|
||||
std::vector<uint8_t> &get_message();
|
||||
|
||||
bool has_payload() const;
|
||||
bool message_type_is(uint8_t message_type) const;
|
||||
bool message_length_is(uint8_t message_length, bool recompute = false);
|
||||
bool gid_is(uint8_t gid) const;
|
||||
bool oid_is(uint8_t oid) const;
|
||||
bool simple_status_response_is(uint8_t response) const;
|
||||
|
||||
void set_header(uint8_t message_type, uint8_t gid, uint8_t oid);
|
||||
void set_message(uint8_t message_type, const std::vector<uint8_t> &payload);
|
||||
void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector<uint8_t> &payload);
|
||||
void set_message_type(uint8_t message_type);
|
||||
void set_gid(uint8_t gid);
|
||||
void set_oid(uint8_t oid);
|
||||
void set_payload(const std::vector<uint8_t> &payload);
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> nci_message_{0, 0, 0}; // three bytes, MT/PBF/GID, OID, payload length/size
|
||||
};
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace esphome
|
|
@ -53,7 +53,7 @@ uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data) {
|
|||
}
|
||||
|
||||
bool decode_mifare_classic_tlv(std::vector<uint8_t> &data, uint32_t &message_length, uint8_t &message_start_index) {
|
||||
uint8_t i = get_mifare_classic_ndef_start_index(data);
|
||||
auto i = get_mifare_classic_ndef_start_index(data);
|
||||
if (data[i] != 0x03) {
|
||||
ESP_LOGE(TAG, "Error, Can't decode message length.");
|
||||
return false;
|
||||
|
|
47
esphome/components/nfc/nfc_helpers.cpp
Normal file
47
esphome/components/nfc/nfc_helpers.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include "nfc_helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nfc {
|
||||
|
||||
static const char *const TAG = "nfc.helpers";
|
||||
|
||||
bool has_ha_tag_ndef(NfcTag &tag) { return !get_ha_tag_ndef(tag).empty(); }
|
||||
|
||||
std::string get_ha_tag_ndef(NfcTag &tag) {
|
||||
if (!tag.has_ndef_message()) {
|
||||
return std::string();
|
||||
}
|
||||
auto message = tag.get_ndef_message();
|
||||
auto records = message->get_records();
|
||||
for (const auto &record : records) {
|
||||
std::string payload = record->get_payload();
|
||||
size_t pos = payload.find(HA_TAG_ID_PREFIX);
|
||||
if (pos != std::string::npos) {
|
||||
return payload.substr(pos + sizeof(HA_TAG_ID_PREFIX) - 1);
|
||||
}
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string get_random_ha_tag_ndef() {
|
||||
static const char ALPHANUM[] = "0123456789abcdef";
|
||||
std::string uri = HA_TAG_ID_PREFIX;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)];
|
||||
}
|
||||
uri += "-";
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)];
|
||||
}
|
||||
uri += "-";
|
||||
}
|
||||
for (int i = 0; i < 12; i++) {
|
||||
uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)];
|
||||
}
|
||||
ESP_LOGD("pn7160", "Payload to be written: %s", uri.c_str());
|
||||
return uri;
|
||||
}
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace esphome
|
17
esphome/components/nfc/nfc_helpers.h
Normal file
17
esphome/components/nfc/nfc_helpers.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include "nfc_tag.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nfc {
|
||||
|
||||
static const char HA_TAG_ID_EXT_RECORD_TYPE[] = "android.com:pkg";
|
||||
static const char HA_TAG_ID_EXT_RECORD_PAYLOAD[] = "io.homeassistant.companion.android";
|
||||
static const char HA_TAG_ID_PREFIX[] = "https://www.home-assistant.io/tag/";
|
||||
|
||||
std::string get_ha_tag_ndef(NfcTag &tag);
|
||||
std::string get_random_ha_tag_ndef();
|
||||
bool has_ha_tag_ndef(NfcTag &tag);
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace esphome
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import climate, sensor, output
|
||||
from esphome.const import CONF_ID, CONF_SENSOR
|
||||
from esphome.const import CONF_HUMIDITY_SENSOR, CONF_ID, CONF_SENSOR
|
||||
|
||||
pid_ns = cg.esphome_ns.namespace("pid")
|
||||
PIDClimate = pid_ns.class_("PIDClimate", climate.Climate, cg.Component)
|
||||
|
@ -45,6 +45,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
{
|
||||
cv.GenerateID(): cv.declare_id(PIDClimate),
|
||||
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_COOL_OUTPUT): cv.use_id(output.FloatOutput),
|
||||
cv.Optional(CONF_HEAT_OUTPUT): cv.use_id(output.FloatOutput),
|
||||
|
@ -86,6 +87,10 @@ async def to_code(config):
|
|||
sens = await cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY_SENSOR in config:
|
||||
sens = await cg.get_variable(config[CONF_HUMIDITY_SENSOR])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
|
||||
if CONF_COOL_OUTPUT in config:
|
||||
out = await cg.get_variable(config[CONF_COOL_OUTPUT])
|
||||
cg.add(var.set_cool_output(out))
|
||||
|
|
|
@ -14,6 +14,16 @@ void PIDClimate::setup() {
|
|||
this->update_pid_();
|
||||
});
|
||||
this->current_temperature = this->sensor_->state;
|
||||
|
||||
// register for humidity values and get initial state
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
this->humidity_sensor_->add_on_state_callback([this](float state) {
|
||||
this->current_humidity = state;
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_humidity = this->humidity_sensor_->state;
|
||||
}
|
||||
|
||||
// restore set points
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
|
@ -47,6 +57,9 @@ climate::ClimateTraits PIDClimate::traits() {
|
|||
traits.set_supports_current_temperature(true);
|
||||
traits.set_supports_two_point_target_temperature(false);
|
||||
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
traits.set_supports_current_humidity(true);
|
||||
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF});
|
||||
if (supports_cool_())
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
|
|
|
@ -19,6 +19,7 @@ class PIDClimate : public climate::Climate, public Component {
|
|||
void dump_config() override;
|
||||
|
||||
void set_sensor(sensor::Sensor *sensor) { sensor_ = sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *sensor) { humidity_sensor_ = sensor; }
|
||||
void set_cool_output(output::FloatOutput *cool_output) { cool_output_ = cool_output; }
|
||||
void set_heat_output(output::FloatOutput *heat_output) { heat_output_ = heat_output; }
|
||||
void set_kp(float kp) { controller_.kp_ = kp; }
|
||||
|
@ -85,6 +86,8 @@ class PIDClimate : public climate::Climate, public Component {
|
|||
|
||||
/// The sensor used for getting the current temperature
|
||||
sensor::Sensor *sensor_;
|
||||
/// The sensor used for getting the current humidity
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
output::FloatOutput *cool_output_{nullptr};
|
||||
output::FloatOutput *heat_output_{nullptr};
|
||||
PIDController controller_;
|
||||
|
|
|
@ -16,7 +16,7 @@ float PIDController::update(float setpoint, float process_value) {
|
|||
|
||||
calculate_proportional_term_();
|
||||
calculate_integral_term_();
|
||||
calculate_derivative_term_();
|
||||
calculate_derivative_term_(setpoint);
|
||||
|
||||
// u(t) := p(t) + i(t) + d(t)
|
||||
float output = proportional_term_ + integral_term_ + derivative_term_;
|
||||
|
@ -69,13 +69,18 @@ void PIDController::calculate_integral_term_() {
|
|||
integral_term_ = accumulated_integral_;
|
||||
}
|
||||
|
||||
void PIDController::calculate_derivative_term_() {
|
||||
void PIDController::calculate_derivative_term_(float setpoint) {
|
||||
// derivative_term_
|
||||
// d(t) := K_d * de(t)/dt
|
||||
float derivative = 0.0f;
|
||||
if (dt_ != 0.0f)
|
||||
if (dt_ != 0.0f) {
|
||||
// remove changes to setpoint from error
|
||||
if (!std::isnan(previous_setpoint_) && previous_setpoint_ != setpoint)
|
||||
previous_error_ -= previous_setpoint_ - setpoint;
|
||||
derivative = (error_ - previous_error_) / dt_;
|
||||
}
|
||||
previous_error_ = error_;
|
||||
previous_setpoint_ = setpoint;
|
||||
|
||||
// smooth the derivative samples
|
||||
derivative = weighted_average_(derivative_list_, derivative, derivative_samples_);
|
||||
|
|
|
@ -49,12 +49,13 @@ struct PIDController {
|
|||
|
||||
void calculate_proportional_term_();
|
||||
void calculate_integral_term_();
|
||||
void calculate_derivative_term_();
|
||||
void calculate_derivative_term_(float setpoint);
|
||||
float weighted_average_(std::deque<float> &list, float new_value, int samples);
|
||||
float calculate_relative_time_();
|
||||
|
||||
/// Error from previous update used for derivative term
|
||||
float previous_error_ = 0;
|
||||
float previous_setpoint_ = NAN;
|
||||
/// Accumulated integral value
|
||||
float accumulated_integral_ = 0;
|
||||
uint32_t last_time_ = 0;
|
||||
|
|
215
esphome/components/pn7150/__init__.py
Normal file
215
esphome/components/pn7150/__init__.py
Normal file
|
@ -0,0 +1,215 @@
|
|||
from esphome import automation, pins
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import nfc
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_IRQ_PIN,
|
||||
CONF_ON_TAG_REMOVED,
|
||||
CONF_ON_TAG,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["binary_sensor", "nfc"]
|
||||
CODEOWNERS = ["@kbx81", "@jesserockz"]
|
||||
|
||||
CONF_EMULATION_MESSAGE = "emulation_message"
|
||||
CONF_EMULATION_OFF = "emulation_off"
|
||||
CONF_EMULATION_ON = "emulation_on"
|
||||
CONF_INCLUDE_ANDROID_APP_RECORD = "include_android_app_record"
|
||||
CONF_MESSAGE = "message"
|
||||
CONF_ON_FINISHED_WRITE = "on_finished_write"
|
||||
CONF_ON_EMULATED_TAG_SCAN = "on_emulated_tag_scan"
|
||||
CONF_PN7150_ID = "pn7150_id"
|
||||
CONF_POLLING_OFF = "polling_off"
|
||||
CONF_POLLING_ON = "polling_on"
|
||||
CONF_SET_CLEAN_MODE = "set_clean_mode"
|
||||
CONF_SET_EMULATION_MESSAGE = "set_emulation_message"
|
||||
CONF_SET_FORMAT_MODE = "set_format_mode"
|
||||
CONF_SET_READ_MODE = "set_read_mode"
|
||||
CONF_SET_WRITE_MESSAGE = "set_write_message"
|
||||
CONF_SET_WRITE_MODE = "set_write_mode"
|
||||
CONF_TAG_TTL = "tag_ttl"
|
||||
CONF_VEN_PIN = "ven_pin"
|
||||
|
||||
pn7150_ns = cg.esphome_ns.namespace("pn7150")
|
||||
PN7150 = pn7150_ns.class_("PN7150", cg.Component)
|
||||
|
||||
EmulationOffAction = pn7150_ns.class_("EmulationOffAction", automation.Action)
|
||||
EmulationOnAction = pn7150_ns.class_("EmulationOnAction", automation.Action)
|
||||
PollingOffAction = pn7150_ns.class_("PollingOffAction", automation.Action)
|
||||
PollingOnAction = pn7150_ns.class_("PollingOnAction", automation.Action)
|
||||
SetCleanModeAction = pn7150_ns.class_("SetCleanModeAction", automation.Action)
|
||||
SetEmulationMessageAction = pn7150_ns.class_(
|
||||
"SetEmulationMessageAction", automation.Action
|
||||
)
|
||||
SetFormatModeAction = pn7150_ns.class_("SetFormatModeAction", automation.Action)
|
||||
SetReadModeAction = pn7150_ns.class_("SetReadModeAction", automation.Action)
|
||||
SetWriteMessageAction = pn7150_ns.class_("SetWriteMessageAction", automation.Action)
|
||||
SetWriteModeAction = pn7150_ns.class_("SetWriteModeAction", automation.Action)
|
||||
|
||||
|
||||
PN7150OnEmulatedTagScanTrigger = pn7150_ns.class_(
|
||||
"PN7150OnEmulatedTagScanTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
PN7150OnFinishedWriteTrigger = pn7150_ns.class_(
|
||||
"PN7150OnFinishedWriteTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
PN7150IsWritingCondition = pn7150_ns.class_(
|
||||
"PN7150IsWritingCondition", automation.Condition
|
||||
)
|
||||
|
||||
|
||||
IsWritingCondition = nfc.nfc_ns.class_("IsWritingCondition", automation.Condition)
|
||||
|
||||
|
||||
SIMPLE_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(PN7150),
|
||||
}
|
||||
)
|
||||
|
||||
SET_MESSAGE_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(PN7150),
|
||||
cv.Required(CONF_MESSAGE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_INCLUDE_ANDROID_APP_RECORD, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
PN7150_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PN7150),
|
||||
cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
PN7150OnEmulatedTagScanTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
PN7150OnFinishedWriteTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TAG): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
|
||||
}
|
||||
),
|
||||
cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Required(CONF_VEN_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_EMULATION_MESSAGE): cv.string,
|
||||
cv.Optional(CONF_TAG_TTL): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"tag.set_emulation_message",
|
||||
SetEmulationMessageAction,
|
||||
SET_MESSAGE_ACTION_SCHEMA,
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_write_message",
|
||||
SetWriteMessageAction,
|
||||
SET_MESSAGE_ACTION_SCHEMA,
|
||||
)
|
||||
async def pn7150_set_message_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_MESSAGE], args, cg.std_string)
|
||||
cg.add(var.set_message(template_))
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_INCLUDE_ANDROID_APP_RECORD], args, cg.bool_
|
||||
)
|
||||
cg.add(var.set_include_android_app_record(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"tag.emulation_off", EmulationOffAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action("tag.emulation_on", EmulationOnAction, SIMPLE_ACTION_SCHEMA)
|
||||
@automation.register_action("tag.polling_off", PollingOffAction, SIMPLE_ACTION_SCHEMA)
|
||||
@automation.register_action("tag.polling_on", PollingOnAction, SIMPLE_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"tag.set_clean_mode", SetCleanModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_format_mode", SetFormatModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_read_mode", SetReadModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_write_mode", SetWriteModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
async def pn7150_simple_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
async def setup_pn7150(var, config):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
|
||||
cg.add(var.set_irq_pin(pin))
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_VEN_PIN])
|
||||
cg.add(var.set_ven_pin(pin))
|
||||
|
||||
if emulation_message_config := config.get(CONF_EMULATION_MESSAGE):
|
||||
cg.add(var.set_tag_emulation_message(emulation_message_config))
|
||||
cg.add(var.set_tag_emulation_on())
|
||||
|
||||
if CONF_TAG_TTL in config:
|
||||
cg.add(var.set_tag_ttl(config[CONF_TAG_TTL]))
|
||||
|
||||
for conf in config.get(CONF_ON_TAG, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_ontag_trigger(trigger))
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_TAG_REMOVED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_ontagremoved_trigger(trigger))
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_FINISHED_WRITE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"pn7150.is_writing",
|
||||
PN7150IsWritingCondition,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(PN7150),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def pn7150_is_writing_to_code(config, condition_id, template_arg, args):
|
||||
var = cg.new_Pvariable(condition_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
82
esphome/components/pn7150/automation.h
Normal file
82
esphome/components/pn7150/automation.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/pn7150/pn7150.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150 {
|
||||
|
||||
class PN7150OnEmulatedTagScanTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit PN7150OnEmulatedTagScanTrigger(PN7150 *parent) {
|
||||
parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class PN7150OnFinishedWriteTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit PN7150OnFinishedWriteTrigger(PN7150 *parent) {
|
||||
parent->add_on_finished_write_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PN7150IsWritingCondition : public Condition<Ts...>, public Parented<PN7150> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->is_writing(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOffAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->set_tag_emulation_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOnAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->set_tag_emulation_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOffAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->set_polling_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOnAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->set_polling_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetCleanModeAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->clean_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFormatModeAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->format_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetReadModeAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->read_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetEmulationMessageAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
TEMPLATABLE_VALUE(std::string, message)
|
||||
TEMPLATABLE_VALUE(bool, include_android_app_record)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_tag_emulation_message(this->message_.optional_value(x...),
|
||||
this->include_android_app_record_.optional_value(x...));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetWriteMessageAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
TEMPLATABLE_VALUE(std::string, message)
|
||||
TEMPLATABLE_VALUE(bool, include_android_app_record)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_tag_write_message(this->message_.optional_value(x...),
|
||||
this->include_android_app_record_.optional_value(x...));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetWriteModeAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->write_mode(); }
|
||||
};
|
||||
|
||||
} // namespace pn7150
|
||||
} // namespace esphome
|
1137
esphome/components/pn7150/pn7150.cpp
Normal file
1137
esphome/components/pn7150/pn7150.cpp
Normal file
File diff suppressed because it is too large
Load diff
296
esphome/components/pn7150/pn7150.h
Normal file
296
esphome/components/pn7150/pn7150.h
Normal file
|
@ -0,0 +1,296 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/nfc/automation.h"
|
||||
#include "esphome/components/nfc/nci_core.h"
|
||||
#include "esphome/components/nfc/nci_message.h"
|
||||
#include "esphome/components/nfc/nfc.h"
|
||||
#include "esphome/components/nfc/nfc_helpers.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150 {
|
||||
|
||||
static const uint16_t NFCC_DEFAULT_TIMEOUT = 10;
|
||||
static const uint16_t NFCC_INIT_TIMEOUT = 50;
|
||||
static const uint16_t NFCC_TAG_WRITE_TIMEOUT = 15;
|
||||
|
||||
static const uint8_t NFCC_MAX_COMM_FAILS = 3;
|
||||
static const uint8_t NFCC_MAX_ERROR_COUNT = 10;
|
||||
|
||||
static const uint8_t XCHG_DATA_OID = 0x10;
|
||||
static const uint8_t MF_SECTORSEL_OID = 0x32;
|
||||
static const uint8_t MFC_AUTHENTICATE_OID = 0x40;
|
||||
static const uint8_t TEST_PRBS_OID = 0x30;
|
||||
static const uint8_t TEST_ANTENNA_OID = 0x3D;
|
||||
static const uint8_t TEST_GET_REGISTER_OID = 0x33;
|
||||
|
||||
static const uint8_t MFC_AUTHENTICATE_PARAM_KS_A = 0x00; // key select A
|
||||
static const uint8_t MFC_AUTHENTICATE_PARAM_KS_B = 0x80; // key select B
|
||||
static const uint8_t MFC_AUTHENTICATE_PARAM_EMBED_KEY = 0x10;
|
||||
|
||||
static const uint8_t CARD_EMU_T4T_APP_SELECT[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76,
|
||||
0x00, 0x00, 0x85, 0x01, 0x01, 0x00};
|
||||
static const uint8_t CARD_EMU_T4T_CC[] = {0x00, 0x0F, 0x20, 0x00, 0xFF, 0x00, 0xFF, 0x04,
|
||||
0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00};
|
||||
static const uint8_t CARD_EMU_T4T_CC_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03};
|
||||
static const uint8_t CARD_EMU_T4T_NDEF_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04};
|
||||
static const uint8_t CARD_EMU_T4T_READ[] = {0x00, 0xB0};
|
||||
static const uint8_t CARD_EMU_T4T_WRITE[] = {0x00, 0xD6};
|
||||
static const uint8_t CARD_EMU_T4T_OK[] = {0x90, 0x00};
|
||||
static const uint8_t CARD_EMU_T4T_NOK[] = {0x6A, 0x82};
|
||||
|
||||
static const uint8_t CORE_CONFIG_SOLO[] = {0x01, // Number of parameter fields
|
||||
0x00, // config param identifier (TOTAL_DURATION)
|
||||
0x02, // length of value
|
||||
0x01, // TOTAL_DURATION (low)...
|
||||
0x00}; // TOTAL_DURATION (high): 1 ms
|
||||
|
||||
static const uint8_t CORE_CONFIG_RW_CE[] = {0x01, // Number of parameter fields
|
||||
0x00, // config param identifier (TOTAL_DURATION)
|
||||
0x02, // length of value
|
||||
0xF8, // TOTAL_DURATION (low)...
|
||||
0x02}; // TOTAL_DURATION (high): 760 ms
|
||||
|
||||
static const uint8_t PMU_CFG[] = {
|
||||
0x01, // Number of parameters
|
||||
0xA0, 0x0E, // ext. tag
|
||||
3, // length
|
||||
0x06, // VBAT1 connected to 5V (CFG2)
|
||||
0x64, // TVDD monitoring threshold = 5.0V; TxLDO voltage = 4.7V (in reader & card modes)
|
||||
0x01, // RFU; must be 0x00 for CFG1 and 0x01 for CFG2
|
||||
};
|
||||
|
||||
static const uint8_t RF_DISCOVER_MAP_CONFIG[] = { // poll modes
|
||||
nfc::PROT_T1T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_FRAME, // poll mode
|
||||
nfc::PROT_T2T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_FRAME, // poll mode
|
||||
nfc::PROT_T3T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_FRAME, // poll mode
|
||||
nfc::PROT_ISODEP, nfc::RF_DISCOVER_MAP_MODE_POLL | nfc::RF_DISCOVER_MAP_MODE_LISTEN,
|
||||
nfc::INTF_ISODEP, // poll & listen mode
|
||||
nfc::PROT_MIFARE, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_TAGCMD}; // poll mode
|
||||
|
||||
static const uint8_t RF_DISCOVERY_LISTEN_CONFIG[] = {nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode
|
||||
|
||||
static const uint8_t RF_DISCOVERY_POLL_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF}; // poll mode
|
||||
|
||||
static const uint8_t RF_DISCOVERY_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF, // poll mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode
|
||||
|
||||
static const uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (another message is coming)
|
||||
1, // number of table entries
|
||||
0x01, // type = protocol-based
|
||||
3, // length
|
||||
0, // DH NFCEE ID, a static ID representing the DH-NFCEE
|
||||
0x01, // power state
|
||||
nfc::PROT_ISODEP}; // protocol
|
||||
|
||||
enum class CardEmulationState : uint8_t {
|
||||
CARD_EMU_IDLE,
|
||||
CARD_EMU_NDEF_APP_SELECTED,
|
||||
CARD_EMU_CC_SELECTED,
|
||||
CARD_EMU_NDEF_SELECTED,
|
||||
CARD_EMU_DESFIRE_PROD,
|
||||
};
|
||||
|
||||
enum class NCIState : uint8_t {
|
||||
NONE = 0x00,
|
||||
NFCC_RESET,
|
||||
NFCC_INIT,
|
||||
NFCC_CONFIG,
|
||||
NFCC_SET_DISCOVER_MAP,
|
||||
NFCC_SET_LISTEN_MODE_ROUTING,
|
||||
RFST_IDLE,
|
||||
RFST_DISCOVERY,
|
||||
RFST_W4_ALL_DISCOVERIES,
|
||||
RFST_W4_HOST_SELECT,
|
||||
RFST_LISTEN_ACTIVE,
|
||||
RFST_LISTEN_SLEEP,
|
||||
RFST_POLL_ACTIVE,
|
||||
EP_DEACTIVATING,
|
||||
EP_SELECTING,
|
||||
TEST = 0XFE,
|
||||
FAILED = 0XFF,
|
||||
};
|
||||
|
||||
enum class TestMode : uint8_t {
|
||||
TEST_NONE = 0x00,
|
||||
TEST_PRBS,
|
||||
TEST_ANTENNA,
|
||||
TEST_GET_REGISTER,
|
||||
};
|
||||
|
||||
struct DiscoveredEndpoint {
|
||||
uint8_t id;
|
||||
uint8_t protocol;
|
||||
uint32_t last_seen;
|
||||
std::unique_ptr<nfc::NfcTag> tag;
|
||||
bool trig_called;
|
||||
};
|
||||
|
||||
class PN7150 : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void loop() override;
|
||||
|
||||
void set_irq_pin(GPIOPin *irq_pin) { this->irq_pin_ = irq_pin; }
|
||||
void set_ven_pin(GPIOPin *ven_pin) { this->ven_pin_ = ven_pin; }
|
||||
|
||||
void set_tag_ttl(uint32_t ttl) { this->tag_ttl_ = ttl; }
|
||||
void set_tag_emulation_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||
void set_tag_emulation_message(const optional<std::string> &message, optional<bool> include_android_app_record);
|
||||
void set_tag_emulation_message(const char *message, bool include_android_app_record = true);
|
||||
void set_tag_emulation_off();
|
||||
void set_tag_emulation_on();
|
||||
bool tag_emulation_enabled() { return this->listening_enabled_; }
|
||||
|
||||
void set_polling_off();
|
||||
void set_polling_on();
|
||||
bool polling_enabled() { return this->polling_enabled_; }
|
||||
|
||||
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
|
||||
|
||||
void add_on_emulated_tag_scan_callback(std::function<void()> callback) {
|
||||
this->on_emulated_tag_scan_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void add_on_finished_write_callback(std::function<void()> callback) {
|
||||
this->on_finished_write_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
bool is_writing() { return this->next_task_ != EP_READ; };
|
||||
|
||||
void read_mode();
|
||||
void clean_mode();
|
||||
void format_mode();
|
||||
void write_mode();
|
||||
void set_tag_write_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||
void set_tag_write_message(optional<std::string> message, optional<bool> include_android_app_record);
|
||||
|
||||
uint8_t set_test_mode(TestMode test_mode, const std::vector<uint8_t> &data, std::vector<uint8_t> &result);
|
||||
|
||||
protected:
|
||||
uint8_t reset_core_(bool reset_config, bool power);
|
||||
uint8_t init_core_();
|
||||
uint8_t send_init_config_();
|
||||
uint8_t send_core_config_();
|
||||
uint8_t refresh_core_config_();
|
||||
|
||||
uint8_t set_discover_map_();
|
||||
|
||||
uint8_t set_listen_mode_routing_();
|
||||
|
||||
uint8_t start_discovery_();
|
||||
uint8_t stop_discovery_();
|
||||
uint8_t deactivate_(uint8_t type, uint16_t timeout = NFCC_DEFAULT_TIMEOUT);
|
||||
|
||||
void select_endpoint_();
|
||||
|
||||
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
||||
uint8_t clean_endpoint_(std::vector<uint8_t> &uid);
|
||||
uint8_t format_endpoint_(std::vector<uint8_t> &uid);
|
||||
uint8_t write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
||||
|
||||
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
||||
optional<size_t> find_tag_uid_(const std::vector<uint8_t> &uid);
|
||||
void purge_old_tags_();
|
||||
void erase_tag_(uint8_t tag_index);
|
||||
|
||||
/// advance controller state as required
|
||||
void nci_fsm_transition_();
|
||||
/// set new controller state
|
||||
void nci_fsm_set_state_(NCIState new_state);
|
||||
/// setting controller to this state caused an error; returns true if too many errors/failures
|
||||
bool nci_fsm_set_error_state_(NCIState new_state);
|
||||
/// parse & process incoming messages from the NFCC
|
||||
void process_message_();
|
||||
void process_rf_intf_activated_oid_(nfc::NciMessage &rx);
|
||||
void process_rf_discover_oid_(nfc::NciMessage &rx);
|
||||
void process_rf_deactivate_oid_(nfc::NciMessage &rx);
|
||||
void process_data_message_(nfc::NciMessage &rx);
|
||||
|
||||
void card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vector<uint8_t> &ndef_response);
|
||||
|
||||
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT,
|
||||
bool expect_notification = true);
|
||||
virtual uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) = 0;
|
||||
virtual uint8_t write_nfcc(nfc::NciMessage &tx) = 0;
|
||||
|
||||
uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true);
|
||||
|
||||
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||
uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
||||
uint8_t sect_to_auth_(uint8_t block_num);
|
||||
uint8_t format_mifare_classic_mifare_();
|
||||
uint8_t format_mifare_classic_ndef_();
|
||||
uint8_t write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &message);
|
||||
uint8_t halt_mifare_classic_tag_();
|
||||
|
||||
uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
|
||||
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
|
||||
uint16_t read_mifare_ultralight_capacity_();
|
||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index);
|
||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||
uint8_t write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||
uint8_t clean_mifare_ultralight_();
|
||||
|
||||
enum NfcTask : uint8_t {
|
||||
EP_READ = 0,
|
||||
EP_CLEAN,
|
||||
EP_FORMAT,
|
||||
EP_WRITE,
|
||||
} next_task_{EP_READ};
|
||||
|
||||
bool config_refresh_pending_{false};
|
||||
bool core_config_is_solo_{false};
|
||||
bool listening_enabled_{false};
|
||||
bool polling_enabled_{true};
|
||||
|
||||
uint8_t error_count_{0};
|
||||
uint8_t fail_count_{0};
|
||||
uint32_t last_nci_state_change_{0};
|
||||
uint8_t selecting_endpoint_{0};
|
||||
uint32_t tag_ttl_{250};
|
||||
|
||||
GPIOPin *irq_pin_{nullptr};
|
||||
GPIOPin *ven_pin_{nullptr};
|
||||
|
||||
CallbackManager<void()> on_emulated_tag_scan_callback_;
|
||||
CallbackManager<void()> on_finished_write_callback_;
|
||||
|
||||
std::vector<DiscoveredEndpoint> discovered_endpoint_;
|
||||
|
||||
CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE};
|
||||
NCIState nci_state_{NCIState::NFCC_RESET};
|
||||
NCIState nci_state_error_{NCIState::NONE};
|
||||
|
||||
std::shared_ptr<nfc::NdefMessage> card_emulation_message_;
|
||||
std::shared_ptr<nfc::NdefMessage> next_task_message_to_write_;
|
||||
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
|
||||
};
|
||||
|
||||
} // namespace pn7150
|
||||
} // namespace esphome
|
322
esphome/components/pn7150/pn7150_mifare_classic.cpp
Normal file
322
esphome/components/pn7150/pn7150_mifare_classic.cpp
Normal file
|
@ -0,0 +1,322 @@
|
|||
#include <memory>
|
||||
|
||||
#include "pn7150.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150 {
|
||||
|
||||
static const char *const TAG = "pn7150.mifare_classic";
|
||||
|
||||
uint8_t PN7150::read_mifare_classic_tag_(nfc::NfcTag &tag) {
|
||||
uint8_t current_block = 4;
|
||||
uint8_t message_start_index = 0;
|
||||
uint32_t message_length = 0;
|
||||
|
||||
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Tag auth failed while attempting to read tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
if (this->read_mifare_classic_block_(current_block, data) == nfc::STATUS_OK) {
|
||||
if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to read block %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint32_t index = 0;
|
||||
uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length);
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
while (index < buffer_size) {
|
||||
if (nfc::mifare_classic_is_first_block(current_block)) {
|
||||
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Block authentication failed for %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> block_data;
|
||||
if (this->read_mifare_classic_block_(current_block, block_data) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading block %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
} else {
|
||||
buffer.insert(buffer.end(), block_data.begin(), block_data.end());
|
||||
}
|
||||
|
||||
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||
current_block++;
|
||||
|
||||
if (nfc::mifare_classic_is_trailer_block(current_block)) {
|
||||
current_block++;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.begin() + message_start_index < buffer.end()) {
|
||||
buffer.erase(buffer.begin(), buffer.begin() + message_start_index);
|
||||
} else {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
tag.set_ndef_message(make_unique<nfc::NdefMessage>(buffer));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
|
||||
|
||||
ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Timeout reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(!rx.message_length_is(18))) {
|
||||
ESP_LOGE(TAG, "MFC read block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
data.insert(data.begin(), rx.get_message().begin() + 4, rx.get_message().end() - 1);
|
||||
|
||||
ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes(data).c_str());
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {MFC_AUTHENTICATE_OID, this->sect_to_auth_(block_num), key_num});
|
||||
|
||||
switch (key_num) {
|
||||
case nfc::MIFARE_CMD_AUTH_A:
|
||||
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_A;
|
||||
break;
|
||||
|
||||
case nfc::MIFARE_CMD_AUTH_B:
|
||||
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_B;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (key != nullptr) {
|
||||
tx.get_message().back() |= MFC_AUTHENTICATE_PARAM_EMBED_KEY;
|
||||
tx.get_message().insert(tx.get_message().end(), key, key + 6);
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) ||
|
||||
(rx.get_message()[4] != nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num);
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "MFC block %u authentication succeeded", block_num);
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::sect_to_auth_(const uint8_t block_num) {
|
||||
const uint8_t first_high_block = nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * nfc::MIFARE_CLASSIC_16BLOCK_SECT_START;
|
||||
if (block_num >= first_high_block) {
|
||||
return ((block_num - first_high_block) / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH) +
|
||||
nfc::MIFARE_CLASSIC_16BLOCK_SECT_START;
|
||||
}
|
||||
return block_num / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW;
|
||||
}
|
||||
|
||||
uint8_t PN7150::format_mifare_classic_mifare_() {
|
||||
std::vector<uint8_t> blank_buffer(
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
std::vector<uint8_t> trailer_buffer(
|
||||
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
|
||||
auto status = nfc::STATUS_OK;
|
||||
|
||||
for (int block = 0; block < 64; block += 4) {
|
||||
if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||
continue;
|
||||
}
|
||||
if (block != 0) {
|
||||
if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 3);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t PN7150::format_mifare_classic_ndef_() {
|
||||
std::vector<uint8_t> empty_ndef_message(
|
||||
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
std::vector<uint8_t> blank_block(
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
std::vector<uint8_t> block_1_data(
|
||||
{0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||
std::vector<uint8_t> block_2_data(
|
||||
{0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||
std::vector<uint8_t> block_3_trailer(
|
||||
{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
std::vector<uint8_t> ndef_trailer(
|
||||
{0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
|
||||
if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Sector 0 formatted with NDEF");
|
||||
|
||||
auto status = nfc::STATUS_OK;
|
||||
|
||||
for (int block = 4; block < 64; block += 4) {
|
||||
if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (block == 4) {
|
||||
if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
} else {
|
||||
if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &write_data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
// write command part two
|
||||
tx.set_payload({XCHG_DATA_OID});
|
||||
tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end());
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(rx.get_message()[4] != nfc::MIFARE_CMD_ACK)) {
|
||||
ESP_LOGE(TAG, "MFC write block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &message) {
|
||||
auto encoded = message->encode();
|
||||
|
||||
uint32_t message_length = encoded.size();
|
||||
uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length);
|
||||
|
||||
encoded.insert(encoded.begin(), 0x03);
|
||||
if (message_length < 255) {
|
||||
encoded.insert(encoded.begin() + 1, message_length);
|
||||
} else {
|
||||
encoded.insert(encoded.begin() + 1, 0xFF);
|
||||
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
|
||||
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
|
||||
}
|
||||
encoded.push_back(0xFE);
|
||||
|
||||
encoded.resize(buffer_length, 0);
|
||||
|
||||
uint32_t index = 0;
|
||||
uint8_t current_block = 4;
|
||||
|
||||
while (index < buffer_length) {
|
||||
if (nfc::mifare_classic_is_first_block(current_block)) {
|
||||
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE);
|
||||
if (this->write_mifare_classic_block_(current_block, data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||
current_block++;
|
||||
|
||||
if (nfc::mifare_classic_is_trailer_block(current_block)) {
|
||||
// Skipping as cannot write to trailer
|
||||
current_block++;
|
||||
}
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::halt_mifare_classic_tag_() {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0});
|
||||
|
||||
ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
} // namespace pn7150
|
||||
} // namespace esphome
|
186
esphome/components/pn7150/pn7150_mifare_ultralight.cpp
Normal file
186
esphome/components/pn7150/pn7150_mifare_ultralight.cpp
Normal file
|
@ -0,0 +1,186 @@
|
|||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
#include "pn7150.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150 {
|
||||
|
||||
static const char *const TAG = "pn7150.mifare_ultralight";
|
||||
|
||||
uint8_t PN7150::read_mifare_ultralight_tag_(nfc::NfcTag &tag) {
|
||||
std::vector<uint8_t> data;
|
||||
// pages 3 to 6 contain various info we are interested in -- do one read to grab it all
|
||||
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
|
||||
data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!this->is_mifare_ultralight_formatted_(data)) {
|
||||
ESP_LOGW(TAG, "Not NDEF formatted");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t message_length;
|
||||
uint8_t message_start_index;
|
||||
if (this->find_mifare_ultralight_ndef_(data, message_length, message_start_index) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "Couldn't find NDEF message");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index);
|
||||
|
||||
if (message_length == 0) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
// we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages
|
||||
const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0;
|
||||
if (read_length) {
|
||||
if (read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data) !=
|
||||
nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
// we need to trim off page 3 as well as any bytes ahead of message_start_index
|
||||
data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
|
||||
|
||||
tag.set_ndef_message(make_unique<nfc::NdefMessage>(data));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data) {
|
||||
const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_READ, start_page});
|
||||
|
||||
for (size_t i = 0; i * read_increment < num_bytes; i++) {
|
||||
tx.get_message().back() = i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page;
|
||||
do { // loop because sometimes we struggle here...???...
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
} while (rx.get_payload_size() < read_increment);
|
||||
uint16_t bytes_offset = (i + 1) * read_increment;
|
||||
auto pages_in_end_itr = bytes_offset <= num_bytes ? rx.get_message().end() - 1
|
||||
: rx.get_message().end() - (bytes_offset - num_bytes + 1);
|
||||
|
||||
if ((pages_in_end_itr > rx.get_message().begin()) && (pages_in_end_itr < rx.get_message().end())) {
|
||||
data.insert(data.end(), rx.get_message().begin() + nfc::NCI_PKT_HEADER_SIZE, pages_in_end_itr);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str());
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
bool PN7150::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6) {
|
||||
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
|
||||
|
||||
return (page_3_to_6.size() > p4_offset + 3) &&
|
||||
!((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) &&
|
||||
(page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF));
|
||||
}
|
||||
|
||||
uint16_t PN7150::read_mifare_ultralight_capacity_() {
|
||||
std::vector<uint8_t> data;
|
||||
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) == nfc::STATUS_OK) {
|
||||
ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
|
||||
return data[2] * 8U;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index) {
|
||||
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
|
||||
|
||||
if (!(page_3_to_6.size() > p4_offset + 5)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (page_3_to_6[p4_offset + 0] == 0x03) {
|
||||
message_length = page_3_to_6[p4_offset + 1];
|
||||
message_start_index = 2;
|
||||
return nfc::STATUS_OK;
|
||||
} else if (page_3_to_6[p4_offset + 5] == 0x03) {
|
||||
message_length = page_3_to_6[p4_offset + 6];
|
||||
message_start_index = 7;
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid,
|
||||
const std::shared_ptr<nfc::NdefMessage> &message) {
|
||||
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||
|
||||
auto encoded = message->encode();
|
||||
|
||||
uint32_t message_length = encoded.size();
|
||||
uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length);
|
||||
|
||||
if (buffer_length > capacity) {
|
||||
ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
encoded.insert(encoded.begin(), 0x03);
|
||||
if (message_length < 255) {
|
||||
encoded.insert(encoded.begin() + 1, message_length);
|
||||
} else {
|
||||
encoded.insert(encoded.begin() + 1, 0xFF);
|
||||
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
|
||||
encoded.insert(encoded.begin() + 2, message_length & 0xFF);
|
||||
}
|
||||
encoded.push_back(0xFE);
|
||||
|
||||
encoded.resize(buffer_length, 0);
|
||||
|
||||
uint32_t index = 0;
|
||||
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
||||
|
||||
while (index < buffer_length) {
|
||||
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
|
||||
if (this->write_mifare_ultralight_page_(current_page, data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||
current_page++;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::clean_mifare_ultralight_() {
|
||||
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||
uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
||||
|
||||
std::vector<uint8_t> blank_data = {0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
|
||||
if (this->write_mifare_ultralight_page_(i, blank_data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data) {
|
||||
std::vector<uint8_t> payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num};
|
||||
payload.insert(payload.end(), write_data.begin(), write_data.end());
|
||||
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload);
|
||||
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error writing page %u", page_num);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
} // namespace pn7150
|
||||
} // namespace esphome
|
25
esphome/components/pn7150_i2c/__init__.py
Normal file
25
esphome/components/pn7150_i2c/__init__.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, pn7150
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ["pn7150"]
|
||||
CODEOWNERS = ["@kbx81", "@jesserockz"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
pn7150_i2c_ns = cg.esphome_ns.namespace("pn7150_i2c")
|
||||
PN7150I2C = pn7150_i2c_ns.class_("PN7150I2C", pn7150.PN7150, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
pn7150.PN7150_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PN7150I2C),
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x28))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await pn7150.setup_pn7150(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
49
esphome/components/pn7150_i2c/pn7150_i2c.cpp
Normal file
49
esphome/components/pn7150_i2c/pn7150_i2c.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#include "pn7150_i2c.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150_i2c {
|
||||
|
||||
static const char *const TAG = "pn7150_i2c";
|
||||
|
||||
uint8_t PN7150I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
|
||||
if (this->wait_for_irq_(timeout) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "read_nfcc_() timeout waiting for IRQ");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
if (!this->read_bytes_raw(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t length = rx.get_payload_size();
|
||||
if (length > 0) {
|
||||
rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE);
|
||||
if (!this->read_bytes_raw(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
// semaphore to ensure transaction is complete before returning
|
||||
if (this->wait_for_irq_(pn7150::NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "read_nfcc_() post-read timeout waiting for IRQ line to clear");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150I2C::write_nfcc(nfc::NciMessage &tx) {
|
||||
if (this->write(tx.encode().data(), tx.encode().size()) == i2c::ERROR_OK) {
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
void PN7150I2C::dump_config() {
|
||||
PN7150::dump_config();
|
||||
LOG_I2C_DEVICE(this);
|
||||
}
|
||||
|
||||
} // namespace pn7150_i2c
|
||||
} // namespace esphome
|
22
esphome/components/pn7150_i2c/pn7150_i2c.h
Normal file
22
esphome/components/pn7150_i2c/pn7150_i2c.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/pn7150/pn7150.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150_i2c {
|
||||
|
||||
class PN7150I2C : public pn7150::PN7150, public i2c::I2CDevice {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) override;
|
||||
uint8_t write_nfcc(nfc::NciMessage &tx) override;
|
||||
};
|
||||
|
||||
} // namespace pn7150_i2c
|
||||
} // namespace esphome
|
227
esphome/components/pn7160/__init__.py
Normal file
227
esphome/components/pn7160/__init__.py
Normal file
|
@ -0,0 +1,227 @@
|
|||
from esphome import automation, pins
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import nfc
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_IRQ_PIN,
|
||||
CONF_ON_TAG_REMOVED,
|
||||
CONF_ON_TAG,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["binary_sensor", "nfc"]
|
||||
CODEOWNERS = ["@kbx81", "@jesserockz"]
|
||||
|
||||
CONF_DWL_REQ_PIN = "dwl_req_pin"
|
||||
CONF_EMULATION_MESSAGE = "emulation_message"
|
||||
CONF_EMULATION_OFF = "emulation_off"
|
||||
CONF_EMULATION_ON = "emulation_on"
|
||||
CONF_INCLUDE_ANDROID_APP_RECORD = "include_android_app_record"
|
||||
CONF_MESSAGE = "message"
|
||||
CONF_ON_FINISHED_WRITE = "on_finished_write"
|
||||
CONF_ON_EMULATED_TAG_SCAN = "on_emulated_tag_scan"
|
||||
CONF_PN7160_ID = "pn7160_id"
|
||||
CONF_POLLING_OFF = "polling_off"
|
||||
CONF_POLLING_ON = "polling_on"
|
||||
CONF_SET_CLEAN_MODE = "set_clean_mode"
|
||||
CONF_SET_EMULATION_MESSAGE = "set_emulation_message"
|
||||
CONF_SET_FORMAT_MODE = "set_format_mode"
|
||||
CONF_SET_READ_MODE = "set_read_mode"
|
||||
CONF_SET_WRITE_MESSAGE = "set_write_message"
|
||||
CONF_SET_WRITE_MODE = "set_write_mode"
|
||||
CONF_TAG_TTL = "tag_ttl"
|
||||
CONF_VEN_PIN = "ven_pin"
|
||||
CONF_WKUP_REQ_PIN = "wkup_req_pin"
|
||||
|
||||
pn7160_ns = cg.esphome_ns.namespace("pn7160")
|
||||
PN7160 = pn7160_ns.class_("PN7160", cg.Component)
|
||||
|
||||
EmulationOffAction = pn7160_ns.class_("EmulationOffAction", automation.Action)
|
||||
EmulationOnAction = pn7160_ns.class_("EmulationOnAction", automation.Action)
|
||||
PollingOffAction = pn7160_ns.class_("PollingOffAction", automation.Action)
|
||||
PollingOnAction = pn7160_ns.class_("PollingOnAction", automation.Action)
|
||||
SetCleanModeAction = pn7160_ns.class_("SetCleanModeAction", automation.Action)
|
||||
SetEmulationMessageAction = pn7160_ns.class_(
|
||||
"SetEmulationMessageAction", automation.Action
|
||||
)
|
||||
SetFormatModeAction = pn7160_ns.class_("SetFormatModeAction", automation.Action)
|
||||
SetReadModeAction = pn7160_ns.class_("SetReadModeAction", automation.Action)
|
||||
SetWriteMessageAction = pn7160_ns.class_("SetWriteMessageAction", automation.Action)
|
||||
SetWriteModeAction = pn7160_ns.class_("SetWriteModeAction", automation.Action)
|
||||
|
||||
|
||||
PN7160OnEmulatedTagScanTrigger = pn7160_ns.class_(
|
||||
"PN7160OnEmulatedTagScanTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
PN7160OnFinishedWriteTrigger = pn7160_ns.class_(
|
||||
"PN7160OnFinishedWriteTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
PN7160IsWritingCondition = pn7160_ns.class_(
|
||||
"PN7160IsWritingCondition", automation.Condition
|
||||
)
|
||||
|
||||
|
||||
IsWritingCondition = nfc.nfc_ns.class_("IsWritingCondition", automation.Condition)
|
||||
|
||||
|
||||
SIMPLE_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(PN7160),
|
||||
}
|
||||
)
|
||||
|
||||
SET_MESSAGE_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(PN7160),
|
||||
cv.Required(CONF_MESSAGE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_INCLUDE_ANDROID_APP_RECORD, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
PN7160_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PN7160),
|
||||
cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
PN7160OnEmulatedTagScanTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
PN7160OnFinishedWriteTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TAG): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_DWL_REQ_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Required(CONF_VEN_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_WKUP_REQ_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_EMULATION_MESSAGE): cv.string,
|
||||
cv.Optional(CONF_TAG_TTL): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"tag.set_emulation_message",
|
||||
SetEmulationMessageAction,
|
||||
SET_MESSAGE_ACTION_SCHEMA,
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_write_message",
|
||||
SetWriteMessageAction,
|
||||
SET_MESSAGE_ACTION_SCHEMA,
|
||||
)
|
||||
async def pn7160_set_message_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_MESSAGE], args, cg.std_string)
|
||||
cg.add(var.set_message(template_))
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_INCLUDE_ANDROID_APP_RECORD], args, cg.bool_
|
||||
)
|
||||
cg.add(var.set_include_android_app_record(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"tag.emulation_off", EmulationOffAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action("tag.emulation_on", EmulationOnAction, SIMPLE_ACTION_SCHEMA)
|
||||
@automation.register_action("tag.polling_off", PollingOffAction, SIMPLE_ACTION_SCHEMA)
|
||||
@automation.register_action("tag.polling_on", PollingOnAction, SIMPLE_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"tag.set_clean_mode", SetCleanModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_format_mode", SetFormatModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_read_mode", SetReadModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_write_mode", SetWriteModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
async def pn7160_simple_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
async def setup_pn7160(var, config):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if dwl_req_pin_config := config.get(CONF_DWL_REQ_PIN):
|
||||
pin = await cg.gpio_pin_expression(dwl_req_pin_config)
|
||||
cg.add(var.set_dwl_req_pin(pin))
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
|
||||
cg.add(var.set_irq_pin(pin))
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_VEN_PIN])
|
||||
cg.add(var.set_ven_pin(pin))
|
||||
|
||||
if wakeup_req_pin_config := config.get(CONF_WKUP_REQ_PIN):
|
||||
pin = await cg.gpio_pin_expression(wakeup_req_pin_config)
|
||||
cg.add(var.set_wkup_req_pin(pin))
|
||||
|
||||
if emulation_message_config := config.get(CONF_EMULATION_MESSAGE):
|
||||
cg.add(var.set_tag_emulation_message(emulation_message_config))
|
||||
cg.add(var.set_tag_emulation_on())
|
||||
|
||||
if CONF_TAG_TTL in config:
|
||||
cg.add(var.set_tag_ttl(config[CONF_TAG_TTL]))
|
||||
|
||||
for conf in config.get(CONF_ON_TAG, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_ontag_trigger(trigger))
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_TAG_REMOVED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_ontagremoved_trigger(trigger))
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_FINISHED_WRITE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"pn7160.is_writing",
|
||||
PN7160IsWritingCondition,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(PN7160),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def pn7160_is_writing_to_code(config, condition_id, template_arg, args):
|
||||
var = cg.new_Pvariable(condition_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
82
esphome/components/pn7160/automation.h
Normal file
82
esphome/components/pn7160/automation.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/pn7160/pn7160.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7160 {
|
||||
|
||||
class PN7160OnEmulatedTagScanTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit PN7160OnEmulatedTagScanTrigger(PN7160 *parent) {
|
||||
parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class PN7160OnFinishedWriteTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit PN7160OnFinishedWriteTrigger(PN7160 *parent) {
|
||||
parent->add_on_finished_write_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PN7160IsWritingCondition : public Condition<Ts...>, public Parented<PN7160> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->is_writing(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOffAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(Ts... x) override { this->parent_->set_tag_emulation_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOnAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(Ts... x) override { this->parent_->set_tag_emulation_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOffAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(Ts... x) override { this->parent_->set_polling_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOnAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(Ts... x) override { this->parent_->set_polling_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetCleanModeAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(Ts... x) override { this->parent_->clean_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFormatModeAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(Ts... x) override { this->parent_->format_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetReadModeAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(Ts... x) override { this->parent_->read_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetEmulationMessageAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
TEMPLATABLE_VALUE(std::string, message)
|
||||
TEMPLATABLE_VALUE(bool, include_android_app_record)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_tag_emulation_message(this->message_.optional_value(x...),
|
||||
this->include_android_app_record_.optional_value(x...));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetWriteMessageAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
TEMPLATABLE_VALUE(std::string, message)
|
||||
TEMPLATABLE_VALUE(bool, include_android_app_record)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_tag_write_message(this->message_.optional_value(x...),
|
||||
this->include_android_app_record_.optional_value(x...));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetWriteModeAction : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(Ts... x) override { this->parent_->write_mode(); }
|
||||
};
|
||||
|
||||
} // namespace pn7160
|
||||
} // namespace esphome
|
1161
esphome/components/pn7160/pn7160.cpp
Normal file
1161
esphome/components/pn7160/pn7160.cpp
Normal file
File diff suppressed because it is too large
Load diff
315
esphome/components/pn7160/pn7160.h
Normal file
315
esphome/components/pn7160/pn7160.h
Normal file
|
@ -0,0 +1,315 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/nfc/automation.h"
|
||||
#include "esphome/components/nfc/nci_core.h"
|
||||
#include "esphome/components/nfc/nci_message.h"
|
||||
#include "esphome/components/nfc/nfc.h"
|
||||
#include "esphome/components/nfc/nfc_helpers.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7160 {
|
||||
|
||||
static const uint16_t NFCC_DEFAULT_TIMEOUT = 10;
|
||||
static const uint16_t NFCC_INIT_TIMEOUT = 50;
|
||||
static const uint16_t NFCC_TAG_WRITE_TIMEOUT = 15;
|
||||
|
||||
static const uint8_t NFCC_MAX_COMM_FAILS = 3;
|
||||
static const uint8_t NFCC_MAX_ERROR_COUNT = 10;
|
||||
|
||||
static const uint8_t XCHG_DATA_OID = 0x10;
|
||||
static const uint8_t MF_SECTORSEL_OID = 0x32;
|
||||
static const uint8_t MFC_AUTHENTICATE_OID = 0x40;
|
||||
static const uint8_t TEST_PRBS_OID = 0x30;
|
||||
static const uint8_t TEST_ANTENNA_OID = 0x3D;
|
||||
static const uint8_t TEST_GET_REGISTER_OID = 0x33;
|
||||
|
||||
static const uint8_t MFC_AUTHENTICATE_PARAM_KS_A = 0x00; // key select A
|
||||
static const uint8_t MFC_AUTHENTICATE_PARAM_KS_B = 0x80; // key select B
|
||||
static const uint8_t MFC_AUTHENTICATE_PARAM_EMBED_KEY = 0x10;
|
||||
|
||||
static const uint8_t CARD_EMU_T4T_APP_SELECT[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76,
|
||||
0x00, 0x00, 0x85, 0x01, 0x01, 0x00};
|
||||
static const uint8_t CARD_EMU_T4T_CC[] = {0x00, 0x0F, 0x20, 0x00, 0xFF, 0x00, 0xFF, 0x04,
|
||||
0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00};
|
||||
static const uint8_t CARD_EMU_T4T_CC_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03};
|
||||
static const uint8_t CARD_EMU_T4T_NDEF_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04};
|
||||
static const uint8_t CARD_EMU_T4T_READ[] = {0x00, 0xB0};
|
||||
static const uint8_t CARD_EMU_T4T_WRITE[] = {0x00, 0xD6};
|
||||
static const uint8_t CARD_EMU_T4T_OK[] = {0x90, 0x00};
|
||||
static const uint8_t CARD_EMU_T4T_NOK[] = {0x6A, 0x82};
|
||||
|
||||
static const uint8_t CORE_CONFIG_SOLO[] = {0x01, // Number of parameter fields
|
||||
0x00, // config param identifier (TOTAL_DURATION)
|
||||
0x02, // length of value
|
||||
0x01, // TOTAL_DURATION (low)...
|
||||
0x00}; // TOTAL_DURATION (high): 1 ms
|
||||
|
||||
static const uint8_t CORE_CONFIG_RW_CE[] = {0x01, // Number of parameter fields
|
||||
0x00, // config param identifier (TOTAL_DURATION)
|
||||
0x02, // length of value
|
||||
0xF8, // TOTAL_DURATION (low)...
|
||||
0x02}; // TOTAL_DURATION (high): 760 ms
|
||||
|
||||
static const uint8_t PMU_CFG[] = {
|
||||
0x01, // Number of parameters
|
||||
0xA0, 0x0E, // ext. tag
|
||||
11, // length
|
||||
0x11, // IRQ Enable: PVDD + temp sensor IRQs
|
||||
0x01, // RFU
|
||||
0x01, // Power and Clock Configuration, device on (CFG1)
|
||||
0x01, // Power and Clock Configuration, device off (CFG1)
|
||||
0x00, // RFU
|
||||
0x00, // DC-DC 0
|
||||
0x00, // DC-DC 1
|
||||
// 0x14, // TXLDO (3.3V / 4.75V)
|
||||
// 0xBB, // TXLDO (4.7V / 4.7V)
|
||||
0xFF, // TXLDO (5.0V / 5.0V)
|
||||
0x00, // RFU
|
||||
0xD0, // TXLDO check
|
||||
0x0C, // RFU
|
||||
};
|
||||
|
||||
static const uint8_t RF_DISCOVER_MAP_CONFIG[] = { // poll modes
|
||||
nfc::PROT_T1T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_FRAME, // poll mode
|
||||
nfc::PROT_T2T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_FRAME, // poll mode
|
||||
nfc::PROT_T3T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_FRAME, // poll mode
|
||||
nfc::PROT_ISODEP, nfc::RF_DISCOVER_MAP_MODE_POLL | nfc::RF_DISCOVER_MAP_MODE_LISTEN,
|
||||
nfc::INTF_ISODEP, // poll & listen mode
|
||||
nfc::PROT_MIFARE, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_TAGCMD}; // poll mode
|
||||
|
||||
static const uint8_t RF_DISCOVERY_LISTEN_CONFIG[] = {nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode
|
||||
|
||||
static const uint8_t RF_DISCOVERY_POLL_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF}; // poll mode
|
||||
|
||||
static const uint8_t RF_DISCOVERY_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF, // poll mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode
|
||||
|
||||
static const uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (another message is coming)
|
||||
2, // number of table entries
|
||||
0x01, // type = protocol-based
|
||||
3, // length
|
||||
0, // DH NFCEE ID, a static ID representing the DH-NFCEE
|
||||
0x07, // power state
|
||||
nfc::PROT_ISODEP, // protocol
|
||||
0x00, // type = technology-based
|
||||
3, // length
|
||||
0, // DH NFCEE ID, a static ID representing the DH-NFCEE
|
||||
0x07, // power state
|
||||
nfc::TECH_PASSIVE_NFCA}; // technology
|
||||
|
||||
enum class CardEmulationState : uint8_t {
|
||||
CARD_EMU_IDLE,
|
||||
CARD_EMU_NDEF_APP_SELECTED,
|
||||
CARD_EMU_CC_SELECTED,
|
||||
CARD_EMU_NDEF_SELECTED,
|
||||
CARD_EMU_DESFIRE_PROD,
|
||||
};
|
||||
|
||||
enum class NCIState : uint8_t {
|
||||
NONE = 0x00,
|
||||
NFCC_RESET,
|
||||
NFCC_INIT,
|
||||
NFCC_CONFIG,
|
||||
NFCC_SET_DISCOVER_MAP,
|
||||
NFCC_SET_LISTEN_MODE_ROUTING,
|
||||
RFST_IDLE,
|
||||
RFST_DISCOVERY,
|
||||
RFST_W4_ALL_DISCOVERIES,
|
||||
RFST_W4_HOST_SELECT,
|
||||
RFST_LISTEN_ACTIVE,
|
||||
RFST_LISTEN_SLEEP,
|
||||
RFST_POLL_ACTIVE,
|
||||
EP_DEACTIVATING,
|
||||
EP_SELECTING,
|
||||
TEST = 0XFE,
|
||||
FAILED = 0XFF,
|
||||
};
|
||||
|
||||
enum class TestMode : uint8_t {
|
||||
TEST_NONE = 0x00,
|
||||
TEST_PRBS,
|
||||
TEST_ANTENNA,
|
||||
TEST_GET_REGISTER,
|
||||
};
|
||||
|
||||
struct DiscoveredEndpoint {
|
||||
uint8_t id;
|
||||
uint8_t protocol;
|
||||
uint32_t last_seen;
|
||||
std::unique_ptr<nfc::NfcTag> tag;
|
||||
bool trig_called;
|
||||
};
|
||||
|
||||
class PN7160 : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void loop() override;
|
||||
|
||||
void set_dwl_req_pin(GPIOPin *dwl_req_pin) { this->dwl_req_pin_ = dwl_req_pin; }
|
||||
void set_irq_pin(GPIOPin *irq_pin) { this->irq_pin_ = irq_pin; }
|
||||
void set_ven_pin(GPIOPin *ven_pin) { this->ven_pin_ = ven_pin; }
|
||||
void set_wkup_req_pin(GPIOPin *wkup_req_pin) { this->wkup_req_pin_ = wkup_req_pin; }
|
||||
|
||||
void set_tag_ttl(uint32_t ttl) { this->tag_ttl_ = ttl; }
|
||||
void set_tag_emulation_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||
void set_tag_emulation_message(const optional<std::string> &message, optional<bool> include_android_app_record);
|
||||
void set_tag_emulation_message(const char *message, bool include_android_app_record = true);
|
||||
void set_tag_emulation_off();
|
||||
void set_tag_emulation_on();
|
||||
bool tag_emulation_enabled() { return this->listening_enabled_; }
|
||||
|
||||
void set_polling_off();
|
||||
void set_polling_on();
|
||||
bool polling_enabled() { return this->polling_enabled_; }
|
||||
|
||||
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
|
||||
|
||||
void add_on_emulated_tag_scan_callback(std::function<void()> callback) {
|
||||
this->on_emulated_tag_scan_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void add_on_finished_write_callback(std::function<void()> callback) {
|
||||
this->on_finished_write_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
bool is_writing() { return this->next_task_ != EP_READ; };
|
||||
|
||||
void read_mode();
|
||||
void clean_mode();
|
||||
void format_mode();
|
||||
void write_mode();
|
||||
void set_tag_write_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||
void set_tag_write_message(optional<std::string> message, optional<bool> include_android_app_record);
|
||||
|
||||
uint8_t set_test_mode(TestMode test_mode, const std::vector<uint8_t> &data, std::vector<uint8_t> &result);
|
||||
|
||||
protected:
|
||||
uint8_t reset_core_(bool reset_config, bool power);
|
||||
uint8_t init_core_();
|
||||
uint8_t send_init_config_();
|
||||
uint8_t send_core_config_();
|
||||
uint8_t refresh_core_config_();
|
||||
|
||||
uint8_t set_discover_map_();
|
||||
|
||||
uint8_t set_listen_mode_routing_();
|
||||
|
||||
uint8_t start_discovery_();
|
||||
uint8_t stop_discovery_();
|
||||
uint8_t deactivate_(uint8_t type, uint16_t timeout = NFCC_DEFAULT_TIMEOUT);
|
||||
|
||||
void select_endpoint_();
|
||||
|
||||
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
||||
uint8_t clean_endpoint_(std::vector<uint8_t> &uid);
|
||||
uint8_t format_endpoint_(std::vector<uint8_t> &uid);
|
||||
uint8_t write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
||||
|
||||
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
||||
optional<size_t> find_tag_uid_(const std::vector<uint8_t> &uid);
|
||||
void purge_old_tags_();
|
||||
void erase_tag_(uint8_t tag_index);
|
||||
|
||||
/// advance controller state as required
|
||||
void nci_fsm_transition_();
|
||||
/// set new controller state
|
||||
void nci_fsm_set_state_(NCIState new_state);
|
||||
/// setting controller to this state caused an error; returns true if too many errors/failures
|
||||
bool nci_fsm_set_error_state_(NCIState new_state);
|
||||
/// parse & process incoming messages from the NFCC
|
||||
void process_message_();
|
||||
void process_rf_intf_activated_oid_(nfc::NciMessage &rx);
|
||||
void process_rf_discover_oid_(nfc::NciMessage &rx);
|
||||
void process_rf_deactivate_oid_(nfc::NciMessage &rx);
|
||||
void process_data_message_(nfc::NciMessage &rx);
|
||||
|
||||
void card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vector<uint8_t> &ndef_response);
|
||||
|
||||
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT,
|
||||
bool expect_notification = true);
|
||||
virtual uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) = 0;
|
||||
virtual uint8_t write_nfcc(nfc::NciMessage &tx) = 0;
|
||||
|
||||
uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true);
|
||||
|
||||
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||
uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
||||
uint8_t sect_to_auth_(uint8_t block_num);
|
||||
uint8_t format_mifare_classic_mifare_();
|
||||
uint8_t format_mifare_classic_ndef_();
|
||||
uint8_t write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &message);
|
||||
uint8_t halt_mifare_classic_tag_();
|
||||
|
||||
uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
|
||||
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
|
||||
uint16_t read_mifare_ultralight_capacity_();
|
||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index);
|
||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||
uint8_t write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||
uint8_t clean_mifare_ultralight_();
|
||||
|
||||
enum NfcTask : uint8_t {
|
||||
EP_READ = 0,
|
||||
EP_CLEAN,
|
||||
EP_FORMAT,
|
||||
EP_WRITE,
|
||||
} next_task_{EP_READ};
|
||||
|
||||
bool config_refresh_pending_{false};
|
||||
bool core_config_is_solo_{false};
|
||||
bool listening_enabled_{false};
|
||||
bool polling_enabled_{true};
|
||||
|
||||
uint8_t error_count_{0};
|
||||
uint8_t fail_count_{0};
|
||||
uint32_t last_nci_state_change_{0};
|
||||
uint8_t selecting_endpoint_{0};
|
||||
uint32_t tag_ttl_{250};
|
||||
|
||||
GPIOPin *dwl_req_pin_{nullptr};
|
||||
GPIOPin *irq_pin_{nullptr};
|
||||
GPIOPin *ven_pin_{nullptr};
|
||||
GPIOPin *wkup_req_pin_{nullptr};
|
||||
|
||||
CallbackManager<void()> on_emulated_tag_scan_callback_;
|
||||
CallbackManager<void()> on_finished_write_callback_;
|
||||
|
||||
std::vector<DiscoveredEndpoint> discovered_endpoint_;
|
||||
|
||||
CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE};
|
||||
NCIState nci_state_{NCIState::NFCC_RESET};
|
||||
NCIState nci_state_error_{NCIState::NONE};
|
||||
|
||||
std::shared_ptr<nfc::NdefMessage> card_emulation_message_;
|
||||
std::shared_ptr<nfc::NdefMessage> next_task_message_to_write_;
|
||||
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
|
||||
};
|
||||
|
||||
} // namespace pn7160
|
||||
} // namespace esphome
|
322
esphome/components/pn7160/pn7160_mifare_classic.cpp
Normal file
322
esphome/components/pn7160/pn7160_mifare_classic.cpp
Normal file
|
@ -0,0 +1,322 @@
|
|||
#include <memory>
|
||||
|
||||
#include "pn7160.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7160 {
|
||||
|
||||
static const char *const TAG = "pn7160.mifare_classic";
|
||||
|
||||
uint8_t PN7160::read_mifare_classic_tag_(nfc::NfcTag &tag) {
|
||||
uint8_t current_block = 4;
|
||||
uint8_t message_start_index = 0;
|
||||
uint32_t message_length = 0;
|
||||
|
||||
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Tag auth failed while attempting to read tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
if (this->read_mifare_classic_block_(current_block, data) == nfc::STATUS_OK) {
|
||||
if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to read block %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint32_t index = 0;
|
||||
uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length);
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
while (index < buffer_size) {
|
||||
if (nfc::mifare_classic_is_first_block(current_block)) {
|
||||
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Block authentication failed for %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> block_data;
|
||||
if (this->read_mifare_classic_block_(current_block, block_data) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading block %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
} else {
|
||||
buffer.insert(buffer.end(), block_data.begin(), block_data.end());
|
||||
}
|
||||
|
||||
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||
current_block++;
|
||||
|
||||
if (nfc::mifare_classic_is_trailer_block(current_block)) {
|
||||
current_block++;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.begin() + message_start_index < buffer.end()) {
|
||||
buffer.erase(buffer.begin(), buffer.begin() + message_start_index);
|
||||
} else {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
tag.set_ndef_message(make_unique<nfc::NdefMessage>(buffer));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7160::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
|
||||
|
||||
ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Timeout reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(!rx.message_length_is(18))) {
|
||||
ESP_LOGE(TAG, "MFC read block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
data.insert(data.begin(), rx.get_message().begin() + 4, rx.get_message().end() - 1);
|
||||
|
||||
ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes(data).c_str());
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7160::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {MFC_AUTHENTICATE_OID, this->sect_to_auth_(block_num), key_num});
|
||||
|
||||
switch (key_num) {
|
||||
case nfc::MIFARE_CMD_AUTH_A:
|
||||
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_A;
|
||||
break;
|
||||
|
||||
case nfc::MIFARE_CMD_AUTH_B:
|
||||
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_B;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (key != nullptr) {
|
||||
tx.get_message().back() |= MFC_AUTHENTICATE_PARAM_EMBED_KEY;
|
||||
tx.get_message().insert(tx.get_message().end(), key, key + 6);
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) ||
|
||||
(rx.get_message()[4] != nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num);
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "MFC block %u authentication succeeded", block_num);
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7160::sect_to_auth_(const uint8_t block_num) {
|
||||
const uint8_t first_high_block = nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * nfc::MIFARE_CLASSIC_16BLOCK_SECT_START;
|
||||
if (block_num >= first_high_block) {
|
||||
return ((block_num - first_high_block) / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH) +
|
||||
nfc::MIFARE_CLASSIC_16BLOCK_SECT_START;
|
||||
}
|
||||
return block_num / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW;
|
||||
}
|
||||
|
||||
uint8_t PN7160::format_mifare_classic_mifare_() {
|
||||
std::vector<uint8_t> blank_buffer(
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
std::vector<uint8_t> trailer_buffer(
|
||||
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
|
||||
auto status = nfc::STATUS_OK;
|
||||
|
||||
for (int block = 0; block < 64; block += 4) {
|
||||
if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||
continue;
|
||||
}
|
||||
if (block != 0) {
|
||||
if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 3);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t PN7160::format_mifare_classic_ndef_() {
|
||||
std::vector<uint8_t> empty_ndef_message(
|
||||
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
std::vector<uint8_t> blank_block(
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
std::vector<uint8_t> block_1_data(
|
||||
{0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||
std::vector<uint8_t> block_2_data(
|
||||
{0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||
std::vector<uint8_t> block_3_trailer(
|
||||
{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
std::vector<uint8_t> ndef_trailer(
|
||||
{0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
|
||||
if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Sector 0 formatted with NDEF");
|
||||
|
||||
auto status = nfc::STATUS_OK;
|
||||
|
||||
for (int block = 4; block < 64; block += 4) {
|
||||
if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (block == 4) {
|
||||
if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
} else {
|
||||
if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &write_data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
// write command part two
|
||||
tx.set_payload({XCHG_DATA_OID});
|
||||
tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end());
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(rx.get_message()[4] != nfc::MIFARE_CMD_ACK)) {
|
||||
ESP_LOGE(TAG, "MFC write block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7160::write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &message) {
|
||||
auto encoded = message->encode();
|
||||
|
||||
uint32_t message_length = encoded.size();
|
||||
uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length);
|
||||
|
||||
encoded.insert(encoded.begin(), 0x03);
|
||||
if (message_length < 255) {
|
||||
encoded.insert(encoded.begin() + 1, message_length);
|
||||
} else {
|
||||
encoded.insert(encoded.begin() + 1, 0xFF);
|
||||
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
|
||||
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
|
||||
}
|
||||
encoded.push_back(0xFE);
|
||||
|
||||
encoded.resize(buffer_length, 0);
|
||||
|
||||
uint32_t index = 0;
|
||||
uint8_t current_block = 4;
|
||||
|
||||
while (index < buffer_length) {
|
||||
if (nfc::mifare_classic_is_first_block(current_block)) {
|
||||
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE);
|
||||
if (this->write_mifare_classic_block_(current_block, data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||
current_block++;
|
||||
|
||||
if (nfc::mifare_classic_is_trailer_block(current_block)) {
|
||||
// Skipping as cannot write to trailer
|
||||
current_block++;
|
||||
}
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7160::halt_mifare_classic_tag_() {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0});
|
||||
|
||||
ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
} // namespace pn7160
|
||||
} // namespace esphome
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue