forked from esphome/esphome
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add dfrobot_sen0395 mmwave radar component (esphome#4203)
Co-authored-by: Jesse Hills <[email protected]>
- Loading branch information
1 parent
0c5d5cd
commit 907d438
Showing
12 changed files
with
1,265 additions
and
1 deletion.
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,208 @@ | ||
import esphome.codegen as cg | ||
import esphome.config_validation as cv | ||
from esphome import automation | ||
from esphome import core | ||
from esphome.automation import maybe_simple_id | ||
from esphome.const import CONF_ID | ||
from esphome.components import uart | ||
|
||
CODEOWNERS = ["@niklasweber"] | ||
DEPENDENCIES = ["uart"] | ||
MULTI_CONF = True | ||
|
||
dfrobot_sen0395_ns = cg.esphome_ns.namespace("dfrobot_sen0395") | ||
DfrobotSen0395Component = dfrobot_sen0395_ns.class_( | ||
"DfrobotSen0395Component", cg.Component | ||
) | ||
|
||
# Actions | ||
DfrobotSen0395ResetAction = dfrobot_sen0395_ns.class_( | ||
"DfrobotSen0395ResetAction", automation.Action | ||
) | ||
DfrobotSen0395SettingsAction = dfrobot_sen0395_ns.class_( | ||
"DfrobotSen0395SettingsAction", automation.Action | ||
) | ||
|
||
CONF_DFROBOT_SEN0395_ID = "dfrobot_sen0395_id" | ||
|
||
CONF_DELAY_AFTER_DETECT = "delay_after_detect" | ||
CONF_DELAY_AFTER_DISAPPEAR = "delay_after_disappear" | ||
CONF_DETECTION_SEGMENTS = "detection_segments" | ||
CONF_OUTPUT_LATENCY = "output_latency" | ||
CONF_FACTORY_RESET = "factory_reset" | ||
CONF_SENSITIVITY = "sensitivity" | ||
|
||
CONFIG_SCHEMA = cv.All( | ||
cv.Schema( | ||
{ | ||
cv.GenerateID(): cv.declare_id(DfrobotSen0395Component), | ||
} | ||
).extend(uart.UART_DEVICE_SCHEMA) | ||
) | ||
|
||
|
||
async def to_code(config): | ||
var = cg.new_Pvariable(config[CONF_ID]) | ||
await cg.register_component(var, config) | ||
await uart.register_uart_device(var, config) | ||
|
||
|
||
@automation.register_action( | ||
"dfrobot_sen0395.reset", | ||
DfrobotSen0395ResetAction, | ||
maybe_simple_id( | ||
{ | ||
cv.GenerateID(): cv.use_id(DfrobotSen0395Component), | ||
} | ||
), | ||
) | ||
async def dfrobot_sen0395_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 | ||
|
||
|
||
def range_segment_list(input): | ||
"""Validate input is a list of ranges which can be used to configure the dfrobot mmwave radar | ||
A list of segments should be provided. A minimum of one segment is required and a maximum of | ||
four segments is allowed. A segment describes a range of distances. E.g. from 0mm to 1m. | ||
The distances need to be defined in an ascending order and they cannot contain / intersect | ||
each other. | ||
""" | ||
|
||
# Flatten input to one dimensional list | ||
flat_list = [] | ||
if isinstance(input, list): | ||
for list_item in input: | ||
if isinstance(list_item, list): | ||
for item in list_item: | ||
flat_list.append(item) | ||
else: | ||
flat_list.append(list_item) | ||
else: | ||
flat_list.append(input) | ||
|
||
input = flat_list | ||
|
||
if len(input) < 2: | ||
raise cv.Invalid( | ||
"At least two values need to be specified (start + stop distances)" | ||
) | ||
if len(input) % 2 != 0: | ||
raise cv.Invalid( | ||
"An even number of arguments must be specified (pairs of min + max)" | ||
) | ||
if len(input) > 8: | ||
raise cv.Invalid( | ||
"Maximum four segments can be specified (8 values: 4 * min + max)" | ||
) | ||
|
||
largest_distance = -1 | ||
for distance in input: | ||
if isinstance(distance, core.Lambda): | ||
continue | ||
m = cv.distance(distance) | ||
if m > 9: | ||
raise cv.Invalid("Maximum distance is 9m") | ||
if m < 0: | ||
raise cv.Invalid("Minimum distance is 0m") | ||
if m <= largest_distance: | ||
raise cv.Invalid( | ||
"Distances must be delared from small to large " | ||
"and they cannot contain each other" | ||
) | ||
largest_distance = m | ||
# Replace distance object with meters float | ||
input[input.index(distance)] = m | ||
|
||
return input | ||
|
||
|
||
MMWAVE_SETTINGS_SCHEMA = cv.Schema( | ||
{ | ||
cv.GenerateID(): cv.use_id(DfrobotSen0395Component), | ||
cv.Optional(CONF_FACTORY_RESET): cv.templatable(cv.boolean), | ||
cv.Optional(CONF_DETECTION_SEGMENTS): range_segment_list, | ||
cv.Optional(CONF_OUTPUT_LATENCY): { | ||
cv.Required(CONF_DELAY_AFTER_DETECT): cv.templatable( | ||
cv.All( | ||
cv.positive_time_period, | ||
cv.Range(max=core.TimePeriod(seconds=1638.375)), | ||
) | ||
), | ||
cv.Required(CONF_DELAY_AFTER_DISAPPEAR): cv.templatable( | ||
cv.All( | ||
cv.positive_time_period, | ||
cv.Range(max=core.TimePeriod(seconds=1638.375)), | ||
) | ||
), | ||
}, | ||
cv.Optional(CONF_SENSITIVITY): cv.templatable(cv.int_range(min=0, max=9)), | ||
} | ||
).add_extra( | ||
cv.has_at_least_one_key( | ||
CONF_FACTORY_RESET, | ||
CONF_DETECTION_SEGMENTS, | ||
CONF_OUTPUT_LATENCY, | ||
CONF_SENSITIVITY, | ||
) | ||
) | ||
|
||
|
||
@automation.register_action( | ||
"dfrobot_sen0395.settings", | ||
DfrobotSen0395SettingsAction, | ||
MMWAVE_SETTINGS_SCHEMA, | ||
) | ||
async def dfrobot_sen0395_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]) | ||
|
||
if factory_reset_config := config.get(CONF_FACTORY_RESET): | ||
template_ = await cg.templatable(factory_reset_config, args, int) | ||
cg.add(var.set_factory_reset(template_)) | ||
|
||
if CONF_DETECTION_SEGMENTS in config: | ||
segments = config[CONF_DETECTION_SEGMENTS] | ||
|
||
if len(segments) >= 2: | ||
template_ = await cg.templatable(segments[0], args, float) | ||
cg.add(var.set_det_min1(template_)) | ||
template_ = await cg.templatable(segments[1], args, float) | ||
cg.add(var.set_det_max1(template_)) | ||
if len(segments) >= 4: | ||
template_ = await cg.templatable(segments[2], args, float) | ||
cg.add(var.set_det_min2(template_)) | ||
template_ = await cg.templatable(segments[3], args, float) | ||
cg.add(var.set_det_max2(template_)) | ||
if len(segments) >= 6: | ||
template_ = await cg.templatable(segments[4], args, float) | ||
cg.add(var.set_det_min3(template_)) | ||
template_ = await cg.templatable(segments[5], args, float) | ||
cg.add(var.set_det_max3(template_)) | ||
if len(segments) >= 8: | ||
template_ = await cg.templatable(segments[6], args, float) | ||
cg.add(var.set_det_min4(template_)) | ||
template_ = await cg.templatable(segments[7], args, float) | ||
cg.add(var.set_det_max4(template_)) | ||
if CONF_OUTPUT_LATENCY in config: | ||
template_ = await cg.templatable( | ||
config[CONF_OUTPUT_LATENCY][CONF_DELAY_AFTER_DETECT], args, float | ||
) | ||
if isinstance(template_, cv.TimePeriod): | ||
template_ = template_.total_milliseconds / 1000 | ||
cg.add(var.set_delay_after_detect(template_)) | ||
|
||
template_ = await cg.templatable( | ||
config[CONF_OUTPUT_LATENCY][CONF_DELAY_AFTER_DISAPPEAR], args, float | ||
) | ||
if isinstance(template_, cv.TimePeriod): | ||
template_ = template_.total_milliseconds / 1000 | ||
cg.add(var.set_delay_after_disappear(template_)) | ||
if CONF_SENSITIVITY in config: | ||
template_ = await cg.templatable(config[CONF_SENSITIVITY], args, int) | ||
cg.add(var.set_sensitivity(template_)) | ||
|
||
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,89 @@ | ||
#pragma once | ||
|
||
#include "esphome/core/automation.h" | ||
#include "esphome/core/helpers.h" | ||
|
||
#include "dfrobot_sen0395.h" | ||
|
||
namespace esphome { | ||
namespace dfrobot_sen0395 { | ||
|
||
template<typename... Ts> | ||
class DfrobotSen0395ResetAction : public Action<Ts...>, public Parented<DfrobotSen0395Component> { | ||
public: | ||
void play(Ts... x) { this->parent_->enqueue(make_unique<ResetSystemCommand>()); } | ||
}; | ||
|
||
template<typename... Ts> | ||
class DfrobotSen0395SettingsAction : public Action<Ts...>, public Parented<DfrobotSen0395Component> { | ||
public: | ||
TEMPLATABLE_VALUE(int8_t, factory_reset) | ||
TEMPLATABLE_VALUE(int8_t, start_after_power_on) | ||
TEMPLATABLE_VALUE(int8_t, turn_on_led) | ||
TEMPLATABLE_VALUE(int8_t, presence_via_uart) | ||
TEMPLATABLE_VALUE(int8_t, sensitivity) | ||
TEMPLATABLE_VALUE(float, delay_after_detect) | ||
TEMPLATABLE_VALUE(float, delay_after_disappear) | ||
TEMPLATABLE_VALUE(float, det_min1) | ||
TEMPLATABLE_VALUE(float, det_max1) | ||
TEMPLATABLE_VALUE(float, det_min2) | ||
TEMPLATABLE_VALUE(float, det_max2) | ||
TEMPLATABLE_VALUE(float, det_min3) | ||
TEMPLATABLE_VALUE(float, det_max3) | ||
TEMPLATABLE_VALUE(float, det_min4) | ||
TEMPLATABLE_VALUE(float, det_max4) | ||
|
||
void play(Ts... x) { | ||
this->parent_->enqueue(make_unique<PowerCommand>(0)); | ||
if (this->factory_reset_.has_value() && this->factory_reset_.value(x...) == true) { | ||
this->parent_->enqueue(make_unique<FactoryResetCommand>()); | ||
} | ||
if (this->det_min1_.has_value() && this->det_max1_.has_value()) { | ||
if (this->det_min1_.value() >= 0 && this->det_max1_.value() >= 0) { | ||
this->parent_->enqueue(make_unique<DetRangeCfgCommand>( | ||
this->det_min1_.value_or(-1), this->det_max1_.value_or(-1), this->det_min2_.value_or(-1), | ||
this->det_max2_.value_or(-1), this->det_min3_.value_or(-1), this->det_max3_.value_or(-1), | ||
this->det_min4_.value_or(-1), this->det_max4_.value_or(-1))); | ||
} | ||
} | ||
if (this->delay_after_detect_.has_value() && this->delay_after_disappear_.has_value()) { | ||
float detect = this->delay_after_detect_.value(x...); | ||
float disappear = this->delay_after_disappear_.value(x...); | ||
if (detect >= 0 && disappear >= 0) { | ||
this->parent_->enqueue(make_unique<OutputLatencyCommand>(detect, disappear)); | ||
} | ||
} | ||
if (this->start_after_power_on_.has_value()) { | ||
int8_t val = this->start_after_power_on_.value(x...); | ||
if (val >= 0) { | ||
this->parent_->enqueue(make_unique<SensorCfgStartCommand>(val)); | ||
} | ||
} | ||
if (this->turn_on_led_.has_value()) { | ||
int8_t val = this->turn_on_led_.value(x...); | ||
if (val >= 0) { | ||
this->parent_->enqueue(make_unique<LedModeCommand>(val)); | ||
} | ||
} | ||
if (this->presence_via_uart_.has_value()) { | ||
int8_t val = this->presence_via_uart_.value(x...); | ||
if (val >= 0) { | ||
this->parent_->enqueue(make_unique<UartOutputCommand>(val)); | ||
} | ||
} | ||
if (this->sensitivity_.has_value()) { | ||
int8_t val = this->sensitivity_.value(x...); | ||
if (val >= 0) { | ||
if (val > 9) { | ||
val = 9; | ||
} | ||
this->parent_->enqueue(make_unique<SensitivityCommand>(val)); | ||
} | ||
} | ||
this->parent_->enqueue(make_unique<SaveCfgCommand>()); | ||
this->parent_->enqueue(make_unique<PowerCommand>(1)); | ||
} | ||
}; | ||
|
||
} // namespace dfrobot_sen0395 | ||
} // namespace esphome |
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,22 @@ | ||
import esphome.codegen as cg | ||
import esphome.config_validation as cv | ||
from esphome.components import binary_sensor | ||
from esphome.const import DEVICE_CLASS_MOTION | ||
from . import CONF_DFROBOT_SEN0395_ID, DfrobotSen0395Component | ||
|
||
DEPENDENCIES = ["dfrobot_sen0395"] | ||
|
||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema( | ||
device_class=DEVICE_CLASS_MOTION | ||
).extend( | ||
{ | ||
cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(DfrobotSen0395Component), | ||
} | ||
) | ||
|
||
|
||
async def to_code(config): | ||
parent = await cg.get_variable(config[CONF_DFROBOT_SEN0395_ID]) | ||
binary_sens = await binary_sensor.new_binary_sensor(config) | ||
|
||
cg.add(parent.set_detected_binary_sensor(binary_sens)) |
Oops, something went wrong.