-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
610 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.