Skip to content

Commit

Permalink
Add support for AT581x component
Browse files Browse the repository at this point in the history
  • Loading branch information
X-Ryl669 committed Feb 27, 2024
1 parent 37138d4 commit 3f42c7e
Show file tree
Hide file tree
Showing 9 changed files with 610 additions and 0 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze
esphome/components/as7341/* @mrgnr
esphome/components/async_tcp/* @OttoWinter
esphome/components/at581x/* @X-Ryl669
esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner
esphome/components/b_parasite/* @rbaron
Expand Down
155 changes: 155 additions & 0 deletions esphome/components/at581x/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins, automation
from esphome.components import i2c
from esphome.automation import maybe_simple_id
from esphome.const import (
CONF_ID,
CONF_FREQUENCY,
)


CODEOWNERS = ["@X-Ryl669"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True


at581x_ns = cg.esphome_ns.namespace("at581x")
AT581XComponent = at581x_ns.class_("AT581XComponent", cg.Component, i2c.I2CDevice)


CONF_AT581X_ID = "at581x_id"


CONF_SENSING_DISTANCE = "sensing_distance"
CONF_FACTORY_RESET = "factory_reset"
CONF_SENSITIVITY = "sensitivity"
CONF_DETECTION_PIN = "detection_pin"
CONF_POWERON_SELFCHECK_TIME = "poweron_selfcheck_time"
CONF_PROTECT_TIME = "protect_time"
CONF_TRIGGER_BASE = "trigger_base"
CONF_TRIGGER_KEEP = "trigger_keep"
CONF_STAGE_GAIN = "stage_gain"
CONF_POWER_CONSUMPTION = "power_consumption"


CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(AT581XComponent),
cv.Optional(
CONF_DETECTION_PIN, default=21
): pins.internal_gpio_input_pin_schema,
}
)

CONFIG_SCHEMA = cv.All(
CONFIG_SCHEMA.extend(i2c.i2c_device_schema(0x28)).extend(cv.COMPONENT_SCHEMA)
)


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)
pin = await cg.gpio_pin_expression(config[CONF_DETECTION_PIN])
cg.add(var.set_detection_pin(pin))


# Actions
AT581XResetAction = at581x_ns.class_("AT581XResetAction", automation.Action)
AT581XSettingsAction = at581x_ns.class_("AT581XSettingsAction", automation.Action)


@automation.register_action(
"at581x.reset",
AT581XResetAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(AT581XComponent),
}
),
)
async def at581x_reset_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


RADAR_SETTINGS_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(AT581XComponent),
cv.Optional(CONF_FACTORY_RESET): cv.templatable(cv.boolean),
cv.Optional(CONF_FREQUENCY, default=5800): cv.one_of(
5696,
5715,
5730,
5748,
5765,
5784,
5800,
5819,
5836,
5851,
5869,
5888,
int=True,
),
cv.Optional(CONF_SENSING_DISTANCE, default=823): cv.int_range(min=0, max=1023),
cv.Optional(CONF_POWERON_SELFCHECK_TIME, default=2000): cv.int_range(
min=0, max=65535
),
cv.Optional(CONF_POWER_CONSUMPTION, default=70): cv.one_of(
48, 56, 63, 70, 77, 91, 105, 115, 40, 44, 47, 51, 54, 61, 68, 78, int=True
),
cv.Optional(CONF_PROTECT_TIME, default=1000): cv.int_range(min=1, max=65535),
cv.Optional(CONF_TRIGGER_BASE, default=500): cv.int_range(min=1, max=65535),
cv.Optional(CONF_TRIGGER_KEEP, default=1500): cv.int_range(min=1, max=65535),
cv.Optional(CONF_STAGE_GAIN, default=3): cv.int_range(min=0, max=12),
}
).add_extra(
cv.has_at_least_one_key(
CONF_FACTORY_RESET,
CONF_FREQUENCY,
CONF_SENSING_DISTANCE,
)
)


@automation.register_action(
"at581x.settings",
AT581XSettingsAction,
RADAR_SETTINGS_SCHEMA,
)
async def at581x_settings_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])

# Radar configuration
if CONF_FACTORY_RESET in config:
cg.add(var.set_factory_reset(1))

if CONF_FREQUENCY in config:
cg.add(var.set_frequency(config[CONF_FREQUENCY]))

if CONF_SENSING_DISTANCE in config:
cg.add(var.set_sensing_distance(config[CONF_SENSING_DISTANCE]))

if CONF_POWERON_SELFCHECK_TIME in config:
cg.add(var.set_poweron_selfcheck_time(config[CONF_POWERON_SELFCHECK_TIME]))

if CONF_PROTECT_TIME in config:
cg.add(var.set_protect_time(config[CONF_PROTECT_TIME]))

if CONF_TRIGGER_BASE in config:
cg.add(var.set_trigger_base(config[CONF_TRIGGER_BASE]))

if CONF_TRIGGER_KEEP in config:
cg.add(var.set_trigger_keep(config[CONF_TRIGGER_KEEP]))

if CONF_STAGE_GAIN in config:
cg.add(var.set_stage_gain(config[CONF_STAGE_GAIN]))

if CONF_POWER_CONSUMPTION in config:
cg.add(var.set_power_consumption(config[CONF_POWER_CONSUMPTION]))

return var
230 changes: 230 additions & 0 deletions esphome/components/at581x/at581x.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#include "at581x.h"

/* Select gain for AT581X (3dB per step for level1, 6dB per step for level 2), high value = small gain. (p12) */
const uint8_t GainAddrTable[] = {0x5c, 0x63};
const uint8_t Gain5CTable[] = {0x08, 0x18, 0x28, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8};
const uint8_t Gain63Table[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
const uint8_t Gain61Value = 0xCA; // 0xC0 | 0x02 (freq present) | 0x08 (gain present)

/*!< Power consumption configuration table (p12). */
const uint8_t Power_48uA = (0x00 << 4 | 0x01);
const uint8_t Power_56uA = (0x01 << 4 | 0x01);
const uint8_t Power_63uA = (0x02 << 4 | 0x01);
const uint8_t Power_70uA = (0x03 << 4 | 0x01);
const uint8_t Power_77A_uA = (0x04 << 4 | 0x01);
const uint8_t Power_91uA = (0x05 << 4 | 0x01);
const uint8_t Power_105uA = (0x06 << 4 | 0x01);
const uint8_t Power_115uA = (0x07 << 4 | 0x01);

const uint8_t Power_40uA = (0x00 << 4 | 0x03);
const uint8_t Power_44uA = (0x01 << 4 | 0x03);
const uint8_t Power_47uA = (0x02 << 4 | 0x03);
const uint8_t Power_51uA = (0x03 << 4 | 0x03);
const uint8_t Power_54uA = (0x04 << 4 | 0x03);
const uint8_t Power_61uA = (0x05 << 4 | 0x03);
const uint8_t Power_68uA = (0x06 << 4 | 0x03);
const uint8_t Power_77B_uA = (0x07 << 4 | 0x03);
/* Reverse table from position to value for debugging purpose */
const uint8_t PowerTable[] = {48, 56, 63, 70, 77, 91, 105, 115, 40, 44, 47, 51, 54, 61, 68, 78};
const uint8_t Power67Table[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7};
const uint8_t Power68Table[] = {0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8,
24, 24, 24, 24, 24, 24, 24, 24}; // See Page 12, shift by 3 bits

/*!< Frequency Configuration table (p14/15 of datasheet). */
const uint8_t FreqAddr = 0x61;
const uint16_t FreqTable[] = {5696, 5715, 5730, 5748, 5765, 5784, 5800, 5819, 5836, 5851, 5869, 5888};
const uint8_t Freq5FTable[] = {0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x40, 0x41, 0x42, 0x43};
const uint8_t Freq60Table[] = {0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9e};

/*!< Value for RF and analog modules switch (p10). */
const uint8_t RFOffTable[] = {0x46, 0xaa, 0x50};
const uint8_t RFOnTable[] = {0x45, 0x55, 0xA0};
const uint8_t RFRegAddr[] = {0x5d, 0x62, 0x51};

/*!< Registers of Lighting delay time. Unit: ms, min 2s (p8) */
const uint8_t HighLevelDelayControlAddr = 0x41; /*!< Time_flag_out_ctrl 0x01 */
const uint8_t HighLevelDelayValueAddr = 0x42; /*!< Time_flag_out_1 Bit<7:0> */

const uint8_t AT581X_RA_Reset = 0x00;

/*!< Sensing distance address */
const uint8_t SignalDetectionThresholdAddrLo = 0x10;
const uint8_t SignalDetectionThresholdAddrHi = 0x11;

/*!< Bit field value for power registers */
const uint8_t PowerThresholdAddrHi = 0x68;
const uint8_t PowerThresholdAddrLo = 0x67;
const uint8_t PwrWorkTimeEn = 8; // Reg 0x67
const uint8_t PwrBurstTimeEn = 32; // Reg 0x68
const uint8_t PwrThreshEn = 64; // Reg 0x68
const uint8_t PwrThreshValEn = 128; // Reg 0x67

/*!< Times */
const uint8_t TriggerBaseTimeAddr = 0x3D; // 4 bytes, so up to 0x40
const uint8_t ProtectTimeAddr = 0x4E; // 2 bytes, up to 0x4F
const uint8_t TriggerKeepTimeAddr = 0x42; // 4 bytes, so up to 0x45
const uint8_t Time41Value = 1;
const uint8_t SelfCheckTimeAddr = 0x38; // 2 bytes, up to 0x39

namespace esphome {
namespace at581x {

static const char *const TAG = "at581x";

AT581XComponent::AT581XComponent() {}

bool AT581XComponent::i2c_write_reg(uint8_t addr, uint8_t data) {
return write_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
}
bool AT581XComponent::i2c_write_reg(uint8_t addr, uint32_t data) {
return i2c_write_reg(addr + 0, uint8_t(data & 0xFF)) && i2c_write_reg(addr + 1, uint8_t((data >> 8) & 0xFF)) &&
i2c_write_reg(addr + 2, uint8_t((data >> 16) & 0xFF)) && i2c_write_reg(addr + 3, uint8_t((data >> 24) & 0xFF));
}
bool AT581XComponent::i2c_write_reg(uint8_t addr, uint16_t data) {
return i2c_write_reg(addr, uint8_t(data & 0xFF)) && i2c_write_reg(addr + 1, uint8_t((data >> 8) & 0xFF));
}

bool AT581XComponent::i2c_read_reg(uint8_t addr, uint8_t &data) {
return read_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
}

void AT581XComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up AT581X..."); //, this->id_.c_str());
this->detection_pin_->setup();
if (!i2c_write_config()) {
ESP_LOGCONFIG(TAG, "Setting up AT581X failed..."); //, this->id_.c_str());
}
}
void AT581XComponent::loop() {
// The main operation is to detect the human presence
bool state = this->detection_pin_->digital_read();
if (this->motion_binary_sensor_ != nullptr) {
this->motion_binary_sensor_->publish_state(state);
}
}
void AT581XComponent::dump_config() {
if (this->motion_binary_sensor_ != nullptr) {
LOG_BINARY_SENSOR("", "AT581X", this->motion_binary_sensor_);
}
LOG_PIN(" Pin: ", this->detection_pin_);
LOG_I2C_DEVICE(this);
}
#define ArrSz(X) sizeof(X) / sizeof(X[0])
bool AT581XComponent::i2c_write_config() {
ESP_LOGCONFIG(TAG, "Writing new config for AT581X...");
ESP_LOGCONFIG(TAG, "Frequency: %dMHz", this->freq_);
ESP_LOGCONFIG(TAG, "Sensing distance: %d", this->delta_);
ESP_LOGCONFIG(TAG, "Power: %dµA", this->power_);
ESP_LOGCONFIG(TAG, "Gain: %d", this->gain_);
ESP_LOGCONFIG(TAG, "Trigger base time: %dms", this->trigger_base_time_ms_);
ESP_LOGCONFIG(TAG, "Trigger keep time: %dms", this->trigger_keep_time_ms_);
ESP_LOGCONFIG(TAG, "Protect time: %dms", this->protect_time_ms_);
ESP_LOGCONFIG(TAG, "Self check time: %dms", this->self_check_time_ms_);

// Set frequency point
if (!i2c_write_reg(FreqAddr, Gain61Value)) {
ESP_LOGE(TAG, "Failed to write AT581X Freq mode");
return false;
}
// Find the current frequency from the table to know what value to write
for (uint16_t i = 0; i < ArrSz(FreqTable) + 1; i++) {
if (i == ArrSz(FreqTable)) {
ESP_LOGE(TAG, "Set frequency not found");
return false;
}
if (FreqTable[i] == this->freq_) {
if (!i2c_write_reg(0x5F, Freq5FTable[i]) || !i2c_write_reg(0x60, Freq60Table[i])) {
ESP_LOGE(TAG, "Failed to write AT581X Freq value");
return false;
}
break;
}
}

// Set distance
if (!i2c_write_reg(SignalDetectionThresholdAddrLo, (uint8_t) (this->delta_ & 0xFF)) ||
!i2c_write_reg(SignalDetectionThresholdAddrHi, (uint8_t) (this->delta_ >> 8))) {
ESP_LOGE(TAG, "Failed to write AT581X sensing distance low");
return false;
}

// Set power setting
uint8_t pwr67 = PwrThreshValEn | PwrWorkTimeEn, pwr68 = PwrBurstTimeEn | PwrThreshEn;
for (uint16_t i = 0; i < ArrSz(PowerTable) + 1; i++) {
if (i == ArrSz(PowerTable)) {
ESP_LOGE(TAG, "Set power not found");
return false;
}
if (PowerTable[i] == this->power_) {
pwr67 |= Power67Table[i];
pwr68 |= Power68Table[i]; // See Page 12
break;
}
}

if (!i2c_write_reg(PowerThresholdAddrLo, pwr67) || !i2c_write_reg(PowerThresholdAddrHi, pwr68)) {
ESP_LOGE(TAG, "Failed to write AT581X power registers");
return false;
}

// Set gain
if (!i2c_write_reg(GainAddrTable[0], Gain5CTable[this->gain_]) ||
!i2c_write_reg(GainAddrTable[1], Gain63Table[this->gain_ >> 1])) {
ESP_LOGE(TAG, "Failed to write AT581X gain registers");
return false;
}

// Set times
if (!i2c_write_reg(TriggerBaseTimeAddr, (uint32_t) this->trigger_base_time_ms_)) {
ESP_LOGE(TAG, "Failed to write AT581X trigger base time registers");
return false;
}
if (!i2c_write_reg(TriggerKeepTimeAddr, (uint32_t) this->trigger_keep_time_ms_)) {
ESP_LOGE(TAG, "Failed to write AT581X trigger keep time registers");
return false;
}

if (!i2c_write_reg(ProtectTimeAddr, (uint16_t) this->protect_time_ms_)) {
ESP_LOGE(TAG, "Failed to write AT581X protect time registers");
return false;
}
if (!i2c_write_reg(SelfCheckTimeAddr, (uint16_t) this->self_check_time_ms_)) {
ESP_LOGE(TAG, "Failed to write AT581X self check time registers");
return false;
}

if (!i2c_write_reg(0x41, Time41Value)) {
ESP_LOGE(TAG, "Failed to enable AT581X time registers");
return false;
}

// Don't know why it's required in other code, it's not in datasheet
if (!i2c_write_reg(0x55, (uint8_t) 0x04)) {
ESP_LOGE(TAG, "Failed to enable AT581X");
return false;
}

// Ok, config is written, let's reset the chip so it's using the new config
return set_factory_reset();
}

// float AT581XComponent::get_setup_priority() const { return 0; }
bool AT581XComponent::set_factory_reset() {
if (!i2c_write_reg(AT581X_RA_Reset, (uint8_t) 0) || !i2c_write_reg(AT581X_RA_Reset, (uint8_t) 1)) {
ESP_LOGE(TAG, "Failed to reset AT581X component");
return false;
}
return true;
}

void AT581XComponent::set_rf_mode(bool enable) {
const uint8_t *p = enable ? &RFOnTable[0] : &RFOffTable[0];
for (size_t i = 0; i < ArrSz(RFRegAddr); i++)
if (!i2c_write_reg(RFRegAddr[i], p[i])) {
ESP_LOGE(TAG, "Failed to write AT581X RF mode");
return;
}
}

} // namespace at581x
} // namespace esphome

0 comments on commit 3f42c7e

Please sign in to comment.